# Comparison and equality All types support equality and comparison. However, {class}`~whenever.PlainDateTime` instances are never equal or comparable to the "exact" types. ## Exact time For exact types ({class}`~whenever.Instant`, {class}`~whenever.OffsetDateTime`, {class}`~whenever.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: ```python >>> # 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 {meth}`~whenever.ZonedDateTime.exact_eq` method. ```python >>> 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 {class}`~whenever.PlainDateTime`, equality is simply based on whether the values are the same, since there is no concept of timezones or UTC offset: ```python >>> 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 ``` ```{seealso} See the documentation of {meth}`__eq__ (exact) ` and {meth}`PlainDateTime.__eq__ ` for more details. ``` ## Nanosecond precision and interoperability Take care when comparing datetimes after interoperating with databases, the Python standard library, or other systems that may not support nanosecond precision. Since equality is based on full nanosecond precision, two datetimes may no longer be equal after a round-trip that loses precision. This may not be apparent in development if your system's clock only supports microsecond precision (such as MacOS). Use `.round('microsecond')` to explicitly round values to microsecond precision. ## 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](https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-strict-equality). ```python >>> # 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 ``` ```{admonition} Why not raise a TypeError? :class: hint It may *seem* like the equality operator should raise a {exc}`TypeError` in these cases, but this would result in [surprising behavior](https://stackoverflow.com/a/33417512) when using values as dictionary keys. ``` Unfortunately, mypy's `--strict-equality` is *very* strict, forcing you to match exact types exactly. ```python 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: ```python x == OffsetDateTime(2023, 12, 28, 11, offset=1).to_instant() ``` Or annotate with a union: ```python x: OffsetDateTime | Instant == OffsetDateTime(2023, 12, 28, 11, offset=1) ```