Repository: googleapis/google-auth-library-python

Issue:
utcnow is deprecated in python 3.12

#### Environment details

  - OS: Linux
  - Python version: 3.12.0
  - pip version: 23.2.1
  - `google-auth` version: 2.9.1

#### Issue
Here is the related code

https://github.com/googleapis/google-auth-library-python/blob/d2ab3afdb567850121fec7de1d86fb5fb0fa80ed/google/auth/_helpers.py#L89-L95




Discussion:
this can be fixed by replacing `datetime.datetime.utcnow()` with `datetime.datetime.now(datetime.timezone.utc)`, see

https://docs.python.org/3.12/library/datetime.html#datetime.datetime.utcnow
it looks like we cannot simply replace `datetime.datetime.utcnow()` with `datetime.datetime.now(datetime.timezone.utc)`, since `utcnow` is offset-naive (no timezone) but `now` is offset-aware (has timezone). It's very common to compare the token expiry in auth lib itself as well as gcloud, if we simply switch the type, the comparison fails.
I guess you can then just remove the tzinfo afterwards like ".replace(tzinfo=None)" 
@kasium yep, that sounds like the only way. Thanks!

Resolving patch:
diff --git a/google/auth/_helpers.py b/google/auth/_helpers.py
--- a/google/auth/_helpers.py
+++ b/google/auth/_helpers.py
@@ -92,7 +92,14 @@ def utcnow():
     Returns:
         datetime: The current time in UTC.
     """
-    return datetime.datetime.utcnow()
+    # We used datetime.utcnow() before, since it's deprecated from python 3.12,
+    # we are using datetime.now(timezone.utc) now. "utcnow()" is offset-native
+    # (no timezone info), but "now()" is offset-aware (with timezone info).
+    # This will cause datetime comparison problem. For backward compatibility,
+    # we need to remove the timezone info.
+    now = datetime.datetime.now(datetime.timezone.utc)
+    now = now.replace(tzinfo=None)
+    return now
 
 
 def datetime_to_secs(value):


Test patch:
diff --git a/tests/test_credentials.py b/tests/test_credentials.py
--- a/tests/test_credentials.py
+++ b/tests/test_credentials.py
@@ -55,9 +55,7 @@ def test_expired_and_valid():
     # Set the expiration to one second more than now plus the clock skew
     # accomodation. These credentials should be valid.
     credentials.expiry = (
-        datetime.datetime.utcnow()
-        + _helpers.REFRESH_THRESHOLD
-        + datetime.timedelta(seconds=1)
+        _helpers.utcnow() + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=1)
     )
 
     assert credentials.valid
@@ -65,7 +63,7 @@ def test_expired_and_valid():
 
     # Set the credentials expiration to now. Because of the clock skew
     # accomodation, these credentials should report as expired.
-    credentials.expiry = datetime.datetime.utcnow()
+    credentials.expiry = _helpers.utcnow()
 
     assert not credentials.valid
     assert credentials.expired
diff --git a/tests_async/test_credentials_async.py b/tests_async/test_credentials_async.py
--- a/tests_async/test_credentials_async.py
+++ b/tests_async/test_credentials_async.py
@@ -46,9 +46,7 @@ def test_expired_and_valid():
     # Set the expiration to one second more than now plus the clock skew
     # accomodation. These credentials should be valid.
     credentials.expiry = (
-        datetime.datetime.utcnow()
-        + _helpers.REFRESH_THRESHOLD
-        + datetime.timedelta(seconds=1)
+        _helpers.utcnow() + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=1)
     )
 
     assert credentials.valid
@@ -56,7 +54,7 @@ def test_expired_and_valid():
 
     # Set the credentials expiration to now. Because of the clock skew
     # accomodation, these credentials should report as expired.
-    credentials.expiry = datetime.datetime.utcnow()
+    credentials.expiry = _helpers.utcnow()
 
     assert not credentials.valid
     assert credentials.expired