blob: acd463fee7ee9096c044f5b711691c9c3a1307c4 [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
Christian Heimesced16462007-11-12 01:20:56 +00009import sys
10import datetime
11import locale as _locale
Guido van Rossumc6360141990-10-13 19:23:40 +000012
Walter Dörwald58917a62006-03-31 15:26:22 +000013__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
14 "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
15 "monthcalendar", "prmonth", "month", "prcal", "calendar",
16 "timegm", "month_name", "month_abbr", "day_name", "day_abbr"]
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000017
Guido van Rossumc6360141990-10-13 19:23:40 +000018# Exception raised for bad input (with string parameter for details)
Guido van Rossum00245cf1999-05-03 18:07:40 +000019error = ValueError
Guido van Rossumc6360141990-10-13 19:23:40 +000020
Walter Dörwald58917a62006-03-31 15:26:22 +000021# Exceptions raised for bad input
22class IllegalMonthError(ValueError):
23 def __init__(self, month):
24 self.month = month
25 def __str__(self):
26 return "bad month number %r; must be 1-12" % self.month
27
28
29class IllegalWeekdayError(ValueError):
30 def __init__(self, weekday):
31 self.weekday = weekday
32 def __str__(self):
33 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
34
35
Guido van Rossum9b3bc711993-06-20 21:02:22 +000036# Constants for months referenced later
37January = 1
38February = 2
39
40# Number of days per month (except for February in leap years)
41mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
42
Tim Peters0c2c8e72002-03-23 03:26:53 +000043# This module used to have hard-coded lists of day and month names, as
44# English strings. The classes following emulate a read-only version of
45# that, but supply localized names. Note that the values are computed
46# fresh on each call, in case the user changes locale between calls.
47
Raymond Hettinger9c051d72002-06-20 03:38:12 +000048class _localized_month:
Tim Petersbbc0d442004-11-13 16:18:32 +000049
Raymond Hettinger72ef8da2007-05-17 01:08:04 +000050 _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
Tim Petersbbc0d442004-11-13 16:18:32 +000051 _months.insert(0, lambda x: "")
52
Tim Peters0c2c8e72002-03-23 03:26:53 +000053 def __init__(self, format):
Barry Warsaw1d099102001-05-22 15:58:30 +000054 self.format = format
Tim Peters0c2c8e72002-03-23 03:26:53 +000055
56 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000057 funcs = self._months[i]
58 if isinstance(i, slice):
59 return [f(self.format) for f in funcs]
60 else:
61 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000062
Skip Montanaro4c834952002-03-15 04:08:38 +000063 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000064 return 13
65
Walter Dörwald58917a62006-03-31 15:26:22 +000066
Raymond Hettinger9c051d72002-06-20 03:38:12 +000067class _localized_day:
Tim Petersbbc0d442004-11-13 16:18:32 +000068
69 # January 1, 2001, was a Monday.
Raymond Hettinger72ef8da2007-05-17 01:08:04 +000070 _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
Tim Petersbbc0d442004-11-13 16:18:32 +000071
Tim Peters0c2c8e72002-03-23 03:26:53 +000072 def __init__(self, format):
73 self.format = format
74
75 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000076 funcs = self._days[i]
77 if isinstance(i, slice):
78 return [f(self.format) for f in funcs]
79 else:
80 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000081
Neal Norwitz492faa52004-06-07 03:47:06 +000082 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000083 return 7
Barry Warsaw1d099102001-05-22 15:58:30 +000084
Walter Dörwald58917a62006-03-31 15:26:22 +000085
Guido van Rossum9b3bc711993-06-20 21:02:22 +000086# Full and abbreviated names of weekdays
Tim Peters0c2c8e72002-03-23 03:26:53 +000087day_name = _localized_day('%A')
88day_abbr = _localized_day('%a')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000089
Guido van Rossum5cfa5df1993-06-23 09:30:50 +000090# Full and abbreviated names of months (1-based arrays!!!)
Tim Peters0c2c8e72002-03-23 03:26:53 +000091month_name = _localized_month('%B')
92month_abbr = _localized_month('%b')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000093
Skip Montanaroad3bc442000-08-30 14:01:28 +000094# Constants for weekdays
95(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
96
Skip Montanaroad3bc442000-08-30 14:01:28 +000097
Guido van Rossum9b3bc711993-06-20 21:02:22 +000098def isleap(year):
Guido van Rossum4acc25b2000-02-02 15:10:15 +000099 """Return 1 for leap years, 0 for non-leap years."""
Fred Drake8152d322000-12-12 23:20:45 +0000100 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000101
Walter Dörwald58917a62006-03-31 15:26:22 +0000102
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000103def leapdays(y1, y2):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000104 """Return number of leap years in range [y1, y2).
Guido van Rossum46735ad2000-10-09 12:42:04 +0000105 Assume y1 <= y2."""
106 y1 -= 1
107 y2 -= 1
Raymond Hettingere11b5102002-12-25 16:37:19 +0000108 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000109
Walter Dörwald58917a62006-03-31 15:26:22 +0000110
Guido van Rossumc6360141990-10-13 19:23:40 +0000111def weekday(year, month, day):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000112 """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
113 day (1-31)."""
Raymond Hettingere11b5102002-12-25 16:37:19 +0000114 return datetime.date(year, month, day).weekday()
Guido van Rossumc6360141990-10-13 19:23:40 +0000115
Walter Dörwald58917a62006-03-31 15:26:22 +0000116
Guido van Rossumc6360141990-10-13 19:23:40 +0000117def monthrange(year, month):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000118 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
119 year, month."""
120 if not 1 <= month <= 12:
Walter Dörwald58917a62006-03-31 15:26:22 +0000121 raise IllegalMonthError(month)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000122 day1 = weekday(year, month, 1)
123 ndays = mdays[month] + (month == February and isleap(year))
124 return day1, ndays
Guido van Rossumc6360141990-10-13 19:23:40 +0000125
Guido van Rossumc6360141990-10-13 19:23:40 +0000126
Walter Dörwald58917a62006-03-31 15:26:22 +0000127class Calendar(object):
128 """
129 Base calendar class. This class doesn't do any formatting. It simply
130 provides data to subclasses.
131 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000132
Walter Dörwald58917a62006-03-31 15:26:22 +0000133 def __init__(self, firstweekday=0):
Walter Dörwaldf878b812006-04-01 20:40:23 +0000134 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
Walter Dörwald58917a62006-03-31 15:26:22 +0000135
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000136 def getfirstweekday(self):
Walter Dörwald72d84af2006-04-03 15:21:59 +0000137 return self._firstweekday % 7
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000138
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000139 def setfirstweekday(self, firstweekday):
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000140 self._firstweekday = firstweekday
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000141
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000142 firstweekday = property(getfirstweekday, setfirstweekday)
143
Walter Dörwald58917a62006-03-31 15:26:22 +0000144 def iterweekdays(self):
145 """
146 Return a iterator for one week of weekday numbers starting with the
147 configured first one.
148 """
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000149 for i in range(self.firstweekday, self.firstweekday + 7):
Walter Dörwald58917a62006-03-31 15:26:22 +0000150 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
Walter Dörwaldf878b812006-04-01 20:40:23 +0000160 days = (date.weekday() - self.firstweekday) % 7
Walter Dörwald58917a62006-03-31 15:26:22 +0000161 date -= datetime.timedelta(days=days)
162 oneday = datetime.timedelta(days=1)
163 while True:
164 yield date
165 date += oneday
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000166 if date.month != month and date.weekday() == self.firstweekday:
Walter Dörwald58917a62006-03-31 15:26:22 +0000167 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))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000197 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000198
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))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000207 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000208
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))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000215 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000216
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)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000226 for i in range(January, January+12)
Walter Dörwald58917a62006-03-31 15:26:22 +0000227 ]
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000228 return [months[i:i+width] for i in range(0, len(months), width) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000229
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)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000239 for i in range(January, January+12)
Walter Dörwald58917a62006-03-31 15:26:22 +0000240 ]
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000241 return [months[i:i+width] for i in range(0, len(months), width) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000242
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)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000251 for i in range(January, January+12)
Walter Dörwald58917a62006-03-31 15:26:22 +0000252 ]
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000253 return [months[i:i+width] for i in range(0, len(months), width) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000254
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
Anthony Baxter7846f4d2006-04-07 05:41:13 +0000262 def prweek(self, theweek, width):
Walter Dörwald58917a62006-03-31 15:26:22 +0000263 """
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
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000346 months = range(m*i+1, min(m*(i+1)+1, 13))
Walter Dörwald58917a62006-03-31 15:26:22 +0000347 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)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000357 for j in range(height):
Walter Dörwald58917a62006-03-31 15:26:22 +0000358 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))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000449 for i in range(January, January+12, width):
Walter Dörwald58917a62006-03-31 15:26:22 +0000450 # months in this row
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000451 months = range(i, min(i+width, 13))
Walter Dörwald58917a62006-03-31 15:26:22 +0000452 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)
Walter Dörwaldf0d1c1f2007-08-28 16:38:26 +0000476 a('<title>Calendar for %d</title>\n' % theyear)
Walter Dörwald58917a62006-03-31 15:26:22 +0000477 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
Walter Dörwaldbc966092006-04-12 10:09:16 +0000485class TimeEncoding:
486 def __init__(self, locale):
487 self.locale = locale
488
Walter Dörwaldbc966092006-04-12 10:09:16 +0000489 def __enter__(self):
Christian Heimesced16462007-11-12 01:20:56 +0000490 self.oldlocale = _locale.setlocale(_locale.LC_TIME, self.locale)
491 return _locale.getlocale(_locale.LC_TIME)[1]
Walter Dörwaldbc966092006-04-12 10:09:16 +0000492
493 def __exit__(self, *args):
Christian Heimesced16462007-11-12 01:20:56 +0000494 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000495
496
Walter Dörwald48d5e502006-04-01 07:57:00 +0000497class LocaleTextCalendar(TextCalendar):
498 """
499 This class can be passed a locale name in the constructor and will return
500 month and weekday names in the specified locale. If this locale includes
501 an encoding all strings containing month and weekday names will be returned
502 as unicode.
503 """
504
505 def __init__(self, firstweekday=0, locale=None):
506 TextCalendar.__init__(self, firstweekday)
507 if locale is None:
Christian Heimesced16462007-11-12 01:20:56 +0000508 locale = _locale.getdefaultlocale()
Walter Dörwald48d5e502006-04-01 07:57:00 +0000509 self.locale = locale
510
511 def formatweekday(self, day, width):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000512 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000513 if width >= 9:
514 names = day_name
515 else:
516 names = day_abbr
517 name = names[day]
518 if encoding is not None:
519 name = name.decode(encoding)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000520 return name[:width].center(width)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000521
522 def formatmonthname(self, theyear, themonth, width, withyear=True):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000523 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000524 s = month_name[themonth]
525 if encoding is not None:
526 s = s.decode(encoding)
527 if withyear:
528 s = "%s %r" % (s, theyear)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000529 return s.center(width)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000530
531
532class LocaleHTMLCalendar(HTMLCalendar):
533 """
534 This class can be passed a locale name in the constructor and will return
535 month and weekday names in the specified locale. If this locale includes
536 an encoding all strings containing month and weekday names will be returned
537 as unicode.
538 """
539 def __init__(self, firstweekday=0, locale=None):
540 HTMLCalendar.__init__(self, firstweekday)
541 if locale is None:
Christian Heimesced16462007-11-12 01:20:56 +0000542 locale = _locale.getdefaultlocale()
Walter Dörwald48d5e502006-04-01 07:57:00 +0000543 self.locale = locale
544
545 def formatweekday(self, day):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000546 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000547 s = day_abbr[day]
548 if encoding is not None:
549 s = s.decode(encoding)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000550 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000551
552 def formatmonthname(self, theyear, themonth, withyear=True):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000553 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000554 s = month_name[themonth]
555 if encoding is not None:
556 s = s.decode(encoding)
557 if withyear:
558 s = '%s %s' % (s, theyear)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000559 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
Walter Dörwald58917a62006-03-31 15:26:22 +0000560
561
562# Support for old module level interface
563c = TextCalendar()
564
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000565firstweekday = c.getfirstweekday
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000566
567def setfirstweekday(firstweekday):
568 if not MONDAY <= firstweekday <= SUNDAY:
569 raise IllegalWeekdayError(firstweekday)
570 c.firstweekday = firstweekday
571
Walter Dörwald58917a62006-03-31 15:26:22 +0000572monthcalendar = c.monthdayscalendar
573prweek = c.prweek
574week = c.formatweek
575weekheader = c.formatweekheader
576prmonth = c.prmonth
577month = c.formatmonth
578calendar = c.formatyear
579prcal = c.pryear
580
581
582# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000583_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000584_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000585
Guido van Rossumc6360141990-10-13 19:23:40 +0000586
Walter Dörwald58917a62006-03-31 15:26:22 +0000587def format(cols, colwidth=_colwidth, spacing=_spacing):
588 """Prints multi-column formatting for year calendars"""
589 print formatstring(cols, colwidth, spacing)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000590
Skip Montanaroad3bc442000-08-30 14:01:28 +0000591
Walter Dörwald58917a62006-03-31 15:26:22 +0000592def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
593 """Returns a string formatted from n strings, centered within n columns."""
594 spacing *= ' '
595 return spacing.join(c.center(colwidth) for c in cols)
596
Guido van Rossumb39aff81999-06-09 15:07:38 +0000597
Guido van Rossumb39aff81999-06-09 15:07:38 +0000598EPOCH = 1970
Raymond Hettingere11b5102002-12-25 16:37:19 +0000599_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
600
Walter Dörwald58917a62006-03-31 15:26:22 +0000601
Guido van Rossumb39aff81999-06-09 15:07:38 +0000602def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000603 """Unrelated but handy function to calculate Unix timestamp from GMT."""
604 year, month, day, hour, minute, second = tuple[:6]
Raymond Hettinger61436482003-02-13 22:58:02 +0000605 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000606 hours = days*24 + hour
607 minutes = hours*60 + minute
608 seconds = minutes*60 + second
609 return seconds
Walter Dörwald58917a62006-03-31 15:26:22 +0000610
611
612def main(args):
613 import optparse
Walter Dörwald48d5e502006-04-01 07:57:00 +0000614 parser = optparse.OptionParser(usage="usage: %prog [options] [year [month]]")
615 parser.add_option(
616 "-w", "--width",
617 dest="width", type="int", default=2,
618 help="width of date column (default 2, text only)"
619 )
620 parser.add_option(
621 "-l", "--lines",
622 dest="lines", type="int", default=1,
623 help="number of lines for each week (default 1, text only)"
624 )
625 parser.add_option(
626 "-s", "--spacing",
627 dest="spacing", type="int", default=6,
628 help="spacing between months (default 6, text only)"
629 )
630 parser.add_option(
631 "-m", "--months",
632 dest="months", type="int", default=3,
633 help="months per row (default 3, text only)"
634 )
635 parser.add_option(
636 "-c", "--css",
637 dest="css", default="calendar.css",
638 help="CSS to use for page (html only)"
639 )
640 parser.add_option(
641 "-L", "--locale",
642 dest="locale", default=None,
643 help="locale to be used from month and weekday names"
644 )
645 parser.add_option(
646 "-e", "--encoding",
647 dest="encoding", default=None,
648 help="Encoding to use for output"
649 )
650 parser.add_option(
651 "-t", "--type",
652 dest="type", default="text",
653 choices=("text", "html"),
654 help="output type (text or html)"
655 )
Walter Dörwald58917a62006-03-31 15:26:22 +0000656
657 (options, args) = parser.parse_args(args)
658
Walter Dörwald48d5e502006-04-01 07:57:00 +0000659 if options.locale and not options.encoding:
660 parser.error("if --locale is specified --encoding is required")
661 sys.exit(1)
662
Christian Heimesced16462007-11-12 01:20:56 +0000663 locale = options.locale, options.encoding
664
Walter Dörwald58917a62006-03-31 15:26:22 +0000665 if options.type == "html":
Walter Dörwald48d5e502006-04-01 07:57:00 +0000666 if options.locale:
Christian Heimesced16462007-11-12 01:20:56 +0000667 cal = LocaleHTMLCalendar(locale=locale)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000668 else:
669 cal = HTMLCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000670 encoding = options.encoding
671 if encoding is None:
672 encoding = sys.getdefaultencoding()
673 optdict = dict(encoding=encoding, css=options.css)
674 if len(args) == 1:
675 print cal.formatyearpage(datetime.date.today().year, **optdict)
676 elif len(args) == 2:
677 print cal.formatyearpage(int(args[1]), **optdict)
678 else:
679 parser.error("incorrect number of arguments")
680 sys.exit(1)
681 else:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000682 if options.locale:
Christian Heimesced16462007-11-12 01:20:56 +0000683 cal = LocaleTextCalendar(locale=locale)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000684 else:
685 cal = TextCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000686 optdict = dict(w=options.width, l=options.lines)
687 if len(args) != 3:
688 optdict["c"] = options.spacing
689 optdict["m"] = options.months
690 if len(args) == 1:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000691 result = cal.formatyear(datetime.date.today().year, **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000692 elif len(args) == 2:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000693 result = cal.formatyear(int(args[1]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000694 elif len(args) == 3:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000695 result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000696 else:
697 parser.error("incorrect number of arguments")
698 sys.exit(1)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000699 if options.encoding:
700 result = result.encode(options.encoding)
701 print result
Walter Dörwald58917a62006-03-31 15:26:22 +0000702
703
704if __name__ == "__main__":
705 main(sys.argv)