Comparison and equality

All types support equality and comparison. However, PlainDateTime instances are never equal or comparable to the “exact” types.

Exact time

For exact types (Instant, OffsetDateTime, ZonedDateTime), comparison and equality are based on whether they represent the same moment in time. This means that two objects with different values can be equal:

>>> # different ways of representing the same moment in time
>>> inst = Instant.from_utc(2023, 12, 28, 11, 30)
>>> as_5hr_offset = OffsetDateTime(2023, 12, 28, 16, 30, offset=5)
>>> as_8hr_offset = OffsetDateTime(2023, 12, 28, 19, 30, offset=8)
>>> in_nyc = ZonedDateTime(2023, 12, 28, 6, 30, tz="America/New_York")
>>> # all equal
>>> inst == as_5hr_offset == as_8hr_offset == in_nyc
True
>>> # comparison
>>> in_nyc > OffsetDateTime(2023, 12, 28, 11, 30, offset=5)
True

Note that if you want to compare for exact equality on the values (i.e. exactly the same year, month, day, hour, minute, etc.), you can use the exact_eq() method.

>>> d = OffsetDateTime(2023, 12, 28, 11, 30, offset=5)
>>> same = OffsetDateTime(2023, 12, 28, 11, 30, offset=5)
>>> same_moment = OffsetDateTime(2023, 12, 28, 12, 30, offset=6)
>>> d == same_moment
True
>>> d.exact_eq(same_moment)
False
>>> d.exact_eq(same)
True

Local time

For PlainDateTime, equality is simply based on whether the values are the same, since there is no concept of timezones or UTC offset:

>>> d = PlainDateTime(2023, 12, 28, 11, 30)
>>> same = PlainDateTime(2023, 12, 28, 11, 30)
>>> different = PlainDateTime(2023, 12, 28, 11, 31)
>>> d == same
True
>>> d == different
False

See also

See the documentation of __eq__ (exact) and PlainDateTime.__eq__ for more details.

Strict equality

Local and exact types are never equal or comparable to each other. However, to comply with the Python data model, the equality operator won’t prevent you from using == to compare them. To prevent these mix-ups, use mypy’s --strict-equality flag.

>>> # These are never equal, but Python won't stop you from comparing them.
>>> # Mypy will catch this mix-up if you use enable --strict-equality flag.
>>> Instant.from_utc(2023, 12, 28) == PlainDateTime(2023, 12, 28)
False

Why not raise a TypeError?

It may seem like the equality operator should raise a TypeError in these cases, but this would result in surprising behavior when using values as dictionary keys.

Unfortunately, mypy’s --strict-equality is very strict, forcing you to match exact types exactly.


x = Instant.from_utc(2023, 12, 28, 10)

# mypy: ✅
x == Instant.from_utc(2023, 12, 28, 10)

# mypy: ❌ (too strict, this should be allowed)
x == OffsetDateTime(2023, 12, 28, 11, offset=1)

To work around this, you can either convert explicitly:

x == OffsetDateTime(2023, 12, 28, 11, offset=1).to_instant()

Or annotate with a union:

x: OffsetDateTime | Instant == OffsetDateTime(2023, 12, 28, 11, offset=1)