From 3955600f2b062d70f6dca264268ed03e67245b2c Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 21 Jun 2022 15:18:16 -0700 Subject: [PATCH] Datetime serialization and advice (#5422) fixes #5301 See #5301 for motivation --- cirq/json_resolver_cache.py | 11 +++++------ cirq/protocols/json_serialization.py | 7 ------- cirq/protocols/json_serialization_test.py | 14 ++++++++++---- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/cirq/json_resolver_cache.py b/cirq/json_resolver_cache.py index c0ca882caff..d34f338a8ee 100644 --- a/cirq/json_resolver_cache.py +++ b/cirq/json_resolver_cache.py @@ -55,13 +55,12 @@ def _parallel_gate_op(gate, qubits): return cirq.parallel_gate_op(gate, *qubits) def _datetime(timestamp: float) -> datetime.datetime: - # As part of our serialization logic, we make sure we only serialize "aware" - # datetimes with the UTC timezone, so we implicitly add back in the UTC timezone here. + # We serialize datetimes (both with ("aware") and without ("naive") timezone information) + # as unix timestamps. The deserialized datetime will always refer to the + # same point in time, but will be re-constructed as a timezone-aware object. # - # Please note: even if the assumption is somehow violated, the fact that we use - # unix timestamps should mean that the deserialized datetime should refer to the - # same point in time but may not satisfy o = read_json(to_json(o)) because the actual - # timezones, and hour fields will not be identical. + # If `o` is a naive datetime, o != read_json(to_json(o)) because Python doesn't + # let you compare aware and naive datetimes. return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) def _symmetricalqidpair(qids): diff --git a/cirq/protocols/json_serialization.py b/cirq/protocols/json_serialization.py index d66ed3f6615..757037edf24 100644 --- a/cirq/protocols/json_serialization.py +++ b/cirq/protocols/json_serialization.py @@ -398,13 +398,6 @@ def default(self, o): # datetime if isinstance(o, datetime.datetime): - if o.tzinfo is None or o.tzinfo.utcoffset(o) is None: - # Otherwise, the deserialized object may change depending on local timezone. - raise TypeError( - "Can only serialize 'aware' datetime objects with `tzinfo`. " - "Consider using e.g. `datetime.datetime.now(tz=datetime.timezone.utc)`" - ) - return {'cirq_type': 'datetime.datetime', 'timestamp': o.timestamp()} return super().default(o) # coverage: ignore diff --git a/cirq/protocols/json_serialization_test.py b/cirq/protocols/json_serialization_test.py index 7d82b7a4503..76883c56afb 100644 --- a/cirq/protocols/json_serialization_test.py +++ b/cirq/protocols/json_serialization_test.py @@ -945,11 +945,17 @@ def test_basic_time_assertions(): def test_datetime(): naive_dt = datetime.datetime.now() - with pytest.raises(TypeError): - cirq.to_json(naive_dt) + re_naive_dt = cirq.read_json(json_text=cirq.to_json(naive_dt)) + assert re_naive_dt != naive_dt, 'loads in with timezone' + assert re_naive_dt.timestamp() == naive_dt.timestamp() utc_dt = naive_dt.astimezone(datetime.timezone.utc) - assert utc_dt == cirq.read_json(json_text=cirq.to_json(utc_dt)) + re_utc_dt = cirq.read_json(json_text=cirq.to_json(utc_dt)) + assert re_utc_dt == utc_dt + assert re_utc_dt == re_naive_dt pst_dt = naive_dt.astimezone(tz=datetime.timezone(offset=datetime.timedelta(hours=-8))) - assert utc_dt == cirq.read_json(json_text=cirq.to_json(pst_dt)) + re_pst_dt = cirq.read_json(json_text=cirq.to_json(pst_dt)) + assert re_pst_dt == pst_dt + assert re_pst_dt == utc_dt + assert re_pst_dt == re_naive_dt