Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions cirq-core/cirq/json_resolver_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
7 changes: 0 additions & 7 deletions cirq-core/cirq/protocols/json_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 10 additions & 4 deletions cirq-core/cirq/protocols/json_serialization_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
23 changes: 23 additions & 0 deletions docs/dev/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,26 @@ later in the same file.
Using consistent wording across Cirq is important for lowering users
cognitive load. For rule governing naming, see the
[nomenclature guidelines](nomenclature.md).

## Datetimes

Prefer using timezone-aware `datetime` objects.

```python
import datetime
dt = datetime.datetime.now(tz=datetime.timezone.utc)
```

Public components of Protobuf APIs will return "aware" `datetime` objects.
JSON de-serialization will promote values to "aware" `datetime` objects upon deserialization.

Comparing (or testing equality) between "naive" and "aware" `datetime` objects throws
an exception.
If you are implementing a class that has `datetime` member variables, delegate equality
and comparison operators to the built-in `datetime` equality and comparison operators.
If you're writing a function that compares `datetime` objects, you can defensively promote
them to "aware" objects or use their `.timestamp()` properties for a comparison that will
never throw an exception.

Absolutely do not use `datetime.utcnow()` as explained in the warnings in the
Python [documentation](https://docs.python.org/3/library/datetime.html).