blob: 8fcceb7fb76fb7c230d3ac301bbfff94d5f6e7d1 [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 """
Serhiy Storchaka9a118f12016-04-17 09:37:36 +0300145 Return an iterator for one week of weekday numbers starting with the
Walter Dörwald58917a62006-03-31 15:26:22 +0000146 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 """
Alexander Belopolsky8cab4192016-09-27 22:45:20 -0400177 for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
178 yield d, i % 7
Walter Dörwald58917a62006-03-31 15:26:22 +0000179
180 def itermonthdays(self, year, month):
181 """
Walter Dörwaldd0e5b762008-02-07 19:57:32 +0000182 Like itermonthdates(), but will yield day numbers. For days outside
183 the specified month the day number is 0.
Walter Dörwald58917a62006-03-31 15:26:22 +0000184 """
Alexander Belopolsky8cab4192016-09-27 22:45:20 -0400185 day1, ndays = monthrange(year, month)
186 days_before = (day1 - self.firstweekday) % 7
187 for _ in range(days_before):
188 yield 0
189 for d in range(1, ndays + 1):
190 yield d
191 days_after = (self.firstweekday - day1 - ndays) % 7
192 for _ in range(days_after):
193 yield 0
Walter Dörwald58917a62006-03-31 15:26:22 +0000194
195 def monthdatescalendar(self, year, month):
196 """
197 Return a matrix (list of lists) representing a month's calendar.
198 Each row represents a week; week entries are datetime.date values.
199 """
200 dates = list(self.itermonthdates(year, month))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000201 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000202
203 def monthdays2calendar(self, year, month):
204 """
205 Return a matrix representing a month's calendar.
206 Each row represents a week; week entries are
207 (day number, weekday number) tuples. Day numbers outside this month
208 are zero.
209 """
210 days = list(self.itermonthdays2(year, month))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000211 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000212
213 def monthdayscalendar(self, year, month):
214 """
215 Return a matrix representing a month's calendar.
216 Each row represents a week; days outside this month are zero.
217 """
218 days = list(self.itermonthdays(year, month))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000219 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000220
221 def yeardatescalendar(self, year, width=3):
222 """
223 Return the data for the specified year ready for formatting. The return
Ezio Melottif5469cf2013-08-17 15:43:51 +0300224 value is a list of month rows. Each month row contains up to width months.
Walter Dörwald58917a62006-03-31 15:26:22 +0000225 Each month contains between 4 and 6 weeks and each week contains 1-7
226 days. Days are datetime.date objects.
227 """
228 months = [
229 self.monthdatescalendar(year, i)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000230 for i in range(January, January+12)
Walter Dörwald58917a62006-03-31 15:26:22 +0000231 ]
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000232 return [months[i:i+width] for i in range(0, len(months), width) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000233
234 def yeardays2calendar(self, year, width=3):
235 """
236 Return the data for the specified year ready for formatting (similar to
237 yeardatescalendar()). Entries in the week lists are
238 (day number, weekday number) tuples. Day numbers outside this month are
239 zero.
240 """
241 months = [
242 self.monthdays2calendar(year, i)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000243 for i in range(January, January+12)
Walter Dörwald58917a62006-03-31 15:26:22 +0000244 ]
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000245 return [months[i:i+width] for i in range(0, len(months), width) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000246
247 def yeardayscalendar(self, year, width=3):
248 """
249 Return the data for the specified year ready for formatting (similar to
250 yeardatescalendar()). Entries in the week lists are day numbers.
251 Day numbers outside this month are zero.
252 """
253 months = [
254 self.monthdayscalendar(year, i)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000255 for i in range(January, January+12)
Walter Dörwald58917a62006-03-31 15:26:22 +0000256 ]
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000257 return [months[i:i+width] for i in range(0, len(months), width) ]
Walter Dörwald58917a62006-03-31 15:26:22 +0000258
259
260class TextCalendar(Calendar):
261 """
262 Subclass of Calendar that outputs a calendar as a simple plain text
263 similar to the UNIX program cal.
264 """
265
Anthony Baxter7846f4d2006-04-07 05:41:13 +0000266 def prweek(self, theweek, width):
Walter Dörwald58917a62006-03-31 15:26:22 +0000267 """
268 Print a single week (no newline).
269 """
Walter Dörwaldedc526c2007-11-12 10:01:33 +0000270 print self.formatweek(theweek, width),
Walter Dörwald58917a62006-03-31 15:26:22 +0000271
272 def formatday(self, day, weekday, width):
273 """
274 Returns a formatted day.
275 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000276 if day == 0:
277 s = ''
278 else:
279 s = '%2i' % day # right-align single-digit days
Walter Dörwald58917a62006-03-31 15:26:22 +0000280 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000281
Walter Dörwald58917a62006-03-31 15:26:22 +0000282 def formatweek(self, theweek, width):
283 """
284 Returns a single week in a string (no newline).
285 """
286 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000287
Walter Dörwald58917a62006-03-31 15:26:22 +0000288 def formatweekday(self, day, width):
289 """
290 Returns a formatted week day name.
291 """
292 if width >= 9:
293 names = day_name
294 else:
295 names = day_abbr
296 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000297
Walter Dörwald58917a62006-03-31 15:26:22 +0000298 def formatweekheader(self, width):
299 """
300 Return a header for a week.
301 """
302 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000303
Walter Dörwald48d5e502006-04-01 07:57:00 +0000304 def formatmonthname(self, theyear, themonth, width, withyear=True):
Walter Dörwald58917a62006-03-31 15:26:22 +0000305 """
306 Return a formatted month name.
307 """
Walter Dörwald48d5e502006-04-01 07:57:00 +0000308 s = month_name[themonth]
309 if withyear:
310 s = "%s %r" % (s, theyear)
Walter Dörwald58917a62006-03-31 15:26:22 +0000311 return s.center(width)
312
313 def prmonth(self, theyear, themonth, w=0, l=0):
314 """
315 Print a month's calendar.
316 """
317 print self.formatmonth(theyear, themonth, w, l),
318
319 def formatmonth(self, theyear, themonth, w=0, l=0):
320 """
321 Return a month's calendar string (multi-line).
322 """
323 w = max(2, w)
324 l = max(1, l)
325 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
326 s = s.rstrip()
327 s += '\n' * l
328 s += self.formatweekheader(w).rstrip()
329 s += '\n' * l
330 for week in self.monthdays2calendar(theyear, themonth):
331 s += self.formatweek(week, w).rstrip()
332 s += '\n' * l
333 return s
334
335 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
336 """
337 Returns a year's calendar as a multi-line string.
338 """
339 w = max(2, w)
340 l = max(1, l)
341 c = max(2, c)
342 colwidth = (w + 1) * 7 - 1
343 v = []
344 a = v.append
345 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
346 a('\n'*l)
347 header = self.formatweekheader(w)
348 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
349 # months in this row
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000350 months = range(m*i+1, min(m*(i+1)+1, 13))
Walter Dörwald58917a62006-03-31 15:26:22 +0000351 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000352 names = (self.formatmonthname(theyear, k, colwidth, False)
353 for k in months)
354 a(formatstring(names, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000355 a('\n'*l)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000356 headers = (header for k in months)
357 a(formatstring(headers, colwidth, c).rstrip())
Walter Dörwald58917a62006-03-31 15:26:22 +0000358 a('\n'*l)
359 # max number of weeks for this row
360 height = max(len(cal) for cal in row)
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000361 for j in range(height):
Walter Dörwald58917a62006-03-31 15:26:22 +0000362 weeks = []
363 for cal in row:
364 if j >= len(cal):
365 weeks.append('')
366 else:
367 weeks.append(self.formatweek(cal[j], w))
368 a(formatstring(weeks, colwidth, c).rstrip())
369 a('\n' * l)
370 return ''.join(v)
371
372 def pryear(self, theyear, w=0, l=0, c=6, m=3):
373 """Print a year's calendar."""
374 print self.formatyear(theyear, w, l, c, m)
375
376
377class HTMLCalendar(Calendar):
378 """
379 This calendar returns complete HTML pages.
380 """
381
382 # CSS classes for the day <td>s
383 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
384
385 def formatday(self, day, weekday):
386 """
387 Return a day as a table cell.
388 """
389 if day == 0:
390 return '<td class="noday">&nbsp;</td>' # day outside month
391 else:
392 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
393
394 def formatweek(self, theweek):
395 """
396 Return a complete week as a table row.
397 """
398 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
399 return '<tr>%s</tr>' % s
400
401 def formatweekday(self, day):
402 """
403 Return a weekday name as a table header.
404 """
405 return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
406
407 def formatweekheader(self):
408 """
409 Return a header for a week as a table row.
410 """
411 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
412 return '<tr>%s</tr>' % s
413
414 def formatmonthname(self, theyear, themonth, withyear=True):
415 """
416 Return a month name as a table row.
417 """
418 if withyear:
419 s = '%s %s' % (month_name[themonth], theyear)
420 else:
421 s = '%s' % month_name[themonth]
422 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
423
424 def formatmonth(self, theyear, themonth, withyear=True):
425 """
426 Return a formatted month as a table.
427 """
428 v = []
429 a = v.append
430 a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
431 a('\n')
432 a(self.formatmonthname(theyear, themonth, withyear=withyear))
433 a('\n')
434 a(self.formatweekheader())
435 a('\n')
436 for week in self.monthdays2calendar(theyear, themonth):
437 a(self.formatweek(week))
438 a('\n')
439 a('</table>')
440 a('\n')
441 return ''.join(v)
442
443 def formatyear(self, theyear, width=3):
444 """
445 Return a formatted year as a table of tables.
446 """
447 v = []
448 a = v.append
449 width = max(width, 1)
450 a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
451 a('\n')
452 a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000453 for i in range(January, January+12, width):
Walter Dörwald58917a62006-03-31 15:26:22 +0000454 # months in this row
Raymond Hettinger72ef8da2007-05-17 01:08:04 +0000455 months = range(i, min(i+width, 13))
Walter Dörwald58917a62006-03-31 15:26:22 +0000456 a('<tr>')
457 for m in months:
458 a('<td>')
459 a(self.formatmonth(theyear, m, withyear=False))
460 a('</td>')
461 a('</tr>')
462 a('</table>')
463 return ''.join(v)
464
465 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
466 """
467 Return a formatted year as a complete HTML page.
468 """
469 if encoding is None:
470 encoding = sys.getdefaultencoding()
471 v = []
472 a = v.append
473 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
474 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
475 a('<html>\n')
476 a('<head>\n')
477 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
478 if css is not None:
479 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
Walter Dörwaldf0d1c1f2007-08-28 16:38:26 +0000480 a('<title>Calendar for %d</title>\n' % theyear)
Walter Dörwald58917a62006-03-31 15:26:22 +0000481 a('</head>\n')
482 a('<body>\n')
483 a(self.formatyear(theyear, width))
484 a('</body>\n')
485 a('</html>\n')
Walter Dörwald48d5e502006-04-01 07:57:00 +0000486 return ''.join(v).encode(encoding, "xmlcharrefreplace")
487
488
Walter Dörwaldbc966092006-04-12 10:09:16 +0000489class TimeEncoding:
490 def __init__(self, locale):
491 self.locale = locale
492
Walter Dörwaldbc966092006-04-12 10:09:16 +0000493 def __enter__(self):
Georg Brandl79f096a2010-11-26 07:57:57 +0000494 self.oldlocale = _locale.getlocale(_locale.LC_TIME)
495 _locale.setlocale(_locale.LC_TIME, self.locale)
Serhiy Storchaka8d510cd2013-01-31 15:57:51 +0200496 return _locale.getlocale(_locale.LC_TIME)[1]
Walter Dörwaldbc966092006-04-12 10:09:16 +0000497
498 def __exit__(self, *args):
Christian Heimesced16462007-11-12 01:20:56 +0000499 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000500
501
Walter Dörwald48d5e502006-04-01 07:57:00 +0000502class LocaleTextCalendar(TextCalendar):
503 """
504 This class can be passed a locale name in the constructor and will return
505 month and weekday names in the specified locale. If this locale includes
506 an encoding all strings containing month and weekday names will be returned
507 as unicode.
508 """
509
510 def __init__(self, firstweekday=0, locale=None):
511 TextCalendar.__init__(self, firstweekday)
512 if locale is None:
Christian Heimesced16462007-11-12 01:20:56 +0000513 locale = _locale.getdefaultlocale()
Walter Dörwald48d5e502006-04-01 07:57:00 +0000514 self.locale = locale
515
516 def formatweekday(self, day, width):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000517 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000518 if width >= 9:
519 names = day_name
520 else:
521 names = day_abbr
522 name = names[day]
523 if encoding is not None:
524 name = name.decode(encoding)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000525 return name[:width].center(width)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000526
527 def formatmonthname(self, theyear, themonth, width, withyear=True):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000528 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000529 s = month_name[themonth]
530 if encoding is not None:
531 s = s.decode(encoding)
532 if withyear:
533 s = "%s %r" % (s, theyear)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000534 return s.center(width)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000535
536
537class LocaleHTMLCalendar(HTMLCalendar):
538 """
539 This class can be passed a locale name in the constructor and will return
540 month and weekday names in the specified locale. If this locale includes
541 an encoding all strings containing month and weekday names will be returned
542 as unicode.
543 """
544 def __init__(self, firstweekday=0, locale=None):
545 HTMLCalendar.__init__(self, firstweekday)
546 if locale is None:
Christian Heimesced16462007-11-12 01:20:56 +0000547 locale = _locale.getdefaultlocale()
Walter Dörwald48d5e502006-04-01 07:57:00 +0000548 self.locale = locale
549
550 def formatweekday(self, day):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000551 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000552 s = day_abbr[day]
553 if encoding is not None:
554 s = s.decode(encoding)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000555 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000556
557 def formatmonthname(self, theyear, themonth, withyear=True):
Walter Dörwaldbc966092006-04-12 10:09:16 +0000558 with TimeEncoding(self.locale) as encoding:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000559 s = month_name[themonth]
560 if encoding is not None:
561 s = s.decode(encoding)
562 if withyear:
563 s = '%s %s' % (s, theyear)
Walter Dörwaldbc966092006-04-12 10:09:16 +0000564 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
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):
Florent Xicluna1f3b4e12010-03-07 12:14:25 +0000573 try:
574 firstweekday.__index__
575 except AttributeError:
576 raise IllegalWeekdayError(firstweekday)
Walter Dörwald2a1b4a62006-04-03 15:24:49 +0000577 if not MONDAY <= firstweekday <= SUNDAY:
578 raise IllegalWeekdayError(firstweekday)
579 c.firstweekday = firstweekday
580
Walter Dörwald58917a62006-03-31 15:26:22 +0000581monthcalendar = c.monthdayscalendar
582prweek = c.prweek
583week = c.formatweek
584weekheader = c.formatweekheader
585prmonth = c.prmonth
586month = c.formatmonth
587calendar = c.formatyear
588prcal = c.pryear
589
590
591# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000592_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000593_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000594
Guido van Rossumc6360141990-10-13 19:23:40 +0000595
Walter Dörwald58917a62006-03-31 15:26:22 +0000596def format(cols, colwidth=_colwidth, spacing=_spacing):
597 """Prints multi-column formatting for year calendars"""
598 print formatstring(cols, colwidth, spacing)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000599
Skip Montanaroad3bc442000-08-30 14:01:28 +0000600
Walter Dörwald58917a62006-03-31 15:26:22 +0000601def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
602 """Returns a string formatted from n strings, centered within n columns."""
603 spacing *= ' '
604 return spacing.join(c.center(colwidth) for c in cols)
605
Guido van Rossumb39aff81999-06-09 15:07:38 +0000606
Guido van Rossumb39aff81999-06-09 15:07:38 +0000607EPOCH = 1970
Raymond Hettingere11b5102002-12-25 16:37:19 +0000608_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
609
Walter Dörwald58917a62006-03-31 15:26:22 +0000610
Guido van Rossumb39aff81999-06-09 15:07:38 +0000611def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000612 """Unrelated but handy function to calculate Unix timestamp from GMT."""
613 year, month, day, hour, minute, second = tuple[:6]
Raymond Hettinger61436482003-02-13 22:58:02 +0000614 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000615 hours = days*24 + hour
616 minutes = hours*60 + minute
617 seconds = minutes*60 + second
618 return seconds
Walter Dörwald58917a62006-03-31 15:26:22 +0000619
620
621def main(args):
622 import optparse
Walter Dörwald48d5e502006-04-01 07:57:00 +0000623 parser = optparse.OptionParser(usage="usage: %prog [options] [year [month]]")
624 parser.add_option(
625 "-w", "--width",
626 dest="width", type="int", default=2,
627 help="width of date column (default 2, text only)"
628 )
629 parser.add_option(
630 "-l", "--lines",
631 dest="lines", type="int", default=1,
632 help="number of lines for each week (default 1, text only)"
633 )
634 parser.add_option(
635 "-s", "--spacing",
636 dest="spacing", type="int", default=6,
637 help="spacing between months (default 6, text only)"
638 )
639 parser.add_option(
640 "-m", "--months",
641 dest="months", type="int", default=3,
642 help="months per row (default 3, text only)"
643 )
644 parser.add_option(
645 "-c", "--css",
646 dest="css", default="calendar.css",
647 help="CSS to use for page (html only)"
648 )
649 parser.add_option(
650 "-L", "--locale",
651 dest="locale", default=None,
652 help="locale to be used from month and weekday names"
653 )
654 parser.add_option(
655 "-e", "--encoding",
656 dest="encoding", default=None,
657 help="Encoding to use for output"
658 )
659 parser.add_option(
660 "-t", "--type",
661 dest="type", default="text",
662 choices=("text", "html"),
663 help="output type (text or html)"
664 )
Walter Dörwald58917a62006-03-31 15:26:22 +0000665
666 (options, args) = parser.parse_args(args)
667
Walter Dörwald48d5e502006-04-01 07:57:00 +0000668 if options.locale and not options.encoding:
669 parser.error("if --locale is specified --encoding is required")
670 sys.exit(1)
671
Christian Heimesced16462007-11-12 01:20:56 +0000672 locale = options.locale, options.encoding
673
Walter Dörwald58917a62006-03-31 15:26:22 +0000674 if options.type == "html":
Walter Dörwald48d5e502006-04-01 07:57:00 +0000675 if options.locale:
Christian Heimesced16462007-11-12 01:20:56 +0000676 cal = LocaleHTMLCalendar(locale=locale)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000677 else:
678 cal = HTMLCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000679 encoding = options.encoding
680 if encoding is None:
681 encoding = sys.getdefaultencoding()
682 optdict = dict(encoding=encoding, css=options.css)
683 if len(args) == 1:
684 print cal.formatyearpage(datetime.date.today().year, **optdict)
685 elif len(args) == 2:
686 print cal.formatyearpage(int(args[1]), **optdict)
687 else:
688 parser.error("incorrect number of arguments")
689 sys.exit(1)
690 else:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000691 if options.locale:
Christian Heimesced16462007-11-12 01:20:56 +0000692 cal = LocaleTextCalendar(locale=locale)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000693 else:
694 cal = TextCalendar()
Walter Dörwald58917a62006-03-31 15:26:22 +0000695 optdict = dict(w=options.width, l=options.lines)
696 if len(args) != 3:
697 optdict["c"] = options.spacing
698 optdict["m"] = options.months
699 if len(args) == 1:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000700 result = cal.formatyear(datetime.date.today().year, **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000701 elif len(args) == 2:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000702 result = cal.formatyear(int(args[1]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000703 elif len(args) == 3:
Walter Dörwald48d5e502006-04-01 07:57:00 +0000704 result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
Walter Dörwald58917a62006-03-31 15:26:22 +0000705 else:
706 parser.error("incorrect number of arguments")
707 sys.exit(1)
Walter Dörwald48d5e502006-04-01 07:57:00 +0000708 if options.encoding:
709 result = result.encode(options.encoding)
710 print result
Walter Dörwald58917a62006-03-31 15:26:22 +0000711
712
713if __name__ == "__main__":
714 main(sys.argv)