diff --git a/packages/google-cloud-ndb/google/cloud/ndb/model.py b/packages/google-cloud-ndb/google/cloud/ndb/model.py index 8d19dded179a..1becc7db10ff 100644 --- a/packages/google-cloud-ndb/google/cloud/ndb/model.py +++ b/packages/google-cloud-ndb/google/cloud/ndb/model.py @@ -352,6 +352,17 @@ class Person(Model): _getfullargspec = inspect.getfullargspec +def _utcnow(): + """Return the current UTC time as a naive :class:`~datetime.datetime`. + + ``datetime.datetime.utcnow()`` is deprecated since Python 3.12 and + scheduled for removal. Its replacement, ``datetime.datetime.now(timezone.utc)``, + returns an offset-aware value, but ``ndb`` stores and compares naive + datetimes presumed to be UTC, so the offset is stripped again here. + """ + return datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) + + class KindError(exceptions.BadValueError): """Raised when an implementation for a kind can't be found. @@ -2092,7 +2103,7 @@ def _legacy_db_get_value(v, p): # A custom 'meaning' for compressed properties. _MEANING_URI_COMPRESSED = "ZLIB" # The Epoch (a zero POSIX timestamp). - _EPOCH = datetime.datetime.utcfromtimestamp(0) + _EPOCH = datetime.datetime(1970, 1, 1) # This is awkward but there seems to be no faster way to inspect # what union member is present. datastore_types.FromPropertyPb(), # the undisputed authority, has the same series of if-elif blocks. @@ -3933,7 +3944,7 @@ def _now(): Subclasses will override this to return different forms of "now". """ - return datetime.datetime.utcnow() + return _utcnow() def _prepare_for_put(self, entity): """Sets the current timestamp when "auto" is set. @@ -4055,7 +4066,7 @@ def _from_base_type(self, value): @staticmethod def _now(): """datetime.datetime: Return current date.""" - return datetime.datetime.utcnow().date() + return _utcnow().date() class TimeProperty(DateTimeProperty): @@ -4124,7 +4135,7 @@ def _from_base_type(self, value): @staticmethod def _now(): """datetime.datetime: Return current time.""" - return datetime.datetime.utcnow().time() + return _utcnow().time() class StructuredProperty(Property): diff --git a/packages/google-cloud-ndb/tests/system/test_crud.py b/packages/google-cloud-ndb/tests/system/test_crud.py index eea78fcfec7a..2ecad4599e23 100644 --- a/packages/google-cloud-ndb/tests/system/test_crud.py +++ b/packages/google-cloud-ndb/tests/system/test_crud.py @@ -1411,7 +1411,7 @@ class SomeKind(ndb.Model): now = datetime.datetime.now(pytz.utc) entity = SomeKind( alarm1=now, - alarm2=datetime.datetime.utcnow(), # naive + alarm2=now.replace(tzinfo=None), # same instant, naive ) key = entity.put() dispose_of(key._key) diff --git a/packages/google-cloud-ndb/tests/unit/test_model.py b/packages/google-cloud-ndb/tests/unit/test_model.py index 6b5e3fbbc059..d0c235809acd 100644 --- a/packages/google-cloud-ndb/tests/unit/test_model.py +++ b/packages/google-cloud-ndb/tests/unit/test_model.py @@ -297,6 +297,17 @@ def test__fix_up(): assert attr._fix_up(model.Model, "birthdate") is None +def test__utcnow(): + before = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) + value = model._utcnow() + after = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) + + # ndb stores naive datetimes presumed to be UTC, so the helper must not + # leak the tzinfo that datetime.now(timezone.utc) attaches. + assert value.tzinfo is None + assert before <= value <= after + + class Test_BaseValue: @staticmethod def test_constructor(): @@ -2974,7 +2985,7 @@ def test_constructor_defaults(): @staticmethod def test_constructor_explicit(): - now = datetime.datetime.utcnow() + now = model._utcnow() prop = model.DateTimeProperty( name="dt_val", auto_now=True, @@ -3014,7 +3025,7 @@ def test_constructor_repeated(): @staticmethod def test__validate(): prop = model.DateTimeProperty(name="dt_val") - value = datetime.datetime.utcnow() + value = model._utcnow() assert prop._validate(value) is None @staticmethod @@ -3150,7 +3161,7 @@ class TestDateProperty: @staticmethod def test__validate(): prop = model.DateProperty(name="d_val") - value = datetime.datetime.utcnow().date() + value = model._utcnow().date() assert prop._validate(value) is None @staticmethod @@ -3186,7 +3197,7 @@ class TestTimeProperty: @staticmethod def test__validate(): prop = model.TimeProperty(name="t_val") - value = datetime.datetime.utcnow().time() + value = model._utcnow().time() assert prop._validate(value) is None @staticmethod diff --git a/packages/google-cloud-ndb/tests/unit/test_stats.py b/packages/google-cloud-ndb/tests/unit/test_stats.py index 6d3b9a04a9ad..c91d42364486 100644 --- a/packages/google-cloud-ndb/tests/unit/test_stats.py +++ b/packages/google-cloud-ndb/tests/unit/test_stats.py @@ -21,7 +21,7 @@ DEFAULTS = { "bytes": 4, "count": 2, - "timestamp": datetime.datetime.utcfromtimestamp(40), + "timestamp": datetime.datetime(1970, 1, 1, 0, 0, 40), }