# 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. ``` ## 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) ```