summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorAlexander Belopolsky <abalkin@users.noreply.github.com>2017-10-24 17:17:10 (GMT)
committerGitHub <noreply@github.com>2017-10-24 17:17:10 (GMT)
commitfdd9b217c60b454ac6a82f02c8b0b551caeac88b (patch)
treed520a2e9344820bf8f7df0f1e0c4ec1882e4eff4 /Lib
parenteab3ff72ebe79416cc032b8508ae13332955a157 (diff)
downloadcpython-fdd9b217c60b454ac6a82f02c8b0b551caeac88b.zip
cpython-fdd9b217c60b454ac6a82f02c8b0b551caeac88b.tar.gz
cpython-fdd9b217c60b454ac6a82f02c8b0b551caeac88b.tar.bz2
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.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/calendar.py72
-rw-r--r--Lib/test/test_calendar.py16
2 files changed, 63 insertions, 25 deletions
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)