blob: 0c1fdad56749d95a3f463f6a9b282082dcb03eba [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",
15 "timegm", "month_name", "month_abbr", "day_name", "day_abbr"]
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000016
Guido van Rossumc6360141990-10-13 19:23:40 +000017# Exception raised for bad input (with string parameter for details)
Guido van Rossum00245cf1999-05-03 18:07:40 +000018error = ValueError
Guido van Rossumc6360141990-10-13 19:23:40 +000019
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000020# Exceptions raised for bad input
21class IllegalMonthError(ValueError):
22 def __init__(self, month):
23 self.month = month
24 def __str__(self):
25 return "bad month number %r; must be 1-12" % self.month
26
27
28class IllegalWeekdayError(ValueError):
29 def __init__(self, weekday):
30 self.weekday = weekday
31 def __str__(self):
32 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
33
34
Guido van Rossum9b3bc711993-06-20 21:02:22 +000035# Constants for months referenced later
36January = 1
37February = 2
38
39# Number of days per month (except for February in leap years)
40mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
41
Tim Peters0c2c8e72002-03-23 03:26:53 +000042# This module used to have hard-coded lists of day and month names, as
43# English strings. The classes following emulate a read-only version of
44# that, but supply localized names. Note that the values are computed
45# fresh on each call, in case the user changes locale between calls.
46
Raymond Hettinger9c051d72002-06-20 03:38:12 +000047class _localized_month:
Tim Petersbbc0d442004-11-13 16:18:32 +000048
Guido van Rossum805365e2007-05-07 22:24:25 +000049 _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
Tim Petersbbc0d442004-11-13 16:18:32 +000050 _months.insert(0, lambda x: "")
51
Tim Peters0c2c8e72002-03-23 03:26:53 +000052 def __init__(self, format):
Barry Warsaw1d099102001-05-22 15:58:30 +000053 self.format = format
Tim Peters0c2c8e72002-03-23 03:26:53 +000054
55 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000056 funcs = self._months[i]
57 if isinstance(i, slice):
58 return [f(self.format) for f in funcs]
59 else:
60 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000061
Skip Montanaro4c834952002-03-15 04:08:38 +000062 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000063 return 13
64
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000065
Raymond Hettinger9c051d72002-06-20 03:38:12 +000066class _localized_day:
Tim Petersbbc0d442004-11-13 16:18:32 +000067
68 # January 1, 2001, was a Monday.
Guido van Rossum805365e2007-05-07 22:24:25 +000069 _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
Tim Petersbbc0d442004-11-13 16:18:32 +000070
Tim Peters0c2c8e72002-03-23 03:26:53 +000071 def __init__(self, format):
72 self.format = format
73
74 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000075 funcs = self._days[i]
76 if isinstance(i, slice):
77 return [f(self.format) for f in funcs]
78 else:
79 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000080
Neal Norwitz492faa52004-06-07 03:47:06 +000081 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000082 return 7
Barry Warsaw1d099102001-05-22 15:58:30 +000083
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000084
Guido van Rossum9b3bc711993-06-20 21:02:22 +000085# Full and abbreviated names of weekdays
Tim Peters0c2c8e72002-03-23 03:26:53 +000086day_name = _localized_day('%A')
87day_abbr = _localized_day('%a')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000088
Guido van Rossum5cfa5df1993-06-23 09:30:50 +000089# Full and abbreviated names of months (1-based arrays!!!)
Tim Peters0c2c8e72002-03-23 03:26:53 +000090month_name = _localized_month('%B')
91month_abbr = _localized_month('%b')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000092
Skip Montanaroad3bc442000-08-30 14:01:28 +000093# Constants for weekdays
94(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
95
Skip Montanaroad3bc442000-08-30 14:01:28 +000096
Guido van Rossum9b3bc711993-06-20 21:02:22 +000097def isleap(year):
Guido van Rossum4acc25b2000-02-02 15:10:15 +000098 """Return 1 for leap years, 0 for non-leap years."""
Fred Drake8152d322000-12-12 23:20:45 +000099 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000100
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000101
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000102def leapdays(y1, y2):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000103 """Return number of leap years in range [y1, y2).
Guido van Rossum46735ad2000-10-09 12:42:04 +0000104 Assume y1 <= y2."""
105 y1 -= 1
106 y2 -= 1
Raymond Hettingere11b5102002-12-25 16:37:19 +0000107 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000108
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000109
Guido van Rossumc6360141990-10-13 19:23:40 +0000110def weekday(year, month, day):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000111 """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
112 day (1-31)."""
Raymond Hettingere11b5102002-12-25 16:37:19 +0000113 return datetime.date(year, month, day).weekday()
Guido van Rossumc6360141990-10-13 19:23:40 +0000114
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000115
Guido van Rossumc6360141990-10-13 19:23:40 +0000116def monthrange(year, month):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000117 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
118 year, month."""
119 if not 1 <= month <= 12:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000120 raise IllegalMonthError(month)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000121 day1 = weekday(year, month, 1)
122 ndays = mdays[month] + (month == February and isleap(year))
123 return day1, ndays
Guido van Rossumc6360141990-10-13 19:23:40 +0000124
Guido van Rossumc6360141990-10-13 19:23:40 +0000125
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000126class Calendar(object):
127 """
128 Base calendar class. This class doesn't do any formatting. It simply
129 provides data to subclasses.
130 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000131
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000132 def __init__(self, firstweekday=0):
133 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
134
135 def getfirstweekday(self):
136 return self._firstweekday % 7
137
138 def setfirstweekday(self, firstweekday):
139 self._firstweekday = firstweekday
140
141 firstweekday = property(getfirstweekday, setfirstweekday)
142
143 def iterweekdays(self):
144 """
145 Return a iterator for one week of weekday numbers starting with the
146 configured first one.
147 """
Guido van Rossum805365e2007-05-07 22:24:25 +0000148 for i in range(self.firstweekday, self.firstweekday + 7):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000149 yield i%7
150
151 def itermonthdates(self, year, month):
152 """
153 Return an iterator for one month. The iterator will yield datetime.date
154 values and will always iterate through complete weeks, so it will yield
155 dates outside the specified month.
156 """
157 date = datetime.date(year, month, 1)
158 # Go back to the beginning of the week
159 days = (date.weekday() - self.firstweekday) % 7
160 date -= datetime.timedelta(days=days)
161 oneday = datetime.timedelta(days=1)
162 while True:
163 yield date
164 date += oneday
165 if date.month != month and date.weekday() == self.firstweekday:
166 break
167
168 def itermonthdays2(self, year, month):
169 """
170 Like itermonthdates(), but will yield (day number, weekday number)
171 tuples. For days outside the specified month the day number is 0.
172 """
173 for date in self.itermonthdates(year, month):
174 if date.month != month:
175 yield (0, date.weekday())
176 else:
177 yield (date.day, date.weekday())
178
179 def itermonthdays(self, year, month):
180 """
Christian Heimes77c02eb2008-02-09 02:18:51 +0000181 Like itermonthdates(), but will yield day numbers. For days outside
182 the specified month the day number is 0.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000183 """
184 for date in self.itermonthdates(year, month):
185 if date.month != month:
186 yield 0
187 else:
188 yield date.day
189
190 def monthdatescalendar(self, year, month):
191 """
192 Return a matrix (list of lists) representing a month's calendar.
193 Each row represents a week; week entries are datetime.date values.
194 """
195 dates = list(self.itermonthdates(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000196 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000197
198 def monthdays2calendar(self, year, month):
199 """
200 Return a matrix representing a month's calendar.
201 Each row represents a week; week entries are
202 (day number, weekday number) tuples. Day numbers outside this month
203 are zero.
204 """
205 days = list(self.itermonthdays2(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000206 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000207
208 def monthdayscalendar(self, year, month):
209 """
210 Return a matrix representing a month's calendar.
211 Each row represents a week; days outside this month are zero.
212 """
213 days = list(self.itermonthdays(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000214 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000215
216 def yeardatescalendar(self, year, width=3):
217 """
218 Return the data for the specified year ready for formatting. The return
219 value is a list of month rows. Each month row contains upto width months.
220 Each month contains between 4 and 6 weeks and each week contains 1-7
221 days. Days are datetime.date objects.
222 """
223 months = [
224 self.monthdatescalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000225 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000226 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000227 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000228
229 def yeardays2calendar(self, year, width=3):
230 """
231 Return the data for the specified year ready for formatting (similar to
232 yeardatescalendar()). Entries in the week lists are
233 (day number, weekday number) tuples. Day numbers outside this month are
234 zero.
235 """
236 months = [
237 self.monthdays2calendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000238 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000239 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000240 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000241
242 def yeardayscalendar(self, year, width=3):
243 """
244 Return the data for the specified year ready for formatting (similar to
245 yeardatescalendar()). Entries in the week lists are day numbers.
246 Day numbers outside this month are zero.
247 """
248 months = [
249 self.monthdayscalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000250 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000251 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000252 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000253
254
255class TextCalendar(Calendar):
256 """
257 Subclass of Calendar that outputs a calendar as a simple plain text
258 similar to the UNIX program cal.
259 """
260
261 def prweek(self, theweek, width):
262 """
263 Print a single week (no newline).
264 """
Christian Heimes32fbe592007-11-12 15:01:33 +0000265 print(self.formatweek(theweek, width), end=' ')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000266
267 def formatday(self, day, weekday, width):
268 """
269 Returns a formatted day.
270 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000271 if day == 0:
272 s = ''
273 else:
274 s = '%2i' % day # right-align single-digit days
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000275 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000276
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000277 def formatweek(self, theweek, width):
278 """
279 Returns a single week in a string (no newline).
280 """
281 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000282
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000283 def formatweekday(self, day, width):
284 """
285 Returns a formatted week day name.
286 """
287 if width >= 9:
288 names = day_name
289 else:
290 names = day_abbr
291 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000292
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000293 def formatweekheader(self, width):
294 """
295 Return a header for a week.
296 """
297 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000298
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000299 def formatmonthname(self, theyear, themonth, width, withyear=True):
300 """
301 Return a formatted month name.
302 """
303 s = month_name[themonth]
304 if withyear:
305 s = "%s %r" % (s, theyear)
306 return s.center(width)
307
308 def prmonth(self, theyear, themonth, w=0, l=0):
309 """
310 Print a month's calendar.
311 """
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000312 print(self.formatmonth(theyear, themonth, w, l), end=' ')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000313
314 def formatmonth(self, theyear, themonth, w=0, l=0):
315 """
316 Return a month's calendar string (multi-line).
317 """
318 w = max(2, w)
319 l = max(1, l)
320 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
321 s = s.rstrip()
322 s += '\n' * l
323 s += self.formatweekheader(w).rstrip()
324 s += '\n' * l
325 for week in self.monthdays2calendar(theyear, themonth):
326 s += self.formatweek(week, w).rstrip()
327 s += '\n' * l
328 return s
329
330 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
331 """
332 Returns a year's calendar as a multi-line string.
333 """
334 w = max(2, w)
335 l = max(1, l)
336 c = max(2, c)
337 colwidth = (w + 1) * 7 - 1
338 v = []
339 a = v.append
340 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
341 a('\n'*l)
342 header = self.formatweekheader(w)
343 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
344 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000345 months = range(m*i+1, min(m*(i+1)+1, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000346 a('\n'*l)
347 names = (self.formatmonthname(theyear, k, colwidth, False)
348 for k in months)
349 a(formatstring(names, colwidth, c).rstrip())
350 a('\n'*l)
351 headers = (header for k in months)
352 a(formatstring(headers, colwidth, c).rstrip())
353 a('\n'*l)
354 # max number of weeks for this row
355 height = max(len(cal) for cal in row)
Guido van Rossum805365e2007-05-07 22:24:25 +0000356 for j in range(height):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000357 weeks = []
358 for cal in row:
359 if j >= len(cal):
360 weeks.append('')
361 else:
362 weeks.append(self.formatweek(cal[j], w))
363 a(formatstring(weeks, colwidth, c).rstrip())
364 a('\n' * l)
365 return ''.join(v)
366
367 def pryear(self, theyear, w=0, l=0, c=6, m=3):
368 """Print a year's calendar."""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000369 print(self.formatyear(theyear, w, l, c, m))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000370
371
372class HTMLCalendar(Calendar):
373 """
374 This calendar returns complete HTML pages.
375 """
376
377 # CSS classes for the day <td>s
378 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
379
380 def formatday(self, day, weekday):
381 """
382 Return a day as a table cell.
383 """
384 if day == 0:
385 return '<td class="noday">&nbsp;</td>' # day outside month
386 else:
387 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
388
389 def formatweek(self, theweek):
390 """
391 Return a complete week as a table row.
392 """
393 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
394 return '<tr>%s</tr>' % s
395
396 def formatweekday(self, day):
397 """
398 Return a weekday name as a table header.
399 """
400 return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
401
402 def formatweekheader(self):
403 """
404 Return a header for a week as a table row.
405 """
406 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
407 return '<tr>%s</tr>' % s
408
409 def formatmonthname(self, theyear, themonth, withyear=True):
410 """
411 Return a month name as a table row.
412 """
413 if withyear:
414 s = '%s %s' % (month_name[themonth], theyear)
415 else:
416 s = '%s' % month_name[themonth]
417 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
418
419 def formatmonth(self, theyear, themonth, withyear=True):
420 """
421 Return a formatted month as a table.
422 """
423 v = []
424 a = v.append
425 a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
426 a('\n')
427 a(self.formatmonthname(theyear, themonth, withyear=withyear))
428 a('\n')
429 a(self.formatweekheader())
430 a('\n')
431 for week in self.monthdays2calendar(theyear, themonth):
432 a(self.formatweek(week))
433 a('\n')
434 a('</table>')
435 a('\n')
436 return ''.join(v)
437
438 def formatyear(self, theyear, width=3):
439 """
440 Return a formatted year as a table of tables.
441 """
442 v = []
443 a = v.append
444 width = max(width, 1)
445 a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
446 a('\n')
447 a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
Guido van Rossum805365e2007-05-07 22:24:25 +0000448 for i in range(January, January+12, width):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000449 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000450 months = range(i, min(i+width, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000451 a('<tr>')
452 for m in months:
453 a('<td>')
454 a(self.formatmonth(theyear, m, withyear=False))
455 a('</td>')
456 a('</tr>')
457 a('</table>')
458 return ''.join(v)
459
460 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
461 """
462 Return a formatted year as a complete HTML page.
463 """
464 if encoding is None:
465 encoding = sys.getdefaultencoding()
466 v = []
467 a = v.append
468 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
469 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
470 a('<html>\n')
471 a('<head>\n')
472 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
473 if css is not None:
474 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
Thomas Wouters47b49bf2007-08-30 22:15:33 +0000475 a('<title>Calendar for %d</title>\n' % theyear)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000476 a('</head>\n')
477 a('<body>\n')
478 a(self.formatyear(theyear, width))
479 a('</body>\n')
480 a('</html>\n')
481 return ''.join(v).encode(encoding, "xmlcharrefreplace")
482
483
Georg Brandlfd680872008-06-08 08:40:05 +0000484class different_locale:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000485 def __init__(self, locale):
486 self.locale = locale
487
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000488 def __enter__(self):
Christian Heimes96f31632007-11-12 01:32:03 +0000489 self.oldlocale = _locale.setlocale(_locale.LC_TIME, self.locale)
Georg Brandlfd680872008-06-08 08:40:05 +0000490 #return _locale.getlocale(_locale.LC_TIME)[1]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000491
492 def __exit__(self, *args):
Christian Heimes96f31632007-11-12 01:32:03 +0000493 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000494
495
496class LocaleTextCalendar(TextCalendar):
497 """
498 This class can be passed a locale name in the constructor and will return
499 month and weekday names in the specified locale. If this locale includes
500 an encoding all strings containing month and weekday names will be returned
501 as unicode.
502 """
503
504 def __init__(self, firstweekday=0, locale=None):
505 TextCalendar.__init__(self, firstweekday)
506 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000507 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000508 self.locale = locale
509
510 def formatweekday(self, day, width):
Georg Brandlfd680872008-06-08 08:40:05 +0000511 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000512 if width >= 9:
513 names = day_name
514 else:
515 names = day_abbr
516 name = names[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000517 return name[:width].center(width)
518
519 def formatmonthname(self, theyear, themonth, width, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000520 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000521 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000522 if withyear:
523 s = "%s %r" % (s, theyear)
524 return s.center(width)
525
526
527class LocaleHTMLCalendar(HTMLCalendar):
528 """
529 This class can be passed a locale name in the constructor and will return
530 month and weekday names in the specified locale. If this locale includes
531 an encoding all strings containing month and weekday names will be returned
532 as unicode.
533 """
534 def __init__(self, firstweekday=0, locale=None):
535 HTMLCalendar.__init__(self, firstweekday)
536 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000537 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000538 self.locale = locale
539
540 def formatweekday(self, day):
Georg Brandlfd680872008-06-08 08:40:05 +0000541 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000542 s = day_abbr[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000543 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
544
545 def formatmonthname(self, theyear, themonth, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000546 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000547 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000548 if withyear:
549 s = '%s %s' % (s, theyear)
550 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
551
552
553# Support for old module level interface
554c = TextCalendar()
555
556firstweekday = c.getfirstweekday
557
558def setfirstweekday(firstweekday):
559 if not MONDAY <= firstweekday <= SUNDAY:
560 raise IllegalWeekdayError(firstweekday)
561 c.firstweekday = firstweekday
562
563monthcalendar = c.monthdayscalendar
564prweek = c.prweek
565week = c.formatweek
566weekheader = c.formatweekheader
567prmonth = c.prmonth
568month = c.formatmonth
569calendar = c.formatyear
570prcal = c.pryear
571
572
573# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000574_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000575_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000576
Guido van Rossumc6360141990-10-13 19:23:40 +0000577
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000578def format(cols, colwidth=_colwidth, spacing=_spacing):
579 """Prints multi-column formatting for year calendars"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000580 print(formatstring(cols, colwidth, spacing))
Skip Montanaroad3bc442000-08-30 14:01:28 +0000581
Skip Montanaroad3bc442000-08-30 14:01:28 +0000582
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000583def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
584 """Returns a string formatted from n strings, centered within n columns."""
585 spacing *= ' '
586 return spacing.join(c.center(colwidth) for c in cols)
587
Guido van Rossumb39aff81999-06-09 15:07:38 +0000588
Guido van Rossumb39aff81999-06-09 15:07:38 +0000589EPOCH = 1970
Raymond Hettingere11b5102002-12-25 16:37:19 +0000590_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
591
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000592
Guido van Rossumb39aff81999-06-09 15:07:38 +0000593def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000594 """Unrelated but handy function to calculate Unix timestamp from GMT."""
595 year, month, day, hour, minute, second = tuple[:6]
Raymond Hettinger61436482003-02-13 22:58:02 +0000596 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000597 hours = days*24 + hour
598 minutes = hours*60 + minute
599 seconds = minutes*60 + second
600 return seconds
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000601
602
603def main(args):
604 import optparse
605 parser = optparse.OptionParser(usage="usage: %prog [options] [year [month]]")
606 parser.add_option(
607 "-w", "--width",
608 dest="width", type="int", default=2,
609 help="width of date column (default 2, text only)"
610 )
611 parser.add_option(
612 "-l", "--lines",
613 dest="lines", type="int", default=1,
614 help="number of lines for each week (default 1, text only)"
615 )
616 parser.add_option(
617 "-s", "--spacing",
618 dest="spacing", type="int", default=6,
619 help="spacing between months (default 6, text only)"
620 )
621 parser.add_option(
622 "-m", "--months",
623 dest="months", type="int", default=3,
624 help="months per row (default 3, text only)"
625 )
626 parser.add_option(
627 "-c", "--css",
628 dest="css", default="calendar.css",
629 help="CSS to use for page (html only)"
630 )
631 parser.add_option(
632 "-L", "--locale",
633 dest="locale", default=None,
634 help="locale to be used from month and weekday names"
635 )
636 parser.add_option(
637 "-e", "--encoding",
638 dest="encoding", default=None,
639 help="Encoding to use for output"
640 )
641 parser.add_option(
642 "-t", "--type",
643 dest="type", default="text",
644 choices=("text", "html"),
645 help="output type (text or html)"
646 )
647
648 (options, args) = parser.parse_args(args)
649
650 if options.locale and not options.encoding:
651 parser.error("if --locale is specified --encoding is required")
652 sys.exit(1)
653
Christian Heimes96f31632007-11-12 01:32:03 +0000654 locale = options.locale, options.encoding
655
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000656 if options.type == "html":
657 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000658 cal = LocaleHTMLCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000659 else:
660 cal = HTMLCalendar()
661 encoding = options.encoding
662 if encoding is None:
663 encoding = sys.getdefaultencoding()
664 optdict = dict(encoding=encoding, css=options.css)
665 if len(args) == 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000666 print(cal.formatyearpage(datetime.date.today().year, **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000667 elif len(args) == 2:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000668 print(cal.formatyearpage(int(args[1]), **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000669 else:
670 parser.error("incorrect number of arguments")
671 sys.exit(1)
672 else:
673 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000674 cal = LocaleTextCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000675 else:
676 cal = TextCalendar()
677 optdict = dict(w=options.width, l=options.lines)
678 if len(args) != 3:
679 optdict["c"] = options.spacing
680 optdict["m"] = options.months
681 if len(args) == 1:
682 result = cal.formatyear(datetime.date.today().year, **optdict)
683 elif len(args) == 2:
684 result = cal.formatyear(int(args[1]), **optdict)
685 elif len(args) == 3:
686 result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
687 else:
688 parser.error("incorrect number of arguments")
689 sys.exit(1)
690 if options.encoding:
691 result = result.encode(options.encoding)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000692 print(result)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000693
694
695if __name__ == "__main__":
696 main(sys.argv)