From 12901c286d55f1ae6bfd669eaee5231d6ef6ecdd Mon Sep 17 00:00:00 2001 From: Glyph Date: Sun, 5 Oct 2025 00:33:41 -0700 Subject: [PATCH] adjust `DateTime.__sub__` to allow differing tzinfo implementations Fixes #17 This commit was sponsored by Blaise Pabon, Amethyst Reese, and my other patrons. If you want to join them, you can support my work at https://glyph.im/patrons/. --- expected_mypy.txt | 16 +++++++++++++++- expected_mypy_37.txt | 16 +++++++++++++++- src/datetype/__init__.py | 5 ++++- src/datetype/test/test_datetype.py | 24 +++++++++++++++++++++++- tryit.py | 5 +++++ 5 files changed, 62 insertions(+), 4 deletions(-) diff --git a/expected_mypy.txt b/expected_mypy.txt index 1f8eb77..3eeee02 100644 --- a/expected_mypy.txt +++ b/expected_mypy.txt @@ -44,4 +44,18 @@ tryit.py:53: note: Got: tryit.py:53: note: def timetz(self) -> Time[timezone] tryit.py:53: note: tzinfo: expected "None", got "timezone" tryit.py:56: error: Incompatible types in assignment (expression has type "NaiveTime", variable has type "DateTime[timezone]") [assignment] -Found 10 errors in 1 file (checked 1 source file) +tryit.py:58: error: Unsupported operand types for - ("NaiveDateTime" and "AwareDateTime") [operator] +tryit.py:58: note: Following member(s) of "AwareDateTime" have conflicts: +tryit.py:58: note: Expected: +tryit.py:58: note: def timetz(self) -> Time[None] +tryit.py:58: note: Got: +tryit.py:58: note: def timetz(self) -> Time[tzinfo] +tryit.py:58: note: tzinfo: expected "None", got "tzinfo" +tryit.py:59: error: Unsupported operand types for - ("AwareDateTime" and "NaiveDateTime") [operator] +tryit.py:59: note: Following member(s) of "NaiveDateTime" have conflicts: +tryit.py:59: note: Expected: +tryit.py:59: note: def timetz(self) -> Time[tzinfo] +tryit.py:59: note: Got: +tryit.py:59: note: def timetz(self) -> Time[None] +tryit.py:59: note: tzinfo: expected "tzinfo", got "None" +Found 12 errors in 1 file (checked 1 source file) diff --git a/expected_mypy_37.txt b/expected_mypy_37.txt index 386e580..c7e3767 100644 --- a/expected_mypy_37.txt +++ b/expected_mypy_37.txt @@ -30,4 +30,18 @@ tryit.py:53: note: Got: tryit.py:53: note: def timetz(self) -> Time[timezone] tryit.py:53: note: tzinfo: expected "None", got "timezone" tryit.py:56: error: Incompatible types in assignment (expression has type "NaiveTime", variable has type "DateTime[timezone]") [assignment] -Found 8 errors in 1 file (checked 1 source file) +tryit.py:58: error: Unsupported operand types for - ("NaiveDateTime" and "AwareDateTime") [operator] +tryit.py:58: note: Following member(s) of "AwareDateTime" have conflicts: +tryit.py:58: note: Expected: +tryit.py:58: note: def timetz(self) -> Time[None] +tryit.py:58: note: Got: +tryit.py:58: note: def timetz(self) -> Time[tzinfo] +tryit.py:58: note: tzinfo: expected "None", got "tzinfo" +tryit.py:59: error: Unsupported operand types for - ("AwareDateTime" and "NaiveDateTime") [operator] +tryit.py:59: note: Following member(s) of "NaiveDateTime" have conflicts: +tryit.py:59: note: Expected: +tryit.py:59: note: def timetz(self) -> Time[tzinfo] +tryit.py:59: note: Got: +tryit.py:59: note: def timetz(self) -> Time[None] +tryit.py:59: note: tzinfo: expected "tzinfo", got "None" +Found 10 errors in 1 file (checked 1 source file) diff --git a/src/datetype/__init__.py b/src/datetype/__init__.py index a4f2d7c..8a9c680 100644 --- a/src/datetype/__init__.py +++ b/src/datetype/__init__.py @@ -456,7 +456,10 @@ def __gt__(self: Self, __other: Self) -> bool: ... def __sub__(self: Self, __other: _timedelta) -> Self: ... @overload - def __sub__(self: DTSelf, __other: DTSelf) -> _timedelta: ... + def __sub__(self: DateTime[None], __other: DateTime[None]) -> _timedelta: ... + + @overload + def __sub__(self: DateTime[_tzinfo], __other: DateTime[_tzinfo]) -> _timedelta: ... def __add__(self: Self, __other: _timedelta) -> Self: ... diff --git a/src/datetype/test/test_datetype.py b/src/datetype/test/test_datetype.py index 352a800..6d42cbd 100644 --- a/src/datetype/test/test_datetype.py +++ b/src/datetype/test/test_datetype.py @@ -4,7 +4,15 @@ from sys import version_info from unittest import TestCase, skipIf -from datetype import AwareDateTime, NaiveDateTime, NaiveTime, Time, aware, naive +from datetype import ( + AwareDateTime, + NaiveDateTime, + NaiveTime, + Time, + aware, + naive, + DateTime, +) TEST_DATA = (Path(__file__) / "..").resolve() while not (TEST_DATA / ".git").is_dir(): @@ -87,3 +95,17 @@ def test_none_aware(self) -> None: awareified = aware(stddt) self.assertIs(awareified.tzinfo, zi) self.assertEqual(awareified.tzinfo.dst(stddt), timedelta(0)) + + @skipIf(version_info < (3, 9), "ZoneInfo") + def test_differing_zone_subtract(self) -> None: + from zoneinfo import ZoneInfo + + zi = ZoneInfo("US/Pacific") + stddt = datetime(2025, 2, 13, 15, 35, 13, 574354, tzinfo=zi) + inutc = stddt.astimezone(timezone.utc) + + dtzi: DateTime[ZoneInfo] = aware(stddt, ZoneInfo) + dttz: DateTime[timezone] = aware(inutc, timezone) + + self.assertEqual(dtzi - dttz, timedelta(0)) + self.assertEqual(dttz - dtzi, timedelta(0)) diff --git a/tryit.py b/tryit.py index 0a796e0..b7c4845 100644 --- a/tryit.py +++ b/tryit.py @@ -54,3 +54,8 @@ is_aware: DateTime[timezone] = a.astimezone(None) not_aware_method_time: DateTime[timezone] = a.time() # nope; actually naive + +x - y # error; naive & aware are incompatible +y - x # also error +y - is_aware # ok +is_aware - y # ok