From cf3a675018c4b68a50df017dd5299900028e70da Mon Sep 17 00:00:00 2001
From: Alexander Belopolsky
Date: Sun, 22 Oct 2017 23:32:34 -0400
Subject: [PATCH 1/4] Closes bpo-28292: Implemented Calendar.itermonthdays3()
and itermonthdays4().
---
Lib/calendar.py | 60 ++++++++++++++++++++++++++++++---------
Lib/test/test_calendar.py | 16 +++++++----
2 files changed, 57 insertions(+), 19 deletions(-)
diff --git a/Lib/calendar.py b/Lib/calendar.py
index 0218e2d39770fac..73715fb05bb6fed 100644
--- a/Lib/calendar.py
+++ b/Lib/calendar.py
@@ -126,6 +126,24 @@ def monthrange(year, month):
return day1, ndays
+def monthlen(year, month):
+ return mdays[month] + (month == February and isleap(year))
+
+
+def prevmonth(year, month):
+ if month == 1:
+ return year-1, 12
+ else:
+ return year, month-1
+
+
+def nextmonth(year, month):
+ if month == 12:
+ return year+1, 1
+ else:
+ return year, month+1
+
+
class Calendar(object):
"""
Base calendar class. This class doesn't do any formatting. It simply
@@ -157,20 +175,8 @@ def itermonthdates(self, year, month):
values and will always iterate through complete weeks, so it will yield
dates outside the specified month.
"""
- date = datetime.date(year, month, 1)
- # Go back to the beginning of the week
- days = (date.weekday() - self.firstweekday) % 7
- date -= datetime.timedelta(days=days)
- oneday = datetime.timedelta(days=1)
- while True:
- yield date
- try:
- date += oneday
- except OverflowError:
- # Adding one day could fail after datetime.MAXYEAR
- break
- if date.month != month and date.weekday() == self.firstweekday:
- break
+ for y, m, d in self.itermonthdays3(year, month):
+ yield datetime.date(y, m, d)
def itermonthdays2(self, year, month):
"""
@@ -180,6 +186,32 @@ def itermonthdays2(self, year, month):
for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
yield d, i % 7
+ def itermonthdays3(self, year, month):
+ """
+ Like itermonthdates(), but will yield (year, month, day) tuples. Can be
+ used for dates outside of datetime.date range.
+ """
+ day1, ndays = monthrange(year, month)
+ days_before = (day1 - self.firstweekday) % 7
+ days_after = (self.firstweekday - day1 - ndays) % 7
+ y, m = prevmonth(year, month)
+ end = monthlen(y, m) + 1
+ for d in range(end-days_before, end):
+ yield y, m, d
+ for d in range(1, ndays + 1):
+ yield year, month, d
+ y, m = nextmonth(year, month)
+ for d in range(1, days_after + 1):
+ yield y, m, d
+
+ def itermonthdays4(self, year, month):
+ """
+ Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.
+ Can be used for dates outside of datetime.date range.
+ """
+ for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)):
+ yield y, m, d, (self.firstweekday + i) % 7
+
def itermonthdays(self, year, month):
"""
Like itermonthdates(), but will yield day numbers. For days outside
diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py
index c777f6483567ddb..ad8b6bb6b93e348 100644
--- a/Lib/test/test_calendar.py
+++ b/Lib/test/test_calendar.py
@@ -502,10 +502,15 @@ def test_locale_calendars(self):
new_october = calendar.TextCalendar().formatmonthname(2010, 10, 10)
self.assertEqual(old_october, new_october)
- def test_itermonthdates(self):
- # ensure itermonthdates doesn't overflow after datetime.MAXYEAR
- # see #15421
- list(calendar.Calendar().itermonthdates(datetime.MAXYEAR, 12))
+ def test_itermonthdays3(self):
+ # ensure itermonthdays3 doesn't overflow after datetime.MAXYEAR
+ list(calendar.Calendar().itermonthdays3(datetime.MAXYEAR, 12))
+
+ def test_itermonthdays4(self):
+ cal = calendar.Calendar(firstweekday=3)
+ days = list(cal.itermonthdays4(2001, 2))
+ self.assertEqual(days[0], (2001, 2, 1, 3))
+ self.assertEqual(days[-1], (2001, 2, 28, 2))
def test_itermonthdays(self):
for firstweekday in range(7):
@@ -846,7 +851,8 @@ def test__all__(self):
blacklist = {'mdays', 'January', 'February', 'EPOCH',
'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY',
'SATURDAY', 'SUNDAY', 'different_locale', 'c',
- 'prweek', 'week', 'format', 'formatstring', 'main'}
+ 'prweek', 'week', 'format', 'formatstring', 'main',
+ 'monthlen', 'prevmonth', 'nextmonth'}
support.check__all__(self, calendar, blacklist=blacklist)
From e794c0637a5911d8cde3165c8f243fe86f2da846 Mon Sep 17 00:00:00 2001
From: Alexander Belopolsky
Date: Mon, 23 Oct 2017 19:52:37 -0400
Subject: [PATCH 2/4] Documented the new calendar methods.
---
Doc/library/calendar.rst | 29 ++++++++++++++++++++++++++---
1 file changed, 26 insertions(+), 3 deletions(-)
diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst
index a415b4792a6bd6b..8fe93fd4de3fa48 100644
--- a/Doc/library/calendar.rst
+++ b/Doc/library/calendar.rst
@@ -53,17 +53,40 @@ it's the base calendar for all computations.
month that are required to get a complete week.
+ .. method:: itermonthdays(year, month)
+
+ Return an iterator for the month *month* in the year *year* similar to
+ :meth:`itermonthdates`, but not restricted by the :class:`datetime.date`
+ range. Days returned will simply be day of the month numbers. For the
+ days outside of the specified month, the day number is ``0``.
+
+
.. method:: itermonthdays2(year, month)
Return an iterator for the month *month* in the year *year* similar to
- :meth:`itermonthdates`. Days returned will be tuples consisting of a day
+ :meth:`itermonthdates`, but not restricted by the :class:`datetime.date`
+ range. Days returned will be tuples consisting of a day of the month
number and a week day number.
- .. method:: itermonthdays(year, month)
+ .. method:: itermonthdays3(year, month)
+
+ Return an iterator for the month *month* in the year *year* similar to
+ :meth:`itermonthdates`, but not restricted by the :class:`datetime.date`
+ range. Days returned will be tuples consisting of a year, a month and a day
+ of the month numbers.
+
+ .. versionadded:: 3.7
+
+
+ .. method:: itermonthdays4(year, month)
Return an iterator for the month *month* in the year *year* similar to
- :meth:`itermonthdates`. Days returned will simply be day numbers.
+ :meth:`itermonthdates`, but not restricted by the :class:`datetime.date`
+ range. Days returned will be tuples consisting of a year, a month, a day
+ of the month, and a day of the week numbers.
+
+ .. versionadded:: 3.7
.. method:: monthdatescalendar(year, month)
From 3862fec4110f238fffc9c5206590489b93e71858 Mon Sep 17 00:00:00 2001
From: Alexander Belopolsky
Date: Mon, 23 Oct 2017 19:54:03 -0400
Subject: [PATCH 3/4] Changed the order of Calendar method definitions.
---
Lib/calendar.py | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/Lib/calendar.py b/Lib/calendar.py
index 73715fb05bb6fed..fb594e0f5b0e1b4 100644
--- a/Lib/calendar.py
+++ b/Lib/calendar.py
@@ -178,6 +178,18 @@ def itermonthdates(self, year, month):
for y, m, d in self.itermonthdays3(year, month):
yield datetime.date(y, m, d)
+ def itermonthdays(self, year, month):
+ """
+ Like itermonthdates(), but will yield day numbers. For days outside
+ the specified month the day number is 0.
+ """
+ day1, ndays = monthrange(year, month)
+ days_before = (day1 - self.firstweekday) % 7
+ yield from repeat(0, days_before)
+ yield from range(1, ndays + 1)
+ days_after = (self.firstweekday - day1 - ndays) % 7
+ yield from repeat(0, days_after)
+
def itermonthdays2(self, year, month):
"""
Like itermonthdates(), but will yield (day number, weekday number)
@@ -212,18 +224,6 @@ def itermonthdays4(self, year, month):
for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)):
yield y, m, d, (self.firstweekday + i) % 7
- def itermonthdays(self, year, month):
- """
- Like itermonthdates(), but will yield day numbers. For days outside
- the specified month the day number is 0.
- """
- day1, ndays = monthrange(year, month)
- days_before = (day1 - self.firstweekday) % 7
- yield from repeat(0, days_before)
- yield from range(1, ndays + 1)
- days_after = (self.firstweekday - day1 - ndays) % 7
- yield from repeat(0, days_after)
-
def monthdatescalendar(self, year, month):
"""
Return a matrix (list of lists) representing a month's calendar.
From b18ed1aacefd505f6e503ccb0d528e551cf06d02 Mon Sep 17 00:00:00 2001
From: Alexander Belopolsky
Date: Mon, 23 Oct 2017 20:03:53 -0400
Subject: [PATCH 4/4] Added a NEWS entry.
---
.../next/Library/2017-10-23-20-03-36.bpo-28292.1Gkim2.rst | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2017-10-23-20-03-36.bpo-28292.1Gkim2.rst
diff --git a/Misc/NEWS.d/next/Library/2017-10-23-20-03-36.bpo-28292.1Gkim2.rst b/Misc/NEWS.d/next/Library/2017-10-23-20-03-36.bpo-28292.1Gkim2.rst
new file mode 100644
index 000000000000000..e0eb53ea51c2974
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-10-23-20-03-36.bpo-28292.1Gkim2.rst
@@ -0,0 +1,5 @@
+Calendar.itermonthdates() will now consistently raise an exception when a
+date falls outside of the 0001-01-01 through 9999-12-31 range. To support
+applications that cannot tolerate such exceptions, the new methods
+itermonthdays3() and itermonthdays4() are added. The new methods return
+tuples and are not restricted by the range supported by datetime.date.