summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/datetime.py37
-rw-r--r--Lib/test/datetimetester.py51
2 files changed, 72 insertions, 16 deletions
diff --git a/Lib/datetime.py b/Lib/datetime.py
index 6755519..952aebf 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -1095,7 +1095,7 @@ class date:
return self.toordinal() % 7 or 7
def isocalendar(self):
- """Return a 3-tuple containing ISO year, week number, and weekday.
+ """Return a named tuple containing ISO year, week number, and weekday.
The first ISO week of the year is the (Mon-Sun) week
containing the year's first Thursday; everything else derives
@@ -1120,7 +1120,7 @@ class date:
if today >= _isoweek1monday(year+1):
year += 1
week = 0
- return year, week+1, day+1
+ return _IsoCalendarDate(year, week+1, day+1)
# Pickle support.
@@ -1210,6 +1210,36 @@ class tzinfo:
else:
return (self.__class__, args, state)
+
+class IsoCalendarDate(tuple):
+
+ def __new__(cls, year, week, weekday, /):
+ return super().__new__(cls, (year, week, weekday))
+
+ @property
+ def year(self):
+ return self[0]
+
+ @property
+ def week(self):
+ return self[1]
+
+ @property
+ def weekday(self):
+ return self[2]
+
+ def __reduce__(self):
+ # This code is intended to pickle the object without making the
+ # class public. See https://bugs.python.org/msg352381
+ return (tuple, (tuple(self),))
+
+ def __repr__(self):
+ return (f'{self.__class__.__name__}'
+ f'(year={self[0]}, week={self[1]}, weekday={self[2]})')
+
+
+_IsoCalendarDate = IsoCalendarDate
+del IsoCalendarDate
_tzinfo_class = tzinfo
class time:
@@ -1559,6 +1589,7 @@ time.min = time(0, 0, 0)
time.max = time(23, 59, 59, 999999)
time.resolution = timedelta(microseconds=1)
+
class datetime(date):
"""datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
@@ -2514,7 +2545,7 @@ else:
_format_time, _format_offset, _is_leap, _isoweek1monday, _math,
_ord2ymd, _time, _time_class, _tzinfo_class, _wrap_strftime, _ymd2ord,
_divide_and_round, _parse_isoformat_date, _parse_isoformat_time,
- _parse_hh_mm_ss_ff)
+ _parse_hh_mm_ss_ff, _IsoCalendarDate)
# XXX Since import * above excludes names that start with _,
# docstring does not get overwritten. In the future, it may be
# appropriate to maintain a single module level docstring and
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 42e2cec..a9741d6 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -2,6 +2,7 @@
See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
"""
+import io
import itertools
import bisect
import copy
@@ -1355,19 +1356,43 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
def test_isocalendar(self):
# Check examples from
# http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
- for i in range(7):
- d = self.theclass(2003, 12, 22+i)
- self.assertEqual(d.isocalendar(), (2003, 52, i+1))
- d = self.theclass(2003, 12, 29) + timedelta(i)
- self.assertEqual(d.isocalendar(), (2004, 1, i+1))
- d = self.theclass(2004, 1, 5+i)
- self.assertEqual(d.isocalendar(), (2004, 2, i+1))
- d = self.theclass(2009, 12, 21+i)
- self.assertEqual(d.isocalendar(), (2009, 52, i+1))
- d = self.theclass(2009, 12, 28) + timedelta(i)
- self.assertEqual(d.isocalendar(), (2009, 53, i+1))
- d = self.theclass(2010, 1, 4+i)
- self.assertEqual(d.isocalendar(), (2010, 1, i+1))
+ week_mondays = [
+ ((2003, 12, 22), (2003, 52, 1)),
+ ((2003, 12, 29), (2004, 1, 1)),
+ ((2004, 1, 5), (2004, 2, 1)),
+ ((2009, 12, 21), (2009, 52, 1)),
+ ((2009, 12, 28), (2009, 53, 1)),
+ ((2010, 1, 4), (2010, 1, 1)),
+ ]
+
+ test_cases = []
+ for cal_date, iso_date in week_mondays:
+ base_date = self.theclass(*cal_date)
+ # Adds one test case for every day of the specified weeks
+ for i in range(7):
+ new_date = base_date + timedelta(i)
+ new_iso = iso_date[0:2] + (iso_date[2] + i,)
+ test_cases.append((new_date, new_iso))
+
+ for d, exp_iso in test_cases:
+ with self.subTest(d=d, comparison="tuple"):
+ self.assertEqual(d.isocalendar(), exp_iso)
+
+ # Check that the tuple contents are accessible by field name
+ with self.subTest(d=d, comparison="fields"):
+ t = d.isocalendar()
+ self.assertEqual((t.year, t.week, t.weekday), exp_iso)
+
+ def test_isocalendar_pickling(self):
+ """Test that the result of datetime.isocalendar() can be pickled.
+
+ The result of a round trip should be a plain tuple.
+ """
+ d = self.theclass(2019, 1, 1)
+ p = pickle.dumps(d.isocalendar())
+ res = pickle.loads(p)
+ self.assertEqual(type(res), tuple)
+ self.assertEqual(res, (2019, 1, 2))
def test_iso_long_years(self):
# Calculate long ISO years and compare to table from