blob: 00948efe5c084ea059158567377d6000ffc21b10 [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örwaldbc966092006-04-12 10:09:16 +00008from __future__ import with_statement
Walter Dörwald48d5e502006-04-01 07:57:00 +00009import sys, datetime, locale
Guido van Rossumc6360141990-10-13 19:23:40 +000010
Walter Dörwald58917a62006-03-31 15:26:22 +000011__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
12 "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
13 "monthcalendar", "prmonth", "month", "prcal", "calendar",
14 "timegm", "month_name", "month_abbr", "day_name", "day_abbr"]
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000015
Guido van Rossumc6360141990-10-13 19:23:40 +000016# Exception raised for bad input (with string parameter for details)
Guido van Rossum00245cf1999-05-03 18:07:40 +000017error = ValueError
Guido van Rossumc6360141990-10-13 19:23:40 +000018
Walter Dörwald58917a62006-03-31 15:26:22 +000019# Exceptions raised for bad input
20class IllegalMonthError(ValueError):
21 def __init__(self, month):
22 self.month = month
23 def __str__(self):
24 return "bad month number %r; must be 1-12" % self.month
25
26
27class IllegalWeekdayError(ValueError):
28 def __init__(self, weekday):
29 self.weekday = weekday
30 def __str__(self):
31 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
32
33
Guido van Rossum9b3bc711993-06-20 21:02:22 +000034# Constants for months referenced later
35January = 1
36February = 2
37
38# Number of days per month (except for February in leap years)
39mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
40
Tim Peters0c2c8e72002-03-23 03:26:53 +000041# This module used to have hard-coded lists of day and month names, as
42# English strings. The classes following emulate a read-only version of
43# that, but supply localized names. Note that the values are computed
44# fresh on each call, in case the user changes locale between calls.
45
Raymond Hettinger9c051d72002-06-20 03:38:12 +000046class _localized_month:
Tim Petersbbc0d442004-11-13 16:18:32 +000047
Walter Dörwald58917a62006-03-31 15:26:22 +000048 _months = [datetime.date(2001, i+1, 1).strftime for i in xrange(12)]
Tim Petersbbc0d442004-11-13 16:18:32 +000049 _months.insert(0, lambda x: "")
50
Tim Peters0c2c8e72002-03-23 03:26:53 +000051 def __init__(self, format):
Barry Warsaw1d099102001-05-22 15:58:30 +000052 self.format = format
Tim Peters0c2c8e72002-03-23 03:26:53 +000053
54 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000055 funcs = self._months[i]
56 if isinstance(i, slice):
57 return [f(self.format) for f in funcs]
58 else:
59 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000060
Skip Montanaro4c834952002-03-15 04:08:38 +000061 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000062 return 13
63
Walter Dörwald58917a62006-03-31 15:26:22 +000064
Raymond Hettinger9c051d72002-06-20 03:38:12 +000065class _localized_day:
Tim Petersbbc0d442004-11-13 16:18:32 +000066
67 # January 1, 2001, was a Monday.
Walter Dörwald58917a62006-03-31 15:26:22 +000068 _days = [datetime.date(2001, 1, i+1).strftime for i in xrange(7)]
Tim Petersbbc0d442004-11-13 16:18:32 +000069
Tim Peters0c2c8e72002-03-23 03:26:53 +000070 def __init__(self, format):
71 self.format = format
72
73 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000074 funcs = self._days[i]
75 if isinstance(i, slice):
76 return [f(self.format) for f in funcs]
77 else:
78 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000079
Neal Norwitz492faa52004-06-07 03:47:06 +000080 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000081 return 7
Barry Warsaw1d099102001-05-22 15:58:30 +000082
Walter Dörwald58917a62006-03-31 15:26:22 +000083
Guido van Rossum9b3bc711993-06-20 21:02:22 +000084# Full and abbreviated names of weekdays
Tim Peters0c2c8e72002-03-23 03:26:53 +000085day_name = _localized_day('%A')
86day_abbr = _localized_day('%a')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000087
Guido van Rossum5cfa5df1993-06-23 09:30:50 +000088# Full and abbreviated names of months (1-based arrays!!!)
Tim Peters0c2c8e72002-03-23 03:26:53 +000089month_name = _localized_month('%B')
90month_abbr = _localized_month('%b')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000091
Skip Montanaroad3bc442000-08-30 14:01:28 +000092# Constants for weekdays
93(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
94
Skip Montanaroad3bc442000-08-30 14:01:28 +000095
Guido van Rossum9b3bc711993-06-20 21:02:22 +000096def isleap(year):
Guido van Rossum4acc25b2000-02-02 15:10:15 +000097 """Return 1 for leap years, 0 for non-leap years."""
Fred Drake8152d322000-12-12 23:20:45 +000098 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Guido van Rossum9b3bc711993-06-20 21:02:22 +000099
Walter Dörwald58917a62006-03-31 15:26:22 +0000100
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000101def leapdays(y1, y2):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000102 """Return number of leap years in range [y1, y2).
Guido van Rossum46735ad2000-10-09 12:42:04 +0000103 Assume y1 <= y2."""
104 y1 -= 1
105 y2 -= 1
Raymond Hettingere11b5102002-12-25 16:37:19 +0000106 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000107
Walter Dörwald58917a62006-03-31 15:26:22 +0000108
Guido van Rossumc6360141990-10-13 19:23:40 +0000109def weekday(year, month, day):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000110 """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
111 day (1-31)."""
Raymond Hettingere11b5102002-12-25 16:37:19 +0000112 return datetime.date(year, month, day).weekday()
Guido van Rossumc6360141990-10-13 19:23:40 +0000113
Walter Dörwald58917a62006-03-31 15:26:22 +0000114
Guido van Rossumc6360141990-10-13 19:23:40 +0000115def monthrange(year, month):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000116 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
117 year, month."""
118 if not 1 <= month <= 12:
Walter Dörwald58917a62006-03-31 15:26:22 +0000119 raise IllegalMonthError(month)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000120 day1 = weekday(year, month, 1)
121 ndays = mdays[month] + (month == February and isleap(year))
122 return day1, ndays
Guido van Rossumc6360141990-10-13 19:23:40 +0000123
Guido van Rossumc6360141990-10-13 19:23:40 +0000124
Walter Dörwald58917a62006-03-31 15:26:22 +0000125class Calendar(object):
126 """
127 Base calendar class. This class doesn't do any formatting. It simply
128 provides data to subclasses.
129 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000130
Walter Dörwald58917a62006-03-31 15:26:22 +0000131 def __init__(self, firstweekday=0):
Walter Dörwaldf878b812006-04-01 20:40:23 +0000132 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
Walter Dörwald58917a62006-03-31 15:26:22 +0000133
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000134 def getfirstweekday(self):
Walter Dörwald72d84af2006-04-03 15:21:59 +0000135 return self._firstweekday % 7
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000136
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000137 def setfirstweekday(self, firstweekday):
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000138 self._firstweekday = firstweekday
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000139
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000140 firstweekday = property(getfirstweekday, setfirstweekday)
141
Walter Dörwald58917a62006-03-31 15:26:22 +0000142 def iterweekdays(self):
143 """
144 Return a iterator for one week of weekday numbers starting with the
145 configured first one.
146 """
Walter Dörwaldf878b812006-04-01 20:40:23 +0000147 for i in xrange(self.firstweekday, self.firstweekday + 7):
Walter Dörwald58917a62006-03-31 15:26:22 +0000148 yield i%7
149
150 def itermonthdates(self, year, month):
151 """
152 Return an iterator for one month. The iterator will yield datetime.date
153 values and will always iterate through complete weeks, so it will yield
154 dates outside the specified month.
155 """
156 date = datetime.date(year, month, 1)
157 # Go back to the beginning of the week
Walter Dörwaldf878b812006-04-01 20:40:23 +0000158 days = (date.weekday() - self.firstweekday) % 7
Walter Dörwald58917a62006-03-31 15:26:22 +0000159 date -= datetime.timedelta(days=days)
160 oneday = datetime.timedelta(days=1)
161 while True:
162 yield date
163 date += oneday
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000164 if date.month != month and date.weekday() == self.firstweekday:
Walter Dörwald58917a62006-03-31 15:26:22 +0000165 break
166
167 def itermonthdays2(self, year, month):
168 """
169 Like itermonthdates(), but will yield (day number, weekday number)
170 tuples. For days outside the specified month the day number is 0.
171 """
172 for date in self.itermonthdates(year, month):
173 if date.month != month:
174 yield (0, date.weekday())
175 else:
176 yield (date.day, date.weekday())
177
178 def itermonthdays(self, year, month):
179 """
180 Like itermonthdates(), but will yield day numbers tuples. For days
181 outside the specified month the day number is 0.
182 """
183 for date in self.itermonthdates(year, month):
184 if date.month != month:
185 yield 0
186 else:
187 yield date.day
188
189 def monthdatescalendar(self, year, month):
190 """
191 Return a matrix (list of lists) representing a month's calendar.
192 Each row represents a week; week entries are datetime.date values.
193 """
194 dates = list(self.itermonthdates(year, month))
195 return [ dates[i:i+7] for i in xrange(0, len(dates), 7) ]
196
197 def monthdays2calendar(self, year, month):
198 """
199 Return a matrix representing a month's calendar.
200 Each row represents a week; week entries are
201 (day number, weekday number) tuples. Day numbers outside this month
202 are zero.
203 """
204 days = list(self.itermonthdays2(year, month))
205 return [ days[i:i+7] for i in xrange(0, len(days), 7) ]
206
207 def monthdayscalendar(self, year, month):
208 """
209 Return a matrix representing a month's calendar.
210 Each row represents a week; days outside this month are zero.
211 """
212 days = list(self.itermonthdays(year, month))
213 return [ days[i:i+7] for i in xrange(0, len(days), 7) ]
214
215 def yeardatescalendar(self, year, width=3):
216 """
217 Return the data for the specified year ready for formatting. The return
218 value is a list of month rows. Each month row contains upto width months.
219 Each month contains between 4 and 6 weeks and each week contains 1-7
220 days. Days are datetime.date objects.
221 """
222 months = [
223 self.monthdatescalendar(year, i)
224 for i in xrange(January, January+12)
225 ]
226 return [months[i:i+width] for i in xrange(0, len(months), width) ]
227
228 def yeardays2calendar(self, year, width=3):
229 """
230 Return the data for the specified year ready for formatting (similar to
231 yeardatescalendar()). Entries in the week lists are
232 (day number, weekday number) tuples. Day numbers outside this month are
233 zero.
234 """
235 months = [
236 self.monthdays2calendar(year, i)
237 for i in xrange(January, January+12)
238 ]
239 return [months[i:i+width] for i in xrange(0, len(months), width) ]
240
241 def yeardayscalendar(self, year, width=3):
242 """
243 Return the data for the specified year ready for formatting (similar to
244 yeardatescalendar()). Entries in the week lists are day numbers.
245 Day numbers outside this month are zero.
246 """
247 months = [
248 self.monthdayscalendar(year, i)
249 for i in xrange(January, January+12)
250 ]
251 return [months[i:i+width] for i in xrange(0, len(months), width) ]
252
253
254class TextCalendar(Calendar):
255 """
256 Subclass of Calendar that outputs a calendar as a simple plain text
257 similar to the UNIX program cal.
258 """
259
Anthony Baxter7846f4d2006-04-07 05:41:13 +0000260 def prweek(self, theweek, width):
Walter Dörwald58917a62006-03-31 15:26:22 +0000261 """
262 Print a single week (no newline).
263 """
264 print self.week(theweek, width),
265
266 def formatday(self, day, weekday, width):
267 """
268 Returns a formatted day.
269 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000270 if day == 0:
271 s = ''
272 else:
273 s = '%2i' % day # right-align single-digit days
Walter Dörwald58917a62006-03-31 15:26:22 +0000274 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000275
Walter Dörwald58917a62006-03-31 15:26:22 +0000276 def formatweek(self, theweek, width):
277 """
278 Returns a single week in a string (no newline).
279 """
280 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000281
Walter Dörwald58917a62006-03-31 15:26:22 +0000282 def formatweekday(self, day, width):
283 """
284 Returns a formatted week day name.
285 """
286 if width >= 9:
287 names = day_name
288 else:
289 names = day_abbr
290 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000291
Walter Dörwald58917a62006-03-31 15:26:22 +0000292 def formatweekheader(self, width):
293 """
294 Return a header for a week.
295 """
296 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000297
Walter Dörwald48d5e502006-04-01 07:57:00 +0000298 def formatmonthname(self, theyear, themonth, width, withyear=True):
Walter Dörwald58917a62006-03-31 15:26:22 +0000299 """
300 Return a formatted month name.
301 """
Walter Dörwald48d5e502006-04-01 07:57:00 +0000302 s = month_name[themonth]
303 if withyear:
304 s = "%s %r" % (s, theyear)
Walter Dörwald58917a62006-03-31 15:26:22 +0000305 return s.center(width)
306
307 def prmonth(self, theyear, themonth, w=0, l=0):
308 """
309 Print a month's calendar.
310 """
311 print self.formatmonth(theyear, themonth, w, l),
312
313 def formatmonth(self, theyear, themonth, w=0, l=0):
314 """
315 Return a month's calendar string (multi-line).
316 """
317 w = max(2, w)
318 l = max(1, l)
319 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
320 s = s.rstrip()
321 s += '\n' * l
322 s += self.formatweekheader(w).rstrip()
323 s += '\n' * l
324 for week in self.monthdays2calendar(theyear, themonth):
325 s += self.formatweek(week, w).rstrip()
326 s += '\n' * l
327 return s
328
329 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
330 """
331 Returns a year's calendar as a multi-line string.
332 """
333 w = max(2, w)
334 l = max(1, l)
335 c = max(2, c)
336 colwidth = (w + 1) * 7 - 1
337 v = []
338 a = v.append
339 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
340 a('\n'*l)
341 header = self.formatweekheader(w)
342 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
343 # months in this row
344 months = xrange(m*i+1, min(m*(i+1)+1, 13))
345 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000346 names = (self.formatmonthname(theyear, k, colwidth, False)
347 for k in months)
348 a(formatstring(names, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000349 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000350 headers = (header for k in months)
351 a(formatstring(headers, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000352 a('\n'*l)
353 # max number of weeks for this row
354 height = max(len(cal) for cal in row)
355 for j in xrange(height):
356 weeks = []
357 for cal in row:
358 if j >= len(cal):
359 weeks.append('')
360 else:
361 weeks.append(self.formatweek(cal[j], w))
362 a(formatstring(weeks, colwidth, c).rstrip())
363 a('\n' * l)
364 return ''.join(v)
365
366 def pryear(self, theyear, w=0, l=0, c=6, m=3):
367 """Print a year's calendar."""
368 print self.formatyear(theyear, w, l, c, m)
369
370
371class HTMLCalendar(Calendar):
372 """
373 This calendar returns complete HTML pages.
374 """
375
376 # CSS classes for the day <td>s
377 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
378
379 def formatday(self, day, weekday):
380 """
381 Return a day as a table cell.
382 """
383 if day == 0:
384 return '<td class="noday">&nbsp;</td>' # day outside month
385 else:
386 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
387
388 def formatweek(self, theweek):
389 """
390 Return a complete week as a table row.
391 """
392 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
393 return '<tr>%s</tr>' % s
394
395 def formatweekday(self, day):
396 """
397 Return a weekday name as a table header.
398 """
399 return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
400
401 def formatweekheader(self):
402 """
403 Return a header for a week as a table row.
404 """
405 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
406 return '<tr>%s</tr>' % s
407
408 def formatmonthname(self, theyear, themonth, withyear=True):
409 """
410 Return a month name as a table row.
411 """
412 if withyear:
413 s = '%s %s' % (month_name[themonth], theyear)
414 else:
415 s = '%s' % month_name[themonth]
416 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
417
418 def formatmonth(self, theyear, themonth, withyear=True):
419 """
420 Return a formatted month as a table.
421 """
422 v = []
423 a = v.append
424 a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
425 a('\n')
426 a(self.formatmonthname(theyear, themonth, withyear=withyear))
427 a('\n')
428 a(self.formatweekheader())
429 a('\n')
430 for week in self.monthdays2calendar(theyear, themonth):
431 a(self.formatweek(week))
432 a('\n')
433 a('</table>')
434 a('\n')
435 return ''.join(v)
436
437 def formatyear(self, theyear, width=3):
438 """
439 Return a formatted year as a table of tables.
440 """
441 v = []
442 a = v.append
443 width = max(width, 1)
444 a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
445 a('\n')
446 a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
447 for i in xrange(January, January+12, width):
448 # months in this row
449 months = xrange(i, min(i+width, 13))
450 a('<tr>')
451 for m in months:
452 a('<td>')
453 a(self.formatmonth(theyear, m, withyear=False))
454 a('</td>')
455 a('</tr>')
456 a('</table>')
457 return ''.join(v)
458
459 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
460 """
461 Return a formatted year as a complete HTML page.
462 """
463 if encoding is None:
464 encoding = sys.getdefaultencoding()
465 v = []
466 a = v.append
467 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
468 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
469 a('<html>\n')
470 a('<head>\n')
471 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
472 if css is not None:
473 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
474 a('<title>Calendar for %d</title\n' % theyear)
475 a('</head>\n')
476 a('<body>\n')
477 a(self.formatyear(theyear, width))
478 a('</body>\n')
479 a('</html>\n')
Walter Dörwald48d5e502006-04-01 07:57:00 +0000480 return ''.join(v).encode(encoding, "xmlcharrefreplace")
481
482
Walter Dörwaldbc966092006-04-12 10:09:16 +0000483class TimeEncoding:
484 def __init__(self, locale):
485 self.locale = locale
486
Walter Dörwaldbc966092006-04-12 10:09:16 +0000487 def __enter__(self):
488 self.oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
489 return locale.getlocale(locale.LC_TIME)[1]
490
491 def __exit__(self, *args):
492 locale.setlocale(locale.LC_TIME, self.oldlocale)
493
494
Walter Dörwald48d5e502006-04-01 07:57:00 +0000495class LocaleTextCalendar(TextCalendar):
496 """
497 This class can be passed a locale name in the constructor and will return
498 month and weekday names in the specified locale. If this locale includes
499 an encoding all strings containing month and weekday names will be returned
500 as unicode.
501 """
502
503 def __init__(self, firstweekday=0, locale=None):
504 TextCalendar.__init__(self, firstweekday)
505 if locale is None:
506 locale = locale.getdefaultlocale()
507 self.locale = locale
508
509 def formatweekday(self, day, width):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000510 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000511 if width >= 9:
512 names = day_name
513 else:
514 names = day_abbr
515 name = names[day]
516 if encoding is not None:
517 name = name.decode(encoding)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000518 return name[:width].center(width)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000519
520 def formatmonthname(self, theyear, themonth, width, withyear=True):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000521 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000522 s = month_name[themonth]
523 if encoding is not None:
524 s = s.decode(encoding)
525 if withyear:
526 s = "%s %r" % (s, theyear)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000527 return s.center(width)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000528
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):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000544 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000545 s = day_abbr[day]
546 if encoding is not None:
547 s = s.decode(encoding)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000548 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000549
550 def formatmonthname(self, theyear, themonth, withyear=True):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000551 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000552 s = month_name[themonth]
553 if encoding is not None:
554 s = s.decode(encoding)
555 if withyear:
556 s = '%s %s' % (s, theyear)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000557 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
Walter Dörwald58917a62006-03-31 15:26:22 +0000558
559
560# Support for old module level interface
561c = TextCalendar()
562
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000563firstweekday = c.getfirstweekday
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000564
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)