Kaydet (Commit) fdd9b217 authored tarafından Alexander Belopolsky's avatar Alexander Belopolsky Kaydeden (comit) GitHub

Closes bpo-28292: Implemented Calendar.itermonthdays3() and itermonthdays4(). (#4079)

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.

Thanks @serhiy-storchaka for suggesting the itermonthdays4() method and for the review.
üst eab3ff72
...@@ -53,17 +53,40 @@ it's the base calendar for all computations. ...@@ -53,17 +53,40 @@ it's the base calendar for all computations.
month that are required to get a complete week. 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) .. method:: itermonthdays2(year, month)
Return an iterator for the month *month* in the year *year* similar to 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. 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 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) .. method:: monthdatescalendar(year, month)
......
...@@ -126,6 +126,24 @@ def monthrange(year, month): ...@@ -126,6 +126,24 @@ def monthrange(year, month):
return day1, ndays 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): class Calendar(object):
""" """
Base calendar class. This class doesn't do any formatting. It simply Base calendar class. This class doesn't do any formatting. It simply
...@@ -157,20 +175,20 @@ class Calendar(object): ...@@ -157,20 +175,20 @@ class Calendar(object):
values and will always iterate through complete weeks, so it will yield values and will always iterate through complete weeks, so it will yield
dates outside the specified month. dates outside the specified month.
""" """
date = datetime.date(year, month, 1) for y, m, d in self.itermonthdays3(year, month):
# Go back to the beginning of the week yield datetime.date(y, m, d)
days = (date.weekday() - self.firstweekday) % 7
date -= datetime.timedelta(days=days) def itermonthdays(self, year, month):
oneday = datetime.timedelta(days=1) """
while True: Like itermonthdates(), but will yield day numbers. For days outside
yield date the specified month the day number is 0.
try: """
date += oneday day1, ndays = monthrange(year, month)
except OverflowError: days_before = (day1 - self.firstweekday) % 7
# Adding one day could fail after datetime.MAXYEAR yield from repeat(0, days_before)
break yield from range(1, ndays + 1)
if date.month != month and date.weekday() == self.firstweekday: days_after = (self.firstweekday - day1 - ndays) % 7
break yield from repeat(0, days_after)
def itermonthdays2(self, year, month): def itermonthdays2(self, year, month):
""" """
...@@ -180,17 +198,31 @@ class Calendar(object): ...@@ -180,17 +198,31 @@ class Calendar(object):
for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday): for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
yield d, i % 7 yield d, i % 7
def itermonthdays(self, year, month): def itermonthdays3(self, year, month):
""" """
Like itermonthdates(), but will yield day numbers. For days outside Like itermonthdates(), but will yield (year, month, day) tuples. Can be
the specified month the day number is 0. used for dates outside of datetime.date range.
""" """
day1, ndays = monthrange(year, month) day1, ndays = monthrange(year, month)
days_before = (day1 - self.firstweekday) % 7 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 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): def monthdatescalendar(self, year, month):
""" """
......
...@@ -502,10 +502,15 @@ class CalendarTestCase(unittest.TestCase): ...@@ -502,10 +502,15 @@ class CalendarTestCase(unittest.TestCase):
new_october = calendar.TextCalendar().formatmonthname(2010, 10, 10) new_october = calendar.TextCalendar().formatmonthname(2010, 10, 10)
self.assertEqual(old_october, new_october) self.assertEqual(old_october, new_october)
def test_itermonthdates(self): def test_itermonthdays3(self):
# ensure itermonthdates doesn't overflow after datetime.MAXYEAR # ensure itermonthdays3 doesn't overflow after datetime.MAXYEAR
# see #15421 list(calendar.Calendar().itermonthdays3(datetime.MAXYEAR, 12))
list(calendar.Calendar().itermonthdates(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): def test_itermonthdays(self):
for firstweekday in range(7): for firstweekday in range(7):
...@@ -846,7 +851,8 @@ class MiscTestCase(unittest.TestCase): ...@@ -846,7 +851,8 @@ class MiscTestCase(unittest.TestCase):
blacklist = {'mdays', 'January', 'February', 'EPOCH', blacklist = {'mdays', 'January', 'February', 'EPOCH',
'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY',
'SATURDAY', 'SUNDAY', 'different_locale', 'c', '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) support.check__all__(self, calendar, blacklist=blacklist)
......
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.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment