blob: 41537ba7a689dea09c2884b2cbaf7399a5d13ade [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
Walter Dörwald48d5e502006-04-01 07:57:00 +00008import sys, datetime, locale
Guido van Rossumc6360141990-10-13 19:23:40 +00009
Walter Dörwald58917a62006-03-31 15:26:22 +000010__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
11 "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
12 "monthcalendar", "prmonth", "month", "prcal", "calendar",
13 "timegm", "month_name", "month_abbr", "day_name", "day_abbr"]
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000014
Guido van Rossumc6360141990-10-13 19:23:40 +000015# Exception raised for bad input (with string parameter for details)
Guido van Rossum00245cf1999-05-03 18:07:40 +000016error = ValueError
Guido van Rossumc6360141990-10-13 19:23:40 +000017
Walter Dörwald58917a62006-03-31 15:26:22 +000018# Exceptions raised for bad input
19class IllegalMonthError(ValueError):
20 def __init__(self, month):
21 self.month = month
22 def __str__(self):
23 return "bad month number %r; must be 1-12" % self.month
24
25
26class IllegalWeekdayError(ValueError):
27 def __init__(self, weekday):
28 self.weekday = weekday
29 def __str__(self):
30 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
31
32
Guido van Rossum9b3bc711993-06-20 21:02:22 +000033# Constants for months referenced later
34January = 1
35February = 2
36
37# Number of days per month (except for February in leap years)
38mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
39
Tim Peters0c2c8e72002-03-23 03:26:53 +000040# This module used to have hard-coded lists of day and month names, as
41# English strings. The classes following emulate a read-only version of
42# that, but supply localized names. Note that the values are computed
43# fresh on each call, in case the user changes locale between calls.
44
Raymond Hettinger9c051d72002-06-20 03:38:12 +000045class _localized_month:
Tim Petersbbc0d442004-11-13 16:18:32 +000046
Walter Dörwald58917a62006-03-31 15:26:22 +000047 _months = [datetime.date(2001, i+1, 1).strftime for i in xrange(12)]
Tim Petersbbc0d442004-11-13 16:18:32 +000048 _months.insert(0, lambda x: "")
49
Tim Peters0c2c8e72002-03-23 03:26:53 +000050 def __init__(self, format):
Barry Warsaw1d099102001-05-22 15:58:30 +000051 self.format = format
Tim Peters0c2c8e72002-03-23 03:26:53 +000052
53 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000054 funcs = self._months[i]
55 if isinstance(i, slice):
56 return [f(self.format) for f in funcs]
57 else:
58 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000059
Skip Montanaro4c834952002-03-15 04:08:38 +000060 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000061 return 13
62
Walter Dörwald58917a62006-03-31 15:26:22 +000063
Raymond Hettinger9c051d72002-06-20 03:38:12 +000064class _localized_day:
Tim Petersbbc0d442004-11-13 16:18:32 +000065
66 # January 1, 2001, was a Monday.
Walter Dörwald58917a62006-03-31 15:26:22 +000067 _days = [datetime.date(2001, 1, i+1).strftime for i in xrange(7)]
Tim Petersbbc0d442004-11-13 16:18:32 +000068
Tim Peters0c2c8e72002-03-23 03:26:53 +000069 def __init__(self, format):
70 self.format = format
71
72 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000073 funcs = self._days[i]
74 if isinstance(i, slice):
75 return [f(self.format) for f in funcs]
76 else:
77 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000078
Neal Norwitz492faa52004-06-07 03:47:06 +000079 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000080 return 7
Barry Warsaw1d099102001-05-22 15:58:30 +000081
Walter Dörwald58917a62006-03-31 15:26:22 +000082
Guido van Rossum9b3bc711993-06-20 21:02:22 +000083# Full and abbreviated names of weekdays
Tim Peters0c2c8e72002-03-23 03:26:53 +000084day_name = _localized_day('%A')
85day_abbr = _localized_day('%a')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000086
Guido van Rossum5cfa5df1993-06-23 09:30:50 +000087# Full and abbreviated names of months (1-based arrays!!!)
Tim Peters0c2c8e72002-03-23 03:26:53 +000088month_name = _localized_month('%B')
89month_abbr = _localized_month('%b')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000090
Skip Montanaroad3bc442000-08-30 14:01:28 +000091# Constants for weekdays
92(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
93
Skip Montanaroad3bc442000-08-30 14:01:28 +000094
Guido van Rossum9b3bc711993-06-20 21:02:22 +000095def isleap(year):
Guido van Rossum4acc25b2000-02-02 15:10:15 +000096 """Return 1 for leap years, 0 for non-leap years."""
Fred Drake8152d322000-12-12 23:20:45 +000097 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Guido van Rossum9b3bc711993-06-20 21:02:22 +000098
Walter Dörwald58917a62006-03-31 15:26:22 +000099
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000100def leapdays(y1, y2):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000101 """Return number of leap years in range [y1, y2).
Guido van Rossum46735ad2000-10-09 12:42:04 +0000102 Assume y1 <= y2."""
103 y1 -= 1
104 y2 -= 1
Raymond Hettingere11b5102002-12-25 16:37:19 +0000105 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000106
Walter Dörwald58917a62006-03-31 15:26:22 +0000107
Guido van Rossumc6360141990-10-13 19:23:40 +0000108def weekday(year, month, day):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000109 """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
110 day (1-31)."""
Raymond Hettingere11b5102002-12-25 16:37:19 +0000111 return datetime.date(year, month, day).weekday()
Guido van Rossumc6360141990-10-13 19:23:40 +0000112
Walter Dörwald58917a62006-03-31 15:26:22 +0000113
Guido van Rossumc6360141990-10-13 19:23:40 +0000114def monthrange(year, month):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000115 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
116 year, month."""
117 if not 1 <= month <= 12:
Walter Dörwald58917a62006-03-31 15:26:22 +0000118 raise IllegalMonthError(month)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000119 day1 = weekday(year, month, 1)
120 ndays = mdays[month] + (month == February and isleap(year))
121 return day1, ndays
Guido van Rossumc6360141990-10-13 19:23:40 +0000122
Guido van Rossumc6360141990-10-13 19:23:40 +0000123
Walter Dörwald58917a62006-03-31 15:26:22 +0000124class Calendar(object):
125 """
126 Base calendar class. This class doesn't do any formatting. It simply
127 provides data to subclasses.
128 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000129
Walter Dörwald58917a62006-03-31 15:26:22 +0000130 def __init__(self, firstweekday=0):
Walter Dörwaldf878b812006-04-01 20:40:23 +0000131 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
Walter Dörwald58917a62006-03-31 15:26:22 +0000132
133 def iterweekdays(self):
134 """
135 Return a iterator for one week of weekday numbers starting with the
136 configured first one.
137 """
Walter Dörwaldf878b812006-04-01 20:40:23 +0000138 for i in xrange(self.firstweekday, self.firstweekday + 7):
Walter Dörwald58917a62006-03-31 15:26:22 +0000139 yield i%7
140
141 def itermonthdates(self, year, month):
142 """
143 Return an iterator for one month. The iterator will yield datetime.date
144 values and will always iterate through complete weeks, so it will yield
145 dates outside the specified month.
146 """
147 date = datetime.date(year, month, 1)
148 # Go back to the beginning of the week
Walter Dörwaldf878b812006-04-01 20:40:23 +0000149 days = (date.weekday() - self.firstweekday) % 7
Walter Dörwald58917a62006-03-31 15:26:22 +0000150 date -= datetime.timedelta(days=days)
151 oneday = datetime.timedelta(days=1)
152 while True:
153 yield date
154 date += oneday
Walter Dörwaldf878b812006-04-01 20:40:23 +0000155 if date.month != month and date.weekday() == self.firstweekday:
Walter Dörwald58917a62006-03-31 15:26:22 +0000156 break
157
158 def itermonthdays2(self, year, month):
159 """
160 Like itermonthdates(), but will yield (day number, weekday number)
161 tuples. For days outside the specified month the day number is 0.
162 """
163 for date in self.itermonthdates(year, month):
164 if date.month != month:
165 yield (0, date.weekday())
166 else:
167 yield (date.day, date.weekday())
168
169 def itermonthdays(self, year, month):
170 """
171 Like itermonthdates(), but will yield day numbers tuples. For days
172 outside the specified month the day number is 0.
173 """
174 for date in self.itermonthdates(year, month):
175 if date.month != month:
176 yield 0
177 else:
178 yield date.day
179
180 def monthdatescalendar(self, year, month):
181 """
182 Return a matrix (list of lists) representing a month's calendar.
183 Each row represents a week; week entries are datetime.date values.
184 """
185 dates = list(self.itermonthdates(year, month))
186 return [ dates[i:i+7] for i in xrange(0, len(dates), 7) ]
187
188 def monthdays2calendar(self, year, month):
189 """
190 Return a matrix representing a month's calendar.
191 Each row represents a week; week entries are
192 (day number, weekday number) tuples. Day numbers outside this month
193 are zero.
194 """
195 days = list(self.itermonthdays2(year, month))
196 return [ days[i:i+7] for i in xrange(0, len(days), 7) ]
197
198 def monthdayscalendar(self, year, month):
199 """
200 Return a matrix representing a month's calendar.
201 Each row represents a week; days outside this month are zero.
202 """
203 days = list(self.itermonthdays(year, month))
204 return [ days[i:i+7] for i in xrange(0, len(days), 7) ]
205
206 def yeardatescalendar(self, year, width=3):
207 """
208 Return the data for the specified year ready for formatting. The return
209 value is a list of month rows. Each month row contains upto width months.
210 Each month contains between 4 and 6 weeks and each week contains 1-7
211 days. Days are datetime.date objects.
212 """
213 months = [
214 self.monthdatescalendar(year, i)
215 for i in xrange(January, January+12)
216 ]
217 return [months[i:i+width] for i in xrange(0, len(months), width) ]
218
219 def yeardays2calendar(self, year, width=3):
220 """
221 Return the data for the specified year ready for formatting (similar to
222 yeardatescalendar()). Entries in the week lists are
223 (day number, weekday number) tuples. Day numbers outside this month are
224 zero.
225 """
226 months = [
227 self.monthdays2calendar(year, i)
228 for i in xrange(January, January+12)
229 ]
230 return [months[i:i+width] for i in xrange(0, len(months), width) ]
231
232 def yeardayscalendar(self, year, width=3):
233 """
234 Return the data for the specified year ready for formatting (similar to
235 yeardatescalendar()). Entries in the week lists are day numbers.
236 Day numbers outside this month are zero.
237 """
238 months = [
239 self.monthdayscalendar(year, i)
240 for i in xrange(January, January+12)
241 ]
242 return [months[i:i+width] for i in xrange(0, len(months), width) ]
243
244
245class TextCalendar(Calendar):
246 """
247 Subclass of Calendar that outputs a calendar as a simple plain text
248 similar to the UNIX program cal.
249 """
250
251 def prweek(theweek, width):
252 """
253 Print a single week (no newline).
254 """
255 print self.week(theweek, width),
256
257 def formatday(self, day, weekday, width):
258 """
259 Returns a formatted day.
260 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000261 if day == 0:
262 s = ''
263 else:
264 s = '%2i' % day # right-align single-digit days
Walter Dörwald58917a62006-03-31 15:26:22 +0000265 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000266
Walter Dörwald58917a62006-03-31 15:26:22 +0000267 def formatweek(self, theweek, width):
268 """
269 Returns a single week in a string (no newline).
270 """
271 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000272
Walter Dörwald58917a62006-03-31 15:26:22 +0000273 def formatweekday(self, day, width):
274 """
275 Returns a formatted week day name.
276 """
277 if width >= 9:
278 names = day_name
279 else:
280 names = day_abbr
281 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000282
Walter Dörwald58917a62006-03-31 15:26:22 +0000283 def formatweekheader(self, width):
284 """
285 Return a header for a week.
286 """
287 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000288
Walter Dörwald48d5e502006-04-01 07:57:00 +0000289 def formatmonthname(self, theyear, themonth, width, withyear=True):
Walter Dörwald58917a62006-03-31 15:26:22 +0000290 """
291 Return a formatted month name.
292 """
Walter Dörwald48d5e502006-04-01 07:57:00 +0000293 s = month_name[themonth]
294 if withyear:
295 s = "%s %r" % (s, theyear)
Walter Dörwald58917a62006-03-31 15:26:22 +0000296 return s.center(width)
297
298 def prmonth(self, theyear, themonth, w=0, l=0):
299 """
300 Print a month's calendar.
301 """
302 print self.formatmonth(theyear, themonth, w, l),
303
304 def formatmonth(self, theyear, themonth, w=0, l=0):
305 """
306 Return a month's calendar string (multi-line).
307 """
308 w = max(2, w)
309 l = max(1, l)
310 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
311 s = s.rstrip()
312 s += '\n' * l
313 s += self.formatweekheader(w).rstrip()
314 s += '\n' * l
315 for week in self.monthdays2calendar(theyear, themonth):
316 s += self.formatweek(week, w).rstrip()
317 s += '\n' * l
318 return s
319
320 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
321 """
322 Returns a year's calendar as a multi-line string.
323 """
324 w = max(2, w)
325 l = max(1, l)
326 c = max(2, c)
327 colwidth = (w + 1) * 7 - 1
328 v = []
329 a = v.append
330 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
331 a('\n'*l)
332 header = self.formatweekheader(w)
333 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
334 # months in this row
335 months = xrange(m*i+1, min(m*(i+1)+1, 13))
336 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000337 names = (self.formatmonthname(theyear, k, colwidth, False)
338 for k in months)
339 a(formatstring(names, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000340 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000341 headers = (header for k in months)
342 a(formatstring(headers, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000343 a('\n'*l)
344 # max number of weeks for this row
345 height = max(len(cal) for cal in row)
346 for j in xrange(height):
347 weeks = []
348 for cal in row:
349 if j >= len(cal):
350 weeks.append('')
351 else:
352 weeks.append(self.formatweek(cal[j], w))
353 a(formatstring(weeks, colwidth, c).rstrip())
354 a('\n' * l)
355 return ''.join(v)
356
357 def pryear(self, theyear, w=0, l=0, c=6, m=3):
358 """Print a year's calendar."""
359 print self.formatyear(theyear, w, l, c, m)
360
361
362class HTMLCalendar(Calendar):
363 """
364 This calendar returns complete HTML pages.
365 """
366
367 # CSS classes for the day <td>s
368 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
369
370 def formatday(self, day, weekday):
371 """
372 Return a day as a table cell.
373 """
374 if day == 0:
375 return '<td class="noday">&nbsp;</td>' # day outside month
376 else:
377 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
378
379 def formatweek(self, theweek):
380 """
381 Return a complete week as a table row.
382 """
383 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
384 return '<tr>%s</tr>' % s
385
386 def formatweekday(self, day):
387 """
388 Return a weekday name as a table header.
389 """
390 return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
391
392 def formatweekheader(self):
393 """
394 Return a header for a week as a table row.
395 """
396 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
397 return '<tr>%s</tr>' % s
398
399 def formatmonthname(self, theyear, themonth, withyear=True):
400 """
401 Return a month name as a table row.
402 """
403 if withyear:
404 s = '%s %s' % (month_name[themonth], theyear)
405 else:
406 s = '%s' % month_name[themonth]
407 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
408
409 def formatmonth(self, theyear, themonth, withyear=True):
410 """
411 Return a formatted month as a table.
412 """
413 v = []
414 a = v.append
415 a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
416 a('\n')
417 a(self.formatmonthname(theyear, themonth, withyear=withyear))
418 a('\n')
419 a(self.formatweekheader())
420 a('\n')
421 for week in self.monthdays2calendar(theyear, themonth):
422 a(self.formatweek(week))
423 a('\n')
424 a('</table>')
425 a('\n')
426 return ''.join(v)
427
428 def formatyear(self, theyear, width=3):
429 """
430 Return a formatted year as a table of tables.
431 """
432 v = []
433 a = v.append
434 width = max(width, 1)
435 a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
436 a('\n')
437 a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
438 for i in xrange(January, January+12, width):
439 # months in this row
440 months = xrange(i, min(i+width, 13))
441 a('<tr>')
442 for m in months:
443 a('<td>')
444 a(self.formatmonth(theyear, m, withyear=False))
445 a('</td>')
446 a('</tr>')
447 a('</table>')
448 return ''.join(v)
449
450 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
451 """
452 Return a formatted year as a complete HTML page.
453 """
454 if encoding is None:
455 encoding = sys.getdefaultencoding()
456 v = []
457 a = v.append
458 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
459 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
460 a('<html>\n')
461 a('<head>\n')
462 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
463 if css is not None:
464 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
465 a('<title>Calendar for %d</title\n' % theyear)
466 a('</head>\n')
467 a('<body>\n')
468 a(self.formatyear(theyear, width))
469 a('</body>\n')
470 a('</html>\n')
Walter Dörwald48d5e502006-04-01 07:57:00 +0000471 return ''.join(v).encode(encoding, "xmlcharrefreplace")
472
473
474class LocaleTextCalendar(TextCalendar):
475 """
476 This class can be passed a locale name in the constructor and will return
477 month and weekday names in the specified locale. If this locale includes
478 an encoding all strings containing month and weekday names will be returned
479 as unicode.
480 """
481
482 def __init__(self, firstweekday=0, locale=None):
483 TextCalendar.__init__(self, firstweekday)
484 if locale is None:
485 locale = locale.getdefaultlocale()
486 self.locale = locale
487
488 def formatweekday(self, day, width):
489 oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
490 try:
491 encoding = locale.getlocale(locale.LC_TIME)[1]
492 if width >= 9:
493 names = day_name
494 else:
495 names = day_abbr
496 name = names[day]
497 if encoding is not None:
498 name = name.decode(encoding)
499 result = name[:width].center(width)
500 finally:
501 locale.setlocale(locale.LC_TIME, oldlocale)
502 return result
503
504 def formatmonthname(self, theyear, themonth, width, withyear=True):
505 oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
506 try:
507 encoding = locale.getlocale(locale.LC_TIME)[1]
508 s = month_name[themonth]
509 if encoding is not None:
510 s = s.decode(encoding)
511 if withyear:
512 s = "%s %r" % (s, theyear)
513 result = s.center(width)
514 finally:
515 locale.setlocale(locale.LC_TIME, oldlocale)
516 return result
517
518
519class LocaleHTMLCalendar(HTMLCalendar):
520 """
521 This class can be passed a locale name in the constructor and will return
522 month and weekday names in the specified locale. If this locale includes
523 an encoding all strings containing month and weekday names will be returned
524 as unicode.
525 """
526 def __init__(self, firstweekday=0, locale=None):
527 HTMLCalendar.__init__(self, firstweekday)
528 if locale is None:
529 locale = locale.getdefaultlocale()
530 self.locale = locale
531
532 def formatweekday(self, day):
533 oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
534 try:
535 encoding = locale.getlocale(locale.LC_TIME)[1]
536 s = day_abbr[day]
537 if encoding is not None:
538 s = s.decode(encoding)
539 result = '<th class="%s">%s</th>' % (self.cssclasses[day], s)
540 finally:
541 locale.setlocale(locale.LC_TIME, oldlocale)
542 return result
543
544 def formatmonthname(self, theyear, themonth, withyear=True):
545 oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
546 try:
547 encoding = locale.getlocale(locale.LC_TIME)[1]
548 s = month_name[themonth]
549 if encoding is not None:
550 s = s.decode(encoding)
551 if withyear:
552 s = '%s %s' % (s, theyear)
553 result = '<tr><th colspan="7" class="month">%s</th></tr>' % s
554 finally:
555 locale.setlocale(locale.LC_TIME, oldlocale)
556 return result
Walter Dörwald58917a62006-03-31 15:26:22 +0000557
558
559# Support for old module level interface
560c = TextCalendar()
561
Walter Dörwaldf878b812006-04-01 20:40:23 +0000562def firstweekday():
563 return c.firstweekday
564
565def setfirstweekday(firstweekday):
566 if not MONDAY <= firstweekday <= SUNDAY:
567 raise IllegalWeekdayError(firstweekday)
568 c.firstweekday = firstweekday
569
Walter Dörwald58917a62006-03-31 15:26:22 +0000570monthcalendar = c.monthdayscalendar
571prweek = c.prweek
572week = c.formatweek
573weekheader = c.formatweekheader
574prmonth = c.prmonth
575month = c.formatmonth
576calendar = c.formatyear
577prcal = c.pryear
578
579
580# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000581_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000582_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000583
Guido van Rossumc6360141990-10-13 19:23:40 +0000584
Walter Dörwald58917a62006-03-31 15:26:22 +0000585def format(cols, colwidth=_colwidth, spacing=_spacing):
586 """Prints multi-column formatting for year calendars"""
587 print formatstring(cols, colwidth, spacing)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000588
Skip Montanaroad3bc442000-08-30 14:01:28 +0000589
Walter Dörwald58917a62006-03-31 15:26:22 +0000590def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
591 """Returns a string formatted from n strings, centered within n columns."""
592 spacing *= ' '
593 return spacing.join(c.center(colwidth) for c in cols)
594
Guido van Rossumb39aff81999-06-09 15:07:38 +0000595
Guido van Rossumb39aff81999-06-09 15:07:38 +0000596EPOCH = 1970
Raymond Hettingere11b5102002-12-25 16:37:19 +0000597_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
598
Walter Dörwald58917a62006-03-31 15:26:22 +0000599
Guido van Rossumb39aff81999-06-09 15:07:38 +0000600def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000601 """Unrelated but handy function to calculate Unix timestamp from GMT."""
602 year, month, day, hour, minute, second = tuple[:6]
Raymond Hettinger61436482003-02-13 22:58:02 +0000603 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000604 hours = days*24 + hour
605 minutes = hours*60 + minute
606 seconds = minutes*60 + second
607 return seconds
Walter Dörwald58917a62006-03-31 15:26:22 +0000608
609
610def main(args):
611 import optparse
Walter Dörwald48d5e502006-04-01 07:57:00 +0000612 parser = optparse.OptionParser(usage="usage: %prog [options] [year [month]]")
613 parser.add_option(
614 "-w", "--width",
615 dest="width", type="int", default=2,
616 help="width of date column (default 2, text only)"
617 )
618 parser.add_option(
619 "-l", "--lines",
620 dest="lines", type="int", default=1,
621 help="number of lines for each week (default 1, text only)"
622 )
623 parser.add_option(
624 "-s", "--spacing",
625 dest="spacing", type="int", default=6,
626 help="spacing between months (default 6, text only)"
627 )
628 parser.add_option(
629 "-m", "--months",
630 dest="months", type="int", default=3,
631 help="months per row (default 3, text only)"
632 )
633 parser.add_option(
634 "-c", "--css",
635 dest="css", default="calendar.css",
636 help="CSS to use for page (html only)"
637 )
638 parser.add_option(
639 "-L", "--locale",
640 dest="locale", default=None,
641 help="locale to be used from month and weekday names"
642 )
643 parser.add_option(
644 "-e", "--encoding",
645 dest="encoding", default=None,
646 help="Encoding to use for output"
647 )
648 parser.add_option(
649 "-t", "--type",
650 dest="type", default="text",
651 choices=("text", "html"),
652 help="output type (text or html)"
653 )
Walter Dörwald58917a62006-03-31 15:26:22 +0000654
655 (options, args) = parser.parse_args(args)
656
Walter Dörwald48d5e502006-04-01 07:57:00 +0000657 if options.locale and not options.encoding:
658 parser.error("if --locale is specified --encoding is required")
659 sys.exit(1)
660
Walter Dörwald58917a62006-03-31 15:26:22 +0000661 if options.type == "html":
Walter Dörwald48d5e502006-04-01 07:57:00 +0000662 if options.locale:
663 cal = LocaleHTMLCalendar(locale=options.locale)
664 else:
665 cal = HTMLCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000666 encoding = options.encoding
667 if encoding is None:
668 encoding = sys.getdefaultencoding()
669 optdict = dict(encoding=encoding, css=options.css)
670 if len(args) == 1:
671 print cal.formatyearpage(datetime.date.today().year, **optdict)
672 elif len(args) == 2:
673 print cal.formatyearpage(int(args[1]), **optdict)
674 else:
675 parser.error("incorrect number of arguments")
676 sys.exit(1)
677 else:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000678 if options.locale:
679 cal = LocaleTextCalendar(locale=options.locale)
680 else:
681 cal = TextCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000682 optdict = dict(w=options.width, l=options.lines)
683 if len(args) != 3:
684 optdict["c"] = options.spacing
685 optdict["m"] = options.months
686 if len(args) == 1:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000687 result = cal.formatyear(datetime.date.today().year, **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000688 elif len(args) == 2:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000689 result = cal.formatyear(int(args[1]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000690 elif len(args) == 3:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000691 result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000692 else:
693 parser.error("incorrect number of arguments")
694 sys.exit(1)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000695 if options.encoding:
696 result = result.encode(options.encoding)
697 print result
Walter Dörwald58917a62006-03-31 15:26:22 +0000698
699
700if __name__ == "__main__":
701 main(sys.argv)