blob: b5472f3306654aa59be2efdd16ea5c1b0471380d [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 Heimes96f31632007-11-12 01:32:03 +00008import sys
9import datetime
10import locale as _locale
Alexander Belopolsky957b7562016-09-27 20:26:39 -040011from itertools import repeat
Guido van Rossumc6360141990-10-13 19:23:40 +000012
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Guido van Rossum805365e2007-05-07 22:24:25 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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.
Guido van Rossum805365e2007-05-07 22:24:25 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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):
Alexander Belopolskyf87cc042010-10-19 17:43:50 +000099 """Return True for leap years, False 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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000133 def __init__(self, firstweekday=0):
134 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
135
136 def getfirstweekday(self):
137 return self._firstweekday % 7
138
139 def setfirstweekday(self, firstweekday):
140 self._firstweekday = firstweekday
141
142 firstweekday = property(getfirstweekday, setfirstweekday)
143
144 def iterweekdays(self):
145 """
Martin Panter7462b6492015-11-02 03:37:02 +0000146 Return an iterator for one week of weekday numbers starting with the
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000147 configured first one.
148 """
Guido van Rossum805365e2007-05-07 22:24:25 +0000149 for i in range(self.firstweekday, self.firstweekday + 7):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
160 days = (date.weekday() - self.firstweekday) % 7
161 date -= datetime.timedelta(days=days)
162 oneday = datetime.timedelta(days=1)
163 while True:
164 yield date
Ezio Melotti85710a42012-09-21 17:26:35 +0300165 try:
166 date += oneday
167 except OverflowError:
168 # Adding one day could fail after datetime.MAXYEAR
169 break
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000170 if date.month != month and date.weekday() == self.firstweekday:
171 break
172
173 def itermonthdays2(self, year, month):
174 """
175 Like itermonthdates(), but will yield (day number, weekday number)
176 tuples. For days outside the specified month the day number is 0.
177 """
Alexander Belopolsky957b7562016-09-27 20:26:39 -0400178 for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
179 yield d, i % 7
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000180
181 def itermonthdays(self, year, month):
182 """
Christian Heimes77c02eb2008-02-09 02:18:51 +0000183 Like itermonthdates(), but will yield day numbers. For days outside
184 the specified month the day number is 0.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000185 """
Alexander Belopolsky957b7562016-09-27 20:26:39 -0400186 day1, ndays = monthrange(year, month)
187 days_before = (day1 - self.firstweekday) % 7
188 yield from repeat(0, days_before)
189 yield from range(1, ndays + 1)
190 days_after = (self.firstweekday - day1 - ndays) % 7
191 yield from repeat(0, days_after)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000192
193 def monthdatescalendar(self, year, month):
194 """
195 Return a matrix (list of lists) representing a month's calendar.
196 Each row represents a week; week entries are datetime.date values.
197 """
198 dates = list(self.itermonthdates(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000199 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000200
201 def monthdays2calendar(self, year, month):
202 """
203 Return a matrix representing a month's calendar.
204 Each row represents a week; week entries are
205 (day number, weekday number) tuples. Day numbers outside this month
206 are zero.
207 """
208 days = list(self.itermonthdays2(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000209 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000210
211 def monthdayscalendar(self, year, month):
212 """
213 Return a matrix representing a month's calendar.
214 Each row represents a week; days outside this month are zero.
215 """
216 days = list(self.itermonthdays(year, month))
Guido van Rossum805365e2007-05-07 22:24:25 +0000217 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000218
219 def yeardatescalendar(self, year, width=3):
220 """
221 Return the data for the specified year ready for formatting. The return
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300222 value is a list of month rows. Each month row contains up to width months.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000223 Each month contains between 4 and 6 weeks and each week contains 1-7
224 days. Days are datetime.date objects.
225 """
226 months = [
227 self.monthdatescalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000228 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000229 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000230 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000231
232 def yeardays2calendar(self, year, width=3):
233 """
234 Return the data for the specified year ready for formatting (similar to
235 yeardatescalendar()). Entries in the week lists are
236 (day number, weekday number) tuples. Day numbers outside this month are
237 zero.
238 """
239 months = [
240 self.monthdays2calendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000241 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000242 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000243 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000244
245 def yeardayscalendar(self, year, width=3):
246 """
247 Return the data for the specified year ready for formatting (similar to
248 yeardatescalendar()). Entries in the week lists are day numbers.
249 Day numbers outside this month are zero.
250 """
251 months = [
252 self.monthdayscalendar(year, i)
Guido van Rossum805365e2007-05-07 22:24:25 +0000253 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000254 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000255 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000256
257
258class TextCalendar(Calendar):
259 """
260 Subclass of Calendar that outputs a calendar as a simple plain text
261 similar to the UNIX program cal.
262 """
263
264 def prweek(self, theweek, width):
265 """
266 Print a single week (no newline).
267 """
Christian Heimes32fbe592007-11-12 15:01:33 +0000268 print(self.formatweek(theweek, width), end=' ')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000269
270 def formatday(self, day, weekday, width):
271 """
272 Returns a formatted day.
273 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000274 if day == 0:
275 s = ''
276 else:
277 s = '%2i' % day # right-align single-digit days
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000278 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000279
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000280 def formatweek(self, theweek, width):
281 """
282 Returns a single week in a string (no newline).
283 """
284 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
Guido van Rossumc6360141990-10-13 19:23:40 +0000285
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000286 def formatweekday(self, day, width):
287 """
288 Returns a formatted week day name.
289 """
290 if width >= 9:
291 names = day_name
292 else:
293 names = day_abbr
294 return names[day][:width].center(width)
Skip Montanaroad3bc442000-08-30 14:01:28 +0000295
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000296 def formatweekheader(self, width):
297 """
298 Return a header for a week.
299 """
300 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
Guido van Rossumc6360141990-10-13 19:23:40 +0000301
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000302 def formatmonthname(self, theyear, themonth, width, withyear=True):
303 """
304 Return a formatted month name.
305 """
306 s = month_name[themonth]
307 if withyear:
308 s = "%s %r" % (s, theyear)
309 return s.center(width)
310
311 def prmonth(self, theyear, themonth, w=0, l=0):
312 """
313 Print a month's calendar.
314 """
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000315 print(self.formatmonth(theyear, themonth, w, l), end=' ')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000316
317 def formatmonth(self, theyear, themonth, w=0, l=0):
318 """
319 Return a month's calendar string (multi-line).
320 """
321 w = max(2, w)
322 l = max(1, l)
323 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
324 s = s.rstrip()
325 s += '\n' * l
326 s += self.formatweekheader(w).rstrip()
327 s += '\n' * l
328 for week in self.monthdays2calendar(theyear, themonth):
329 s += self.formatweek(week, w).rstrip()
330 s += '\n' * l
331 return s
332
333 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
334 """
335 Returns a year's calendar as a multi-line string.
336 """
337 w = max(2, w)
338 l = max(1, l)
339 c = max(2, c)
340 colwidth = (w + 1) * 7 - 1
341 v = []
342 a = v.append
343 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
344 a('\n'*l)
345 header = self.formatweekheader(w)
346 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
347 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000348 months = range(m*i+1, min(m*(i+1)+1, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000349 a('\n'*l)
350 names = (self.formatmonthname(theyear, k, colwidth, False)
351 for k in months)
352 a(formatstring(names, colwidth, c).rstrip())
353 a('\n'*l)
354 headers = (header for k in months)
355 a(formatstring(headers, colwidth, c).rstrip())
356 a('\n'*l)
357 # max number of weeks for this row
358 height = max(len(cal) for cal in row)
Guido van Rossum805365e2007-05-07 22:24:25 +0000359 for j in range(height):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000360 weeks = []
361 for cal in row:
362 if j >= len(cal):
363 weeks.append('')
364 else:
365 weeks.append(self.formatweek(cal[j], w))
366 a(formatstring(weeks, colwidth, c).rstrip())
367 a('\n' * l)
368 return ''.join(v)
369
370 def pryear(self, theyear, w=0, l=0, c=6, m=3):
371 """Print a year's calendar."""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000372 print(self.formatyear(theyear, w, l, c, m))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000373
374
375class HTMLCalendar(Calendar):
376 """
377 This calendar returns complete HTML pages.
378 """
379
380 # CSS classes for the day <td>s
381 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
382
383 def formatday(self, day, weekday):
384 """
385 Return a day as a table cell.
386 """
387 if day == 0:
388 return '<td class="noday">&nbsp;</td>' # day outside month
389 else:
390 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
391
392 def formatweek(self, theweek):
393 """
394 Return a complete week as a table row.
395 """
396 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
397 return '<tr>%s</tr>' % s
398
399 def formatweekday(self, day):
400 """
401 Return a weekday name as a table header.
402 """
403 return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
404
405 def formatweekheader(self):
406 """
407 Return a header for a week as a table row.
408 """
409 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
410 return '<tr>%s</tr>' % s
411
412 def formatmonthname(self, theyear, themonth, withyear=True):
413 """
414 Return a month name as a table row.
415 """
416 if withyear:
417 s = '%s %s' % (month_name[themonth], theyear)
418 else:
419 s = '%s' % month_name[themonth]
420 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
421
422 def formatmonth(self, theyear, themonth, withyear=True):
423 """
424 Return a formatted month as a table.
425 """
426 v = []
427 a = v.append
428 a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
429 a('\n')
430 a(self.formatmonthname(theyear, themonth, withyear=withyear))
431 a('\n')
432 a(self.formatweekheader())
433 a('\n')
434 for week in self.monthdays2calendar(theyear, themonth):
435 a(self.formatweek(week))
436 a('\n')
437 a('</table>')
438 a('\n')
439 return ''.join(v)
440
441 def formatyear(self, theyear, width=3):
442 """
443 Return a formatted year as a table of tables.
444 """
445 v = []
446 a = v.append
447 width = max(width, 1)
448 a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
449 a('\n')
450 a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
Guido van Rossum805365e2007-05-07 22:24:25 +0000451 for i in range(January, January+12, width):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000452 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000453 months = range(i, min(i+width, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000454 a('<tr>')
455 for m in months:
456 a('<td>')
457 a(self.formatmonth(theyear, m, withyear=False))
458 a('</td>')
459 a('</tr>')
460 a('</table>')
461 return ''.join(v)
462
463 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
464 """
465 Return a formatted year as a complete HTML page.
466 """
467 if encoding is None:
468 encoding = sys.getdefaultencoding()
469 v = []
470 a = v.append
471 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
472 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
473 a('<html>\n')
474 a('<head>\n')
475 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
476 if css is not None:
477 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
Thomas Wouters47b49bf2007-08-30 22:15:33 +0000478 a('<title>Calendar for %d</title>\n' % theyear)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000479 a('</head>\n')
480 a('<body>\n')
481 a(self.formatyear(theyear, width))
482 a('</body>\n')
483 a('</html>\n')
484 return ''.join(v).encode(encoding, "xmlcharrefreplace")
485
486
Georg Brandlfd680872008-06-08 08:40:05 +0000487class different_locale:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000488 def __init__(self, locale):
489 self.locale = locale
490
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000491 def __enter__(self):
Georg Brandl7004bd12010-10-19 18:54:25 +0000492 self.oldlocale = _locale.getlocale(_locale.LC_TIME)
493 _locale.setlocale(_locale.LC_TIME, self.locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000494
495 def __exit__(self, *args):
Christian Heimes96f31632007-11-12 01:32:03 +0000496 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000497
498
499class LocaleTextCalendar(TextCalendar):
500 """
501 This class can be passed a locale name in the constructor and will return
502 month and weekday names in the specified locale. If this locale includes
503 an encoding all strings containing month and weekday names will be returned
504 as unicode.
505 """
506
507 def __init__(self, firstweekday=0, locale=None):
508 TextCalendar.__init__(self, firstweekday)
509 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000510 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000511 self.locale = locale
512
513 def formatweekday(self, day, width):
Georg Brandlfd680872008-06-08 08:40:05 +0000514 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000515 if width >= 9:
516 names = day_name
517 else:
518 names = day_abbr
519 name = names[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000520 return name[:width].center(width)
521
522 def formatmonthname(self, theyear, themonth, width, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000523 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000524 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000525 if withyear:
526 s = "%s %r" % (s, theyear)
527 return s.center(width)
528
529
530class LocaleHTMLCalendar(HTMLCalendar):
531 """
532 This class can be passed a locale name in the constructor and will return
533 month and weekday names in the specified locale. If this locale includes
534 an encoding all strings containing month and weekday names will be returned
535 as unicode.
536 """
537 def __init__(self, firstweekday=0, locale=None):
538 HTMLCalendar.__init__(self, firstweekday)
539 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000540 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000541 self.locale = locale
542
543 def formatweekday(self, day):
Georg Brandlfd680872008-06-08 08:40:05 +0000544 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000545 s = day_abbr[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000546 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
547
548 def formatmonthname(self, theyear, themonth, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000549 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000550 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000551 if withyear:
552 s = '%s %s' % (s, theyear)
553 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
554
555
556# Support for old module level interface
557c = TextCalendar()
558
559firstweekday = c.getfirstweekday
560
561def setfirstweekday(firstweekday):
562 if not MONDAY <= firstweekday <= SUNDAY:
563 raise IllegalWeekdayError(firstweekday)
564 c.firstweekday = firstweekday
565
566monthcalendar = c.monthdayscalendar
567prweek = c.prweek
568week = c.formatweek
569weekheader = c.formatweekheader
570prmonth = c.prmonth
571month = c.formatmonth
572calendar = c.formatyear
573prcal = c.pryear
574
575
576# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000577_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000578_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000579
Guido van Rossumc6360141990-10-13 19:23:40 +0000580
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000581def format(cols, colwidth=_colwidth, spacing=_spacing):
582 """Prints multi-column formatting for year calendars"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000583 print(formatstring(cols, colwidth, spacing))
Skip Montanaroad3bc442000-08-30 14:01:28 +0000584
Skip Montanaroad3bc442000-08-30 14:01:28 +0000585
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000586def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
587 """Returns a string formatted from n strings, centered within n columns."""
588 spacing *= ' '
589 return spacing.join(c.center(colwidth) for c in cols)
590
Guido van Rossumb39aff81999-06-09 15:07:38 +0000591
Guido van Rossumb39aff81999-06-09 15:07:38 +0000592EPOCH = 1970
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000593_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
594
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000595
Guido van Rossumb39aff81999-06-09 15:07:38 +0000596def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000597 """Unrelated but handy function to calculate Unix timestamp from GMT."""
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000598 year, month, day, hour, minute, second = tuple[:6]
599 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
600 hours = days*24 + hour
601 minutes = hours*60 + minute
602 seconds = minutes*60 + second
603 return seconds
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000604
605
606def main(args):
607 import optparse
608 parser = optparse.OptionParser(usage="usage: %prog [options] [year [month]]")
609 parser.add_option(
610 "-w", "--width",
611 dest="width", type="int", default=2,
612 help="width of date column (default 2, text only)"
613 )
614 parser.add_option(
615 "-l", "--lines",
616 dest="lines", type="int", default=1,
617 help="number of lines for each week (default 1, text only)"
618 )
619 parser.add_option(
620 "-s", "--spacing",
621 dest="spacing", type="int", default=6,
622 help="spacing between months (default 6, text only)"
623 )
624 parser.add_option(
625 "-m", "--months",
626 dest="months", type="int", default=3,
627 help="months per row (default 3, text only)"
628 )
629 parser.add_option(
630 "-c", "--css",
631 dest="css", default="calendar.css",
632 help="CSS to use for page (html only)"
633 )
634 parser.add_option(
635 "-L", "--locale",
636 dest="locale", default=None,
637 help="locale to be used from month and weekday names"
638 )
639 parser.add_option(
640 "-e", "--encoding",
641 dest="encoding", default=None,
Senthil Kumaran962fed92011-08-11 09:22:52 +0800642 help="Encoding to use for output."
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000643 )
644 parser.add_option(
645 "-t", "--type",
646 dest="type", default="text",
647 choices=("text", "html"),
648 help="output type (text or html)"
649 )
650
651 (options, args) = parser.parse_args(args)
652
653 if options.locale and not options.encoding:
654 parser.error("if --locale is specified --encoding is required")
655 sys.exit(1)
656
Christian Heimes96f31632007-11-12 01:32:03 +0000657 locale = options.locale, options.encoding
658
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000659 if options.type == "html":
660 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000661 cal = LocaleHTMLCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000662 else:
663 cal = HTMLCalendar()
664 encoding = options.encoding
665 if encoding is None:
666 encoding = sys.getdefaultencoding()
667 optdict = dict(encoding=encoding, css=options.css)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800668 write = sys.stdout.buffer.write
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000669 if len(args) == 1:
Senthil Kumaran962fed92011-08-11 09:22:52 +0800670 write(cal.formatyearpage(datetime.date.today().year, **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000671 elif len(args) == 2:
Senthil Kumaran962fed92011-08-11 09:22:52 +0800672 write(cal.formatyearpage(int(args[1]), **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000673 else:
674 parser.error("incorrect number of arguments")
675 sys.exit(1)
676 else:
677 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000678 cal = LocaleTextCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000679 else:
680 cal = TextCalendar()
681 optdict = dict(w=options.width, l=options.lines)
682 if len(args) != 3:
683 optdict["c"] = options.spacing
684 optdict["m"] = options.months
685 if len(args) == 1:
686 result = cal.formatyear(datetime.date.today().year, **optdict)
687 elif len(args) == 2:
688 result = cal.formatyear(int(args[1]), **optdict)
689 elif len(args) == 3:
690 result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
691 else:
692 parser.error("incorrect number of arguments")
693 sys.exit(1)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800694 write = sys.stdout.write
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000695 if options.encoding:
696 result = result.encode(options.encoding)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800697 write = sys.stdout.buffer.write
698 write(result)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000699
700
701if __name__ == "__main__":
702 main(sys.argv)