Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions Doc/library/calendar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
72 changes: 52 additions & 20 deletions Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -157,20 +175,20 @@ 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 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):
"""
Expand All @@ -180,17 +198,31 @@ def itermonthdays2(self, year, month):
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):
"""
Expand Down
16 changes: 11 additions & 5 deletions Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)


Expand Down
Original file line number Diff line number Diff line change
@@ -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.