blob: 85baf2ee4a01e6de369aceda9672018ad9f1a602 [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
Guido van Rossumc6360141990-10-13 19:23:40 +000011
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000012__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
13 "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
14 "monthcalendar", "prmonth", "month", "prcal", "calendar",
Martin Panter4eb376c2016-01-16 06:49:30 +000015 "timegm", "month_name", "month_abbr", "day_name", "day_abbr",
16 "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
17 "LocaleHTMLCalendar", "weekheader"]
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000018
Guido van Rossumc6360141990-10-13 19:23:40 +000019# Exception raised for bad input (with string parameter for details)
Guido van Rossum00245cf1999-05-03 18:07:40 +000020error = ValueError
Guido van Rossumc6360141990-10-13 19:23:40 +000021
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000022# Exceptions raised for bad input
23class IllegalMonthError(ValueError):
24 def __init__(self, month):
25 self.month = month
26 def __str__(self):
27 return "bad month number %r; must be 1-12" % self.month
28
29
30class IllegalWeekdayError(ValueError):
31 def __init__(self, weekday):
32 self.weekday = weekday
33 def __str__(self):
34 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
35
36
Guido van Rossum9b3bc711993-06-20 21:02:22 +000037# Constants for months referenced later
38January = 1
39February = 2
40
41# Number of days per month (except for February in leap years)
42mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
43
Tim Peters0c2c8e72002-03-23 03:26:53 +000044# This module used to have hard-coded lists of day and month names, as
45# English strings. The classes following emulate a read-only version of
46# that, but supply localized names. Note that the values are computed
47# fresh on each call, in case the user changes locale between calls.
48
Raymond Hettinger9c051d72002-06-20 03:38:12 +000049class _localized_month:
Tim Petersbbc0d442004-11-13 16:18:32 +000050
Guido van Rossum805365e2007-05-07 22:24:25 +000051 _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
Tim Petersbbc0d442004-11-13 16:18:32 +000052 _months.insert(0, lambda x: "")
53
Tim Peters0c2c8e72002-03-23 03:26:53 +000054 def __init__(self, format):
Barry Warsaw1d099102001-05-22 15:58:30 +000055 self.format = format
Tim Peters0c2c8e72002-03-23 03:26:53 +000056
57 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000058 funcs = self._months[i]
59 if isinstance(i, slice):
60 return [f(self.format) for f in funcs]
61 else:
62 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000063
Skip Montanaro4c834952002-03-15 04:08:38 +000064 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000065 return 13
66
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000067
Raymond Hettinger9c051d72002-06-20 03:38:12 +000068class _localized_day:
Tim Petersbbc0d442004-11-13 16:18:32 +000069
70 # January 1, 2001, was a Monday.
Guido van Rossum805365e2007-05-07 22:24:25 +000071 _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
Tim Petersbbc0d442004-11-13 16:18:32 +000072
Tim Peters0c2c8e72002-03-23 03:26:53 +000073 def __init__(self, format):
74 self.format = format
75
76 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000077 funcs = self._days[i]
78 if isinstance(i, slice):
79 return [f(self.format) for f in funcs]
80 else:
81 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000082
Neal Norwitz492faa52004-06-07 03:47:06 +000083 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000084 return 7
Barry Warsaw1d099102001-05-22 15:58:30 +000085
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000086
Guido van Rossum9b3bc711993-06-20 21:02:22 +000087# Full and abbreviated names of weekdays
Tim Peters0c2c8e72002-03-23 03:26:53 +000088day_name = _localized_day('%A')
89day_abbr = _localized_day('%a')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000090
Guido van Rossum5cfa5df1993-06-23 09:30:50 +000091# Full and abbreviated names of months (1-based arrays!!!)
Tim Peters0c2c8e72002-03-23 03:26:53 +000092month_name = _localized_month('%B')
93month_abbr = _localized_month('%b')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000094
Skip Montanaroad3bc442000-08-30 14:01:28 +000095# Constants for weekdays
96(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
97
Skip Montanaroad3bc442000-08-30 14:01:28 +000098
Guido van Rossum9b3bc711993-06-20 21:02:22 +000099def isleap(year):
Alexander Belopolskyf87cc042010-10-19 17:43:50 +0000100 """Return True for leap years, False for non-leap years."""
Fred Drake8152d322000-12-12 23:20:45 +0000101 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000102
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000103
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000104def leapdays(y1, y2):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000105 """Return number of leap years in range [y1, y2).
Guido van Rossum46735ad2000-10-09 12:42:04 +0000106 Assume y1 <= y2."""
107 y1 -= 1
108 y2 -= 1
Raymond Hettingere11b5102002-12-25 16:37:19 +0000109 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000110
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000111
Guido van Rossumc6360141990-10-13 19:23:40 +0000112def weekday(year, month, day):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000113 """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
114 day (1-31)."""
Raymond Hettingere11b5102002-12-25 16:37:19 +0000115 return datetime.date(year, month, day).weekday()
Guido van Rossumc6360141990-10-13 19:23:40 +0000116
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000117
Guido van Rossumc6360141990-10-13 19:23:40 +0000118def monthrange(year, month):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000119 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
120 year, month."""
121 if not 1 <= month <= 12:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000122 raise IllegalMonthError(month)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000123 day1 = weekday(year, month, 1)
124 ndays = mdays[month] + (month == February and isleap(year))
125 return day1, ndays
Guido van Rossumc6360141990-10-13 19:23:40 +0000126
Guido van Rossumc6360141990-10-13 19:23:40 +0000127
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000128class Calendar(object):
129 """
130 Base calendar class. This class doesn't do any formatting. It simply
131 provides data to subclasses.
132 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000133
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000134 def __init__(self, firstweekday=0):
135 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
136
137 def getfirstweekday(self):
138 return self._firstweekday % 7
139
140 def setfirstweekday(self, firstweekday):
141 self._firstweekday = firstweekday
142
143 firstweekday = property(getfirstweekday, setfirstweekday)
144
145 def iterweekdays(self):
146 """
Martin Panter7462b6492015-11-02 03:37:02 +0000147 Return an iterator for one week of weekday numbers starting with the
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000148 configured first one.
149 """
Guido van Rossum805365e2007-05-07 22:24:25 +0000150 for i in range(self.firstweekday, self.firstweekday + 7):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000151 yield i%7
152
153 def itermonthdates(self, year, month):
154 """
155 Return an iterator for one month. The iterator will yield datetime.date
156 values and will always iterate through complete weeks, so it will yield
157 dates outside the specified month.
158 """
159 date = datetime.date(year, month, 1)
160 # Go back to the beginning of the week
161 days = (date.weekday() - self.firstweekday) % 7
162 date -= datetime.timedelta(days=days)
163 oneday = datetime.timedelta(days=1)
164 while True:
165 yield date
Ezio Melotti85710a42012-09-21 17:26:35 +0300166 try:
167 date += oneday
168 except OverflowError:
169 # Adding one day could fail after datetime.MAXYEAR
170 break
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000171 if date.month != month and date.weekday() == self.firstweekday:
172 break
173
174 def itermonthdays2(self, year, month):
175 """
176 Like itermonthdates(), but will yield (day number, weekday number)
177 tuples. For days outside the specified month the day number is 0.
178 """
179 for date in self.itermonthdates(year, month):
180 if date.month != month:
181 yield (0, date.weekday())
182 else:
183 yield (date.day, date.weekday())
184
185 def itermonthdays(self, year, month):
186 """
Christian Heimes77c02eb2008-02-09 02:18:51 +0000187 Like itermonthdates(), but will yield day numbers. For days outside
188 the specified month the day number is 0.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000189 """
190 for date in self.itermonthdates(year, month):
191 if date.month != month:
192 yield 0
193 else:
194 yield date.day
195
196 def monthdatescalendar(self, year, month):
197 """
198 Return a matrix (list of lists) representing a month's calendar.
199 Each row represents a week; week entries are datetime.date values.
200 """
201 dates = list(self.itermonthdates(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000202 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000203
204 def monthdays2calendar(self, year, month):
205 """
206 Return a matrix representing a month's calendar.
207 Each row represents a week; week entries are
208 (day number, weekday number) tuples. Day numbers outside this month
209 are zero.
210 """
211 days = list(self.itermonthdays2(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000212 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000213
214 def monthdayscalendar(self, year, month):
215 """
216 Return a matrix representing a month's calendar.
217 Each row represents a week; days outside this month are zero.
218 """
219 days = list(self.itermonthdays(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000220 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000221
222 def yeardatescalendar(self, year, width=3):
223 """
224 Return the data for the specified year ready for formatting. The return
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300225 value is a list of month rows. Each month row contains up to width months.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000226 Each month contains between 4 and 6 weeks and each week contains 1-7
227 days. Days are datetime.date objects.
228 """
229 months = [
230 self.monthdatescalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000231 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000232 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000233 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000234
235 def yeardays2calendar(self, year, width=3):
236 """
237 Return the data for the specified year ready for formatting (similar to
238 yeardatescalendar()). Entries in the week lists are
239 (day number, weekday number) tuples. Day numbers outside this month are
240 zero.
241 """
242 months = [
243 self.monthdays2calendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000244 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000245 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000246 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000247
248 def yeardayscalendar(self, year, width=3):
249 """
250 Return the data for the specified year ready for formatting (similar to
251 yeardatescalendar()). Entries in the week lists are day numbers.
252 Day numbers outside this month are zero.
253 """
254 months = [
255 self.monthdayscalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000256 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000257 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000258 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000259
260
261class TextCalendar(Calendar):
262 """
263 Subclass of Calendar that outputs a calendar as a simple plain text
264 similar to the UNIX program cal.
265 """
266
267 def prweek(self, theweek, width):
268 """
269 Print a single week (no newline).
270 """
Christian Heimes32fbe592007-11-12 15:01:33 +0000271 print(self.formatweek(theweek, width), end=' ')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000272
273 def formatday(self, day, weekday, width):
274 """
275 Returns a formatted day.
276 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000277 if day == 0:
278 s = ''
279 else:
280 s = '%2i' % day # right-align single-digit days
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000281 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000282
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000283 def formatweek(self, theweek, width):
284 """
285 Returns a single week in a string (no newline).
286 """
287 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000288
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000289 def formatweekday(self, day, width):
290 """
291 Returns a formatted week day name.
292 """
293 if width >= 9:
294 names = day_name
295 else:
296 names = day_abbr
297 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000298
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000299 def formatweekheader(self, width):
300 """
301 Return a header for a week.
302 """
303 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000304
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000305 def formatmonthname(self, theyear, themonth, width, withyear=True):
306 """
307 Return a formatted month name.
308 """
309 s = month_name[themonth]
310 if withyear:
311 s = "%s %r" % (s, theyear)
312 return s.center(width)
313
314 def prmonth(self, theyear, themonth, w=0, l=0):
315 """
316 Print a month's calendar.
317 """
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000318 print(self.formatmonth(theyear, themonth, w, l), end=' ')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000319
320 def formatmonth(self, theyear, themonth, w=0, l=0):
321 """
322 Return a month's calendar string (multi-line).
323 """
324 w = max(2, w)
325 l = max(1, l)
326 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
327 s = s.rstrip()
328 s += '\n' * l
329 s += self.formatweekheader(w).rstrip()
330 s += '\n' * l
331 for week in self.monthdays2calendar(theyear, themonth):
332 s += self.formatweek(week, w).rstrip()
333 s += '\n' * l
334 return s
335
336 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
337 """
338 Returns a year's calendar as a multi-line string.
339 """
340 w = max(2, w)
341 l = max(1, l)
342 c = max(2, c)
343 colwidth = (w + 1) * 7 - 1
344 v = []
345 a = v.append
346 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
347 a('\n'*l)
348 header = self.formatweekheader(w)
349 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
350 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000351 months = range(m*i+1, min(m*(i+1)+1, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000352 a('\n'*l)
353 names = (self.formatmonthname(theyear, k, colwidth, False)
354 for k in months)
355 a(formatstring(names, colwidth, c).rstrip())
356 a('\n'*l)
357 headers = (header for k in months)
358 a(formatstring(headers, colwidth, c).rstrip())
359 a('\n'*l)
360 # max number of weeks for this row
361 height = max(len(cal) for cal in row)
Guido van Rossum805365e2007-05-07 22:24:25 +0000362 for j in range(height):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000363 weeks = []
364 for cal in row:
365 if j >= len(cal):
366 weeks.append('')
367 else:
368 weeks.append(self.formatweek(cal[j], w))
369 a(formatstring(weeks, colwidth, c).rstrip())
370 a('\n' * l)
371 return ''.join(v)
372
373 def pryear(self, theyear, w=0, l=0, c=6, m=3):
374 """Print a year's calendar."""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000375 print(self.formatyear(theyear, w, l, c, m))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000376
377
378class HTMLCalendar(Calendar):
379 """
380 This calendar returns complete HTML pages.
381 """
382
383 # CSS classes for the day <td>s
384 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
385
386 def formatday(self, day, weekday):
387 """
388 Return a day as a table cell.
389 """
390 if day == 0:
391 return '<td class="noday">&nbsp;</td>' # day outside month
392 else:
393 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
394
395 def formatweek(self, theweek):
396 """
397 Return a complete week as a table row.
398 """
399 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
400 return '<tr>%s</tr>' % s
401
402 def formatweekday(self, day):
403 """
404 Return a weekday name as a table header.
405 """
406 return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
407
408 def formatweekheader(self):
409 """
410 Return a header for a week as a table row.
411 """
412 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
413 return '<tr>%s</tr>' % s
414
415 def formatmonthname(self, theyear, themonth, withyear=True):
416 """
417 Return a month name as a table row.
418 """
419 if withyear:
420 s = '%s %s' % (month_name[themonth], theyear)
421 else:
422 s = '%s' % month_name[themonth]
423 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
424
425 def formatmonth(self, theyear, themonth, withyear=True):
426 """
427 Return a formatted month as a table.
428 """
429 v = []
430 a = v.append
431 a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
432 a('\n')
433 a(self.formatmonthname(theyear, themonth, withyear=withyear))
434 a('\n')
435 a(self.formatweekheader())
436 a('\n')
437 for week in self.monthdays2calendar(theyear, themonth):
438 a(self.formatweek(week))
439 a('\n')
440 a('</table>')
441 a('\n')
442 return ''.join(v)
443
444 def formatyear(self, theyear, width=3):
445 """
446 Return a formatted year as a table of tables.
447 """
448 v = []
449 a = v.append
450 width = max(width, 1)
451 a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
452 a('\n')
453 a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
Guido van Rossum805365e2007-05-07 22:24:25 +0000454 for i in range(January, January+12, width):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000455 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000456 months = range(i, min(i+width, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000457 a('<tr>')
458 for m in months:
459 a('<td>')
460 a(self.formatmonth(theyear, m, withyear=False))
461 a('</td>')
462 a('</tr>')
463 a('</table>')
464 return ''.join(v)
465
466 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
467 """
468 Return a formatted year as a complete HTML page.
469 """
470 if encoding is None:
471 encoding = sys.getdefaultencoding()
472 v = []
473 a = v.append
474 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
475 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
476 a('<html>\n')
477 a('<head>\n')
478 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
479 if css is not None:
480 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
Thomas Wouters47b49bf2007-08-30 22:15:33 +0000481 a('<title>Calendar for %d</title>\n' % theyear)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000482 a('</head>\n')
483 a('<body>\n')
484 a(self.formatyear(theyear, width))
485 a('</body>\n')
486 a('</html>\n')
487 return ''.join(v).encode(encoding, "xmlcharrefreplace")
488
489
Georg Brandlfd680872008-06-08 08:40:05 +0000490class different_locale:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000491 def __init__(self, locale):
492 self.locale = locale
493
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000494 def __enter__(self):
Georg Brandl7004bd12010-10-19 18:54:25 +0000495 self.oldlocale = _locale.getlocale(_locale.LC_TIME)
496 _locale.setlocale(_locale.LC_TIME, self.locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000497
498 def __exit__(self, *args):
Christian Heimes96f31632007-11-12 01:32:03 +0000499 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000500
501
502class LocaleTextCalendar(TextCalendar):
503 """
504 This class can be passed a locale name in the constructor and will return
505 month and weekday names in the specified locale. If this locale includes
506 an encoding all strings containing month and weekday names will be returned
507 as unicode.
508 """
509
510 def __init__(self, firstweekday=0, locale=None):
511 TextCalendar.__init__(self, firstweekday)
512 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000513 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000514 self.locale = locale
515
516 def formatweekday(self, day, width):
Georg Brandlfd680872008-06-08 08:40:05 +0000517 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000518 if width >= 9:
519 names = day_name
520 else:
521 names = day_abbr
522 name = names[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000523 return name[:width].center(width)
524
525 def formatmonthname(self, theyear, themonth, width, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000526 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000527 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000528 if withyear:
529 s = "%s %r" % (s, theyear)
530 return s.center(width)
531
532
533class LocaleHTMLCalendar(HTMLCalendar):
534 """
535 This class can be passed a locale name in the constructor and will return
536 month and weekday names in the specified locale. If this locale includes
537 an encoding all strings containing month and weekday names will be returned
538 as unicode.
539 """
540 def __init__(self, firstweekday=0, locale=None):
541 HTMLCalendar.__init__(self, firstweekday)
542 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000543 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000544 self.locale = locale
545
546 def formatweekday(self, day):
Georg Brandlfd680872008-06-08 08:40:05 +0000547 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000548 s = day_abbr[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000549 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
550
551 def formatmonthname(self, theyear, themonth, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000552 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000553 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000554 if withyear:
555 s = '%s %s' % (s, theyear)
556 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
557
558
559# Support for old module level interface
560c = TextCalendar()
561
562firstweekday = c.getfirstweekday
563
564def setfirstweekday(firstweekday):
565 if not MONDAY <= firstweekday <= SUNDAY:
566 raise IllegalWeekdayError(firstweekday)
567 c.firstweekday = firstweekday
568
569monthcalendar = c.monthdayscalendar
570prweek = c.prweek
571week = c.formatweek
572weekheader = c.formatweekheader
573prmonth = c.prmonth
574month = c.formatmonth
575calendar = c.formatyear
576prcal = c.pryear
577
578
579# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000580_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000581_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000582
Guido van Rossumc6360141990-10-13 19:23:40 +0000583
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000584def format(cols, colwidth=_colwidth, spacing=_spacing):
585 """Prints multi-column formatting for year calendars"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000586 print(formatstring(cols, colwidth, spacing))
Skip Montanaroad3bc442000-08-30 14:01:28 +0000587
Skip Montanaroad3bc442000-08-30 14:01:28 +0000588
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000589def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
590 """Returns a string formatted from n strings, centered within n columns."""
591 spacing *= ' '
592 return spacing.join(c.center(colwidth) for c in cols)
593
Guido van Rossumb39aff81999-06-09 15:07:38 +0000594
Guido van Rossumb39aff81999-06-09 15:07:38 +0000595EPOCH = 1970
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000596_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
597
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000598
Guido van Rossumb39aff81999-06-09 15:07:38 +0000599def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000600 """Unrelated but handy function to calculate Unix timestamp from GMT."""
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000601 year, month, day, hour, minute, second = tuple[:6]
602 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
603 hours = days*24 + hour
604 minutes = hours*60 + minute
605 seconds = minutes*60 + second
606 return seconds
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000607
608
609def main(args):
Serhiy Storchaka97852612015-11-01 17:14:27 +0200610 import argparse
611 parser = argparse.ArgumentParser()
612 textgroup = parser.add_argument_group('text only arguments')
613 htmlgroup = parser.add_argument_group('html only arguments')
614 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000615 "-w", "--width",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200616 type=int, default=2,
617 help="width of date column (default 2)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000618 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200619 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000620 "-l", "--lines",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200621 type=int, default=1,
622 help="number of lines for each week (default 1)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000623 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200624 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000625 "-s", "--spacing",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200626 type=int, default=6,
627 help="spacing between months (default 6)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000628 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200629 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000630 "-m", "--months",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200631 type=int, default=3,
632 help="months per row (default 3)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000633 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200634 htmlgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000635 "-c", "--css",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200636 default="calendar.css",
637 help="CSS to use for page"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000638 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200639 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000640 "-L", "--locale",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200641 default=None,
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000642 help="locale to be used from month and weekday names"
643 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200644 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000645 "-e", "--encoding",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200646 default=None,
647 help="encoding to use for output"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000648 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200649 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000650 "-t", "--type",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200651 default="text",
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000652 choices=("text", "html"),
653 help="output type (text or html)"
654 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200655 parser.add_argument(
656 "year",
657 nargs='?', type=int,
658 help="year number (1-9999)"
659 )
660 parser.add_argument(
661 "month",
662 nargs='?', type=int,
663 help="month number (1-12, text only)"
664 )
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000665
Serhiy Storchaka97852612015-11-01 17:14:27 +0200666 options = parser.parse_args(args[1:])
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000667
668 if options.locale and not options.encoding:
669 parser.error("if --locale is specified --encoding is required")
670 sys.exit(1)
671
Christian Heimes96f31632007-11-12 01:32:03 +0000672 locale = options.locale, options.encoding
673
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000674 if options.type == "html":
675 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000676 cal = LocaleHTMLCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000677 else:
678 cal = HTMLCalendar()
679 encoding = options.encoding
680 if encoding is None:
681 encoding = sys.getdefaultencoding()
682 optdict = dict(encoding=encoding, css=options.css)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800683 write = sys.stdout.buffer.write
Serhiy Storchaka97852612015-11-01 17:14:27 +0200684 if options.year is None:
Senthil Kumaran962fed92011-08-11 09:22:52 +0800685 write(cal.formatyearpage(datetime.date.today().year, **optdict))
Serhiy Storchaka97852612015-11-01 17:14:27 +0200686 elif options.month is None:
687 write(cal.formatyearpage(options.year, **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000688 else:
689 parser.error("incorrect number of arguments")
690 sys.exit(1)
691 else:
692 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000693 cal = LocaleTextCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000694 else:
695 cal = TextCalendar()
696 optdict = dict(w=options.width, l=options.lines)
Serhiy Storchaka97852612015-11-01 17:14:27 +0200697 if options.month is None:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000698 optdict["c"] = options.spacing
699 optdict["m"] = options.months
Serhiy Storchaka97852612015-11-01 17:14:27 +0200700 if options.year is None:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000701 result = cal.formatyear(datetime.date.today().year, **optdict)
Serhiy Storchaka97852612015-11-01 17:14:27 +0200702 elif options.month is None:
703 result = cal.formatyear(options.year, **optdict)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000704 else:
Serhiy Storchaka97852612015-11-01 17:14:27 +0200705 result = cal.formatmonth(options.year, options.month, **optdict)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800706 write = sys.stdout.write
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000707 if options.encoding:
708 result = result.encode(options.encoding)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800709 write = sys.stdout.buffer.write
710 write(result)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000711
712
713if __name__ == "__main__":
714 main(sys.argv)