blob: e243255b6793a1926ec464e6fdba92c9489a3b6d [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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00008from __future__ import with_statement
9import sys, datetime, locale
Guido van Rossumc6360141990-10-13 19:23:40 +000010
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Guido van Rossum805365e2007-05-07 22:24:25 +000048 _months = [datetime.date(2001, i+1, 1).strftime for i in range(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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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.
Guido van Rossum805365e2007-05-07 22:24:25 +000068 _days = [datetime.date(2001, 1, i+1).strftime for i in range(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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000131 def __init__(self, firstweekday=0):
132 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
133
134 def getfirstweekday(self):
135 return self._firstweekday % 7
136
137 def setfirstweekday(self, firstweekday):
138 self._firstweekday = firstweekday
139
140 firstweekday = property(getfirstweekday, setfirstweekday)
141
142 def iterweekdays(self):
143 """
144 Return a iterator for one week of weekday numbers starting with the
145 configured first one.
146 """
Guido van Rossum805365e2007-05-07 22:24:25 +0000147 for i in range(self.firstweekday, self.firstweekday + 7):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
158 days = (date.weekday() - self.firstweekday) % 7
159 date -= datetime.timedelta(days=days)
160 oneday = datetime.timedelta(days=1)
161 while True:
162 yield date
163 date += oneday
164 if date.month != month and date.weekday() == self.firstweekday:
165 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))
Guido van Rossum805365e2007-05-07 22:24:25 +0000195 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000196
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))
Guido van Rossum805365e2007-05-07 22:24:25 +0000205 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000206
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))
Guido van Rossum805365e2007-05-07 22:24:25 +0000213 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000214
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)
Guido van Rossum805365e2007-05-07 22:24:25 +0000224 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000225 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000226 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000227
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)
Guido van Rossum805365e2007-05-07 22:24:25 +0000237 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000238 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000239 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000240
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)
Guido van Rossum805365e2007-05-07 22:24:25 +0000249 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000250 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000251 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000252
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
260 def prweek(self, theweek, width):
261 """
262 Print a single week (no newline).
263 """
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000264 print(self.week(theweek, width), end=' ')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000265
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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000274 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000275
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000298 def formatmonthname(self, theyear, themonth, width, withyear=True):
299 """
300 Return a formatted month name.
301 """
302 s = month_name[themonth]
303 if withyear:
304 s = "%s %r" % (s, theyear)
305 return s.center(width)
306
307 def prmonth(self, theyear, themonth, w=0, l=0):
308 """
309 Print a month's calendar.
310 """
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000311 print(self.formatmonth(theyear, themonth, w, l), end=' ')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000312
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
Guido van Rossum805365e2007-05-07 22:24:25 +0000344 months = range(m*i+1, min(m*(i+1)+1, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000345 a('\n'*l)
346 names = (self.formatmonthname(theyear, k, colwidth, False)
347 for k in months)
348 a(formatstring(names, colwidth, c).rstrip())
349 a('\n'*l)
350 headers = (header for k in months)
351 a(formatstring(headers, colwidth, c).rstrip())
352 a('\n'*l)
353 # max number of weeks for this row
354 height = max(len(cal) for cal in row)
Guido van Rossum805365e2007-05-07 22:24:25 +0000355 for j in range(height):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000356 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."""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000368 print(self.formatyear(theyear, w, l, c, m))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000369
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))
Guido van Rossum805365e2007-05-07 22:24:25 +0000447 for i in range(January, January+12, width):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000448 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000449 months = range(i, min(i+width, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000450 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')
480 return ''.join(v).encode(encoding, "xmlcharrefreplace")
481
482
483class TimeEncoding:
484 def __init__(self, locale):
485 self.locale = locale
486
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
495class 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):
510 with TimeEncoding(self.locale) as encoding:
511 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)
518 return name[:width].center(width)
519
520 def formatmonthname(self, theyear, themonth, width, withyear=True):
521 with TimeEncoding(self.locale) as encoding:
522 s = month_name[themonth]
523 if encoding is not None:
524 s = s.decode(encoding)
525 if withyear:
526 s = "%s %r" % (s, theyear)
527 return s.center(width)
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 with TimeEncoding(self.locale) as encoding:
545 s = day_abbr[day]
546 if encoding is not None:
547 s = s.decode(encoding)
548 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
549
550 def formatmonthname(self, theyear, themonth, withyear=True):
551 with TimeEncoding(self.locale) as encoding:
552 s = month_name[themonth]
553 if encoding is not None:
554 s = s.decode(encoding)
555 if withyear:
556 s = '%s %s' % (s, theyear)
557 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
558
559
560# Support for old module level interface
561c = TextCalendar()
562
563firstweekday = c.getfirstweekday
564
565def setfirstweekday(firstweekday):
566 if not MONDAY <= firstweekday <= SUNDAY:
567 raise IllegalWeekdayError(firstweekday)
568 c.firstweekday = firstweekday
569
570monthcalendar = 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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000585def format(cols, colwidth=_colwidth, spacing=_spacing):
586 """Prints multi-column formatting for year calendars"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000587 print(formatstring(cols, colwidth, spacing))
Skip Montanaroad3bc442000-08-30 14:01:28 +0000588
Skip Montanaroad3bc442000-08-30 14:01:28 +0000589
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000608
609
610def main(args):
611 import optparse
612 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 )
654
655 (options, args) = parser.parse_args(args)
656
657 if options.locale and not options.encoding:
658 parser.error("if --locale is specified --encoding is required")
659 sys.exit(1)
660
661 if options.type == "html":
662 if options.locale:
663 cal = LocaleHTMLCalendar(locale=options.locale)
664 else:
665 cal = HTMLCalendar()
666 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:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000671 print(cal.formatyearpage(datetime.date.today().year, **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000672 elif len(args) == 2:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000673 print(cal.formatyearpage(int(args[1]), **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000674 else:
675 parser.error("incorrect number of arguments")
676 sys.exit(1)
677 else:
678 if options.locale:
679 cal = LocaleTextCalendar(locale=options.locale)
680 else:
681 cal = TextCalendar()
682 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:
687 result = cal.formatyear(datetime.date.today().year, **optdict)
688 elif len(args) == 2:
689 result = cal.formatyear(int(args[1]), **optdict)
690 elif len(args) == 3:
691 result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
692 else:
693 parser.error("incorrect number of arguments")
694 sys.exit(1)
695 if options.encoding:
696 result = result.encode(options.encoding)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000697 print(result)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000698
699
700if __name__ == "__main__":
701 main(sys.argv)