(root)/
Python-3.12.0/
Lib/
calendar.py
       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">&nbsp;</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)