@@ -111,8 +111,9 @@ def leapdays(y1, y2):
111111
112112
113113def weekday (year , month , day ):
114- """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
115- day (1-31)."""
114+ """Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31)."""
115+ if not datetime .MINYEAR <= year <= datetime .MAXYEAR :
116+ year = 2000 + year % 400
116117 return datetime .date (year , month , day ).weekday ()
117118
118119
@@ -126,6 +127,24 @@ def monthrange(year, month):
126127 return day1 , ndays
127128
128129
130+ def _monthlen (year , month ):
131+ return mdays [month ] + (month == February and isleap (year ))
132+
133+
134+ def _prevmonth (year , month ):
135+ if month == 1 :
136+ return year - 1 , 12
137+ else :
138+ return year , month - 1
139+
140+
141+ def _nextmonth (year , month ):
142+ if month == 12 :
143+ return year + 1 , 1
144+ else :
145+ return year , month + 1
146+
147+
129148class Calendar (object ):
130149 """
131150 Base calendar class. This class doesn't do any formatting. It simply
@@ -157,20 +176,20 @@ def itermonthdates(self, year, month):
157176 values and will always iterate through complete weeks, so it will yield
158177 dates outside the specified month.
159178 """
160- date = datetime . date (year , month , 1 )
161- # Go back to the beginning of the week
162- days = ( date . weekday () - self . firstweekday ) % 7
163- date -= datetime . timedelta ( days = days )
164- oneday = datetime . timedelta ( days = 1 )
165- while True :
166- yield date
167- try :
168- date += oneday
169- except OverflowError :
170- # Adding one day could fail after datetime.MAXYEAR
171- break
172- if date . month != month and date . weekday () == self . firstweekday :
173- break
179+ for y , m , d in self . itermonthdays3 (year , month ):
180+ yield datetime . date ( y , m , d )
181+
182+ def itermonthdays ( self , year , month ):
183+ """
184+ Like itermonthdates(), but will yield day numbers. For days outside
185+ the specified month the day number is 0.
186+ """
187+ day1 , ndays = monthrange ( year , month )
188+ days_before = ( day1 - self . firstweekday ) % 7
189+ yield from repeat ( 0 , days_before )
190+ yield from range ( 1 , ndays + 1 )
191+ days_after = ( self . firstweekday - day1 - ndays ) % 7
192+ yield from repeat ( 0 , days_after )
174193
175194 def itermonthdays2 (self , year , month ):
176195 """
@@ -180,17 +199,31 @@ def itermonthdays2(self, year, month):
180199 for i , d in enumerate (self .itermonthdays (year , month ), self .firstweekday ):
181200 yield d , i % 7
182201
183- def itermonthdays (self , year , month ):
202+ def itermonthdays3 (self , year , month ):
184203 """
185- Like itermonthdates(), but will yield day numbers. For days outside
186- the specified month the day number is 0 .
204+ Like itermonthdates(), but will yield (year, month, day) tuples. Can be
205+ used for dates outside of datetime.date range .
187206 """
188207 day1 , ndays = monthrange (year , month )
189208 days_before = (day1 - self .firstweekday ) % 7
190- yield from repeat (0 , days_before )
191- yield from range (1 , ndays + 1 )
192209 days_after = (self .firstweekday - day1 - ndays ) % 7
193- yield from repeat (0 , days_after )
210+ y , m = _prevmonth (year , month )
211+ end = _monthlen (y , m ) + 1
212+ for d in range (end - days_before , end ):
213+ yield y , m , d
214+ for d in range (1 , ndays + 1 ):
215+ yield year , month , d
216+ y , m = _nextmonth (year , month )
217+ for d in range (1 , days_after + 1 ):
218+ yield y , m , d
219+
220+ def itermonthdays4 (self , year , month ):
221+ """
222+ Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.
223+ Can be used for dates outside of datetime.date range.
224+ """
225+ for i , (y , m , d ) in enumerate (self .itermonthdays3 (year , month )):
226+ yield y , m , d , (self .firstweekday + i ) % 7
194227
195228 def monthdatescalendar (self , year , month ):
196229 """
@@ -267,7 +300,7 @@ def prweek(self, theweek, width):
267300 """
268301 Print a single week (no newline).
269302 """
270- print (self .formatweek (theweek , width ), end = ' ' )
303+ print (self .formatweek (theweek , width ), end = '' )
271304
272305 def formatday (self , day , weekday , width ):
273306 """
@@ -371,7 +404,7 @@ def formatyear(self, theyear, w=2, l=1, c=6, m=3):
371404
372405 def pryear (self , theyear , w = 0 , l = 0 , c = 6 , m = 3 ):
373406 """Print a year's calendar."""
374- print (self .formatyear (theyear , w , l , c , m ))
407+ print (self .formatyear (theyear , w , l , c , m ), end = '' )
375408
376409
377410class HTMLCalendar (Calendar ):
@@ -382,12 +415,31 @@ class HTMLCalendar(Calendar):
382415 # CSS classes for the day <td>s
383416 cssclasses = ["mon" , "tue" , "wed" , "thu" , "fri" , "sat" , "sun" ]
384417
418+ # CSS classes for the day <th>s
419+ cssclasses_weekday_head = cssclasses
420+
421+ # CSS class for the days before and after current month
422+ cssclass_noday = "noday"
423+
424+ # CSS class for the month's head
425+ cssclass_month_head = "month"
426+
427+ # CSS class for the month
428+ cssclass_month = "month"
429+
430+ # CSS class for the year's table head
431+ cssclass_year_head = "year"
432+
433+ # CSS class for the whole year table
434+ cssclass_year = "year"
435+
385436 def formatday (self , day , weekday ):
386437 """
387438 Return a day as a table cell.
388439 """
389440 if day == 0 :
390- return '<td class="noday"> </td>' # day outside month
441+ # day outside month
442+ return '<td class="%s"> </td>' % self .cssclass_noday
391443 else :
392444 return '<td class="%s">%d</td>' % (self .cssclasses [weekday ], day )
393445
@@ -402,7 +454,8 @@ def formatweekday(self, day):
402454 """
403455 Return a weekday name as a table header.
404456 """
405- return '<th class="%s">%s</th>' % (self .cssclasses [day ], day_abbr [day ])
457+ return '<th class="%s">%s</th>' % (
458+ self .cssclasses_weekday_head [day ], day_abbr [day ])
406459
407460 def formatweekheader (self ):
408461 """
@@ -419,15 +472,17 @@ def formatmonthname(self, theyear, themonth, withyear=True):
419472 s = '%s %s' % (month_name [themonth ], theyear )
420473 else :
421474 s = '%s' % month_name [themonth ]
422- return '<tr><th colspan="7" class="month">%s</th></tr>' % s
475+ return '<tr><th colspan="7" class="%s">%s</th></tr>' % (
476+ self .cssclass_month_head , s )
423477
424478 def formatmonth (self , theyear , themonth , withyear = True ):
425479 """
426480 Return a formatted month as a table.
427481 """
428482 v = []
429483 a = v .append
430- a ('<table border="0" cellpadding="0" cellspacing="0" class="month">' )
484+ a ('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % (
485+ self .cssclass_month ))
431486 a ('\n ' )
432487 a (self .formatmonthname (theyear , themonth , withyear = withyear ))
433488 a ('\n ' )
@@ -447,9 +502,11 @@ def formatyear(self, theyear, width=3):
447502 v = []
448503 a = v .append
449504 width = max (width , 1 )
450- a ('<table border="0" cellpadding="0" cellspacing="0" class="year">' )
505+ a ('<table border="0" cellpadding="0" cellspacing="0" class="%s">' %
506+ self .cssclass_year )
451507 a ('\n ' )
452- a ('<tr><th colspan="%d" class="year">%s</th></tr>' % (width , theyear ))
508+ a ('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
509+ width , self .cssclass_year_head , theyear ))
453510 for i in range (January , January + 12 , width ):
454511 # months in this row
455512 months = range (i , min (i + width , 13 ))
0 commit comments