Home | History | Annotate | Download | only in Lib
      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 import locale as _locale
     11 from itertools import repeat
     12 
     13 __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",
     17            "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
     18            "LocaleHTMLCalendar", "weekheader"]
     19 
     20 # Exception raised for bad input (with string parameter for details)
     21 error = ValueError
     22 
     23 # Exceptions raised for bad input
     24 class 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 
     31 class 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 
     38 # Constants for months referenced later
     39 January = 1
     40 February = 2
     41 
     42 # Number of days per month (except for February in leap years)
     43 mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
     44 
     45 # 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 
     50 class _localized_month:
     51 
     52     _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
     53     _months.insert(0, lambda x: "")
     54 
     55     def __init__(self, format):
     56         self.format = format
     57 
     58     def __getitem__(self, i):
     59         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)
     64 
     65     def __len__(self):
     66         return 13
     67 
     68 
     69 class _localized_day:
     70 
     71     # January 1, 2001, was a Monday.
     72     _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
     73 
     74     def __init__(self, format):
     75         self.format = format
     76 
     77     def __getitem__(self, i):
     78         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)
     83 
     84     def __len__(self):
     85         return 7
     86 
     87 
     88 # Full and abbreviated names of weekdays
     89 day_name = _localized_day('%A')
     90 day_abbr = _localized_day('%a')
     91 
     92 # Full and abbreviated names of months (1-based arrays!!!)
     93 month_name = _localized_month('%B')
     94 month_abbr = _localized_month('%b')
     95 
     96 # Constants for weekdays
     97 (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
     98 
     99 
    100 def isleap(year):
    101     """Return True for leap years, False for non-leap years."""
    102     return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
    103 
    104 
    105 def leapdays(y1, y2):
    106     """Return number of leap years in range [y1, y2).
    107        Assume y1 <= y2."""
    108     y1 -= 1
    109     y2 -= 1
    110     return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
    111 
    112 
    113 def weekday(year, month, day):
    114     """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
    115        day (1-31)."""
    116     return datetime.date(year, month, day).weekday()
    117 
    118 
    119 def monthrange(year, month):
    120     """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
    121        year, month."""
    122     if not 1 <= month <= 12:
    123         raise IllegalMonthError(month)
    124     day1 = weekday(year, month, 1)
    125     ndays = mdays[month] + (month == February and isleap(year))
    126     return day1, ndays
    127 
    128 
    129 class Calendar(object):
    130     """
    131     Base calendar class. This class doesn't do any formatting. It simply
    132     provides data to subclasses.
    133     """
    134 
    135     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         """
    148         Return an iterator for one week of weekday numbers starting with the
    149         configured first one.
    150         """
    151         for i in range(self.firstweekday, self.firstweekday + 7):
    152             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
    167             try:
    168                 date += oneday
    169             except OverflowError:
    170                 # Adding one day could fail after datetime.MAXYEAR
    171                 break
    172             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         """
    180         for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
    181             yield d, i % 7
    182 
    183     def itermonthdays(self, year, month):
    184         """
    185         Like itermonthdates(), but will yield day numbers. For days outside
    186         the specified month the day number is 0.
    187         """
    188         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)
    194 
    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))
    201         return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
    202 
    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))
    211         return [ days[i:i+7] for i in range(0, len(days), 7) ]
    212 
    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))
    219         return [ days[i:i+7] for i in range(0, len(days), 7) ]
    220 
    221     def yeardatescalendar(self, year, width=3):
    222         """
    223         Return the data for the specified year ready for formatting. The return
    224         value is a list of month rows. Each month row contains up to width months.
    225         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)
    230             for i in range(January, January+12)
    231         ]
    232         return [months[i:i+width] for i in range(0, len(months), width) ]
    233 
    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)
    243             for i in range(January, January+12)
    244         ]
    245         return [months[i:i+width] for i in range(0, len(months), width) ]
    246 
    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)
    255             for i in range(January, January+12)
    256         ]
    257         return [months[i:i+width] for i in range(0, len(months), width) ]
    258 
    259 
    260 class 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         """
    270         print(self.formatweek(theweek, width), end=' ')
    271 
    272     def formatday(self, day, weekday, width):
    273         """
    274         Returns a formatted day.
    275         """
    276         if day == 0:
    277             s = ''
    278         else:
    279             s = '%2i' % day             # right-align single-digit days
    280         return s.center(width)
    281 
    282     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)
    287 
    288     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)
    297 
    298     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())
    303 
    304     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         """
    317         print(self.formatmonth(theyear, themonth, w, l), end='')
    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
    350             months = range(m*i+1, min(m*(i+1)+1, 13))
    351             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)
    361             for j in range(height):
    362                 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 
    377 class 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))
    453         for i in range(January, January+12, width):
    454             # months in this row
    455             months = range(i, min(i+width, 13))
    456             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)
    480         a('<title>Calendar for %d</title>\n' % theyear)
    481         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 
    489 class different_locale:
    490     def __init__(self, locale):
    491         self.locale = locale
    492 
    493     def __enter__(self):
    494         self.oldlocale = _locale.getlocale(_locale.LC_TIME)
    495         _locale.setlocale(_locale.LC_TIME, self.locale)
    496 
    497     def __exit__(self, *args):
    498         _locale.setlocale(_locale.LC_TIME, self.oldlocale)
    499 
    500 
    501 class 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:
    512             locale = _locale.getdefaultlocale()
    513         self.locale = locale
    514 
    515     def formatweekday(self, day, width):
    516         with different_locale(self.locale):
    517             if width >= 9:
    518                 names = day_name
    519             else:
    520                 names = day_abbr
    521             name = names[day]
    522             return name[:width].center(width)
    523 
    524     def formatmonthname(self, theyear, themonth, width, withyear=True):
    525         with different_locale(self.locale):
    526             s = month_name[themonth]
    527             if withyear:
    528                 s = "%s %r" % (s, theyear)
    529             return s.center(width)
    530 
    531 
    532 class 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:
    542             locale = _locale.getdefaultlocale()
    543         self.locale = locale
    544 
    545     def formatweekday(self, day):
    546         with different_locale(self.locale):
    547             s = day_abbr[day]
    548             return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
    549 
    550     def formatmonthname(self, theyear, themonth, withyear=True):
    551         with different_locale(self.locale):
    552             s = month_name[themonth]
    553             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
    559 c = TextCalendar()
    560 
    561 firstweekday = c.getfirstweekday
    562 
    563 def setfirstweekday(firstweekday):
    564     if not MONDAY <= firstweekday <= SUNDAY:
    565         raise IllegalWeekdayError(firstweekday)
    566     c.firstweekday = firstweekday
    567 
    568 monthcalendar = c.monthdayscalendar
    569 prweek = c.prweek
    570 week = c.formatweek
    571 weekheader = c.formatweekheader
    572 prmonth = c.prmonth
    573 month = c.formatmonth
    574 calendar = c.formatyear
    575 prcal = c.pryear
    576 
    577 
    578 # Spacing of month columns for multi-column year calendar
    579 _colwidth = 7*3 - 1         # Amount printed by prweek()
    580 _spacing = 6                # Number of spaces between columns
    581 
    582 
    583 def format(cols, colwidth=_colwidth, spacing=_spacing):
    584     """Prints multi-column formatting for year calendars"""
    585     print(formatstring(cols, colwidth, spacing))
    586 
    587 
    588 def 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 
    593 
    594 EPOCH = 1970
    595 _EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
    596 
    597 
    598 def timegm(tuple):
    599     """Unrelated but handy function to calculate Unix timestamp from GMT."""
    600     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
    606 
    607 
    608 def main(args):
    609     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(
    614         "-w", "--width",
    615         type=int, default=2,
    616         help="width of date column (default 2)"
    617     )
    618     textgroup.add_argument(
    619         "-l", "--lines",
    620         type=int, default=1,
    621         help="number of lines for each week (default 1)"
    622     )
    623     textgroup.add_argument(
    624         "-s", "--spacing",
    625         type=int, default=6,
    626         help="spacing between months (default 6)"
    627     )
    628     textgroup.add_argument(
    629         "-m", "--months",
    630         type=int, default=3,
    631         help="months per row (default 3)"
    632     )
    633     htmlgroup.add_argument(
    634         "-c", "--css",
    635         default="calendar.css",
    636         help="CSS to use for page"
    637     )
    638     parser.add_argument(
    639         "-L", "--locale",
    640         default=None,
    641         help="locale to be used from month and weekday names"
    642     )
    643     parser.add_argument(
    644         "-e", "--encoding",
    645         default=None,
    646         help="encoding to use for output"
    647     )
    648     parser.add_argument(
    649         "-t", "--type",
    650         default="text",
    651         choices=("text", "html"),
    652         help="output type (text or html)"
    653     )
    654     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     )
    664 
    665     options = parser.parse_args(args[1:])
    666 
    667     if options.locale and not options.encoding:
    668         parser.error("if --locale is specified --encoding is required")
    669         sys.exit(1)
    670 
    671     locale = options.locale, options.encoding
    672 
    673     if options.type == "html":
    674         if options.locale:
    675             cal = LocaleHTMLCalendar(locale=locale)
    676         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)
    682         write = sys.stdout.buffer.write
    683         if options.year is None:
    684             write(cal.formatyearpage(datetime.date.today().year, **optdict))
    685         elif options.month is None:
    686             write(cal.formatyearpage(options.year, **optdict))
    687         else:
    688             parser.error("incorrect number of arguments")
    689             sys.exit(1)
    690     else:
    691         if options.locale:
    692             cal = LocaleTextCalendar(locale=locale)
    693         else:
    694             cal = TextCalendar()
    695         optdict = dict(w=options.width, l=options.lines)
    696         if options.month is None:
    697             optdict["c"] = options.spacing
    698             optdict["m"] = options.months
    699         if options.year is None:
    700             result = cal.formatyear(datetime.date.today().year, **optdict)
    701         elif options.month is None:
    702             result = cal.formatyear(options.year, **optdict)
    703         else:
    704             result = cal.formatmonth(options.year, options.month, **optdict)
    705         write = sys.stdout.write
    706         if options.encoding:
    707             result = result.encode(options.encoding)
    708             write = sys.stdout.buffer.write
    709         write(result)
    710 
    711 
    712 if __name__ == "__main__":
    713     main(sys.argv)
    714