blob: a003b46a52de7b5b13723a2227a77f82956e6c14 [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):
131 self._firstweekday = firstweekday # 0 = Monday, 6 = Sunday
132
133 def firstweekday(self):
134 return self._firstweekday
135
136 def setfirstweekday(self, weekday):
137 """
138 Set weekday (Monday=0, Sunday=6) to start each week.
139 """
140 if not MONDAY <= weekday <= SUNDAY:
141 raise IllegalWeekdayError(weekday)
142 self._firstweekday = weekday
143
144 def iterweekdays(self):
145 """
146 Return a iterator for one week of weekday numbers starting with the
147 configured first one.
148 """
149 for i in xrange(self._firstweekday, self._firstweekday + 7):
150 yield i%7
151
152 def itermonthdates(self, year, month):
153 """
154 Return an iterator for one month. The iterator will yield datetime.date
155 values and will always iterate through complete weeks, so it will yield
156 dates outside the specified month.
157 """
158 date = datetime.date(year, month, 1)
159 # Go back to the beginning of the week
160 days = (date.weekday() - self._firstweekday) % 7
161 date -= datetime.timedelta(days=days)
162 oneday = datetime.timedelta(days=1)
163 while True:
164 yield date
165 date += oneday
166 if date.month != month and date.weekday() == self._firstweekday:
167 break
168
169 def itermonthdays2(self, year, month):
170 """
171 Like itermonthdates(), but will yield (day number, weekday number)
172 tuples. For days 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, date.weekday())
177 else:
178 yield (date.day, date.weekday())
179
180 def itermonthdays(self, year, month):
181 """
182 Like itermonthdates(), but will yield day numbers tuples. For days
183 outside the specified month the day number is 0.
184 """
185 for date in self.itermonthdates(year, month):
186 if date.month != month:
187 yield 0
188 else:
189 yield date.day
190
191 def monthdatescalendar(self, year, month):
192 """
193 Return a matrix (list of lists) representing a month's calendar.
194 Each row represents a week; week entries are datetime.date values.
195 """
196 dates = list(self.itermonthdates(year, month))
197 return [ dates[i:i+7] for i in xrange(0, len(dates), 7) ]
198
199 def monthdays2calendar(self, year, month):
200 """
201 Return a matrix representing a month's calendar.
202 Each row represents a week; week entries are
203 (day number, weekday number) tuples. Day numbers outside this month
204 are zero.
205 """
206 days = list(self.itermonthdays2(year, month))
207 return [ days[i:i+7] for i in xrange(0, len(days), 7) ]
208
209 def monthdayscalendar(self, year, month):
210 """
211 Return a matrix representing a month's calendar.
212 Each row represents a week; days outside this month are zero.
213 """
214 days = list(self.itermonthdays(year, month))
215 return [ days[i:i+7] for i in xrange(0, len(days), 7) ]
216
217 def yeardatescalendar(self, year, width=3):
218 """
219 Return the data for the specified year ready for formatting. The return
220 value is a list of month rows. Each month row contains upto width months.
221 Each month contains between 4 and 6 weeks and each week contains 1-7
222 days. Days are datetime.date objects.
223 """
224 months = [
225 self.monthdatescalendar(year, i)
226 for i in xrange(January, January+12)
227 ]
228 return [months[i:i+width] for i in xrange(0, len(months), width) ]
229
230 def yeardays2calendar(self, year, width=3):
231 """
232 Return the data for the specified year ready for formatting (similar to
233 yeardatescalendar()). Entries in the week lists are
234 (day number, weekday number) tuples. Day numbers outside this month are
235 zero.
236 """
237 months = [
238 self.monthdays2calendar(year, i)
239 for i in xrange(January, January+12)
240 ]
241 return [months[i:i+width] for i in xrange(0, len(months), width) ]
242
243 def yeardayscalendar(self, year, width=3):
244 """
245 Return the data for the specified year ready for formatting (similar to
246 yeardatescalendar()). Entries in the week lists are day numbers.
247 Day numbers outside this month are zero.
248 """
249 months = [
250 self.monthdayscalendar(year, i)
251 for i in xrange(January, January+12)
252 ]
253 return [months[i:i+width] for i in xrange(0, len(months), width) ]
254
255
256class TextCalendar(Calendar):
257 """
258 Subclass of Calendar that outputs a calendar as a simple plain text
259 similar to the UNIX program cal.
260 """
261
262 def prweek(theweek, width):
263 """
264 Print a single week (no newline).
265 """
266 print self.week(theweek, width),
267
268 def formatday(self, day, weekday, width):
269 """
270 Returns a formatted day.
271 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000272 if day == 0:
273 s = ''
274 else:
275 s = '%2i' % day # right-align single-digit days
Walter Dörwald58917a62006-03-31 15:26:22 +0000276 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000277
Walter Dörwald58917a62006-03-31 15:26:22 +0000278 def formatweek(self, theweek, width):
279 """
280 Returns a single week in a string (no newline).
281 """
282 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000283
Walter Dörwald58917a62006-03-31 15:26:22 +0000284 def formatweekday(self, day, width):
285 """
286 Returns a formatted week day name.
287 """
288 if width >= 9:
289 names = day_name
290 else:
291 names = day_abbr
292 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000293
Walter Dörwald58917a62006-03-31 15:26:22 +0000294 def formatweekheader(self, width):
295 """
296 Return a header for a week.
297 """
298 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000299
Walter Dörwald48d5e502006-04-01 07:57:00 +0000300 def formatmonthname(self, theyear, themonth, width, withyear=True):
Walter Dörwald58917a62006-03-31 15:26:22 +0000301 """
302 Return a formatted month name.
303 """
Walter Dörwald48d5e502006-04-01 07:57:00 +0000304 s = month_name[themonth]
305 if withyear:
306 s = "%s %r" % (s, theyear)
Walter Dörwald58917a62006-03-31 15:26:22 +0000307 return s.center(width)
308
309 def prmonth(self, theyear, themonth, w=0, l=0):
310 """
311 Print a month's calendar.
312 """
313 print self.formatmonth(theyear, themonth, w, l),
314
315 def formatmonth(self, theyear, themonth, w=0, l=0):
316 """
317 Return a month's calendar string (multi-line).
318 """
319 w = max(2, w)
320 l = max(1, l)
321 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
322 s = s.rstrip()
323 s += '\n' * l
324 s += self.formatweekheader(w).rstrip()
325 s += '\n' * l
326 for week in self.monthdays2calendar(theyear, themonth):
327 s += self.formatweek(week, w).rstrip()
328 s += '\n' * l
329 return s
330
331 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
332 """
333 Returns a year's calendar as a multi-line string.
334 """
335 w = max(2, w)
336 l = max(1, l)
337 c = max(2, c)
338 colwidth = (w + 1) * 7 - 1
339 v = []
340 a = v.append
341 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
342 a('\n'*l)
343 header = self.formatweekheader(w)
344 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
345 # months in this row
346 months = xrange(m*i+1, min(m*(i+1)+1, 13))
347 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000348 names = (self.formatmonthname(theyear, k, colwidth, False)
349 for k in months)
350 a(formatstring(names, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000351 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000352 headers = (header for k in months)
353 a(formatstring(headers, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000354 a('\n'*l)
355 # max number of weeks for this row
356 height = max(len(cal) for cal in row)
357 for j in xrange(height):
358 weeks = []
359 for cal in row:
360 if j >= len(cal):
361 weeks.append('')
362 else:
363 weeks.append(self.formatweek(cal[j], w))
364 a(formatstring(weeks, colwidth, c).rstrip())
365 a('\n' * l)
366 return ''.join(v)
367
368 def pryear(self, theyear, w=0, l=0, c=6, m=3):
369 """Print a year's calendar."""
370 print self.formatyear(theyear, w, l, c, m)
371
372
373class HTMLCalendar(Calendar):
374 """
375 This calendar returns complete HTML pages.
376 """
377
378 # CSS classes for the day <td>s
379 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
380
381 def formatday(self, day, weekday):
382 """
383 Return a day as a table cell.
384 """
385 if day == 0:
386 return '<td class="noday">&nbsp;</td>' # day outside month
387 else:
388 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
389
390 def formatweek(self, theweek):
391 """
392 Return a complete week as a table row.
393 """
394 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
395 return '<tr>%s</tr>' % s
396
397 def formatweekday(self, day):
398 """
399 Return a weekday name as a table header.
400 """
401 return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
402
403 def formatweekheader(self):
404 """
405 Return a header for a week as a table row.
406 """
407 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
408 return '<tr>%s</tr>' % s
409
410 def formatmonthname(self, theyear, themonth, withyear=True):
411 """
412 Return a month name as a table row.
413 """
414 if withyear:
415 s = '%s %s' % (month_name[themonth], theyear)
416 else:
417 s = '%s' % month_name[themonth]
418 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
419
420 def formatmonth(self, theyear, themonth, withyear=True):
421 """
422 Return a formatted month as a table.
423 """
424 v = []
425 a = v.append
426 a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
427 a('\n')
428 a(self.formatmonthname(theyear, themonth, withyear=withyear))
429 a('\n')
430 a(self.formatweekheader())
431 a('\n')
432 for week in self.monthdays2calendar(theyear, themonth):
433 a(self.formatweek(week))
434 a('\n')
435 a('</table>')
436 a('\n')
437 return ''.join(v)
438
439 def formatyear(self, theyear, width=3):
440 """
441 Return a formatted year as a table of tables.
442 """
443 v = []
444 a = v.append
445 width = max(width, 1)
446 a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
447 a('\n')
448 a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
449 for i in xrange(January, January+12, width):
450 # months in this row
451 months = xrange(i, min(i+width, 13))
452 a('<tr>')
453 for m in months:
454 a('<td>')
455 a(self.formatmonth(theyear, m, withyear=False))
456 a('</td>')
457 a('</tr>')
458 a('</table>')
459 return ''.join(v)
460
461 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
462 """
463 Return a formatted year as a complete HTML page.
464 """
465 if encoding is None:
466 encoding = sys.getdefaultencoding()
467 v = []
468 a = v.append
469 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
470 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
471 a('<html>\n')
472 a('<head>\n')
473 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
474 if css is not None:
475 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
476 a('<title>Calendar for %d</title\n' % theyear)
477 a('</head>\n')
478 a('<body>\n')
479 a(self.formatyear(theyear, width))
480 a('</body>\n')
481 a('</html>\n')
Walter Dörwald48d5e502006-04-01 07:57:00 +0000482 return ''.join(v).encode(encoding, "xmlcharrefreplace")
483
484
485class LocaleTextCalendar(TextCalendar):
486 """
487 This class can be passed a locale name in the constructor and will return
488 month and weekday names in the specified locale. If this locale includes
489 an encoding all strings containing month and weekday names will be returned
490 as unicode.
491 """
492
493 def __init__(self, firstweekday=0, locale=None):
494 TextCalendar.__init__(self, firstweekday)
495 if locale is None:
496 locale = locale.getdefaultlocale()
497 self.locale = locale
498
499 def formatweekday(self, day, width):
500 oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
501 try:
502 encoding = locale.getlocale(locale.LC_TIME)[1]
503 if width >= 9:
504 names = day_name
505 else:
506 names = day_abbr
507 name = names[day]
508 if encoding is not None:
509 name = name.decode(encoding)
510 result = name[:width].center(width)
511 finally:
512 locale.setlocale(locale.LC_TIME, oldlocale)
513 return result
514
515 def formatmonthname(self, theyear, themonth, width, withyear=True):
516 oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
517 try:
518 encoding = locale.getlocale(locale.LC_TIME)[1]
519 s = month_name[themonth]
520 if encoding is not None:
521 s = s.decode(encoding)
522 if withyear:
523 s = "%s %r" % (s, theyear)
524 result = s.center(width)
525 finally:
526 locale.setlocale(locale.LC_TIME, oldlocale)
527 return result
528
529
530class LocaleHTMLCalendar(HTMLCalendar):
531 """
532 This class can be passed a locale name in the constructor and will return
533 month and weekday names in the specified locale. If this locale includes
534 an encoding all strings containing month and weekday names will be returned
535 as unicode.
536 """
537 def __init__(self, firstweekday=0, locale=None):
538 HTMLCalendar.__init__(self, firstweekday)
539 if locale is None:
540 locale = locale.getdefaultlocale()
541 self.locale = locale
542
543 def formatweekday(self, day):
544 oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
545 try:
546 encoding = locale.getlocale(locale.LC_TIME)[1]
547 s = day_abbr[day]
548 if encoding is not None:
549 s = s.decode(encoding)
550 result = '<th class="%s">%s</th>' % (self.cssclasses[day], s)
551 finally:
552 locale.setlocale(locale.LC_TIME, oldlocale)
553 return result
554
555 def formatmonthname(self, theyear, themonth, withyear=True):
556 oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
557 try:
558 encoding = locale.getlocale(locale.LC_TIME)[1]
559 s = month_name[themonth]
560 if encoding is not None:
561 s = s.decode(encoding)
562 if withyear:
563 s = '%s %s' % (s, theyear)
564 result = '<tr><th colspan="7" class="month">%s</th></tr>' % s
565 finally:
566 locale.setlocale(locale.LC_TIME, oldlocale)
567 return result
Walter Dörwald58917a62006-03-31 15:26:22 +0000568
569
570# Support for old module level interface
571c = TextCalendar()
572
573firstweekday = c.firstweekday
574setfirstweekday = c.setfirstweekday
575monthcalendar = c.monthdayscalendar
576prweek = c.prweek
577week = c.formatweek
578weekheader = c.formatweekheader
579prmonth = c.prmonth
580month = c.formatmonth
581calendar = c.formatyear
582prcal = c.pryear
583
584
585# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000586_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000587_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000588
Guido van Rossumc6360141990-10-13 19:23:40 +0000589
Walter Dörwald58917a62006-03-31 15:26:22 +0000590def format(cols, colwidth=_colwidth, spacing=_spacing):
591 """Prints multi-column formatting for year calendars"""
592 print formatstring(cols, colwidth, spacing)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000593
Skip Montanaroad3bc442000-08-30 14:01:28 +0000594
Walter Dörwald58917a62006-03-31 15:26:22 +0000595def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
596 """Returns a string formatted from n strings, centered within n columns."""
597 spacing *= ' '
598 return spacing.join(c.center(colwidth) for c in cols)
599
Guido van Rossumb39aff81999-06-09 15:07:38 +0000600
Guido van Rossumb39aff81999-06-09 15:07:38 +0000601EPOCH = 1970
Raymond Hettingere11b5102002-12-25 16:37:19 +0000602_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
603
Walter Dörwald58917a62006-03-31 15:26:22 +0000604
Guido van Rossumb39aff81999-06-09 15:07:38 +0000605def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000606 """Unrelated but handy function to calculate Unix timestamp from GMT."""
607 year, month, day, hour, minute, second = tuple[:6]
Raymond Hettinger61436482003-02-13 22:58:02 +0000608 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000609 hours = days*24 + hour
610 minutes = hours*60 + minute
611 seconds = minutes*60 + second
612 return seconds
Walter Dörwald58917a62006-03-31 15:26:22 +0000613
614
615def main(args):
616 import optparse
Walter Dörwald48d5e502006-04-01 07:57:00 +0000617 parser = optparse.OptionParser(usage="usage: %prog [options] [year [month]]")
618 parser.add_option(
619 "-w", "--width",
620 dest="width", type="int", default=2,
621 help="width of date column (default 2, text only)"
622 )
623 parser.add_option(
624 "-l", "--lines",
625 dest="lines", type="int", default=1,
626 help="number of lines for each week (default 1, text only)"
627 )
628 parser.add_option(
629 "-s", "--spacing",
630 dest="spacing", type="int", default=6,
631 help="spacing between months (default 6, text only)"
632 )
633 parser.add_option(
634 "-m", "--months",
635 dest="months", type="int", default=3,
636 help="months per row (default 3, text only)"
637 )
638 parser.add_option(
639 "-c", "--css",
640 dest="css", default="calendar.css",
641 help="CSS to use for page (html only)"
642 )
643 parser.add_option(
644 "-L", "--locale",
645 dest="locale", default=None,
646 help="locale to be used from month and weekday names"
647 )
648 parser.add_option(
649 "-e", "--encoding",
650 dest="encoding", default=None,
651 help="Encoding to use for output"
652 )
653 parser.add_option(
654 "-t", "--type",
655 dest="type", default="text",
656 choices=("text", "html"),
657 help="output type (text or html)"
658 )
Walter Dörwald58917a62006-03-31 15:26:22 +0000659
660 (options, args) = parser.parse_args(args)
661
Walter Dörwald48d5e502006-04-01 07:57:00 +0000662 if options.locale and not options.encoding:
663 parser.error("if --locale is specified --encoding is required")
664 sys.exit(1)
665
Walter Dörwald58917a62006-03-31 15:26:22 +0000666 if options.type == "html":
Walter Dörwald48d5e502006-04-01 07:57:00 +0000667 if options.locale:
668 cal = LocaleHTMLCalendar(locale=options.locale)
669 else:
670 cal = HTMLCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000671 encoding = options.encoding
672 if encoding is None:
673 encoding = sys.getdefaultencoding()
674 optdict = dict(encoding=encoding, css=options.css)
675 if len(args) == 1:
676 print cal.formatyearpage(datetime.date.today().year, **optdict)
677 elif len(args) == 2:
678 print cal.formatyearpage(int(args[1]), **optdict)
679 else:
680 parser.error("incorrect number of arguments")
681 sys.exit(1)
682 else:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000683 if options.locale:
684 cal = LocaleTextCalendar(locale=options.locale)
685 else:
686 cal = TextCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000687 optdict = dict(w=options.width, l=options.lines)
688 if len(args) != 3:
689 optdict["c"] = options.spacing
690 optdict["m"] = options.months
691 if len(args) == 1:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000692 result = cal.formatyear(datetime.date.today().year, **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000693 elif len(args) == 2:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000694 result = cal.formatyear(int(args[1]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000695 elif len(args) == 3:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000696 result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000697 else:
698 parser.error("incorrect number of arguments")
699 sys.exit(1)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000700 if options.encoding:
701 result = result.encode(options.encoding)
702 print result
Walter Dörwald58917a62006-03-31 15:26:22 +0000703
704
705if __name__ == "__main__":
706 main(sys.argv)