blob: fb594e0f5b0e1b41d3fbb837633c83f7085562b6 [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):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000114 """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
115 day (1-31)."""
Raymond Hettingere11b5102002-12-25 16:37:19 +0000116 return datetime.date(year, month, day).weekday()
Guido van Rossumc6360141990-10-13 19:23:40 +0000117
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000118
Guido van Rossumc6360141990-10-13 19:23:40 +0000119def monthrange(year, month):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000120 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
121 year, month."""
122 if not 1 <= month <= 12:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000123 raise IllegalMonthError(month)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000124 day1 = weekday(year, month, 1)
125 ndays = mdays[month] + (month == February and isleap(year))
126 return day1, ndays
Guido van Rossumc6360141990-10-13 19:23:40 +0000127
Guido van Rossumc6360141990-10-13 19:23:40 +0000128
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400129def monthlen(year, month):
130 return mdays[month] + (month == February and isleap(year))
131
132
133def prevmonth(year, month):
134 if month == 1:
135 return year-1, 12
136 else:
137 return year, month-1
138
139
140def nextmonth(year, month):
141 if month == 12:
142 return year+1, 1
143 else:
144 return year, month+1
145
146
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000147class Calendar(object):
148 """
149 Base calendar class. This class doesn't do any formatting. It simply
150 provides data to subclasses.
151 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000152
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000153 def __init__(self, firstweekday=0):
154 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
155
156 def getfirstweekday(self):
157 return self._firstweekday % 7
158
159 def setfirstweekday(self, firstweekday):
160 self._firstweekday = firstweekday
161
162 firstweekday = property(getfirstweekday, setfirstweekday)
163
164 def iterweekdays(self):
165 """
Martin Panter7462b6492015-11-02 03:37:02 +0000166 Return an iterator for one week of weekday numbers starting with the
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000167 configured first one.
168 """
Guido van Rossum805365e2007-05-07 22:24:25 +0000169 for i in range(self.firstweekday, self.firstweekday + 7):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000170 yield i%7
171
172 def itermonthdates(self, year, month):
173 """
174 Return an iterator for one month. The iterator will yield datetime.date
175 values and will always iterate through complete weeks, so it will yield
176 dates outside the specified month.
177 """
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400178 for y, m, d in self.itermonthdays3(year, month):
179 yield datetime.date(y, m, d)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000180
181 def itermonthdays(self, year, month):
182 """
Christian Heimes77c02eb2008-02-09 02:18:51 +0000183 Like itermonthdates(), but will yield day numbers. For days outside
184 the specified month the day number is 0.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000185 """
Alexander Belopolsky957b7562016-09-27 20:26:39 -0400186 day1, ndays = monthrange(year, month)
187 days_before = (day1 - self.firstweekday) % 7
188 yield from repeat(0, days_before)
189 yield from range(1, ndays + 1)
190 days_after = (self.firstweekday - day1 - ndays) % 7
191 yield from repeat(0, days_after)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000192
Alexander Belopolskyfdd9b212017-10-24 13:17:10 -0400193 def itermonthdays2(self, year, month):
194 """
195 Like itermonthdates(), but will yield (day number, weekday number)
196 tuples. For days outside the specified month the day number is 0.
197 """
198 for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
199 yield d, i % 7
200
201 def itermonthdays3(self, year, month):
202 """
203 Like itermonthdates(), but will yield (year, month, day) tuples. Can be
204 used for dates outside of datetime.date range.
205 """
206 day1, ndays = monthrange(year, month)
207 days_before = (day1 - self.firstweekday) % 7
208 days_after = (self.firstweekday - day1 - ndays) % 7
209 y, m = prevmonth(year, month)
210 end = monthlen(y, m) + 1
211 for d in range(end-days_before, end):
212 yield y, m, d
213 for d in range(1, ndays + 1):
214 yield year, month, d
215 y, m = nextmonth(year, month)
216 for d in range(1, days_after + 1):
217 yield y, m, d
218
219 def itermonthdays4(self, year, month):
220 """
221 Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.
222 Can be used for dates outside of datetime.date range.
223 """
224 for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)):
225 yield y, m, d, (self.firstweekday + i) % 7
226
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000227 def monthdatescalendar(self, year, month):
228 """
229 Return a matrix (list of lists) representing a month's calendar.
230 Each row represents a week; week entries are datetime.date values.
231 """
232 dates = list(self.itermonthdates(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000233 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000234
235 def monthdays2calendar(self, year, month):
236 """
237 Return a matrix representing a month's calendar.
238 Each row represents a week; week entries are
239 (day number, weekday number) tuples. Day numbers outside this month
240 are zero.
241 """
242 days = list(self.itermonthdays2(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000243 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000244
245 def monthdayscalendar(self, year, month):
246 """
247 Return a matrix representing a month's calendar.
248 Each row represents a week; days outside this month are zero.
249 """
250 days = list(self.itermonthdays(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000251 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000252
253 def yeardatescalendar(self, year, width=3):
254 """
255 Return the data for the specified year ready for formatting. The return
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300256 value is a list of month rows. Each month row contains up to width months.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000257 Each month contains between 4 and 6 weeks and each week contains 1-7
258 days. Days are datetime.date objects.
259 """
260 months = [
261 self.monthdatescalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000262 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000263 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000264 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000265
266 def yeardays2calendar(self, year, width=3):
267 """
268 Return the data for the specified year ready for formatting (similar to
269 yeardatescalendar()). Entries in the week lists are
270 (day number, weekday number) tuples. Day numbers outside this month are
271 zero.
272 """
273 months = [
274 self.monthdays2calendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000275 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000276 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000277 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000278
279 def yeardayscalendar(self, year, width=3):
280 """
281 Return the data for the specified year ready for formatting (similar to
282 yeardatescalendar()). Entries in the week lists are day numbers.
283 Day numbers outside this month are zero.
284 """
285 months = [
286 self.monthdayscalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000287 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000288 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000289 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000290
291
292class TextCalendar(Calendar):
293 """
294 Subclass of Calendar that outputs a calendar as a simple plain text
295 similar to the UNIX program cal.
296 """
297
298 def prweek(self, theweek, width):
299 """
300 Print a single week (no newline).
301 """
Serhiy Storchaka0595ed22016-10-25 15:20:58 +0300302 print(self.formatweek(theweek, width), end='')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000303
304 def formatday(self, day, weekday, width):
305 """
306 Returns a formatted day.
307 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000308 if day == 0:
309 s = ''
310 else:
311 s = '%2i' % day # right-align single-digit days
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000312 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000313
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000314 def formatweek(self, theweek, width):
315 """
316 Returns a single week in a string (no newline).
317 """
318 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000319
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000320 def formatweekday(self, day, width):
321 """
322 Returns a formatted week day name.
323 """
324 if width >= 9:
325 names = day_name
326 else:
327 names = day_abbr
328 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000329
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000330 def formatweekheader(self, width):
331 """
332 Return a header for a week.
333 """
334 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000335
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000336 def formatmonthname(self, theyear, themonth, width, withyear=True):
337 """
338 Return a formatted month name.
339 """
340 s = month_name[themonth]
341 if withyear:
342 s = "%s %r" % (s, theyear)
343 return s.center(width)
344
345 def prmonth(self, theyear, themonth, w=0, l=0):
346 """
347 Print a month's calendar.
348 """
Serhiy Storchaka7ff51bd2016-10-25 15:00:52 +0300349 print(self.formatmonth(theyear, themonth, w, l), end='')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000350
351 def formatmonth(self, theyear, themonth, w=0, l=0):
352 """
353 Return a month's calendar string (multi-line).
354 """
355 w = max(2, w)
356 l = max(1, l)
357 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
358 s = s.rstrip()
359 s += '\n' * l
360 s += self.formatweekheader(w).rstrip()
361 s += '\n' * l
362 for week in self.monthdays2calendar(theyear, themonth):
363 s += self.formatweek(week, w).rstrip()
364 s += '\n' * l
365 return s
366
367 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
368 """
369 Returns a year's calendar as a multi-line string.
370 """
371 w = max(2, w)
372 l = max(1, l)
373 c = max(2, c)
374 colwidth = (w + 1) * 7 - 1
375 v = []
376 a = v.append
377 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
378 a('\n'*l)
379 header = self.formatweekheader(w)
380 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
381 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000382 months = range(m*i+1, min(m*(i+1)+1, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000383 a('\n'*l)
384 names = (self.formatmonthname(theyear, k, colwidth, False)
385 for k in months)
386 a(formatstring(names, colwidth, c).rstrip())
387 a('\n'*l)
388 headers = (header for k in months)
389 a(formatstring(headers, colwidth, c).rstrip())
390 a('\n'*l)
391 # max number of weeks for this row
392 height = max(len(cal) for cal in row)
Guido van Rossum805365e2007-05-07 22:24:25 +0000393 for j in range(height):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000394 weeks = []
395 for cal in row:
396 if j >= len(cal):
397 weeks.append('')
398 else:
399 weeks.append(self.formatweek(cal[j], w))
400 a(formatstring(weeks, colwidth, c).rstrip())
401 a('\n' * l)
402 return ''.join(v)
403
404 def pryear(self, theyear, w=0, l=0, c=6, m=3):
405 """Print a year's calendar."""
Serhiy Storchaka0595ed22016-10-25 15:20:58 +0300406 print(self.formatyear(theyear, w, l, c, m), end='')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000407
408
409class HTMLCalendar(Calendar):
410 """
411 This calendar returns complete HTML pages.
412 """
413
414 # CSS classes for the day <td>s
415 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
416
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200417 # CSS classes for the day <th>s
418 cssclasses_weekday_head = cssclasses
419
420 # CSS class for the days before and after current month
421 cssclass_noday = "noday"
422
423 # CSS class for the month's head
424 cssclass_month_head = "month"
425
426 # CSS class for the month
427 cssclass_month = "month"
428
429 # CSS class for the year's table head
430 cssclass_year_head = "year"
431
432 # CSS class for the whole year table
433 cssclass_year = "year"
434
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000435 def formatday(self, day, weekday):
436 """
437 Return a day as a table cell.
438 """
439 if day == 0:
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200440 # day outside month
441 return '<td class="%s">&nbsp;</td>' % self.cssclass_noday
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000442 else:
443 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
444
445 def formatweek(self, theweek):
446 """
447 Return a complete week as a table row.
448 """
449 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
450 return '<tr>%s</tr>' % s
451
452 def formatweekday(self, day):
453 """
454 Return a weekday name as a table header.
455 """
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200456 return '<th class="%s">%s</th>' % (
457 self.cssclasses_weekday_head[day], day_abbr[day])
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000458
459 def formatweekheader(self):
460 """
461 Return a header for a week as a table row.
462 """
463 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
464 return '<tr>%s</tr>' % s
465
466 def formatmonthname(self, theyear, themonth, withyear=True):
467 """
468 Return a month name as a table row.
469 """
470 if withyear:
471 s = '%s %s' % (month_name[themonth], theyear)
472 else:
473 s = '%s' % month_name[themonth]
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200474 return '<tr><th colspan="7" class="%s">%s</th></tr>' % (
475 self.cssclass_month_head, s)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000476
477 def formatmonth(self, theyear, themonth, withyear=True):
478 """
479 Return a formatted month as a table.
480 """
481 v = []
482 a = v.append
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200483 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % (
484 self.cssclass_month))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000485 a('\n')
486 a(self.formatmonthname(theyear, themonth, withyear=withyear))
487 a('\n')
488 a(self.formatweekheader())
489 a('\n')
490 for week in self.monthdays2calendar(theyear, themonth):
491 a(self.formatweek(week))
492 a('\n')
493 a('</table>')
494 a('\n')
495 return ''.join(v)
496
497 def formatyear(self, theyear, width=3):
498 """
499 Return a formatted year as a table of tables.
500 """
501 v = []
502 a = v.append
503 width = max(width, 1)
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200504 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' %
505 self.cssclass_year)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000506 a('\n')
Oz N Tiram8b7a4cc2017-06-06 11:35:59 +0200507 a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
508 width, self.cssclass_year_head, theyear))
Guido van Rossum805365e2007-05-07 22:24:25 +0000509 for i in range(January, January+12, width):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000510 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000511 months = range(i, min(i+width, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000512 a('<tr>')
513 for m in months:
514 a('<td>')
515 a(self.formatmonth(theyear, m, withyear=False))
516 a('</td>')
517 a('</tr>')
518 a('</table>')
519 return ''.join(v)
520
521 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
522 """
523 Return a formatted year as a complete HTML page.
524 """
525 if encoding is None:
526 encoding = sys.getdefaultencoding()
527 v = []
528 a = v.append
529 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
530 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
531 a('<html>\n')
532 a('<head>\n')
533 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
534 if css is not None:
535 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
Thomas Wouters47b49bf2007-08-30 22:15:33 +0000536 a('<title>Calendar for %d</title>\n' % theyear)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000537 a('</head>\n')
538 a('<body>\n')
539 a(self.formatyear(theyear, width))
540 a('</body>\n')
541 a('</html>\n')
542 return ''.join(v).encode(encoding, "xmlcharrefreplace")
543
544
Georg Brandlfd680872008-06-08 08:40:05 +0000545class different_locale:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000546 def __init__(self, locale):
547 self.locale = locale
548
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000549 def __enter__(self):
Georg Brandl7004bd12010-10-19 18:54:25 +0000550 self.oldlocale = _locale.getlocale(_locale.LC_TIME)
551 _locale.setlocale(_locale.LC_TIME, self.locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000552
553 def __exit__(self, *args):
Christian Heimes96f31632007-11-12 01:32:03 +0000554 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000555
556
557class LocaleTextCalendar(TextCalendar):
558 """
559 This class can be passed a locale name in the constructor and will return
560 month and weekday names in the specified locale. If this locale includes
561 an encoding all strings containing month and weekday names will be returned
562 as unicode.
563 """
564
565 def __init__(self, firstweekday=0, locale=None):
566 TextCalendar.__init__(self, firstweekday)
567 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000568 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000569 self.locale = locale
570
571 def formatweekday(self, day, width):
Georg Brandlfd680872008-06-08 08:40:05 +0000572 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000573 if width >= 9:
574 names = day_name
575 else:
576 names = day_abbr
577 name = names[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000578 return name[:width].center(width)
579
580 def formatmonthname(self, theyear, themonth, width, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000581 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000582 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000583 if withyear:
584 s = "%s %r" % (s, theyear)
585 return s.center(width)
586
587
588class LocaleHTMLCalendar(HTMLCalendar):
589 """
590 This class can be passed a locale name in the constructor and will return
591 month and weekday names in the specified locale. If this locale includes
592 an encoding all strings containing month and weekday names will be returned
593 as unicode.
594 """
595 def __init__(self, firstweekday=0, locale=None):
596 HTMLCalendar.__init__(self, firstweekday)
597 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000598 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000599 self.locale = locale
600
601 def formatweekday(self, day):
Georg Brandlfd680872008-06-08 08:40:05 +0000602 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000603 s = day_abbr[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000604 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
605
606 def formatmonthname(self, theyear, themonth, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000607 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000608 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000609 if withyear:
610 s = '%s %s' % (s, theyear)
611 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
612
613
614# Support for old module level interface
615c = TextCalendar()
616
617firstweekday = c.getfirstweekday
618
619def setfirstweekday(firstweekday):
620 if not MONDAY <= firstweekday <= SUNDAY:
621 raise IllegalWeekdayError(firstweekday)
622 c.firstweekday = firstweekday
623
624monthcalendar = c.monthdayscalendar
625prweek = c.prweek
626week = c.formatweek
627weekheader = c.formatweekheader
628prmonth = c.prmonth
629month = c.formatmonth
630calendar = c.formatyear
631prcal = c.pryear
632
633
634# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000635_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000636_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000637
Guido van Rossumc6360141990-10-13 19:23:40 +0000638
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000639def format(cols, colwidth=_colwidth, spacing=_spacing):
640 """Prints multi-column formatting for year calendars"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000641 print(formatstring(cols, colwidth, spacing))
Skip Montanaroad3bc442000-08-30 14:01:28 +0000642
Skip Montanaroad3bc442000-08-30 14:01:28 +0000643
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000644def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
645 """Returns a string formatted from n strings, centered within n columns."""
646 spacing *= ' '
647 return spacing.join(c.center(colwidth) for c in cols)
648
Guido van Rossumb39aff81999-06-09 15:07:38 +0000649
Guido van Rossumb39aff81999-06-09 15:07:38 +0000650EPOCH = 1970
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000651_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
652
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000653
Guido van Rossumb39aff81999-06-09 15:07:38 +0000654def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000655 """Unrelated but handy function to calculate Unix timestamp from GMT."""
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000656 year, month, day, hour, minute, second = tuple[:6]
657 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
658 hours = days*24 + hour
659 minutes = hours*60 + minute
660 seconds = minutes*60 + second
661 return seconds
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000662
663
664def main(args):
Serhiy Storchaka97852612015-11-01 17:14:27 +0200665 import argparse
666 parser = argparse.ArgumentParser()
667 textgroup = parser.add_argument_group('text only arguments')
668 htmlgroup = parser.add_argument_group('html only arguments')
669 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000670 "-w", "--width",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200671 type=int, default=2,
672 help="width of date column (default 2)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000673 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200674 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000675 "-l", "--lines",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200676 type=int, default=1,
677 help="number of lines for each week (default 1)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000678 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200679 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000680 "-s", "--spacing",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200681 type=int, default=6,
682 help="spacing between months (default 6)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000683 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200684 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000685 "-m", "--months",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200686 type=int, default=3,
687 help="months per row (default 3)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000688 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200689 htmlgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000690 "-c", "--css",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200691 default="calendar.css",
692 help="CSS to use for page"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000693 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200694 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000695 "-L", "--locale",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200696 default=None,
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000697 help="locale to be used from month and weekday names"
698 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200699 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000700 "-e", "--encoding",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200701 default=None,
702 help="encoding to use for output"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000703 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200704 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000705 "-t", "--type",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200706 default="text",
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000707 choices=("text", "html"),
708 help="output type (text or html)"
709 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200710 parser.add_argument(
711 "year",
712 nargs='?', type=int,
713 help="year number (1-9999)"
714 )
715 parser.add_argument(
716 "month",
717 nargs='?', type=int,
718 help="month number (1-12, text only)"
719 )
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000720
Serhiy Storchaka97852612015-11-01 17:14:27 +0200721 options = parser.parse_args(args[1:])
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000722
723 if options.locale and not options.encoding:
724 parser.error("if --locale is specified --encoding is required")
725 sys.exit(1)
726
Christian Heimes96f31632007-11-12 01:32:03 +0000727 locale = options.locale, options.encoding
728
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000729 if options.type == "html":
730 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000731 cal = LocaleHTMLCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000732 else:
733 cal = HTMLCalendar()
734 encoding = options.encoding
735 if encoding is None:
736 encoding = sys.getdefaultencoding()
737 optdict = dict(encoding=encoding, css=options.css)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800738 write = sys.stdout.buffer.write
Serhiy Storchaka97852612015-11-01 17:14:27 +0200739 if options.year is None:
Senthil Kumaran962fed92011-08-11 09:22:52 +0800740 write(cal.formatyearpage(datetime.date.today().year, **optdict))
Serhiy Storchaka97852612015-11-01 17:14:27 +0200741 elif options.month is None:
742 write(cal.formatyearpage(options.year, **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000743 else:
744 parser.error("incorrect number of arguments")
745 sys.exit(1)
746 else:
747 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000748 cal = LocaleTextCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000749 else:
750 cal = TextCalendar()
751 optdict = dict(w=options.width, l=options.lines)
Serhiy Storchaka97852612015-11-01 17:14:27 +0200752 if options.month is None:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000753 optdict["c"] = options.spacing
754 optdict["m"] = options.months
Serhiy Storchaka97852612015-11-01 17:14:27 +0200755 if options.year is None:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000756 result = cal.formatyear(datetime.date.today().year, **optdict)
Serhiy Storchaka97852612015-11-01 17:14:27 +0200757 elif options.month is None:
758 result = cal.formatyear(options.year, **optdict)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000759 else:
Serhiy Storchaka97852612015-11-01 17:14:27 +0200760 result = cal.formatmonth(options.year, options.month, **optdict)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800761 write = sys.stdout.write
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000762 if options.encoding:
763 result = result.encode(options.encoding)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800764 write = sys.stdout.buffer.write
765 write(result)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000766
767
768if __name__ == "__main__":
769 main(sys.argv)