blob: 7311a0173729ecc9c858dd43f39dc14e5c2865c8 [file] [log] [blame]
Skip Montanaroad3bc442000-08-30 14:01:28 +00001"""Calendar printing functions
2
3Note when comparing these calendars to the ones printed by cal(1): By
4default, these calendars have Monday as the first day of the week, and
5Sunday as the last (the European convention). Use setfirstweekday() to
6set the first day of the week (0=Monday, 6=Sunday)."""
Guido van Rossumc6360141990-10-13 19:23:40 +00007
Christian Heimes96f31632007-11-12 01:32:03 +00008import sys
9import datetime
10import locale as _locale
Alexander Belopolsky957b7562016-09-27 20:26:39 -040011from itertools import repeat
Guido van Rossumc6360141990-10-13 19:23:40 +000012
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000013__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
14 "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
15 "monthcalendar", "prmonth", "month", "prcal", "calendar",
Martin Panter4eb376c2016-01-16 06:49:30 +000016 "timegm", "month_name", "month_abbr", "day_name", "day_abbr",
17 "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
18 "LocaleHTMLCalendar", "weekheader"]
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000019
Guido van Rossumc6360141990-10-13 19:23:40 +000020# Exception raised for bad input (with string parameter for details)
Guido van Rossum00245cf1999-05-03 18:07:40 +000021error = ValueError
Guido van Rossumc6360141990-10-13 19:23:40 +000022
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000023# Exceptions raised for bad input
24class IllegalMonthError(ValueError):
25 def __init__(self, month):
26 self.month = month
27 def __str__(self):
28 return "bad month number %r; must be 1-12" % self.month
29
30
31class IllegalWeekdayError(ValueError):
32 def __init__(self, weekday):
33 self.weekday = weekday
34 def __str__(self):
35 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
36
37
Guido van Rossum9b3bc711993-06-20 21:02:22 +000038# Constants for months referenced later
39January = 1
40February = 2
41
42# Number of days per month (except for February in leap years)
43mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
44
Tim Peters0c2c8e72002-03-23 03:26:53 +000045# This module used to have hard-coded lists of day and month names, as
46# English strings. The classes following emulate a read-only version of
47# that, but supply localized names. Note that the values are computed
48# fresh on each call, in case the user changes locale between calls.
49
Raymond Hettinger9c051d72002-06-20 03:38:12 +000050class _localized_month:
Tim Petersbbc0d442004-11-13 16:18:32 +000051
Guido van Rossum805365e2007-05-07 22:24:25 +000052 _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
Tim Petersbbc0d442004-11-13 16:18:32 +000053 _months.insert(0, lambda x: "")
54
Tim Peters0c2c8e72002-03-23 03:26:53 +000055 def __init__(self, format):
Barry Warsaw1d099102001-05-22 15:58:30 +000056 self.format = format
Tim Peters0c2c8e72002-03-23 03:26:53 +000057
58 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000059 funcs = self._months[i]
60 if isinstance(i, slice):
61 return [f(self.format) for f in funcs]
62 else:
63 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000064
Skip Montanaro4c834952002-03-15 04:08:38 +000065 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000066 return 13
67
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000068
Raymond Hettinger9c051d72002-06-20 03:38:12 +000069class _localized_day:
Tim Petersbbc0d442004-11-13 16:18:32 +000070
71 # January 1, 2001, was a Monday.
Guido van Rossum805365e2007-05-07 22:24:25 +000072 _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
Tim Petersbbc0d442004-11-13 16:18:32 +000073
Tim Peters0c2c8e72002-03-23 03:26:53 +000074 def __init__(self, format):
75 self.format = format
76
77 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000078 funcs = self._days[i]
79 if isinstance(i, slice):
80 return [f(self.format) for f in funcs]
81 else:
82 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000083
Neal Norwitz492faa52004-06-07 03:47:06 +000084 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000085 return 7
Barry Warsaw1d099102001-05-22 15:58:30 +000086
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000087
Guido van Rossum9b3bc711993-06-20 21:02:22 +000088# Full and abbreviated names of weekdays
Tim Peters0c2c8e72002-03-23 03:26:53 +000089day_name = _localized_day('%A')
90day_abbr = _localized_day('%a')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000091
Guido van Rossum5cfa5df1993-06-23 09:30:50 +000092# Full and abbreviated names of months (1-based arrays!!!)
Tim Peters0c2c8e72002-03-23 03:26:53 +000093month_name = _localized_month('%B')
94month_abbr = _localized_month('%b')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000095
Skip Montanaroad3bc442000-08-30 14:01:28 +000096# Constants for weekdays
97(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
98
Skip Montanaroad3bc442000-08-30 14:01:28 +000099
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000100def isleap(year):
Alexander Belopolskyf87cc042010-10-19 17:43:50 +0000101 """Return True for leap years, False for non-leap years."""
Fred Drake8152d322000-12-12 23:20:45 +0000102 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000103
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000104
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000105def leapdays(y1, y2):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000106 """Return number of leap years in range [y1, y2).
Guido van Rossum46735ad2000-10-09 12:42:04 +0000107 Assume y1 <= y2."""
108 y1 -= 1
109 y2 -= 1
Raymond Hettingere11b5102002-12-25 16:37:19 +0000110 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000111
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000112
Guido van Rossumc6360141990-10-13 19:23:40 +0000113def weekday(year, month, day):
Alexander Belopolsky66c88ce2017-10-26 15:34:11 -0400114 """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
Raymond Hettingere11b5102002-12-25 16:37:19 +0000117 return datetime.date(year, month, day).weekday()
Guido van Rossumc6360141990-10-13 19:23:40 +0000118
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000119
Guido van Rossumc6360141990-10-13 19:23:40 +0000120def monthrange(year, month):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000121 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
122 year, month."""
123 if not 1 <= month <= 12:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000124 raise IllegalMonthError(month)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000125 day1 = weekday(year, month, 1)
126 ndays = mdays[month] + (month == February and isleap(year))
127 return day1, ndays
Guido van Rossumc6360141990-10-13 19:23:40 +0000128
Guido van Rossumc6360141990-10-13 19:23:40 +0000129
Raymond Hettingerb1c8ec02019-08-04 13:14:03 -0700130def _monthlen(year, month):
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400131 return mdays[month] + (month == February and isleap(year))
132
133
Raymond Hettingerb1c8ec02019-08-04 13:14:03 -0700134def _prevmonth(year, month):
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400135 if month == 1:
136 return year-1, 12
137 else:
138 return year, month-1
139
140
Raymond Hettingerb1c8ec02019-08-04 13:14:03 -0700141def _nextmonth(year, month):
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400142 if month == 12:
143 return year+1, 1
144 else:
145 return year, month+1
146
147
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000148class Calendar(object):
149 """
150 Base calendar class. This class doesn't do any formatting. It simply
151 provides data to subclasses.
152 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000153
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000154 def __init__(self, firstweekday=0):
155 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
156
157 def getfirstweekday(self):
158 return self._firstweekday % 7
159
160 def setfirstweekday(self, firstweekday):
161 self._firstweekday = firstweekday
162
163 firstweekday = property(getfirstweekday, setfirstweekday)
164
165 def iterweekdays(self):
166 """
Martin Panter7462b6492015-11-02 03:37:02 +0000167 Return an iterator for one week of weekday numbers starting with the
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000168 configured first one.
169 """
Guido van Rossum805365e2007-05-07 22:24:25 +0000170 for i in range(self.firstweekday, self.firstweekday + 7):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000171 yield i%7
172
173 def itermonthdates(self, year, month):
174 """
175 Return an iterator for one month. The iterator will yield datetime.date
176 values and will always iterate through complete weeks, so it will yield
177 dates outside the specified month.
178 """
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400179 for y, m, d in self.itermonthdays3(year, month):
180 yield datetime.date(y, m, d)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000181
182 def itermonthdays(self, year, month):
183 """
Christian Heimes77c02eb2008-02-09 02:18:51 +0000184 Like itermonthdates(), but will yield day numbers. For days outside
185 the specified month the day number is 0.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000186 """
Alexander Belopolsky957b7562016-09-27 20:26:39 -0400187 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)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000193
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400194 def itermonthdays2(self, year, month):
195 """
196 Like itermonthdates(), but will yield (day number, weekday number)
197 tuples. For days outside the specified month the day number is 0.
198 """
199 for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
200 yield d, i % 7
201
202 def itermonthdays3(self, year, month):
203 """
204 Like itermonthdates(), but will yield (year, month, day) tuples. Can be
205 used for dates outside of datetime.date range.
206 """
207 day1, ndays = monthrange(year, month)
208 days_before = (day1 - self.firstweekday) % 7
209 days_after = (self.firstweekday - day1 - ndays) % 7
Raymond Hettingerb1c8ec02019-08-04 13:14:03 -0700210 y, m = _prevmonth(year, month)
211 end = _monthlen(y, m) + 1
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400212 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
Raymond Hettingerb1c8ec02019-08-04 13:14:03 -0700216 y, m = _nextmonth(year, month)
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400217 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
227
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000228 def monthdatescalendar(self, year, month):
229 """
230 Return a matrix (list of lists) representing a month's calendar.
231 Each row represents a week; week entries are datetime.date values.
232 """
233 dates = list(self.itermonthdates(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000234 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000235
236 def monthdays2calendar(self, year, month):
237 """
238 Return a matrix representing a month's calendar.
239 Each row represents a week; week entries are
240 (day number, weekday number) tuples. Day numbers outside this month
241 are zero.
242 """
243 days = list(self.itermonthdays2(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000244 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000245
246 def monthdayscalendar(self, year, month):
247 """
248 Return a matrix representing a month's calendar.
249 Each row represents a week; days outside this month are zero.
250 """
251 days = list(self.itermonthdays(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000252 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000253
254 def yeardatescalendar(self, year, width=3):
255 """
256 Return the data for the specified year ready for formatting. The return
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300257 value is a list of month rows. Each month row contains up to width months.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000258 Each month contains between 4 and 6 weeks and each week contains 1-7
259 days. Days are datetime.date objects.
260 """
261 months = [
262 self.monthdatescalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000263 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000264 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000265 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000266
267 def yeardays2calendar(self, year, width=3):
268 """
269 Return the data for the specified year ready for formatting (similar to
270 yeardatescalendar()). Entries in the week lists are
271 (day number, weekday number) tuples. Day numbers outside this month are
272 zero.
273 """
274 months = [
275 self.monthdays2calendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000276 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000277 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000278 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000279
280 def yeardayscalendar(self, year, width=3):
281 """
282 Return the data for the specified year ready for formatting (similar to
283 yeardatescalendar()). Entries in the week lists are day numbers.
284 Day numbers outside this month are zero.
285 """
286 months = [
287 self.monthdayscalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000288 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000289 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000290 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000291
292
293class TextCalendar(Calendar):
294 """
295 Subclass of Calendar that outputs a calendar as a simple plain text
296 similar to the UNIX program cal.
297 """
298
299 def prweek(self, theweek, width):
300 """
301 Print a single week (no newline).
302 """
Serhiy Storchaka0595ed22016-10-25 15:20:58 +0300303 print(self.formatweek(theweek, width), end='')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000304
305 def formatday(self, day, weekday, width):
306 """
307 Returns a formatted day.
308 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000309 if day == 0:
310 s = ''
311 else:
312 s = '%2i' % day # right-align single-digit days
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000313 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000314
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000315 def formatweek(self, theweek, width):
316 """
317 Returns a single week in a string (no newline).
318 """
319 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000320
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000321 def formatweekday(self, day, width):
322 """
323 Returns a formatted week day name.
324 """
325 if width >= 9:
326 names = day_name
327 else:
328 names = day_abbr
329 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000330
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000331 def formatweekheader(self, width):
332 """
333 Return a header for a week.
334 """
335 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000336
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000337 def formatmonthname(self, theyear, themonth, width, withyear=True):
338 """
339 Return a formatted month name.
340 """
341 s = month_name[themonth]
342 if withyear:
343 s = "%s %r" % (s, theyear)
344 return s.center(width)
345
346 def prmonth(self, theyear, themonth, w=0, l=0):
347 """
348 Print a month's calendar.
349 """
Serhiy Storchaka7ff51bd2016-10-25 15:00:52 +0300350 print(self.formatmonth(theyear, themonth, w, l), end='')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000351
352 def formatmonth(self, theyear, themonth, w=0, l=0):
353 """
354 Return a month's calendar string (multi-line).
355 """
356 w = max(2, w)
357 l = max(1, l)
358 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
359 s = s.rstrip()
360 s += '\n' * l
361 s += self.formatweekheader(w).rstrip()
362 s += '\n' * l
363 for week in self.monthdays2calendar(theyear, themonth):
364 s += self.formatweek(week, w).rstrip()
365 s += '\n' * l
366 return s
367
368 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
369 """
370 Returns a year's calendar as a multi-line string.
371 """
372 w = max(2, w)
373 l = max(1, l)
374 c = max(2, c)
375 colwidth = (w + 1) * 7 - 1
376 v = []
377 a = v.append
378 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
379 a('\n'*l)
380 header = self.formatweekheader(w)
381 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
382 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000383 months = range(m*i+1, min(m*(i+1)+1, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000384 a('\n'*l)
385 names = (self.formatmonthname(theyear, k, colwidth, False)
386 for k in months)
387 a(formatstring(names, colwidth, c).rstrip())
388 a('\n'*l)
389 headers = (header for k in months)
390 a(formatstring(headers, colwidth, c).rstrip())
391 a('\n'*l)
392 # max number of weeks for this row
393 height = max(len(cal) for cal in row)
Guido van Rossum805365e2007-05-07 22:24:25 +0000394 for j in range(height):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000395 weeks = []
396 for cal in row:
397 if j >= len(cal):
398 weeks.append('')
399 else:
400 weeks.append(self.formatweek(cal[j], w))
401 a(formatstring(weeks, colwidth, c).rstrip())
402 a('\n' * l)
403 return ''.join(v)
404
405 def pryear(self, theyear, w=0, l=0, c=6, m=3):
406 """Print a year's calendar."""
Serhiy Storchaka0595ed22016-10-25 15:20:58 +0300407 print(self.formatyear(theyear, w, l, c, m), end='')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000408
409
410class HTMLCalendar(Calendar):
411 """
412 This calendar returns complete HTML pages.
413 """
414
415 # CSS classes for the day <td>s
416 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
417
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200418 # 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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000436 def formatday(self, day, weekday):
437 """
438 Return a day as a table cell.
439 """
440 if day == 0:
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200441 # day outside month
442 return '<td class="%s">&nbsp;</td>' % self.cssclass_noday
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000443 else:
444 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
445
446 def formatweek(self, theweek):
447 """
448 Return a complete week as a table row.
449 """
450 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
451 return '<tr>%s</tr>' % s
452
453 def formatweekday(self, day):
454 """
455 Return a weekday name as a table header.
456 """
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200457 return '<th class="%s">%s</th>' % (
458 self.cssclasses_weekday_head[day], day_abbr[day])
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000459
460 def formatweekheader(self):
461 """
462 Return a header for a week as a table row.
463 """
464 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
465 return '<tr>%s</tr>' % s
466
467 def formatmonthname(self, theyear, themonth, withyear=True):
468 """
469 Return a month name as a table row.
470 """
471 if withyear:
472 s = '%s %s' % (month_name[themonth], theyear)
473 else:
474 s = '%s' % month_name[themonth]
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200475 return '<tr><th colspan="7" class="%s">%s</th></tr>' % (
476 self.cssclass_month_head, s)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000477
478 def formatmonth(self, theyear, themonth, withyear=True):
479 """
480 Return a formatted month as a table.
481 """
482 v = []
483 a = v.append
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200484 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % (
485 self.cssclass_month))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000486 a('\n')
487 a(self.formatmonthname(theyear, themonth, withyear=withyear))
488 a('\n')
489 a(self.formatweekheader())
490 a('\n')
491 for week in self.monthdays2calendar(theyear, themonth):
492 a(self.formatweek(week))
493 a('\n')
494 a('</table>')
495 a('\n')
496 return ''.join(v)
497
498 def formatyear(self, theyear, width=3):
499 """
500 Return a formatted year as a table of tables.
501 """
502 v = []
503 a = v.append
504 width = max(width, 1)
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200505 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' %
506 self.cssclass_year)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000507 a('\n')
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200508 a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
509 width, self.cssclass_year_head, theyear))
Guido van Rossum805365e2007-05-07 22:24:25 +0000510 for i in range(January, January+12, width):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000511 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000512 months = range(i, min(i+width, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000513 a('<tr>')
514 for m in months:
515 a('<td>')
516 a(self.formatmonth(theyear, m, withyear=False))
517 a('</td>')
518 a('</tr>')
519 a('</table>')
520 return ''.join(v)
521
522 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
523 """
524 Return a formatted year as a complete HTML page.
525 """
526 if encoding is None:
527 encoding = sys.getdefaultencoding()
528 v = []
529 a = v.append
530 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
531 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
532 a('<html>\n')
533 a('<head>\n')
534 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
535 if css is not None:
536 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
Thomas Wouters47b49bf2007-08-30 22:15:33 +0000537 a('<title>Calendar for %d</title>\n' % theyear)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000538 a('</head>\n')
539 a('<body>\n')
540 a(self.formatyear(theyear, width))
541 a('</body>\n')
542 a('</html>\n')
543 return ''.join(v).encode(encoding, "xmlcharrefreplace")
544
545
Georg Brandlfd680872008-06-08 08:40:05 +0000546class different_locale:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000547 def __init__(self, locale):
548 self.locale = locale
549
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000550 def __enter__(self):
Georg Brandl7004bd12010-10-19 18:54:25 +0000551 self.oldlocale = _locale.getlocale(_locale.LC_TIME)
552 _locale.setlocale(_locale.LC_TIME, self.locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000553
554 def __exit__(self, *args):
Christian Heimes96f31632007-11-12 01:32:03 +0000555 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000556
557
558class LocaleTextCalendar(TextCalendar):
559 """
560 This class can be passed a locale name in the constructor and will return
561 month and weekday names in the specified locale. If this locale includes
562 an encoding all strings containing month and weekday names will be returned
563 as unicode.
564 """
565
566 def __init__(self, firstweekday=0, locale=None):
567 TextCalendar.__init__(self, firstweekday)
568 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000569 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000570 self.locale = locale
571
572 def formatweekday(self, day, width):
Georg Brandlfd680872008-06-08 08:40:05 +0000573 with different_locale(self.locale):
Srinivas Reddy Thatiparthy (శ్రీనివాస్ రెడ్డి తాటిపర్తి)85339f52020-06-02 17:03:09 +0530574 return super().formatweekday(day, width)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000575
576 def formatmonthname(self, theyear, themonth, width, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000577 with different_locale(self.locale):
Srinivas Reddy Thatiparthy (శ్రీనివాస్ రెడ్డి తాటిపర్తి)85339f52020-06-02 17:03:09 +0530578 return super().formatmonthname(theyear, themonth, width, withyear)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000579
580
581class LocaleHTMLCalendar(HTMLCalendar):
582 """
583 This class can be passed a locale name in the constructor and will return
584 month and weekday names in the specified locale. If this locale includes
585 an encoding all strings containing month and weekday names will be returned
586 as unicode.
587 """
588 def __init__(self, firstweekday=0, locale=None):
589 HTMLCalendar.__init__(self, firstweekday)
590 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000591 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000592 self.locale = locale
593
594 def formatweekday(self, day):
Georg Brandlfd680872008-06-08 08:40:05 +0000595 with different_locale(self.locale):
Srinivas Reddy Thatiparthy (శ్రీనివాస్ రెడ్డి తాటిపర్తి)85339f52020-06-02 17:03:09 +0530596 return super().formatweekday(day)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000597
598 def formatmonthname(self, theyear, themonth, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000599 with different_locale(self.locale):
Srinivas Reddy Thatiparthy (శ్రీనివాస్ రెడ్డి తాటిపర్తి)85339f52020-06-02 17:03:09 +0530600 return super().formatmonthname(theyear, themonth, withyear)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000601
602# Support for old module level interface
603c = TextCalendar()
604
605firstweekday = c.getfirstweekday
606
607def setfirstweekday(firstweekday):
608 if not MONDAY <= firstweekday <= SUNDAY:
609 raise IllegalWeekdayError(firstweekday)
610 c.firstweekday = firstweekday
611
612monthcalendar = c.monthdayscalendar
613prweek = c.prweek
614week = c.formatweek
615weekheader = c.formatweekheader
616prmonth = c.prmonth
617month = c.formatmonth
618calendar = c.formatyear
619prcal = c.pryear
620
621
622# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000623_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000624_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000625
Guido van Rossumc6360141990-10-13 19:23:40 +0000626
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000627def format(cols, colwidth=_colwidth, spacing=_spacing):
628 """Prints multi-column formatting for year calendars"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000629 print(formatstring(cols, colwidth, spacing))
Skip Montanaroad3bc442000-08-30 14:01:28 +0000630
Skip Montanaroad3bc442000-08-30 14:01:28 +0000631
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000632def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
633 """Returns a string formatted from n strings, centered within n columns."""
634 spacing *= ' '
635 return spacing.join(c.center(colwidth) for c in cols)
636
Guido van Rossumb39aff81999-06-09 15:07:38 +0000637
Guido van Rossumb39aff81999-06-09 15:07:38 +0000638EPOCH = 1970
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000639_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
640
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000641
Guido van Rossumb39aff81999-06-09 15:07:38 +0000642def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000643 """Unrelated but handy function to calculate Unix timestamp from GMT."""
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000644 year, month, day, hour, minute, second = tuple[:6]
645 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
646 hours = days*24 + hour
647 minutes = hours*60 + minute
648 seconds = minutes*60 + second
649 return seconds
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000650
651
652def main(args):
Serhiy Storchaka97852612015-11-01 17:14:27 +0200653 import argparse
654 parser = argparse.ArgumentParser()
655 textgroup = parser.add_argument_group('text only arguments')
656 htmlgroup = parser.add_argument_group('html only arguments')
657 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000658 "-w", "--width",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200659 type=int, default=2,
660 help="width of date column (default 2)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000661 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200662 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000663 "-l", "--lines",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200664 type=int, default=1,
665 help="number of lines for each week (default 1)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000666 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200667 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000668 "-s", "--spacing",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200669 type=int, default=6,
670 help="spacing between months (default 6)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000671 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200672 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000673 "-m", "--months",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200674 type=int, default=3,
675 help="months per row (default 3)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000676 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200677 htmlgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000678 "-c", "--css",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200679 default="calendar.css",
680 help="CSS to use for page"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000681 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200682 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000683 "-L", "--locale",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200684 default=None,
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000685 help="locale to be used from month and weekday names"
686 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200687 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000688 "-e", "--encoding",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200689 default=None,
690 help="encoding to use for output"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000691 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200692 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000693 "-t", "--type",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200694 default="text",
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000695 choices=("text", "html"),
696 help="output type (text or html)"
697 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200698 parser.add_argument(
699 "year",
700 nargs='?', type=int,
701 help="year number (1-9999)"
702 )
703 parser.add_argument(
704 "month",
705 nargs='?', type=int,
706 help="month number (1-12, text only)"
707 )
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000708
Serhiy Storchaka97852612015-11-01 17:14:27 +0200709 options = parser.parse_args(args[1:])
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000710
711 if options.locale and not options.encoding:
712 parser.error("if --locale is specified --encoding is required")
713 sys.exit(1)
714
Christian Heimes96f31632007-11-12 01:32:03 +0000715 locale = options.locale, options.encoding
716
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000717 if options.type == "html":
718 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000719 cal = LocaleHTMLCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000720 else:
721 cal = HTMLCalendar()
722 encoding = options.encoding
723 if encoding is None:
724 encoding = sys.getdefaultencoding()
725 optdict = dict(encoding=encoding, css=options.css)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800726 write = sys.stdout.buffer.write
Serhiy Storchaka97852612015-11-01 17:14:27 +0200727 if options.year is None:
Senthil Kumaran962fed92011-08-11 09:22:52 +0800728 write(cal.formatyearpage(datetime.date.today().year, **optdict))
Serhiy Storchaka97852612015-11-01 17:14:27 +0200729 elif options.month is None:
730 write(cal.formatyearpage(options.year, **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000731 else:
732 parser.error("incorrect number of arguments")
733 sys.exit(1)
734 else:
735 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000736 cal = LocaleTextCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000737 else:
738 cal = TextCalendar()
739 optdict = dict(w=options.width, l=options.lines)
Serhiy Storchaka97852612015-11-01 17:14:27 +0200740 if options.month is None:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000741 optdict["c"] = options.spacing
742 optdict["m"] = options.months
Serhiy Storchaka97852612015-11-01 17:14:27 +0200743 if options.year is None:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000744 result = cal.formatyear(datetime.date.today().year, **optdict)
Serhiy Storchaka97852612015-11-01 17:14:27 +0200745 elif options.month is None:
746 result = cal.formatyear(options.year, **optdict)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000747 else:
Serhiy Storchaka97852612015-11-01 17:14:27 +0200748 result = cal.formatmonth(options.year, options.month, **optdict)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800749 write = sys.stdout.write
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000750 if options.encoding:
751 result = result.encode(options.encoding)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800752 write = sys.stdout.buffer.write
753 write(result)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000754
755
756if __name__ == "__main__":
757 main(sys.argv)