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