1 """Calendar printing functions
2
3 Note when comparing these calendars to the ones printed by cal(1): By
4 default, these calendars have Monday as the first day of the week, and
5 Sunday as the last (the European convention). Use setfirstweekday() to
6 set the first day of the week (0=Monday, 6=Sunday)."""
7
8 import sys
9 import datetime
10 from enum import IntEnum, global_enum
11 import locale as _locale
12 from itertools import repeat
13 import warnings
14
15 __all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
16 "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
17 "monthcalendar", "prmonth", "month", "prcal", "calendar",
18 "timegm", "month_name", "month_abbr", "day_name", "day_abbr",
19 "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
20 "LocaleHTMLCalendar", "weekheader",
21 "Day", "Month", "JANUARY", "FEBRUARY", "MARCH",
22 "APRIL", "MAY", "JUNE", "JULY",
23 "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
24 "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY",
25 "SATURDAY", "SUNDAY"]
26
27 # Exception raised for bad input (with string parameter for details)
28 error = ValueError
29
30 # Exceptions raised for bad input
31 class ESC[4;38;5;81mIllegalMonthError(ESC[4;38;5;149mValueError):
32 def __init__(self, month):
33 self.month = month
34 def __str__(self):
35 return "bad month number %r; must be 1-12" % self.month
36
37
38 class ESC[4;38;5;81mIllegalWeekdayError(ESC[4;38;5;149mValueError):
39 def __init__(self, weekday):
40 self.weekday = weekday
41 def __str__(self):
42 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
43
44
45 def __getattr__(name):
46 if name in ('January', 'February'):
47 warnings.warn(f"The '{name}' attribute is deprecated, use '{name.upper()}' instead",
48 DeprecationWarning, stacklevel=2)
49 if name == 'January':
50 return 1
51 else:
52 return 2
53
54 raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
55
56
57 # Constants for months
58 @global_enum
59 class ESC[4;38;5;81mMonth(ESC[4;38;5;149mIntEnum):
60 JANUARY = 1
61 FEBRUARY = 2
62 MARCH = 3
63 APRIL = 4
64 MAY = 5
65 JUNE = 6
66 JULY = 7
67 AUGUST = 8
68 SEPTEMBER = 9
69 OCTOBER = 10
70 NOVEMBER = 11
71 DECEMBER = 12
72
73
74 # Constants for days
75 @global_enum
76 class ESC[4;38;5;81mDay(ESC[4;38;5;149mIntEnum):
77 MONDAY = 0
78 TUESDAY = 1
79 WEDNESDAY = 2
80 THURSDAY = 3
81 FRIDAY = 4
82 SATURDAY = 5
83 SUNDAY = 6
84
85
86 # Number of days per month (except for February in leap years)
87 mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
88
89 # This module used to have hard-coded lists of day and month names, as
90 # English strings. The classes following emulate a read-only version of
91 # that, but supply localized names. Note that the values are computed
92 # fresh on each call, in case the user changes locale between calls.
93
94 class ESC[4;38;5;81m_localized_month:
95
96 _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
97 _months.insert(0, lambda x: "")
98
99 def __init__(self, format):
100 self.format = format
101
102 def __getitem__(self, i):
103 funcs = self._months[i]
104 if isinstance(i, slice):
105 return [f(self.format) for f in funcs]
106 else:
107 return funcs(self.format)
108
109 def __len__(self):
110 return 13
111
112
113 class ESC[4;38;5;81m_localized_day:
114
115 # January 1, 2001, was a Monday.
116 _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
117
118 def __init__(self, format):
119 self.format = format
120
121 def __getitem__(self, i):
122 funcs = self._days[i]
123 if isinstance(i, slice):
124 return [f(self.format) for f in funcs]
125 else:
126 return funcs(self.format)
127
128 def __len__(self):
129 return 7
130
131
132 # Full and abbreviated names of weekdays
133 day_name = _localized_day('%A')
134 day_abbr = _localized_day('%a')
135
136 # Full and abbreviated names of months (1-based arrays!!!)
137 month_name = _localized_month('%B')
138 month_abbr = _localized_month('%b')
139
140
141 def isleap(year):
142 """Return True for leap years, False for non-leap years."""
143 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
144
145
146 def leapdays(y1, y2):
147 """Return number of leap years in range [y1, y2).
148 Assume y1 <= y2."""
149 y1 -= 1
150 y2 -= 1
151 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
152
153
154 def weekday(year, month, day):
155 """Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31)."""
156 if not datetime.MINYEAR <= year <= datetime.MAXYEAR:
157 year = 2000 + year % 400
158 return Day(datetime.date(year, month, day).weekday())
159
160
161 def monthrange(year, month):
162 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
163 year, month."""
164 if not 1 <= month <= 12:
165 raise IllegalMonthError(month)
166 day1 = weekday(year, month, 1)
167 ndays = mdays[month] + (month == FEBRUARY and isleap(year))
168 return day1, ndays
169
170
171 def _monthlen(year, month):
172 return mdays[month] + (month == FEBRUARY and isleap(year))
173
174
175 def _prevmonth(year, month):
176 if month == 1:
177 return year-1, 12
178 else:
179 return year, month-1
180
181
182 def _nextmonth(year, month):
183 if month == 12:
184 return year+1, 1
185 else:
186 return year, month+1
187
188
189 class ESC[4;38;5;81mCalendar(ESC[4;38;5;149mobject):
190 """
191 Base calendar class. This class doesn't do any formatting. It simply
192 provides data to subclasses.
193 """
194
195 def __init__(self, firstweekday=0):
196 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
197
198 def getfirstweekday(self):
199 return self._firstweekday % 7
200
201 def setfirstweekday(self, firstweekday):
202 self._firstweekday = firstweekday
203
204 firstweekday = property(getfirstweekday, setfirstweekday)
205
206 def iterweekdays(self):
207 """
208 Return an iterator for one week of weekday numbers starting with the
209 configured first one.
210 """
211 for i in range(self.firstweekday, self.firstweekday + 7):
212 yield i%7
213
214 def itermonthdates(self, year, month):
215 """
216 Return an iterator for one month. The iterator will yield datetime.date
217 values and will always iterate through complete weeks, so it will yield
218 dates outside the specified month.
219 """
220 for y, m, d in self.itermonthdays3(year, month):
221 yield datetime.date(y, m, d)
222
223 def itermonthdays(self, year, month):
224 """
225 Like itermonthdates(), but will yield day numbers. For days outside
226 the specified month the day number is 0.
227 """
228 day1, ndays = monthrange(year, month)
229 days_before = (day1 - self.firstweekday) % 7
230 yield from repeat(0, days_before)
231 yield from range(1, ndays + 1)
232 days_after = (self.firstweekday - day1 - ndays) % 7
233 yield from repeat(0, days_after)
234
235 def itermonthdays2(self, year, month):
236 """
237 Like itermonthdates(), but will yield (day number, weekday number)
238 tuples. For days outside the specified month the day number is 0.
239 """
240 for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
241 yield d, i % 7
242
243 def itermonthdays3(self, year, month):
244 """
245 Like itermonthdates(), but will yield (year, month, day) tuples. Can be
246 used for dates outside of datetime.date range.
247 """
248 day1, ndays = monthrange(year, month)
249 days_before = (day1 - self.firstweekday) % 7
250 days_after = (self.firstweekday - day1 - ndays) % 7
251 y, m = _prevmonth(year, month)
252 end = _monthlen(y, m) + 1
253 for d in range(end-days_before, end):
254 yield y, m, d
255 for d in range(1, ndays + 1):
256 yield year, month, d
257 y, m = _nextmonth(year, month)
258 for d in range(1, days_after + 1):
259 yield y, m, d
260
261 def itermonthdays4(self, year, month):
262 """
263 Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.
264 Can be used for dates outside of datetime.date range.
265 """
266 for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)):
267 yield y, m, d, (self.firstweekday + i) % 7
268
269 def monthdatescalendar(self, year, month):
270 """
271 Return a matrix (list of lists) representing a month's calendar.
272 Each row represents a week; week entries are datetime.date values.
273 """
274 dates = list(self.itermonthdates(year, month))
275 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
276
277 def monthdays2calendar(self, year, month):
278 """
279 Return a matrix representing a month's calendar.
280 Each row represents a week; week entries are
281 (day number, weekday number) tuples. Day numbers outside this month
282 are zero.
283 """
284 days = list(self.itermonthdays2(year, month))
285 return [ days[i:i+7] for i in range(0, len(days), 7) ]
286
287 def monthdayscalendar(self, year, month):
288 """
289 Return a matrix representing a month's calendar.
290 Each row represents a week; days outside this month are zero.
291 """
292 days = list(self.itermonthdays(year, month))
293 return [ days[i:i+7] for i in range(0, len(days), 7) ]
294
295 def yeardatescalendar(self, year, width=3):
296 """
297 Return the data for the specified year ready for formatting. The return
298 value is a list of month rows. Each month row contains up to width months.
299 Each month contains between 4 and 6 weeks and each week contains 1-7
300 days. Days are datetime.date objects.
301 """
302 months = [self.monthdatescalendar(year, m) for m in Month]
303 return [months[i:i+width] for i in range(0, len(months), width) ]
304
305 def yeardays2calendar(self, year, width=3):
306 """
307 Return the data for the specified year ready for formatting (similar to
308 yeardatescalendar()). Entries in the week lists are
309 (day number, weekday number) tuples. Day numbers outside this month are
310 zero.
311 """
312 months = [self.monthdays2calendar(year, m) for m in Month]
313 return [months[i:i+width] for i in range(0, len(months), width) ]
314
315 def yeardayscalendar(self, year, width=3):
316 """
317 Return the data for the specified year ready for formatting (similar to
318 yeardatescalendar()). Entries in the week lists are day numbers.
319 Day numbers outside this month are zero.
320 """
321 months = [self.monthdayscalendar(year, m) for m in Month]
322 return [months[i:i+width] for i in range(0, len(months), width) ]
323
324
325 class ESC[4;38;5;81mTextCalendar(ESC[4;38;5;149mCalendar):
326 """
327 Subclass of Calendar that outputs a calendar as a simple plain text
328 similar to the UNIX program cal.
329 """
330
331 def prweek(self, theweek, width):
332 """
333 Print a single week (no newline).
334 """
335 print(self.formatweek(theweek, width), end='')
336
337 def formatday(self, day, weekday, width):
338 """
339 Returns a formatted day.
340 """
341 if day == 0:
342 s = ''
343 else:
344 s = '%2i' % day # right-align single-digit days
345 return s.center(width)
346
347 def formatweek(self, theweek, width):
348 """
349 Returns a single week in a string (no newline).
350 """
351 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
352
353 def formatweekday(self, day, width):
354 """
355 Returns a formatted week day name.
356 """
357 if width >= 9:
358 names = day_name
359 else:
360 names = day_abbr
361 return names[day][:width].center(width)
362
363 def formatweekheader(self, width):
364 """
365 Return a header for a week.
366 """
367 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
368
369 def formatmonthname(self, theyear, themonth, width, withyear=True):
370 """
371 Return a formatted month name.
372 """
373 s = month_name[themonth]
374 if withyear:
375 s = "%s %r" % (s, theyear)
376 return s.center(width)
377
378 def prmonth(self, theyear, themonth, w=0, l=0):
379 """
380 Print a month's calendar.
381 """
382 print(self.formatmonth(theyear, themonth, w, l), end='')
383
384 def formatmonth(self, theyear, themonth, w=0, l=0):
385 """
386 Return a month's calendar string (multi-line).
387 """
388 w = max(2, w)
389 l = max(1, l)
390 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
391 s = s.rstrip()
392 s += '\n' * l
393 s += self.formatweekheader(w).rstrip()
394 s += '\n' * l
395 for week in self.monthdays2calendar(theyear, themonth):
396 s += self.formatweek(week, w).rstrip()
397 s += '\n' * l
398 return s
399
400 def formatyear(self, theyear, w=2, l=1, c=6, m=3):
401 """
402 Returns a year's calendar as a multi-line string.
403 """
404 w = max(2, w)
405 l = max(1, l)
406 c = max(2, c)
407 colwidth = (w + 1) * 7 - 1
408 v = []
409 a = v.append
410 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
411 a('\n'*l)
412 header = self.formatweekheader(w)
413 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
414 # months in this row
415 months = range(m*i+1, min(m*(i+1)+1, 13))
416 a('\n'*l)
417 names = (self.formatmonthname(theyear, k, colwidth, False)
418 for k in months)
419 a(formatstring(names, colwidth, c).rstrip())
420 a('\n'*l)
421 headers = (header for k in months)
422 a(formatstring(headers, colwidth, c).rstrip())
423 a('\n'*l)
424 # max number of weeks for this row
425 height = max(len(cal) for cal in row)
426 for j in range(height):
427 weeks = []
428 for cal in row:
429 if j >= len(cal):
430 weeks.append('')
431 else:
432 weeks.append(self.formatweek(cal[j], w))
433 a(formatstring(weeks, colwidth, c).rstrip())
434 a('\n' * l)
435 return ''.join(v)
436
437 def pryear(self, theyear, w=0, l=0, c=6, m=3):
438 """Print a year's calendar."""
439 print(self.formatyear(theyear, w, l, c, m), end='')
440
441
442 class ESC[4;38;5;81mHTMLCalendar(ESC[4;38;5;149mCalendar):
443 """
444 This calendar returns complete HTML pages.
445 """
446
447 # CSS classes for the day <td>s
448 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
449
450 # CSS classes for the day <th>s
451 cssclasses_weekday_head = cssclasses
452
453 # CSS class for the days before and after current month
454 cssclass_noday = "noday"
455
456 # CSS class for the month's head
457 cssclass_month_head = "month"
458
459 # CSS class for the month
460 cssclass_month = "month"
461
462 # CSS class for the year's table head
463 cssclass_year_head = "year"
464
465 # CSS class for the whole year table
466 cssclass_year = "year"
467
468 def formatday(self, day, weekday):
469 """
470 Return a day as a table cell.
471 """
472 if day == 0:
473 # day outside month
474 return '<td class="%s"> </td>' % self.cssclass_noday
475 else:
476 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
477
478 def formatweek(self, theweek):
479 """
480 Return a complete week as a table row.
481 """
482 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
483 return '<tr>%s</tr>' % s
484
485 def formatweekday(self, day):
486 """
487 Return a weekday name as a table header.
488 """
489 return '<th class="%s">%s</th>' % (
490 self.cssclasses_weekday_head[day], day_abbr[day])
491
492 def formatweekheader(self):
493 """
494 Return a header for a week as a table row.
495 """
496 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
497 return '<tr>%s</tr>' % s
498
499 def formatmonthname(self, theyear, themonth, withyear=True):
500 """
501 Return a month name as a table row.
502 """
503 if withyear:
504 s = '%s %s' % (month_name[themonth], theyear)
505 else:
506 s = '%s' % month_name[themonth]
507 return '<tr><th colspan="7" class="%s">%s</th></tr>' % (
508 self.cssclass_month_head, s)
509
510 def formatmonth(self, theyear, themonth, withyear=True):
511 """
512 Return a formatted month as a table.
513 """
514 v = []
515 a = v.append
516 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % (
517 self.cssclass_month))
518 a('\n')
519 a(self.formatmonthname(theyear, themonth, withyear=withyear))
520 a('\n')
521 a(self.formatweekheader())
522 a('\n')
523 for week in self.monthdays2calendar(theyear, themonth):
524 a(self.formatweek(week))
525 a('\n')
526 a('</table>')
527 a('\n')
528 return ''.join(v)
529
530 def formatyear(self, theyear, width=3):
531 """
532 Return a formatted year as a table of tables.
533 """
534 v = []
535 a = v.append
536 width = max(width, 1)
537 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' %
538 self.cssclass_year)
539 a('\n')
540 a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
541 width, self.cssclass_year_head, theyear))
542 for i in range(JANUARY, JANUARY+12, width):
543 # months in this row
544 months = range(i, min(i+width, 13))
545 a('<tr>')
546 for m in months:
547 a('<td>')
548 a(self.formatmonth(theyear, m, withyear=False))
549 a('</td>')
550 a('</tr>')
551 a('</table>')
552 return ''.join(v)
553
554 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
555 """
556 Return a formatted year as a complete HTML page.
557 """
558 if encoding is None:
559 encoding = sys.getdefaultencoding()
560 v = []
561 a = v.append
562 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
563 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
564 a('<html>\n')
565 a('<head>\n')
566 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
567 if css is not None:
568 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
569 a('<title>Calendar for %d</title>\n' % theyear)
570 a('</head>\n')
571 a('<body>\n')
572 a(self.formatyear(theyear, width))
573 a('</body>\n')
574 a('</html>\n')
575 return ''.join(v).encode(encoding, "xmlcharrefreplace")
576
577
578 class ESC[4;38;5;81mdifferent_locale:
579 def __init__(self, locale):
580 self.locale = locale
581 self.oldlocale = None
582
583 def __enter__(self):
584 self.oldlocale = _locale.setlocale(_locale.LC_TIME, None)
585 _locale.setlocale(_locale.LC_TIME, self.locale)
586
587 def __exit__(self, *args):
588 if self.oldlocale is None:
589 return
590 _locale.setlocale(_locale.LC_TIME, self.oldlocale)
591
592
593 def _get_default_locale():
594 locale = _locale.setlocale(_locale.LC_TIME, None)
595 if locale == "C":
596 with different_locale(""):
597 # The LC_TIME locale does not seem to be configured:
598 # get the user preferred locale.
599 locale = _locale.setlocale(_locale.LC_TIME, None)
600 return locale
601
602
603 class ESC[4;38;5;81mLocaleTextCalendar(ESC[4;38;5;149mTextCalendar):
604 """
605 This class can be passed a locale name in the constructor and will return
606 month and weekday names in the specified locale.
607 """
608
609 def __init__(self, firstweekday=0, locale=None):
610 TextCalendar.__init__(self, firstweekday)
611 if locale is None:
612 locale = _get_default_locale()
613 self.locale = locale
614
615 def formatweekday(self, day, width):
616 with different_locale(self.locale):
617 return super().formatweekday(day, width)
618
619 def formatmonthname(self, theyear, themonth, width, withyear=True):
620 with different_locale(self.locale):
621 return super().formatmonthname(theyear, themonth, width, withyear)
622
623
624 class ESC[4;38;5;81mLocaleHTMLCalendar(ESC[4;38;5;149mHTMLCalendar):
625 """
626 This class can be passed a locale name in the constructor and will return
627 month and weekday names in the specified locale.
628 """
629 def __init__(self, firstweekday=0, locale=None):
630 HTMLCalendar.__init__(self, firstweekday)
631 if locale is None:
632 locale = _get_default_locale()
633 self.locale = locale
634
635 def formatweekday(self, day):
636 with different_locale(self.locale):
637 return super().formatweekday(day)
638
639 def formatmonthname(self, theyear, themonth, withyear=True):
640 with different_locale(self.locale):
641 return super().formatmonthname(theyear, themonth, withyear)
642
643 # Support for old module level interface
644 c = TextCalendar()
645
646 firstweekday = c.getfirstweekday
647
648 def setfirstweekday(firstweekday):
649 if not MONDAY <= firstweekday <= SUNDAY:
650 raise IllegalWeekdayError(firstweekday)
651 c.firstweekday = firstweekday
652
653 monthcalendar = c.monthdayscalendar
654 prweek = c.prweek
655 week = c.formatweek
656 weekheader = c.formatweekheader
657 prmonth = c.prmonth
658 month = c.formatmonth
659 calendar = c.formatyear
660 prcal = c.pryear
661
662
663 # Spacing of month columns for multi-column year calendar
664 _colwidth = 7*3 - 1 # Amount printed by prweek()
665 _spacing = 6 # Number of spaces between columns
666
667
668 def format(cols, colwidth=_colwidth, spacing=_spacing):
669 """Prints multi-column formatting for year calendars"""
670 print(formatstring(cols, colwidth, spacing))
671
672
673 def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
674 """Returns a string formatted from n strings, centered within n columns."""
675 spacing *= ' '
676 return spacing.join(c.center(colwidth) for c in cols)
677
678
679 EPOCH = 1970
680 _EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
681
682
683 def timegm(tuple):
684 """Unrelated but handy function to calculate Unix timestamp from GMT."""
685 year, month, day, hour, minute, second = tuple[:6]
686 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
687 hours = days*24 + hour
688 minutes = hours*60 + minute
689 seconds = minutes*60 + second
690 return seconds
691
692
693 def main(args):
694 import argparse
695 parser = argparse.ArgumentParser()
696 textgroup = parser.add_argument_group('text only arguments')
697 htmlgroup = parser.add_argument_group('html only arguments')
698 textgroup.add_argument(
699 "-w", "--width",
700 type=int, default=2,
701 help="width of date column (default 2)"
702 )
703 textgroup.add_argument(
704 "-l", "--lines",
705 type=int, default=1,
706 help="number of lines for each week (default 1)"
707 )
708 textgroup.add_argument(
709 "-s", "--spacing",
710 type=int, default=6,
711 help="spacing between months (default 6)"
712 )
713 textgroup.add_argument(
714 "-m", "--months",
715 type=int, default=3,
716 help="months per row (default 3)"
717 )
718 htmlgroup.add_argument(
719 "-c", "--css",
720 default="calendar.css",
721 help="CSS to use for page"
722 )
723 parser.add_argument(
724 "-L", "--locale",
725 default=None,
726 help="locale to use for month and weekday names"
727 )
728 parser.add_argument(
729 "-e", "--encoding",
730 default=None,
731 help="encoding to use for output"
732 )
733 parser.add_argument(
734 "-t", "--type",
735 default="text",
736 choices=("text", "html"),
737 help="output type (text or html)"
738 )
739 parser.add_argument(
740 "year",
741 nargs='?', type=int,
742 help="year number (1-9999)"
743 )
744 parser.add_argument(
745 "month",
746 nargs='?', type=int,
747 help="month number (1-12, text only)"
748 )
749
750 options = parser.parse_args(args[1:])
751
752 if options.locale and not options.encoding:
753 parser.error("if --locale is specified --encoding is required")
754 sys.exit(1)
755
756 locale = options.locale, options.encoding
757
758 if options.type == "html":
759 if options.locale:
760 cal = LocaleHTMLCalendar(locale=locale)
761 else:
762 cal = HTMLCalendar()
763 encoding = options.encoding
764 if encoding is None:
765 encoding = sys.getdefaultencoding()
766 optdict = dict(encoding=encoding, css=options.css)
767 write = sys.stdout.buffer.write
768 if options.year is None:
769 write(cal.formatyearpage(datetime.date.today().year, **optdict))
770 elif options.month is None:
771 write(cal.formatyearpage(options.year, **optdict))
772 else:
773 parser.error("incorrect number of arguments")
774 sys.exit(1)
775 else:
776 if options.locale:
777 cal = LocaleTextCalendar(locale=locale)
778 else:
779 cal = TextCalendar()
780 optdict = dict(w=options.width, l=options.lines)
781 if options.month is None:
782 optdict["c"] = options.spacing
783 optdict["m"] = options.months
784 if options.year is None:
785 result = cal.formatyear(datetime.date.today().year, **optdict)
786 elif options.month is None:
787 result = cal.formatyear(options.year, **optdict)
788 else:
789 result = cal.formatmonth(options.year, options.month, **optdict)
790 write = sys.stdout.write
791 if options.encoding:
792 result = result.encode(options.encoding)
793 write = sys.stdout.buffer.write
794 write(result)
795
796
797 if __name__ == "__main__":
798 main(sys.argv)