From 81a9b53fee79c4581bc1e2acae1c98ee0b692c7d Mon Sep 17 00:00:00 2001 From: donBarbos Date: Mon, 24 Feb 2025 01:29:31 +0400 Subject: gh-107773: Make `datetime` subclass `repr` consistent both implementations (#130308) --------- Co-authored-by: Jason R. Coombs --- Lib/_pydatetime.py | 44 +++++++++++----------- Lib/test/datetimetester.py | 43 +++++++++++++++++++++ .../2025-02-19-20-29-33.gh-issue-107773.7y6Ug2.rst | 2 + 3 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-02-19-20-29-33.gh-issue-107773.7y6Ug2.rst diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index ed8a5cd..fcf4416 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -3,6 +3,8 @@ __all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo", "MINYEAR", "MAXYEAR", "UTC") +__name__ = "datetime" + import time as _time import math as _math @@ -14,10 +16,10 @@ def _cmp(x, y): def _get_class_module(self): module_name = self.__class__.__module__ - if module_name == '_pydatetime': - return 'datetime' + if module_name == 'datetime': + return 'datetime.' else: - return module_name + return '' MINYEAR = 1 MAXYEAR = 9999 @@ -767,9 +769,9 @@ class timedelta: args.append("microseconds=%d" % self._microseconds) if not args: args.append('0') - return "%s.%s(%s)" % (_get_class_module(self), - self.__class__.__qualname__, - ', '.join(args)) + return "%s%s(%s)" % (_get_class_module(self), + self.__class__.__qualname__, + ', '.join(args)) def __str__(self): mm, ss = divmod(self._seconds, 60) @@ -1082,11 +1084,11 @@ class date: >>> repr(d) 'datetime.date(2010, 1, 1)' """ - return "%s.%s(%d, %d, %d)" % (_get_class_module(self), - self.__class__.__qualname__, - self._year, - self._month, - self._day) + return "%s%s(%d, %d, %d)" % (_get_class_module(self), + self.__class__.__qualname__, + self._year, + self._month, + self._day) # XXX These shouldn't depend on time.localtime(), because that # clips the usable dates to [1970 .. 2038). At least ctime() is # easily done without using strftime() -- that's better too because @@ -1586,7 +1588,7 @@ class time: s = ", %d" % self._second else: s = "" - s= "%s.%s(%d, %d%s)" % (_get_class_module(self), + s = "%s%s(%d, %d%s)" % (_get_class_module(self), self.__class__.__qualname__, self._hour, self._minute, s) if self._tzinfo is not None: @@ -2162,9 +2164,9 @@ class datetime(date): del L[-1] if L[-1] == 0: del L[-1] - s = "%s.%s(%s)" % (_get_class_module(self), - self.__class__.__qualname__, - ", ".join(map(str, L))) + s = "%s%s(%s)" % (_get_class_module(self), + self.__class__.__qualname__, + ", ".join(map(str, L))) if self._tzinfo is not None: assert s[-1:] == ")" s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")" @@ -2461,12 +2463,12 @@ class timezone(tzinfo): if self is self.utc: return 'datetime.timezone.utc' if self._name is None: - return "%s.%s(%r)" % (_get_class_module(self), - self.__class__.__qualname__, - self._offset) - return "%s.%s(%r, %r)" % (_get_class_module(self), - self.__class__.__qualname__, - self._offset, self._name) + return "%s%s(%r)" % (_get_class_module(self), + self.__class__.__qualname__, + self._offset) + return "%s%s(%r, %r)" % (_get_class_module(self), + self.__class__.__qualname__, + self._offset, self._name) def __str__(self): return self.tzname(None) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index b670973..ceeac94 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -504,6 +504,9 @@ class HarmlessMixedComparison: ############################################################################# # timedelta tests +class SubclassTimeDelta(timedelta): + sub_var = 1 + class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): theclass = timedelta @@ -788,6 +791,15 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)), "%s(seconds=1, microseconds=100)" % name) + def test_repr_subclass(self): + """Subclasses should have bare names in the repr (gh-107773).""" + td = SubclassTimeDelta(days=1) + self.assertEqual(repr(td), "SubclassTimeDelta(days=1)") + td = SubclassTimeDelta(seconds=30) + self.assertEqual(repr(td), "SubclassTimeDelta(seconds=30)") + td = SubclassTimeDelta(weeks=2) + self.assertEqual(repr(td), "SubclassTimeDelta(days=14)") + def test_roundtrip(self): for td in (timedelta(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999), @@ -1224,6 +1236,15 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase): dt2 = self.theclass(dt.year, dt.month, dt.day) self.assertEqual(dt, dt2) + def test_repr_subclass(self): + """Subclasses should have bare names in the repr (gh-107773).""" + td = SubclassDate(1, 2, 3) + self.assertEqual(repr(td), "SubclassDate(1, 2, 3)") + td = SubclassDate(2014, 1, 1) + self.assertEqual(repr(td), "SubclassDate(2014, 1, 1)") + td = SubclassDate(2010, 10, day=10) + self.assertEqual(repr(td), "SubclassDate(2010, 10, 10)") + def test_ordinal_conversions(self): # Check some fixed values. for y, m, d, n in [(1, 1, 1, 1), # calendar origin @@ -3587,6 +3608,15 @@ class TestDateTime(TestDate): self.assertEqual(dt, dt_rt) self.assertIsInstance(dt_rt, DateTimeSubclass) + def test_repr_subclass(self): + """Subclasses should have bare names in the repr (gh-107773).""" + td = SubclassDatetime(2014, 1, 1) + self.assertEqual(repr(td), "SubclassDatetime(2014, 1, 1, 0, 0)") + td = SubclassDatetime(2010, 10, day=10) + self.assertEqual(repr(td), "SubclassDatetime(2010, 10, 10, 0, 0)") + td = SubclassDatetime(2010, 10, 2, second=3) + self.assertEqual(repr(td), "SubclassDatetime(2010, 10, 2, 0, 0, 3)") + class TestSubclassDateTime(TestDateTime): theclass = SubclassDatetime @@ -3897,6 +3927,19 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase): self.assertEqual(repr(self.theclass(23, 15, 0, 0)), "%s(23, 15)" % name) + def test_repr_subclass(self): + """Subclasses should have bare names in the repr (gh-107773).""" + td = SubclassTime(hour=1) + self.assertEqual(repr(td), "SubclassTime(1, 0)") + td = SubclassTime(hour=2, minute=30) + self.assertEqual(repr(td), "SubclassTime(2, 30)") + td = SubclassTime(hour=2, minute=30, second=11) + self.assertEqual(repr(td), "SubclassTime(2, 30, 11)") + td = SubclassTime(minute=30, second=11, fold=0) + self.assertEqual(repr(td), "SubclassTime(0, 30, 11)") + td = SubclassTime(minute=30, second=11, fold=1) + self.assertEqual(repr(td), "SubclassTime(0, 30, 11, fold=1)") + def test_resolution_info(self): self.assertIsInstance(self.theclass.min, self.theclass) self.assertIsInstance(self.theclass.max, self.theclass) diff --git a/Misc/NEWS.d/next/Library/2025-02-19-20-29-33.gh-issue-107773.7y6Ug2.rst b/Misc/NEWS.d/next/Library/2025-02-19-20-29-33.gh-issue-107773.7y6Ug2.rst new file mode 100644 index 0000000..147010f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-19-20-29-33.gh-issue-107773.7y6Ug2.rst @@ -0,0 +1,2 @@ +Make :mod:`datetime` subclass :meth:`~object.__repr__` consistent both +implementations. Patch by Semyon Moroz. -- cgit v0.12