blob: 07594f3a833d7b8f4eb18a5596bf4fe55ae1775d [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",
Martin Panter4eb376c2016-01-16 06:49:30 +000016 "timegm", "month_name", "month_abbr", "day_name", "day_abbr",
17 "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
18 "LocaleHTMLCalendar", "weekheader"]
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000019
Guido van Rossumc6360141990-10-13 19:23:40 +000020# Exception raised for bad input (with string parameter for details)
Guido van Rossum00245cf1999-05-03 18:07:40 +000021error = ValueError
Guido van Rossumc6360141990-10-13 19:23:40 +000022
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000023# Exceptions raised for bad input
24class IllegalMonthError(ValueError):
25 def __init__(self, month):
26 self.month = month
27 def __str__(self):
28 return "bad month number %r; must be 1-12" % self.month
29
30
31class IllegalWeekdayError(ValueError):
32 def __init__(self, weekday):
33 self.weekday = weekday
34 def __str__(self):
35 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
36
37
Guido van Rossum9b3bc711993-06-20 21:02:22 +000038# Constants for months referenced later
39January = 1
40February = 2
41
42# Number of days per month (except for February in leap years)
43mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
44
Tim Peters0c2c8e72002-03-23 03:26:53 +000045# This module used to have hard-coded lists of day and month names, as
46# English strings. The classes following emulate a read-only version of
47# that, but supply localized names. Note that the values are computed
48# fresh on each call, in case the user changes locale between calls.
49
Raymond Hettinger9c051d72002-06-20 03:38:12 +000050class _localized_month:
Tim Petersbbc0d442004-11-13 16:18:32 +000051
Guido van Rossum805365e2007-05-07 22:24:25 +000052 _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
Tim Petersbbc0d442004-11-13 16:18:32 +000053 _months.insert(0, lambda x: "")
54
Tim Peters0c2c8e72002-03-23 03:26:53 +000055 def __init__(self, format):
Barry Warsaw1d099102001-05-22 15:58:30 +000056 self.format = format
Tim Peters0c2c8e72002-03-23 03:26:53 +000057
58 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000059 funcs = self._months[i]
60 if isinstance(i, slice):
61 return [f(self.format) for f in funcs]
62 else:
63 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000064
Skip Montanaro4c834952002-03-15 04:08:38 +000065 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000066 return 13
67
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000068
Raymond Hettinger9c051d72002-06-20 03:38:12 +000069class _localized_day:
Tim Petersbbc0d442004-11-13 16:18:32 +000070
71 # January 1, 2001, was a Monday.
Guido van Rossum805365e2007-05-07 22:24:25 +000072 _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
Tim Petersbbc0d442004-11-13 16:18:32 +000073
Tim Peters0c2c8e72002-03-23 03:26:53 +000074 def __init__(self, format):
75 self.format = format
76
77 def __getitem__(self, i):
Tim Petersbbc0d442004-11-13 16:18:32 +000078 funcs = self._days[i]
79 if isinstance(i, slice):
80 return [f(self.format) for f in funcs]
81 else:
82 return funcs(self.format)
Tim Peters0c2c8e72002-03-23 03:26:53 +000083
Neal Norwitz492faa52004-06-07 03:47:06 +000084 def __len__(self):
Tim Peters0c2c8e72002-03-23 03:26:53 +000085 return 7
Barry Warsaw1d099102001-05-22 15:58:30 +000086
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000087
Guido van Rossum9b3bc711993-06-20 21:02:22 +000088# Full and abbreviated names of weekdays
Tim Peters0c2c8e72002-03-23 03:26:53 +000089day_name = _localized_day('%A')
90day_abbr = _localized_day('%a')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000091
Guido van Rossum5cfa5df1993-06-23 09:30:50 +000092# Full and abbreviated names of months (1-based arrays!!!)
Tim Peters0c2c8e72002-03-23 03:26:53 +000093month_name = _localized_month('%B')
94month_abbr = _localized_month('%b')
Guido van Rossum9b3bc711993-06-20 21:02:22 +000095
Skip Montanaroad3bc442000-08-30 14:01:28 +000096# Constants for weekdays
97(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
98
Skip Montanaroad3bc442000-08-30 14:01:28 +000099
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000100def isleap(year):
Alexander Belopolskyf87cc042010-10-19 17:43:50 +0000101 """Return True for leap years, False for non-leap years."""
Fred Drake8152d322000-12-12 23:20:45 +0000102 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000103
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000104
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000105def leapdays(y1, y2):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000106 """Return number of leap years in range [y1, y2).
Guido van Rossum46735ad2000-10-09 12:42:04 +0000107 Assume y1 <= y2."""
108 y1 -= 1
109 y2 -= 1
Raymond Hettingere11b5102002-12-25 16:37:19 +0000110 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000111
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000112
Guido van Rossumc6360141990-10-13 19:23:40 +0000113def weekday(year, month, day):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000114 """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
115 day (1-31)."""
Raymond Hettingere11b5102002-12-25 16:37:19 +0000116 return datetime.date(year, month, day).weekday()
Guido van Rossumc6360141990-10-13 19:23:40 +0000117
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000118
Guido van Rossumc6360141990-10-13 19:23:40 +0000119def monthrange(year, month):
Skip Montanaroad3bc442000-08-30 14:01:28 +0000120 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
121 year, month."""
122 if not 1 <= month <= 12:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000123 raise IllegalMonthError(month)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000124 day1 = weekday(year, month, 1)
125 ndays = mdays[month] + (month == February and isleap(year))
126 return day1, ndays
Guido van Rossumc6360141990-10-13 19:23:40 +0000127
Guido van Rossumc6360141990-10-13 19:23:40 +0000128
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000129class Calendar(object):
130 """
131 Base calendar class. This class doesn't do any formatting. It simply
132 provides data to subclasses.
133 """
Skip Montanaroad3bc442000-08-30 14:01:28 +0000134
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000135 def __init__(self, firstweekday=0):
136 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
137
138 def getfirstweekday(self):
139 return self._firstweekday % 7
140
141 def setfirstweekday(self, firstweekday):
142 self._firstweekday = firstweekday
143
144 firstweekday = property(getfirstweekday, setfirstweekday)
145
146 def iterweekdays(self):
147 """
Martin Panter7462b6492015-11-02 03:37:02 +0000148 Return an iterator for one week of weekday numbers starting with the
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000149 configured first one.
150 """
Guido van Rossum805365e2007-05-07 22:24:25 +0000151 for i in range(self.firstweekday, self.firstweekday + 7):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000152 yield i%7
153
154 def itermonthdates(self, year, month):
155 """
156 Return an iterator for one month. The iterator will yield datetime.date
157 values and will always iterate through complete weeks, so it will yield
158 dates outside the specified month.
159 """
160 date = datetime.date(year, month, 1)
161 # Go back to the beginning of the week
162 days = (date.weekday() - self.firstweekday) % 7
163 date -= datetime.timedelta(days=days)
164 oneday = datetime.timedelta(days=1)
165 while True:
166 yield date
Ezio Melotti85710a42012-09-21 17:26:35 +0300167 try:
168 date += oneday
169 except OverflowError:
170 # Adding one day could fail after datetime.MAXYEAR
171 break
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000172 if date.month != month and date.weekday() == self.firstweekday:
173 break
174
175 def itermonthdays2(self, year, month):
176 """
177 Like itermonthdates(), but will yield (day number, weekday number)
178 tuples. For days outside the specified month the day number is 0.
179 """
Alexander Belopolsky957b7562016-09-27 20:26:39 -0400180 for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
181 yield d, i % 7
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000182
183 def itermonthdays(self, year, month):
184 """
Christian Heimes77c02eb2008-02-09 02:18:51 +0000185 Like itermonthdates(), but will yield day numbers. For days outside
186 the specified month the day number is 0.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000187 """
Alexander Belopolsky957b7562016-09-27 20:26:39 -0400188 day1, ndays = monthrange(year, month)
189 days_before = (day1 - self.firstweekday) % 7
190 yield from repeat(0, days_before)
191 yield from range(1, ndays + 1)
192 days_after = (self.firstweekday - day1 - ndays) % 7
193 yield from repeat(0, days_after)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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))
Guido van Rossum805365e2007-05-07 22:24:25 +0000201 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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))
Guido van Rossum805365e2007-05-07 22:24:25 +0000211 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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))
Guido van Rossum805365e2007-05-07 22:24:25 +0000219 return [ days[i:i+7] for i in range(0, len(days), 7) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000220
221 def yeardatescalendar(self, year, width=3):
222 """
223 Return the data for the specified year ready for formatting. The return
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300224 value is a list of month rows. Each month row contains up to width months.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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)
Guido van Rossum805365e2007-05-07 22:24:25 +0000230 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000231 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000232 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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)
Guido van Rossum805365e2007-05-07 22:24:25 +0000243 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000244 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000245 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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)
Guido van Rossum805365e2007-05-07 22:24:25 +0000255 for i in range(January, January+12)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000256 ]
Guido van Rossum805365e2007-05-07 22:24:25 +0000257 return [months[i:i+width] for i in range(0, len(months), width) ]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
266 def prweek(self, theweek, width):
267 """
268 Print a single week (no newline).
269 """
Christian Heimes32fbe592007-11-12 15:01:33 +0000270 print(self.formatweek(theweek, width), end=' ')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000280 return s.center(width)
Guido van Rossumc6360141990-10-13 19:23:40 +0000281
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000304 def formatmonthname(self, theyear, themonth, width, withyear=True):
305 """
306 Return a formatted month name.
307 """
308 s = month_name[themonth]
309 if withyear:
310 s = "%s %r" % (s, theyear)
311 return s.center(width)
312
313 def prmonth(self, theyear, themonth, w=0, l=0):
314 """
315 Print a month's calendar.
316 """
Serhiy Storchaka7ff51bd2016-10-25 15:00:52 +0300317 print(self.formatmonth(theyear, themonth, w, l), end='')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000318
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
Guido van Rossum805365e2007-05-07 22:24:25 +0000350 months = range(m*i+1, min(m*(i+1)+1, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000351 a('\n'*l)
352 names = (self.formatmonthname(theyear, k, colwidth, False)
353 for k in months)
354 a(formatstring(names, colwidth, c).rstrip())
355 a('\n'*l)
356 headers = (header for k in months)
357 a(formatstring(headers, colwidth, c).rstrip())
358 a('\n'*l)
359 # max number of weeks for this row
360 height = max(len(cal) for cal in row)
Guido van Rossum805365e2007-05-07 22:24:25 +0000361 for j in range(height):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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."""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000374 print(self.formatyear(theyear, w, l, c, m))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000375
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))
Guido van Rossum805365e2007-05-07 22:24:25 +0000453 for i in range(January, January+12, width):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000454 # months in this row
Guido van Rossum805365e2007-05-07 22:24:25 +0000455 months = range(i, min(i+width, 13))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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)
Thomas Wouters47b49bf2007-08-30 22:15:33 +0000480 a('<title>Calendar for %d</title>\n' % theyear)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000481 a('</head>\n')
482 a('<body>\n')
483 a(self.formatyear(theyear, width))
484 a('</body>\n')
485 a('</html>\n')
486 return ''.join(v).encode(encoding, "xmlcharrefreplace")
487
488
Georg Brandlfd680872008-06-08 08:40:05 +0000489class different_locale:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000490 def __init__(self, locale):
491 self.locale = locale
492
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000493 def __enter__(self):
Georg Brandl7004bd12010-10-19 18:54:25 +0000494 self.oldlocale = _locale.getlocale(_locale.LC_TIME)
495 _locale.setlocale(_locale.LC_TIME, self.locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000496
497 def __exit__(self, *args):
Christian Heimes96f31632007-11-12 01:32:03 +0000498 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000499
500
501class LocaleTextCalendar(TextCalendar):
502 """
503 This class can be passed a locale name in the constructor and will return
504 month and weekday names in the specified locale. If this locale includes
505 an encoding all strings containing month and weekday names will be returned
506 as unicode.
507 """
508
509 def __init__(self, firstweekday=0, locale=None):
510 TextCalendar.__init__(self, firstweekday)
511 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000512 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000513 self.locale = locale
514
515 def formatweekday(self, day, width):
Georg Brandlfd680872008-06-08 08:40:05 +0000516 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000517 if width >= 9:
518 names = day_name
519 else:
520 names = day_abbr
521 name = names[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000522 return name[:width].center(width)
523
524 def formatmonthname(self, theyear, themonth, width, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000525 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000526 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000527 if withyear:
528 s = "%s %r" % (s, theyear)
529 return s.center(width)
530
531
532class LocaleHTMLCalendar(HTMLCalendar):
533 """
534 This class can be passed a locale name in the constructor and will return
535 month and weekday names in the specified locale. If this locale includes
536 an encoding all strings containing month and weekday names will be returned
537 as unicode.
538 """
539 def __init__(self, firstweekday=0, locale=None):
540 HTMLCalendar.__init__(self, firstweekday)
541 if locale is None:
Christian Heimes96f31632007-11-12 01:32:03 +0000542 locale = _locale.getdefaultlocale()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000543 self.locale = locale
544
545 def formatweekday(self, day):
Georg Brandlfd680872008-06-08 08:40:05 +0000546 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000547 s = day_abbr[day]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000548 return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
549
550 def formatmonthname(self, theyear, themonth, withyear=True):
Georg Brandlfd680872008-06-08 08:40:05 +0000551 with different_locale(self.locale):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000552 s = month_name[themonth]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000553 if withyear:
554 s = '%s %s' % (s, theyear)
555 return '<tr><th colspan="7" class="month">%s</th></tr>' % s
556
557
558# Support for old module level interface
559c = TextCalendar()
560
561firstweekday = c.getfirstweekday
562
563def setfirstweekday(firstweekday):
564 if not MONDAY <= firstweekday <= SUNDAY:
565 raise IllegalWeekdayError(firstweekday)
566 c.firstweekday = firstweekday
567
568monthcalendar = c.monthdayscalendar
569prweek = c.prweek
570week = c.formatweek
571weekheader = c.formatweekheader
572prmonth = c.prmonth
573month = c.formatmonth
574calendar = c.formatyear
575prcal = c.pryear
576
577
578# Spacing of month columns for multi-column year calendar
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000579_colwidth = 7*3 - 1 # Amount printed by prweek()
Skip Montanaroad3bc442000-08-30 14:01:28 +0000580_spacing = 6 # Number of spaces between columns
Guido van Rossumc6360141990-10-13 19:23:40 +0000581
Guido van Rossumc6360141990-10-13 19:23:40 +0000582
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000583def format(cols, colwidth=_colwidth, spacing=_spacing):
584 """Prints multi-column formatting for year calendars"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000585 print(formatstring(cols, colwidth, spacing))
Skip Montanaroad3bc442000-08-30 14:01:28 +0000586
Skip Montanaroad3bc442000-08-30 14:01:28 +0000587
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000588def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
589 """Returns a string formatted from n strings, centered within n columns."""
590 spacing *= ' '
591 return spacing.join(c.center(colwidth) for c in cols)
592
Guido van Rossumb39aff81999-06-09 15:07:38 +0000593
Guido van Rossumb39aff81999-06-09 15:07:38 +0000594EPOCH = 1970
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000595_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
596
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000597
Guido van Rossumb39aff81999-06-09 15:07:38 +0000598def timegm(tuple):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000599 """Unrelated but handy function to calculate Unix timestamp from GMT."""
Alexander Belopolsky97958cf2010-06-14 18:33:19 +0000600 year, month, day, hour, minute, second = tuple[:6]
601 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
602 hours = days*24 + hour
603 minutes = hours*60 + minute
604 seconds = minutes*60 + second
605 return seconds
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000606
607
608def main(args):
Serhiy Storchaka97852612015-11-01 17:14:27 +0200609 import argparse
610 parser = argparse.ArgumentParser()
611 textgroup = parser.add_argument_group('text only arguments')
612 htmlgroup = parser.add_argument_group('html only arguments')
613 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000614 "-w", "--width",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200615 type=int, default=2,
616 help="width of date column (default 2)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000617 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200618 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000619 "-l", "--lines",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200620 type=int, default=1,
621 help="number of lines for each week (default 1)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000622 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200623 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000624 "-s", "--spacing",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200625 type=int, default=6,
626 help="spacing between months (default 6)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000627 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200628 textgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000629 "-m", "--months",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200630 type=int, default=3,
631 help="months per row (default 3)"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000632 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200633 htmlgroup.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000634 "-c", "--css",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200635 default="calendar.css",
636 help="CSS to use for page"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000637 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200638 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000639 "-L", "--locale",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200640 default=None,
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000641 help="locale to be used from month and weekday names"
642 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200643 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000644 "-e", "--encoding",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200645 default=None,
646 help="encoding to use for output"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000647 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200648 parser.add_argument(
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000649 "-t", "--type",
Serhiy Storchaka97852612015-11-01 17:14:27 +0200650 default="text",
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000651 choices=("text", "html"),
652 help="output type (text or html)"
653 )
Serhiy Storchaka97852612015-11-01 17:14:27 +0200654 parser.add_argument(
655 "year",
656 nargs='?', type=int,
657 help="year number (1-9999)"
658 )
659 parser.add_argument(
660 "month",
661 nargs='?', type=int,
662 help="month number (1-12, text only)"
663 )
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000664
Serhiy Storchaka97852612015-11-01 17:14:27 +0200665 options = parser.parse_args(args[1:])
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000666
667 if options.locale and not options.encoding:
668 parser.error("if --locale is specified --encoding is required")
669 sys.exit(1)
670
Christian Heimes96f31632007-11-12 01:32:03 +0000671 locale = options.locale, options.encoding
672
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000673 if options.type == "html":
674 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000675 cal = LocaleHTMLCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000676 else:
677 cal = HTMLCalendar()
678 encoding = options.encoding
679 if encoding is None:
680 encoding = sys.getdefaultencoding()
681 optdict = dict(encoding=encoding, css=options.css)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800682 write = sys.stdout.buffer.write
Serhiy Storchaka97852612015-11-01 17:14:27 +0200683 if options.year is None:
Senthil Kumaran962fed92011-08-11 09:22:52 +0800684 write(cal.formatyearpage(datetime.date.today().year, **optdict))
Serhiy Storchaka97852612015-11-01 17:14:27 +0200685 elif options.month is None:
686 write(cal.formatyearpage(options.year, **optdict))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000687 else:
688 parser.error("incorrect number of arguments")
689 sys.exit(1)
690 else:
691 if options.locale:
Christian Heimes96f31632007-11-12 01:32:03 +0000692 cal = LocaleTextCalendar(locale=locale)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000693 else:
694 cal = TextCalendar()
695 optdict = dict(w=options.width, l=options.lines)
Serhiy Storchaka97852612015-11-01 17:14:27 +0200696 if options.month is None:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000697 optdict["c"] = options.spacing
698 optdict["m"] = options.months
Serhiy Storchaka97852612015-11-01 17:14:27 +0200699 if options.year is None:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000700 result = cal.formatyear(datetime.date.today().year, **optdict)
Serhiy Storchaka97852612015-11-01 17:14:27 +0200701 elif options.month is None:
702 result = cal.formatyear(options.year, **optdict)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000703 else:
Serhiy Storchaka97852612015-11-01 17:14:27 +0200704 result = cal.formatmonth(options.year, options.month, **optdict)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800705 write = sys.stdout.write
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000706 if options.encoding:
707 result = result.encode(options.encoding)
Senthil Kumaran962fed92011-08-11 09:22:52 +0800708 write = sys.stdout.buffer.write
709 write(result)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000710
711
712if __name__ == "__main__":
713 main(sys.argv)