blob: 232957829f093b9aa0c57b349f1ed2e6b66c37c1 [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
Christian Heimesced16462007-11-12 01:20:56 +00008import sys
9import datetime
10import locale as _locale
Guido van Rossumc6360141990-10-13 19:23:40 +000011
Walter Dörwald58917a62006-03-31 15:26:22 +000012__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
13 "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
14 "monthcalendar", "prmonth", "month", "prcal", "calendar",
15 "timegm", "month_name", "month_abbr", "day_name", "day_abbr"]
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000016
Guido van Rossumc6360141990-10-13 19:23:40 +000017# Exception raised for bad input (with string parameter for details)
Guido van Rossum00245cf1999-05-03 18:07:40 +000018error = ValueError
Guido van Rossumc6360141990-10-13 19:23:40 +000019
Walter Dörwald58917a62006-03-31 15:26:22 +000020# Exceptions raised for bad input
21class IllegalMonthError(ValueError):
22 def __init__(self, month):
23 self.month = month
24 def __str__(self):
25 return "bad month number %r; must be 1-12" % self.month
26
27
28class IllegalWeekdayError(ValueError):
29 def __init__(self, weekday):
30 self.weekday = weekday
31 def __str__(self):
32 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
33
34
Guido van Rossum9b3bc711993-06-20 21:02:22 +000035# Constants for months referenced later
36January = 1
37February = 2
38
39# Number of days per month (except for February in leap years)
40mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
41
Tim Peters0c2c8e72002-03-23 03:26:53 +000042# This module used to have hard-coded lists of day and month names, as
43# English strings. The classes following emulate a read-only version of
44# that, but supply localized names. Note that the values are computed
45# fresh on each call, in case the user changes locale between calls.
46
Raymond Hettinger9c051d72002-06-20 03:38:12 +000047class _localized_month:
Tim Petersbbc0d442004-11-13 16:18:32 +000048
Raymond Hettinger72ef8da2007-05-17 01:08:04 +000049 _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
Tim Petersbbc0d442004-11-13 16:18:32 +000050 _months.insert(0, lambda x: "")
51
Tim Peters0c2c8e72002-03-23 03:26:53 +000052 def __init__(self, format):
Barry Warsaw1d099102001-05-22 15:58:30 +000053 self.format = format
Tim Peters0c2c8e72002-03-23 03:26:53 +000054
55 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000056 funcs = self._months[i]
57 if isinstance(i, slice):
58 return [f(self.format) for f in funcs]
59 else:
60 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000061
Skip Montanaro4c834952002-03-15 04:08:38 +000062 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000063 return 13
64
Walter Dörwald58917a62006-03-31 15:26:22 +000065
Raymond Hettinger9c051d72002-06-20 03:38:12 +000066class _localized_day:
Tim Petersbbc0d442004-11-13 16:18:32 +000067
68 # January 1, 2001, was a Monday.
Raymond Hettinger72ef8da2007-05-17 01:08:04 +000069 _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
Tim Petersbbc0d442004-11-13 16:18:32 +000070
Tim Peters0c2c8e72002-03-23 03:26:53 +000071 def __init__(self, format):
72 self.format = format
73
74 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000075 funcs = self._days[i]
76 if isinstance(i, slice):
77 return [f(self.format) for f in funcs]
78 else:
79 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000080
Neal Norwitz492faa52004-06-07 03:47:06 +000081 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000082 return 7
Barry Warsaw1d099102001-05-22 15:58:30 +000083
Walter Dörwald58917a62006-03-31 15:26:22 +000084
Guido van Rossum9b3bc711993-06-20 21:02:22 +000085# Full and abbreviated names of weekdays
Tim Peters0c2c8e72002-03-23 03:26:53 +000086day_name = _localized_day('%A')
87day_abbr = _localized_day('%a')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000088
Guido van Rossum5cfa5df1993-06-23 09:30:50 +000089# Full and abbreviated names of months (1-based arrays!!!)
Tim Peters0c2c8e72002-03-23 03:26:53 +000090month_name = _localized_month('%B')
91month_abbr = _localized_month('%b')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000092
Skip Montanaroad3bc442000-08-30 14:01:28 +000093# Constants for weekdays
94(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
95
Skip Montanaroad3bc442000-08-30 14:01:28 +000096
Guido van Rossum9b3bc711993-06-20 21:02:22 +000097def isleap(year):
Alexander Belopolsky78025222010-10-19 17:52:22 +000098 """Return True for leap years, False for non-leap years."""
Fred Drake8152d322000-12-12 23:20:45 +000099 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000100
Walter Dörwald58917a62006-03-31 15:26:22 +0000101
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000102def leapdays(y1, y2):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000103 """Return number of leap years in range [y1, y2).
Guido van Rossum46735ad2000-10-09 12:42:04 +0000104 Assume y1 <= y2."""
105 y1 -= 1
106 y2 -= 1
Raymond Hettingere11b5102002-12-25 16:37:19 +0000107 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000108
Walter Dörwald58917a62006-03-31 15:26:22 +0000109
Guido van Rossumc6360141990-10-13 19:23:40 +0000110def weekday(year, month, day):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000111 """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
112 day (1-31)."""
Raymond Hettingere11b5102002-12-25 16:37:19 +0000113 return datetime.date(year, month, day).weekday()
Guido van Rossumc6360141990-10-13 19:23:40 +0000114
Walter Dörwald58917a62006-03-31 15:26:22 +0000115
Guido van Rossumc6360141990-10-13 19:23:40 +0000116def monthrange(year, month):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000117 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
118 year, month."""
119 if not 1 <= month <= 12:
Walter Dörwald58917a62006-03-31 15:26:22 +0000120 raise IllegalMonthError(month)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000121 day1 = weekday(year, month, 1)
122 ndays = mdays[month] + (month == February and isleap(year))
123 return day1, ndays
Guido van Rossumc6360141990-10-13 19:23:40 +0000124
Guido van Rossumc6360141990-10-13 19:23:40 +0000125
Walter Dörwald58917a62006-03-31 15:26:22 +0000126class Calendar(object):
127 """
128 Base calendar class. This class doesn't do any formatting. It simply
129 provides data to subclasses.
130 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000131
Walter Dörwald58917a62006-03-31 15:26:22 +0000132 def __init__(self, firstweekday=0):
Walter Dörwaldf878b812006-04-01 20:40:23 +0000133 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
Walter Dörwald58917a62006-03-31 15:26:22 +0000134
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000135 def getfirstweekday(self):
Walter Dörwald72d84af2006-04-03 15:21:59 +0000136 return self._firstweekday % 7
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000137
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000138 def setfirstweekday(self, firstweekday):
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000139 self._firstweekday = firstweekday
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000140
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000141 firstweekday = property(getfirstweekday, setfirstweekday)
142
Walter Dörwald58917a62006-03-31 15:26:22 +0000143 def iterweekdays(self):
144 """
145 Return a iterator for one week of weekday numbers starting with the
146 configured first one.
147 """
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000148 for i in range(self.firstweekday, self.firstweekday + 7):
Walter Dörwald58917a62006-03-31 15:26:22 +0000149 yield i%7
150
151 def itermonthdates(self, year, month):
152 """
153 Return an iterator for one month. The iterator will yield datetime.date
154 values and will always iterate through complete weeks, so it will yield
155 dates outside the specified month.
156 """
157 date = datetime.date(year, month, 1)
158 # Go back to the beginning of the week
Walter Dörwaldf878b812006-04-01 20:40:23 +0000159 days = (date.weekday() - self.firstweekday) % 7
Walter Dörwald58917a62006-03-31 15:26:22 +0000160 date -= datetime.timedelta(days=days)
161 oneday = datetime.timedelta(days=1)
162 while True:
163 yield date
Ezio Melotticadff702012-09-21 17:26:35 +0300164 try:
165 date += oneday
166 except OverflowError:
167 # Adding one day could fail after datetime.MAXYEAR
168 break
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000169 if date.month != month and date.weekday() == self.firstweekday:
Walter Dörwald58917a62006-03-31 15:26:22 +0000170 break
171
172 def itermonthdays2(self, year, month):
173 """
174 Like itermonthdates(), but will yield (day number, weekday number)
175 tuples. For days outside the specified month the day number is 0.
176 """
177 for date in self.itermonthdates(year, month):
178 if date.month != month:
179 yield (0, date.weekday())
180 else:
181 yield (date.day, date.weekday())
182
183 def itermonthdays(self, year, month):
184 """
Walter Dörwaldd0e5b762008-02-07 19:57:32 +0000185 Like itermonthdates(), but will yield day numbers. For days outside
186 the specified month the day number is 0.
Walter Dörwald58917a62006-03-31 15:26:22 +0000187 """
188 for date in self.itermonthdates(year, month):
189 if date.month != month:
190 yield 0
191 else:
192 yield date.day
193
194 def monthdatescalendar(self, year, month):
195 """
196 Return a matrix (list of lists) representing a month's calendar.
197 Each row represents a week; week entries are datetime.date values.
198 """
199 dates = list(self.itermonthdates(year, month))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000200 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000201
202 def monthdays2calendar(self, year, month):
203 """
204 Return a matrix representing a month's calendar.
205 Each row represents a week; week entries are
206 (day number, weekday number) tuples. Day numbers outside this month
207 are zero.
208 """
209 days = list(self.itermonthdays2(year, month))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000210 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000211
212 def monthdayscalendar(self, year, month):
213 """
214 Return a matrix representing a month's calendar.
215 Each row represents a week; days outside this month are zero.
216 """
217 days = list(self.itermonthdays(year, month))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000218 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000219
220 def yeardatescalendar(self, year, width=3):
221 """
222 Return the data for the specified year ready for formatting. The return
223 value is a list of month rows. Each month row contains upto width months.
224 Each month contains between 4 and 6 weeks and each week contains 1-7
225 days. Days are datetime.date objects.
226 """
227 months = [
228 self.monthdatescalendar(year, i)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000229 for i in range(January, January+12)
Walter Dörwald58917a62006-03-31 15:26:22 +0000230 ]
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000231 return [months[i:i+width] for i in range(0, len(months), width) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000232
233 def yeardays2calendar(self, year, width=3):
234 """
235 Return the data for the specified year ready for formatting (similar to
236 yeardatescalendar()). Entries in the week lists are
237 (day number, weekday number) tuples. Day numbers outside this month are
238 zero.
239 """
240 months = [
241 self.monthdays2calendar(year, i)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000242 for i in range(January, January+12)
Walter Dörwald58917a62006-03-31 15:26:22 +0000243 ]
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000244 return [months[i:i+width] for i in range(0, len(months), width) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000245
246 def yeardayscalendar(self, year, width=3):
247 """
248 Return the data for the specified year ready for formatting (similar to
249 yeardatescalendar()). Entries in the week lists are day numbers.
250 Day numbers outside this month are zero.
251 """
252 months = [
253 self.monthdayscalendar(year, i)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000254 for i in range(January, January+12)
Walter Dörwald58917a62006-03-31 15:26:22 +0000255 ]
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000256 return [months[i:i+width] for i in range(0, len(months), width) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000257
258
259class TextCalendar(Calendar):
260 """
261 Subclass of Calendar that outputs a calendar as a simple plain text
262 similar to the UNIX program cal.
263 """
264
Anthony Baxter7846f4d2006-04-07 05:41:13 +0000265 def prweek(self, theweek, width):
Walter Dörwald58917a62006-03-31 15:26:22 +0000266 """
267 Print a single week (no newline).
268 """
Walter Dörwaldedc526c2007-11-12 10:01:33 +0000269 print self.formatweek(theweek, width),
Walter Dörwald58917a62006-03-31 15:26:22 +0000270
271 def formatday(self, day, weekday, width):
272 """
273 Returns a formatted day.
274 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000275 if day == 0:
276 s = ''
277 else:
278 s = '%2i' % day # right-align single-digit days
Walter Dörwald58917a62006-03-31 15:26:22 +0000279 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000280
Walter Dörwald58917a62006-03-31 15:26:22 +0000281 def formatweek(self, theweek, width):
282 """
283 Returns a single week in a string (no newline).
284 """
285 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000286
Walter Dörwald58917a62006-03-31 15:26:22 +0000287 def formatweekday(self, day, width):
288 """
289 Returns a formatted week day name.
290 """
291 if width >= 9:
292 names = day_name
293 else:
294 names = day_abbr
295 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000296
Walter Dörwald58917a62006-03-31 15:26:22 +0000297 def formatweekheader(self, width):
298 """
299 Return a header for a week.
300 """
301 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000302
Walter Dörwald48d5e502006-04-01 07:57:00 +0000303 def formatmonthname(self, theyear, themonth, width, withyear=True):
Walter Dörwald58917a62006-03-31 15:26:22 +0000304 """
305 Return a formatted month name.
306 """
Walter Dörwald48d5e502006-04-01 07:57:00 +0000307 s = month_name[themonth]
308 if withyear:
309 s = "%s %r" % (s, theyear)
Walter Dörwald58917a62006-03-31 15:26:22 +0000310 return s.center(width)
311
312 def prmonth(self, theyear, themonth, w=0, l=0):
313 """
314 Print a month's calendar.
315 """
316 print self.formatmonth(theyear, themonth, w, l),
317
318 def formatmonth(self, theyear, themonth, w=0, l=0):
319 """
320 Return a month's calendar string (multi-line).
321 """
322 w = max(2, w)
323 l = max(1, l)
324 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
325 s = s.rstrip()
326 s += '\n' * l
327 s += self.formatweekheader(w).rstrip()
328 s += '\n' * l
329 for week in self.monthdays2calendar(theyear, themonth):
330 s += self.formatweek(week, w).rstrip()
331 s += '\n' * l
332 return s
333
334 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
335 """
336 Returns a year's calendar as a multi-line string.
337 """
338 w = max(2, w)
339 l = max(1, l)
340 c = max(2, c)
341 colwidth = (w + 1) * 7 - 1
342 v = []
343 a = v.append
344 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
345 a('\n'*l)
346 header = self.formatweekheader(w)
347 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
348 # months in this row
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000349 months = range(m*i+1, min(m*(i+1)+1, 13))
Walter Dörwald58917a62006-03-31 15:26:22 +0000350 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000351 names = (self.formatmonthname(theyear, k, colwidth, False)
352 for k in months)
353 a(formatstring(names, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000354 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000355 headers = (header for k in months)
356 a(formatstring(headers, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000357 a('\n'*l)
358 # max number of weeks for this row
359 height = max(len(cal) for cal in row)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000360 for j in range(height):
Walter Dörwald58917a62006-03-31 15:26:22 +0000361 weeks = []
362 for cal in row:
363 if j >= len(cal):
364 weeks.append('')
365 else:
366 weeks.append(self.formatweek(cal[j], w))
367 a(formatstring(weeks, colwidth, c).rstrip())
368 a('\n' * l)
369 return ''.join(v)
370
371 def pryear(self, theyear, w=0, l=0, c=6, m=3):
372 """Print a year's calendar."""
373 print self.formatyear(theyear, w, l, c, m)
374
375
376class HTMLCalendar(Calendar):
377 """
378 This calendar returns complete HTML pages.
379 """
380
381 # CSS classes for the day <td>s
382 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
383
384 def formatday(self, day, weekday):
385 """
386 Return a day as a table cell.
387 """
388 if day == 0:
389 return '<td class="noday">&nbsp;</td>' # day outside month
390 else:
391 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
392
393 def formatweek(self, theweek):
394 """
395 Return a complete week as a table row.
396 """
397 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
398 return '<tr>%s</tr>' % s
399
400 def formatweekday(self, day):
401 """
402 Return a weekday name as a table header.
403 """
404 return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
405
406 def formatweekheader(self):
407 """
408 Return a header for a week as a table row.
409 """
410 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
411 return '<tr>%s</tr>' % s
412
413 def formatmonthname(self, theyear, themonth, withyear=True):
414 """
415 Return a month name as a table row.
416 """
417 if withyear:
418 s = '%s %s' % (month_name[themonth], theyear)
419 else:
420 s = '%s' % month_name[themonth]
421 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
422
423 def formatmonth(self, theyear, themonth, withyear=True):
424 """
425 Return a formatted month as a table.
426 """
427 v = []
428 a = v.append
429 a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
430 a('\n')
431 a(self.formatmonthname(theyear, themonth, withyear=withyear))
432 a('\n')
433 a(self.formatweekheader())
434 a('\n')
435 for week in self.monthdays2calendar(theyear, themonth):
436 a(self.formatweek(week))
437 a('\n')
438 a('</table>')
439 a('\n')
440 return ''.join(v)
441
442 def formatyear(self, theyear, width=3):
443 """
444 Return a formatted year as a table of tables.
445 """
446 v = []
447 a = v.append
448 width = max(width, 1)
449 a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
450 a('\n')
451 a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000452 for i in range(January, January+12, width):
Walter Dörwald58917a62006-03-31 15:26:22 +0000453 # months in this row
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000454 months = range(i, min(i+width, 13))
Walter Dörwald58917a62006-03-31 15:26:22 +0000455 a('<tr>')
456 for m in months:
457 a('<td>')
458 a(self.formatmonth(theyear, m, withyear=False))
459 a('</td>')
460 a('</tr>')
461 a('</table>')
462 return ''.join(v)
463
464 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
465 """
466 Return a formatted year as a complete HTML page.
467 """
468 if encoding is None:
469 encoding = sys.getdefaultencoding()
470 v = []
471 a = v.append
472 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
473 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
474 a('<html>\n')
475 a('<head>\n')
476 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
477 if css is not None:
478 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
Walter Dörwaldf0d1c1f2007-08-28 16:38:26 +0000479 a('<title>Calendar for %d</title>\n' % theyear)
Walter Dörwald58917a62006-03-31 15:26:22 +0000480 a('</head>\n')
481 a('<body>\n')
482 a(self.formatyear(theyear, width))
483 a('</body>\n')
484 a('</html>\n')
Walter Dörwald48d5e502006-04-01 07:57:00 +0000485 return ''.join(v).encode(encoding, "xmlcharrefreplace")
486
487
Walter Dörwaldbc966092006-04-12 10:09:16 +0000488class TimeEncoding:
489 def __init__(self, locale):
490 self.locale = locale
491
Walter Dörwaldbc966092006-04-12 10:09:16 +0000492 def __enter__(self):
Georg Brandl79f096a2010-11-26 07:57:57 +0000493 self.oldlocale = _locale.getlocale(_locale.LC_TIME)
494 _locale.setlocale(_locale.LC_TIME, self.locale)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000495
496 def __exit__(self, *args):
Christian Heimesced16462007-11-12 01:20:56 +0000497 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000498
499
Walter Dörwald48d5e502006-04-01 07:57:00 +0000500class LocaleTextCalendar(TextCalendar):
501 """
502 This class can be passed a locale name in the constructor and will return
503 month and weekday names in the specified locale. If this locale includes
504 an encoding all strings containing month and weekday names will be returned
505 as unicode.
506 """
507
508 def __init__(self, firstweekday=0, locale=None):
509 TextCalendar.__init__(self, firstweekday)
510 if locale is None:
Christian Heimesced16462007-11-12 01:20:56 +0000511 locale = _locale.getdefaultlocale()
Walter Dörwald48d5e502006-04-01 07:57:00 +0000512 self.locale = locale
513
514 def formatweekday(self, day, width):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000515 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000516 if width >= 9:
517 names = day_name
518 else:
519 names = day_abbr
520 name = names[day]
521 if encoding is not None:
522 name = name.decode(encoding)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000523 return name[:width].center(width)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000524
525 def formatmonthname(self, theyear, themonth, width, withyear=True):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000526 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000527 s = month_name[themonth]
528 if encoding is not None:
529 s = s.decode(encoding)
530 if withyear:
531 s = "%s %r" % (s, theyear)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000532 return s.center(width)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000533
534
535class LocaleHTMLCalendar(HTMLCalendar):
536 """
537 This class can be passed a locale name in the constructor and will return
538 month and weekday names in the specified locale. If this locale includes
539 an encoding all strings containing month and weekday names will be returned
540 as unicode.
541 """
542 def __init__(self, firstweekday=0, locale=None):
543 HTMLCalendar.__init__(self, firstweekday)
544 if locale is None:
Christian Heimesced16462007-11-12 01:20:56 +0000545 locale = _locale.getdefaultlocale()
Walter Dörwald48d5e502006-04-01 07:57:00 +0000546 self.locale = locale
547
548 def formatweekday(self, day):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000549 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000550 s = day_abbr[day]
551 if encoding is not None:
552 s = s.decode(encoding)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000553 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000554
555 def formatmonthname(self, theyear, themonth, withyear=True):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000556 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000557 s = month_name[themonth]
558 if encoding is not None:
559 s = s.decode(encoding)
560 if withyear:
561 s = '%s %s' % (s, theyear)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000562 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
Walter Dörwald58917a62006-03-31 15:26:22 +0000563
564
565# Support for old module level interface
566c = TextCalendar()
567
Walter Dörwaldaba10cf2006-04-03 15:20:28 +0000568firstweekday = c.getfirstweekday
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000569
570def setfirstweekday(firstweekday):
Florent Xicluna1f3b4e12010-03-07 12:14:25 +0000571 try:
572 firstweekday.__index__
573 except AttributeError:
574 raise IllegalWeekdayError(firstweekday)
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000575 if not MONDAY <= firstweekday <= SUNDAY:
576 raise IllegalWeekdayError(firstweekday)
577 c.firstweekday = firstweekday
578
Walter Dörwald58917a62006-03-31 15:26:22 +0000579monthcalendar = c.monthdayscalendar
580prweek = c.prweek
581week = c.formatweek
582weekheader = c.formatweekheader
583prmonth = c.prmonth
584month = c.formatmonth
585calendar = c.formatyear
586prcal = c.pryear
587
588
589# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000590_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000591_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000592
Guido van Rossumc6360141990-10-13 19:23:40 +0000593
Walter Dörwald58917a62006-03-31 15:26:22 +0000594def format(cols, colwidth=_colwidth, spacing=_spacing):
595 """Prints multi-column formatting for year calendars"""
596 print formatstring(cols, colwidth, spacing)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000597
Skip Montanaroad3bc442000-08-30 14:01:28 +0000598
Walter Dörwald58917a62006-03-31 15:26:22 +0000599def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
600 """Returns a string formatted from n strings, centered within n columns."""
601 spacing *= ' '
602 return spacing.join(c.center(colwidth) for c in cols)
603
Guido van Rossumb39aff81999-06-09 15:07:38 +0000604
Guido van Rossumb39aff81999-06-09 15:07:38 +0000605EPOCH = 1970
Raymond Hettingere11b5102002-12-25 16:37:19 +0000606_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
607
Walter Dörwald58917a62006-03-31 15:26:22 +0000608
Guido van Rossumb39aff81999-06-09 15:07:38 +0000609def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000610 """Unrelated but handy function to calculate Unix timestamp from GMT."""
611 year, month, day, hour, minute, second = tuple[:6]
Raymond Hettinger61436482003-02-13 22:58:02 +0000612 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000613 hours = days*24 + hour
614 minutes = hours*60 + minute
615 seconds = minutes*60 + second
616 return seconds
Walter Dörwald58917a62006-03-31 15:26:22 +0000617
618
619def main(args):
620 import optparse
Walter Dörwald48d5e502006-04-01 07:57:00 +0000621 parser = optparse.OptionParser(usage="usage: %prog [options] [year [month]]")
622 parser.add_option(
623 "-w", "--width",
624 dest="width", type="int", default=2,
625 help="width of date column (default 2, text only)"
626 )
627 parser.add_option(
628 "-l", "--lines",
629 dest="lines", type="int", default=1,
630 help="number of lines for each week (default 1, text only)"
631 )
632 parser.add_option(
633 "-s", "--spacing",
634 dest="spacing", type="int", default=6,
635 help="spacing between months (default 6, text only)"
636 )
637 parser.add_option(
638 "-m", "--months",
639 dest="months", type="int", default=3,
640 help="months per row (default 3, text only)"
641 )
642 parser.add_option(
643 "-c", "--css",
644 dest="css", default="calendar.css",
645 help="CSS to use for page (html only)"
646 )
647 parser.add_option(
648 "-L", "--locale",
649 dest="locale", default=None,
650 help="locale to be used from month and weekday names"
651 )
652 parser.add_option(
653 "-e", "--encoding",
654 dest="encoding", default=None,
655 help="Encoding to use for output"
656 )
657 parser.add_option(
658 "-t", "--type",
659 dest="type", default="text",
660 choices=("text", "html"),
661 help="output type (text or html)"
662 )
Walter Dörwald58917a62006-03-31 15:26:22 +0000663
664 (options, args) = parser.parse_args(args)
665
Walter Dörwald48d5e502006-04-01 07:57:00 +0000666 if options.locale and not options.encoding:
667 parser.error("if --locale is specified --encoding is required")
668 sys.exit(1)
669
Christian Heimesced16462007-11-12 01:20:56 +0000670 locale = options.locale, options.encoding
671
Walter Dörwald58917a62006-03-31 15:26:22 +0000672 if options.type == "html":
Walter Dörwald48d5e502006-04-01 07:57:00 +0000673 if options.locale:
Christian Heimesced16462007-11-12 01:20:56 +0000674 cal = LocaleHTMLCalendar(locale=locale)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000675 else:
676 cal = HTMLCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000677 encoding = options.encoding
678 if encoding is None:
679 encoding = sys.getdefaultencoding()
680 optdict = dict(encoding=encoding, css=options.css)
681 if len(args) == 1:
682 print cal.formatyearpage(datetime.date.today().year, **optdict)
683 elif len(args) == 2:
684 print cal.formatyearpage(int(args[1]), **optdict)
685 else:
686 parser.error("incorrect number of arguments")
687 sys.exit(1)
688 else:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000689 if options.locale:
Christian Heimesced16462007-11-12 01:20:56 +0000690 cal = LocaleTextCalendar(locale=locale)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000691 else:
692 cal = TextCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000693 optdict = dict(w=options.width, l=options.lines)
694 if len(args) != 3:
695 optdict["c"] = options.spacing
696 optdict["m"] = options.months
697 if len(args) == 1:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000698 result = cal.formatyear(datetime.date.today().year, **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000699 elif len(args) == 2:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000700 result = cal.formatyear(int(args[1]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000701 elif len(args) == 3:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000702 result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000703 else:
704 parser.error("incorrect number of arguments")
705 sys.exit(1)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000706 if options.encoding:
707 result = result.encode(options.encoding)
708 print result
Walter Dörwald58917a62006-03-31 15:26:22 +0000709
710
711if __name__ == "__main__":
712 main(sys.argv)