summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/calendar.rst29
-rw-r--r--Lib/calendar.py72
-rw-r--r--Lib/test/test_calendar.py16
-rw-r--r--Misc/NEWS.d/next/Library/2017-10-23-20-03-36.bpo-28292.1Gkim2.rst5
4 files changed, 94 insertions, 28 deletions
diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst
index a415b47..8fe93fd 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)
diff --git a/Lib/calendar.py b/Lib/calendar.py
index 0218e2d..fb594e0 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,20 @@ class Calendar(object):
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 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):
"""
@@ -180,17 +198,31 @@ class Calendar(object):
for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
yield d, i % 7
- def itermonthdays(self, year, month):
+ def itermonthdays3(self, year, month):
"""
- Like itermonthdates(), but will yield day numbers. For days outside
- the specified month the day number is 0.
+ 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
- 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)
+ 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 monthdatescalendar(self, year, month):
"""
diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py
index c777f64..ad8b6bb 100644
--- a/Lib/test/test_calendar.py
+++ b/Lib/test/test_calendar.py
@@ -502,10 +502,15 @@ class CalendarTestCase(unittest.TestCase):
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 @@ class MiscTestCase(unittest.TestCase):
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)
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 0000000..e0eb53e
--- /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.