Home | History | Annotate | Download | only in python2.7
      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 
     12 __all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
     13            "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
     14            "monthcalendar", "prmonth", "month", "prcal", "calendar",
     15            "timegm", "month_name", "month_abbr", "day_name", "day_abbr"]
     16 
     17 # Exception raised for bad input (with string parameter for details)
     18 error = ValueError
     19 
     20 # Exceptions raised for bad input
     21 class IllegalMonthError(ValueError):
     22     def __init__(self, month):
     23         self.month = month
     24     def __str__(self):
     25         return "bad month number %r; must be 1-12" % self.month
     26 
     27 
     28 class IllegalWeekdayError(ValueError):
     29     def __init__(self, weekday):
     30         self.weekday = weekday
     31     def __str__(self):
     32         return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
     33 
     34 
     35 # Constants for months referenced later
     36 January = 1
     37 February = 2
     38 
     39 # Number of days per month (except for February in leap years)
     40 mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
     41 
     42 # This module used to have hard-coded lists of day and month names, as
     43 # English strings.  The classes following emulate a read-only version of
     44 # that, but supply localized names.  Note that the values are computed
     45 # fresh on each call, in case the user changes locale between calls.
     46 
     47 class _localized_month:
     48 
     49     _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
     50     _months.insert(0, lambda x: "")
     51 
     52     def __init__(self, format):
     53         self.format = format
     54 
     55     def __getitem__(self, i):
     56         funcs = self._months[i]
     57         if isinstance(i, slice):
     58             return [f(self.format) for f in funcs]
     59         else:
     60             return funcs(self.format)
     61 
     62     def __len__(self):
     63         return 13
     64 
     65 
     66 class _localized_day:
     67 
     68     # January 1, 2001, was a Monday.
     69     _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
     70 
     71     def __init__(self, format):
     72         self.format = format
     73 
     74     def __getitem__(self, i):
     75         funcs = self._days[i]
     76         if isinstance(i, slice):
     77             return [f(self.format) for f in funcs]
     78         else:
     79             return funcs(self.format)
     80 
     81     def __len__(self):
     82         return 7
     83 
     84 
     85 # Full and abbreviated names of weekdays
     86 day_name = _localized_day('%A')
     87 day_abbr = _localized_day('%a')
     88 
     89 # Full and abbreviated names of months (1-based arrays!!!)
     90 month_name = _localized_month('%B')
     91 month_abbr = _localized_month('%b')
     92 
     93 # Constants for weekdays
     94 (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
     95 
     96 
     97 def isleap(year):
     98     """Return True for leap years, False for non-leap years."""
     99     return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
    100 
    101 
    102 def leapdays(y1, y2):
    103     """Return number of leap years in range [y1, y2).
    104        Assume y1 <= y2."""
    105     y1 -= 1
    106     y2 -= 1
    107     return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
    108 
    109 
    110 def weekday(year, month, day):
    111     """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
    112        day (1-31)."""
    113     return datetime.date(year, month, day).weekday()
    114 
    115 
    116 def monthrange(year, month):
    117     """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
    118        year, month."""
    119     if not 1 <= month <= 12:
    120         raise IllegalMonthError(month)
    121     day1 = weekday(year, month, 1)
    122     ndays = mdays[month] + (month == February and isleap(year))
    123     return day1, ndays
    124 
    125 
    126 class Calendar(object):
    127     """
    128     Base calendar class. This class doesn't do any formatting. It simply
    129     provides data to subclasses.
    130     """
    131 
    132     def __init__(self, firstweekday=0):
    133         self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
    134 
    135     def getfirstweekday(self):
    136         return self._firstweekday % 7
    137 
    138     def setfirstweekday(self, firstweekday):
    139         self._firstweekday = firstweekday
    140 
    141     firstweekday = property(getfirstweekday, setfirstweekday)
    142 
    143     def iterweekdays(self):
    144         """
    145         Return a iterator for one week of weekday numbers starting with the
    146         configured first one.
    147         """
    148         for i in range(self.firstweekday, self.firstweekday + 7):
    149             yield i%7
    150 
    151     def itermonthdates(self, year, month):
    152         """
    153         Return an iterator for one month. The iterator will yield datetime.date
    154         values and will always iterate through complete weeks, so it will yield
    155         dates outside the specified month.
    156         """
    157         date = datetime.date(year, month, 1)
    158         # Go back to the beginning of the week
    159         days = (date.weekday() - self.firstweekday) % 7
    160         date -= datetime.timedelta(days=days)
    161         oneday = datetime.timedelta(days=1)
    162         while True:
    163             yield date
    164             try:
    165                 date += oneday
    166             except OverflowError:
    167                 # Adding one day could fail after datetime.MAXYEAR
    168                 break
    169             if date.month != month and date.weekday() == self.firstweekday:
    170                 break
    171 
    172     def itermonthdays2(self, year, month):
    173         """
    174         Like itermonthdates(), but will yield (day number, weekday number)
    175         tuples. For days outside the specified month the day number is 0.
    176         """
    177         for date in self.itermonthdates(year, month):
    178             if date.month != month:
    179                 yield (0, date.weekday())
    180             else:
    181                 yield (date.day, date.weekday())
    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         for date in self.itermonthdates(year, month):
    189             if date.month != month:
    190                 yield 0
    191             else:
    192                 yield date.day
    193 
    194     def monthdatescalendar(self, year, month):
    195         """
    196         Return a matrix (list of lists) representing a month's calendar.
    197         Each row represents a week; week entries are datetime.date values.
    198         """
    199         dates = list(self.itermonthdates(year, month))
    200         return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
    201 
    202     def monthdays2calendar(self, year, month):
    203         """
    204         Return a matrix representing a month's calendar.
    205         Each row represents a week; week entries are
    206         (day number, weekday number) tuples. Day numbers outside this month
    207         are zero.
    208         """
    209         days = list(self.itermonthdays2(year, month))
    210         return [ days[i:i+7] for i in range(0, len(days), 7) ]
    211 
    212     def monthdayscalendar(self, year, month):
    213         """
    214         Return a matrix representing a month's calendar.
    215         Each row represents a week; days outside this month are zero.
    216         """
    217         days = list(self.itermonthdays(year, month))
    218         return [ days[i:i+7] for i in range(0, len(days), 7) ]
    219 
    220     def yeardatescalendar(self, year, width=3):
    221         """
    222         Return the data for the specified year ready for formatting. The return
    223         value is a list of month rows. Each month row contains upto width months.
    224         Each month contains between 4 and 6 weeks and each week contains 1-7
    225         days. Days are datetime.date objects.
    226         """
    227         months = [
    228             self.monthdatescalendar(year, i)
    229             for i in range(January, January+12)
    230         ]
    231         return [months[i:i+width] for i in range(0, len(months), width) ]
    232 
    233     def yeardays2calendar(self, year, width=3):
    234         """
    235         Return the data for the specified year ready for formatting (similar to
    236         yeardatescalendar()). Entries in the week lists are
    237         (day number, weekday number) tuples. Day numbers outside this month are
    238         zero.
    239         """
    240         months = [
    241             self.monthdays2calendar(year, i)
    242             for i in range(January, January+12)
    243         ]
    244         return [months[i:i+width] for i in range(0, len(months), width) ]
    245 
    246     def yeardayscalendar(self, year, width=3):
    247         """
    248         Return the data for the specified year ready for formatting (similar to
    249         yeardatescalendar()). Entries in the week lists are day numbers.
    250         Day numbers outside this month are zero.
    251         """
    252         months = [
    253             self.monthdayscalendar(year, i)
    254             for i in range(January, January+12)
    255         ]
    256         return [months[i:i+width] for i in range(0, len(months), width) ]
    257 
    258 
    259 class TextCalendar(Calendar):
    260     """
    261     Subclass of Calendar that outputs a calendar as a simple plain text
    262     similar to the UNIX program cal.
    263     """
    264 
    265     def prweek(self, theweek, width):
    266         """
    267         Print a single week (no newline).
    268         """
    269         print self.formatweek(theweek, width),
    270 
    271     def formatday(self, day, weekday, width):
    272         """
    273         Returns a formatted day.
    274         """
    275         if day == 0:
    276             s = ''
    277         else:
    278             s = '%2i' % day             # right-align single-digit days
    279         return s.center(width)
    280 
    281     def formatweek(self, theweek, width):
    282         """
    283         Returns a single week in a string (no newline).
    284         """
    285         return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
    286 
    287     def formatweekday(self, day, width):
    288         """
    289         Returns a formatted week day name.
    290         """
    291         if width >= 9:
    292             names = day_name
    293         else:
    294             names = day_abbr
    295         return names[day][:width].center(width)
    296 
    297     def formatweekheader(self, width):
    298         """
    299         Return a header for a week.
    300         """
    301         return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
    302 
    303     def formatmonthname(self, theyear, themonth, width, withyear=True):
    304         """
    305         Return a formatted month name.
    306         """
    307         s = month_name[themonth]
    308         if withyear:
    309             s = "%s %r" % (s, theyear)
    310         return s.center(width)
    311 
    312     def prmonth(self, theyear, themonth, w=0, l=0):
    313         """
    314         Print a month's calendar.
    315         """
    316         print self.formatmonth(theyear, themonth, w, l),
    317 
    318     def formatmonth(self, theyear, themonth, w=0, l=0):
    319         """
    320         Return a month's calendar string (multi-line).
    321         """
    322         w = max(2, w)
    323         l = max(1, l)
    324         s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
    325         s = s.rstrip()
    326         s += '\n' * l
    327         s += self.formatweekheader(w).rstrip()
    328         s += '\n' * l
    329         for week in self.monthdays2calendar(theyear, themonth):
    330             s += self.formatweek(week, w).rstrip()
    331             s += '\n' * l
    332         return s
    333 
    334     def formatyear(self, theyear, w=2, l=1, c=6, m=3):
    335         """
    336         Returns a year's calendar as a multi-line string.
    337         """
    338         w = max(2, w)
    339         l = max(1, l)
    340         c = max(2, c)
    341         colwidth = (w + 1) * 7 - 1
    342         v = []
    343         a = v.append
    344         a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
    345         a('\n'*l)
    346         header = self.formatweekheader(w)
    347         for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
    348             # months in this row
    349             months = range(m*i+1, min(m*(i+1)+1, 13))
    350             a('\n'*l)
    351             names = (self.formatmonthname(theyear, k, colwidth, False)
    352                      for k in months)
    353             a(formatstring(names, colwidth, c).rstrip())
    354             a('\n'*l)
    355             headers = (header for k in months)
    356             a(formatstring(headers, colwidth, c).rstrip())
    357             a('\n'*l)
    358             # max number of weeks for this row
    359             height = max(len(cal) for cal in row)
    360             for j in range(height):
    361                 weeks = []
    362                 for cal in row:
    363                     if j >= len(cal):
    364                         weeks.append('')
    365                     else:
    366                         weeks.append(self.formatweek(cal[j], w))
    367                 a(formatstring(weeks, colwidth, c).rstrip())
    368                 a('\n' * l)
    369         return ''.join(v)
    370 
    371     def pryear(self, theyear, w=0, l=0, c=6, m=3):
    372         """Print a year's calendar."""
    373         print self.formatyear(theyear, w, l, c, m)
    374 
    375 
    376 class HTMLCalendar(Calendar):
    377     """
    378     This calendar returns complete HTML pages.
    379     """
    380 
    381     # CSS classes for the day <td>s
    382     cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
    383 
    384     def formatday(self, day, weekday):
    385         """
    386         Return a day as a table cell.
    387         """
    388         if day == 0:
    389             return '<td class="noday">&nbsp;</td>' # day outside month
    390         else:
    391             return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
    392 
    393     def formatweek(self, theweek):
    394         """
    395         Return a complete week as a table row.
    396         """
    397         s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
    398         return '<tr>%s</tr>' % s
    399 
    400     def formatweekday(self, day):
    401         """
    402         Return a weekday name as a table header.
    403         """
    404         return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
    405 
    406     def formatweekheader(self):
    407         """
    408         Return a header for a week as a table row.
    409         """
    410         s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
    411         return '<tr>%s</tr>' % s
    412 
    413     def formatmonthname(self, theyear, themonth, withyear=True):
    414         """
    415         Return a month name as a table row.
    416         """
    417         if withyear:
    418             s = '%s %s' % (month_name[themonth], theyear)
    419         else:
    420             s = '%s' % month_name[themonth]
    421         return '<tr><th colspan="7" class="month">%s</th></tr>' % s
    422 
    423     def formatmonth(self, theyear, themonth, withyear=True):
    424         """
    425         Return a formatted month as a table.
    426         """
    427         v = []
    428         a = v.append
    429         a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
    430         a('\n')
    431         a(self.formatmonthname(theyear, themonth, withyear=withyear))
    432         a('\n')
    433         a(self.formatweekheader())
    434         a('\n')
    435         for week in self.monthdays2calendar(theyear, themonth):
    436             a(self.formatweek(week))
    437             a('\n')
    438         a('</table>')
    439         a('\n')
    440         return ''.join(v)
    441 
    442     def formatyear(self, theyear, width=3):
    443         """
    444         Return a formatted year as a table of tables.
    445         """
    446         v = []
    447         a = v.append
    448         width = max(width, 1)
    449         a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
    450         a('\n')
    451         a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
    452         for i in range(January, January+12, width):
    453             # months in this row
    454             months = range(i, min(i+width, 13))
    455             a('<tr>')
    456             for m in months:
    457                 a('<td>')
    458                 a(self.formatmonth(theyear, m, withyear=False))
    459                 a('</td>')
    460             a('</tr>')
    461         a('</table>')
    462         return ''.join(v)
    463 
    464     def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
    465         """
    466         Return a formatted year as a complete HTML page.
    467         """
    468         if encoding is None:
    469             encoding = sys.getdefaultencoding()
    470         v = []
    471         a = v.append
    472         a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
    473         a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
    474         a('<html>\n')
    475         a('<head>\n')
    476         a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
    477         if css is not None:
    478             a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
    479         a('<title>Calendar for %d</title>\n' % theyear)
    480         a('</head>\n')
    481         a('<body>\n')
    482         a(self.formatyear(theyear, width))
    483         a('</body>\n')
    484         a('</html>\n')
    485         return ''.join(v).encode(encoding, "xmlcharrefreplace")
    486 
    487 
    488 class TimeEncoding:
    489     def __init__(self, locale):
    490         self.locale = locale
    491 
    492     def __enter__(self):
    493         self.oldlocale = _locale.getlocale(_locale.LC_TIME)
    494         _locale.setlocale(_locale.LC_TIME, self.locale)
    495         return _locale.getlocale(_locale.LC_TIME)[1]
    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 TimeEncoding(self.locale) as encoding:
    517             if width >= 9:
    518                 names = day_name
    519             else:
    520                 names = day_abbr
    521             name = names[day]
    522             if encoding is not None:
    523                 name = name.decode(encoding)
    524             return name[:width].center(width)
    525 
    526     def formatmonthname(self, theyear, themonth, width, withyear=True):
    527         with TimeEncoding(self.locale) as encoding:
    528             s = month_name[themonth]
    529             if encoding is not None:
    530                 s = s.decode(encoding)
    531             if withyear:
    532                 s = "%s %r" % (s, theyear)
    533             return s.center(width)
    534 
    535 
    536 class LocaleHTMLCalendar(HTMLCalendar):
    537     """
    538     This class can be passed a locale name in the constructor and will return
    539     month and weekday names in the specified locale. If this locale includes
    540     an encoding all strings containing month and weekday names will be returned
    541     as unicode.
    542     """
    543     def __init__(self, firstweekday=0, locale=None):
    544         HTMLCalendar.__init__(self, firstweekday)
    545         if locale is None:
    546             locale = _locale.getdefaultlocale()
    547         self.locale = locale
    548 
    549     def formatweekday(self, day):
    550         with TimeEncoding(self.locale) as encoding:
    551             s = day_abbr[day]
    552             if encoding is not None:
    553                 s = s.decode(encoding)
    554             return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
    555 
    556     def formatmonthname(self, theyear, themonth, withyear=True):
    557         with TimeEncoding(self.locale) as encoding:
    558             s = month_name[themonth]
    559             if encoding is not None:
    560                 s = s.decode(encoding)
    561             if withyear:
    562                 s = '%s %s' % (s, theyear)
    563             return '<tr><th colspan="7" class="month">%s</th></tr>' % s
    564 
    565 
    566 # Support for old module level interface
    567 c = TextCalendar()
    568 
    569 firstweekday = c.getfirstweekday
    570 
    571 def setfirstweekday(firstweekday):
    572     try:
    573         firstweekday.__index__
    574     except AttributeError:
    575         raise IllegalWeekdayError(firstweekday)
    576     if not MONDAY <= firstweekday <= SUNDAY:
    577         raise IllegalWeekdayError(firstweekday)
    578     c.firstweekday = firstweekday
    579 
    580 monthcalendar = c.monthdayscalendar
    581 prweek = c.prweek
    582 week = c.formatweek
    583 weekheader = c.formatweekheader
    584 prmonth = c.prmonth
    585 month = c.formatmonth
    586 calendar = c.formatyear
    587 prcal = c.pryear
    588 
    589 
    590 # Spacing of month columns for multi-column year calendar
    591 _colwidth = 7*3 - 1         # Amount printed by prweek()
    592 _spacing = 6                # Number of spaces between columns
    593 
    594 
    595 def format(cols, colwidth=_colwidth, spacing=_spacing):
    596     """Prints multi-column formatting for year calendars"""
    597     print formatstring(cols, colwidth, spacing)
    598 
    599 
    600 def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
    601     """Returns a string formatted from n strings, centered within n columns."""
    602     spacing *= ' '
    603     return spacing.join(c.center(colwidth) for c in cols)
    604 
    605 
    606 EPOCH = 1970
    607 _EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
    608 
    609 
    610 def timegm(tuple):
    611     """Unrelated but handy function to calculate Unix timestamp from GMT."""
    612     year, month, day, hour, minute, second = tuple[:6]
    613     days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
    614     hours = days*24 + hour
    615     minutes = hours*60 + minute
    616     seconds = minutes*60 + second
    617     return seconds
    618 
    619 
    620 def main(args):
    621     import optparse
    622     parser = optparse.OptionParser(usage="usage: %prog [options] [year [month]]")
    623     parser.add_option(
    624         "-w", "--width",
    625         dest="width", type="int", default=2,
    626         help="width of date column (default 2, text only)"
    627     )
    628     parser.add_option(
    629         "-l", "--lines",
    630         dest="lines", type="int", default=1,
    631         help="number of lines for each week (default 1, text only)"
    632     )
    633     parser.add_option(
    634         "-s", "--spacing",
    635         dest="spacing", type="int", default=6,
    636         help="spacing between months (default 6, text only)"
    637     )
    638     parser.add_option(
    639         "-m", "--months",
    640         dest="months", type="int", default=3,
    641         help="months per row (default 3, text only)"
    642     )
    643     parser.add_option(
    644         "-c", "--css",
    645         dest="css", default="calendar.css",
    646         help="CSS to use for page (html only)"
    647     )
    648     parser.add_option(
    649         "-L", "--locale",
    650         dest="locale", default=None,
    651         help="locale to be used from month and weekday names"
    652     )
    653     parser.add_option(
    654         "-e", "--encoding",
    655         dest="encoding", default=None,
    656         help="Encoding to use for output"
    657     )
    658     parser.add_option(
    659         "-t", "--type",
    660         dest="type", default="text",
    661         choices=("text", "html"),
    662         help="output type (text or html)"
    663     )
    664 
    665     (options, args) = parser.parse_args(args)
    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         if len(args) == 1:
    683             print cal.formatyearpage(datetime.date.today().year, **optdict)
    684         elif len(args) == 2:
    685             print cal.formatyearpage(int(args[1]), **optdict)
    686         else:
    687             parser.error("incorrect number of arguments")
    688             sys.exit(1)
    689     else:
    690         if options.locale:
    691             cal = LocaleTextCalendar(locale=locale)
    692         else:
    693             cal = TextCalendar()
    694         optdict = dict(w=options.width, l=options.lines)
    695         if len(args) != 3:
    696             optdict["c"] = options.spacing
    697             optdict["m"] = options.months
    698         if len(args) == 1:
    699             result = cal.formatyear(datetime.date.today().year, **optdict)
    700         elif len(args) == 2:
    701             result = cal.formatyear(int(args[1]), **optdict)
    702         elif len(args) == 3:
    703             result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
    704         else:
    705             parser.error("incorrect number of arguments")
    706             sys.exit(1)
    707         if options.encoding:
    708             result = result.encode(options.encoding)
    709         print result
    710 
    711 
    712 if __name__ == "__main__":
    713     main(sys.argv)
    714