codekingpro/portable-devtools
114k
1"""Calendar printing functions2 3Note when comparing these calendars to the ones printed by cal(1): By4default, these calendars have Monday as the first day of the week, and5Sunday as the last (the European convention). Use setfirstweekday() to6set the first day of the week (0=Monday, 6=Sunday)."""7 8import sys9import datetime10from enum import IntEnum, global_enum11import locale as _locale12from itertools import repeat13 14__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",15 "firstweekday", "isleap", "leapdays", "weekday", "monthrange",16 "monthcalendar", "prmonth", "month", "prcal", "calendar",17 "timegm", "month_name", "month_abbr", "day_name", "day_abbr",18 "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",19 "LocaleHTMLCalendar", "weekheader",20 "Day", "Month", "JANUARY", "FEBRUARY", "MARCH",21 "APRIL", "MAY", "JUNE", "JULY",22 "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",23 "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY",24 "SATURDAY", "SUNDAY"]25 26# Exception raised for bad input (with string parameter for details)27error = ValueError28 29# Exceptions raised for bad input30# This is trick for backward compatibility. Since 3.13, we will raise IllegalMonthError instead of31# IndexError for bad month number(out of 1-12). But we can't remove IndexError for backward compatibility.32class IllegalMonthError(ValueError, IndexError):33 def __init__(self, month):34 self.month = month35 def __str__(self):36 return "bad month number %r; must be 1-12" % self.month37 38 39class IllegalWeekdayError(ValueError):40 def __init__(self, weekday):41 self.weekday = weekday42 def __str__(self):43 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday44 45 46def __getattr__(name):47 if name in ('January', 'February'):48 import warnings49 warnings.warn(f"The '{name}' attribute is deprecated, use '{name.upper()}' instead",50 DeprecationWarning, stacklevel=2)51 if name == 'January':52 return 153 else:54 return 255 56 raise AttributeError(f"module '{__name__}' has no attribute '{name}'")57 58 59# Constants for months60@global_enum61class Month(IntEnum):62 JANUARY = 163 FEBRUARY = 264 MARCH = 365 APRIL = 466 MAY = 567 JUNE = 668 JULY = 769 AUGUST = 870 SEPTEMBER = 971 OCTOBER = 1072 NOVEMBER = 1173 DECEMBER = 1274 75 76# Constants for days77@global_enum78class Day(IntEnum):79 MONDAY = 080 TUESDAY = 181 WEDNESDAY = 282 THURSDAY = 383 FRIDAY = 484 SATURDAY = 585 SUNDAY = 686 87 88# Number of days per month (except for February in leap years)89mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]90 91# This module used to have hard-coded lists of day and month names, as92# English strings. The classes following emulate a read-only version of93# that, but supply localized names. Note that the values are computed94# fresh on each call, in case the user changes locale between calls.95 96class _localized_month:97 98 _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]99 _months.insert(0, lambda x: "")100 101 def __init__(self, format):102 self.format = format103 104 def __getitem__(self, i):105 funcs = self._months[i]106 if isinstance(i, slice):107 return [f(self.format) for f in funcs]108 else:109 return funcs(self.format)110 111 def __len__(self):112 return 13113 114 115class _localized_day:116 117 # January 1, 2001, was a Monday.118 _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]119 120 def __init__(self, format):121 self.format = format122 123 def __getitem__(self, i):124 funcs = self._days[i]125 if isinstance(i, slice):126 return [f(self.format) for f in funcs]127 else:128 return funcs(self.format)129 130 def __len__(self):131 return 7132 133 134# Full and abbreviated names of weekdays135day_name = _localized_day('%A')136day_abbr = _localized_day('%a')137 138# Full and abbreviated names of months (1-based arrays!!!)139month_name = _localized_month('%B')140month_abbr = _localized_month('%b')141 142 143def isleap(year):144 """Return True for leap years, False for non-leap years."""145 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)146 147 148def leapdays(y1, y2):149 """Return number of leap years in range [y1, y2).150 Assume y1 <= y2."""151 y1 -= 1152 y2 -= 1153 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)154 155 156def weekday(year, month, day):157 """Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31)."""158 if not datetime.MINYEAR <= year <= datetime.MAXYEAR:159 year = 2000 + year % 400160 return Day(datetime.date(year, month, day).weekday())161 162 163def _validate_month(month):164 if not 1 <= month <= 12:165 raise IllegalMonthError(month)166 167def monthrange(year, month):168 """Return weekday of first day of month (0-6 ~ Mon-Sun)169 and number of days (28-31) for year, month."""170 _validate_month(month)171 day1 = weekday(year, month, 1)172 ndays = mdays[month] + (month == FEBRUARY and isleap(year))173 return day1, ndays174 175 176def _monthlen(year, month):177 return mdays[month] + (month == FEBRUARY and isleap(year))178 179 180def _prevmonth(year, month):181 if month == 1:182 return year-1, 12183 else:184 return year, month-1185 186 187def _nextmonth(year, month):188 if month == 12:189 return year+1, 1190 else:191 return year, month+1192 193 194class Calendar(object):195 """196 Base calendar class. This class doesn't do any formatting. It simply197 provides data to subclasses.198 """199 200 def __init__(self, firstweekday=0):201 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday202 203 def getfirstweekday(self):204 return self._firstweekday % 7205 206 def setfirstweekday(self, firstweekday):207 self._firstweekday = firstweekday208 209 firstweekday = property(getfirstweekday, setfirstweekday)210 211 def iterweekdays(self):212 """213 Return an iterator for one week of weekday numbers starting with the214 configured first one.215 """216 for i in range(self.firstweekday, self.firstweekday + 7):217 yield i%7218 219 def itermonthdates(self, year, month):220 """221 Return an iterator for one month. The iterator will yield datetime.date222 values and will always iterate through complete weeks, so it will yield223 dates outside the specified month.224 """225 for y, m, d in self.itermonthdays3(year, month):226 yield datetime.date(y, m, d)227 228 def itermonthdays(self, year, month):229 """230 Like itermonthdates(), but will yield day numbers. For days outside231 the specified month the day number is 0.232 """233 day1, ndays = monthrange(year, month)234 days_before = (day1 - self.firstweekday) % 7235 yield from repeat(0, days_before)236 yield from range(1, ndays + 1)237 days_after = (self.firstweekday - day1 - ndays) % 7238 yield from repeat(0, days_after)239 240 def itermonthdays2(self, year, month):241 """242 Like itermonthdates(), but will yield (day number, weekday number)243 tuples. For days outside the specified month the day number is 0.244 """245 for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):246 yield d, i % 7247 248 def itermonthdays3(self, year, month):249 """250 Like itermonthdates(), but will yield (year, month, day) tuples. Can be251 used for dates outside of datetime.date range.252 """253 day1, ndays = monthrange(year, month)254 days_before = (day1 - self.firstweekday) % 7255 days_after = (self.firstweekday - day1 - ndays) % 7256 y, m = _prevmonth(year, month)257 end = _monthlen(y, m) + 1258 for d in range(end-days_before, end):259 yield y, m, d260 for d in range(1, ndays + 1):261 yield year, month, d262 y, m = _nextmonth(year, month)263 for d in range(1, days_after + 1):264 yield y, m, d265 266 def itermonthdays4(self, year, month):267 """268 Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.269 Can be used for dates outside of datetime.date range.270 """271 for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)):272 yield y, m, d, (self.firstweekday + i) % 7273 274 def monthdatescalendar(self, year, month):275 """276 Return a matrix (list of lists) representing a month's calendar.277 Each row represents a week; week entries are datetime.date values.278 """279 dates = list(self.itermonthdates(year, month))280 return [ dates[i:i+7] for i in range(0, len(dates), 7) ]281 282 def monthdays2calendar(self, year, month):283 """284 Return a matrix representing a month's calendar.285 Each row represents a week; week entries are286 (day number, weekday number) tuples. Day numbers outside this month287 are zero.288 """289 days = list(self.itermonthdays2(year, month))290 return [ days[i:i+7] for i in range(0, len(days), 7) ]291 292 def monthdayscalendar(self, year, month):293 """294 Return a matrix representing a month's calendar.295 Each row represents a week; days outside this month are zero.296 """297 days = list(self.itermonthdays(year, month))298 return [ days[i:i+7] for i in range(0, len(days), 7) ]299 300 def yeardatescalendar(self, year, width=3):301 """302 Return the data for the specified year ready for formatting. The return303 value is a list of month rows. Each month row contains up to width months.304 Each month contains between 4 and 6 weeks and each week contains 1-7305 days. Days are datetime.date objects.306 """307 months = [self.monthdatescalendar(year, m) for m in Month]308 return [months[i:i+width] for i in range(0, len(months), width) ]309 310 def yeardays2calendar(self, year, width=3):311 """312 Return the data for the specified year ready for formatting (similar to313 yeardatescalendar()). Entries in the week lists are314 (day number, weekday number) tuples. Day numbers outside this month are315 zero.316 """317 months = [self.monthdays2calendar(year, m) for m in Month]318 return [months[i:i+width] for i in range(0, len(months), width) ]319 320 def yeardayscalendar(self, year, width=3):321 """322 Return the data for the specified year ready for formatting (similar to323 yeardatescalendar()). Entries in the week lists are day numbers.324 Day numbers outside this month are zero.325 """326 months = [self.monthdayscalendar(year, m) for m in Month]327 return [months[i:i+width] for i in range(0, len(months), width) ]328 329 330class TextCalendar(Calendar):331 """332 Subclass of Calendar that outputs a calendar as a simple plain text333 similar to the UNIX program cal.334 """335 336 def prweek(self, theweek, width):337 """338 Print a single week (no newline).339 """340 print(self.formatweek(theweek, width), end='')341 342 def formatday(self, day, weekday, width):343 """344 Returns a formatted day.345 """346 if day == 0:347 s = ''348 else:349 s = '%2i' % day # right-align single-digit days350 return s.center(width)351 352 def formatweek(self, theweek, width):353 """354 Returns a single week in a string (no newline).355 """356 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)357 358 def formatweekday(self, day, width):359 """360 Returns a formatted week day name.361 """362 if width >= 9:363 names = day_name364 else:365 names = day_abbr366 return names[day][:width].center(width)367 368 def formatweekheader(self, width):369 """370 Return a header for a week.371 """372 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())373 374 def formatmonthname(self, theyear, themonth, width, withyear=True):375 """376 Return a formatted month name.377 """378 _validate_month(themonth)379 380 s = month_name[themonth]381 if withyear:382 s = "%s %r" % (s, theyear)383 return s.center(width)384 385 def prmonth(self, theyear, themonth, w=0, l=0):386 """387 Print a month's calendar.388 """389 print(self.formatmonth(theyear, themonth, w, l), end='')390 391 def formatmonth(self, theyear, themonth, w=0, l=0):392 """393 Return a month's calendar string (multi-line).394 """395 w = max(2, w)396 l = max(1, l)397 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)398 s = s.rstrip()399 s += '\n' * l400 s += self.formatweekheader(w).rstrip()401 s += '\n' * l402 for week in self.monthdays2calendar(theyear, themonth):403 s += self.formatweek(week, w).rstrip()404 s += '\n' * l405 return s406 407 def formatyear(self, theyear, w=2, l=1, c=6, m=3):408 """409 Returns a year's calendar as a multi-line string.410 """411 w = max(2, w)412 l = max(1, l)413 c = max(2, c)414 colwidth = (w + 1) * 7 - 1415 v = []416 a = v.append417 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())418 a('\n'*l)419 header = self.formatweekheader(w)420 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):421 # months in this row422 months = range(m*i+1, min(m*(i+1)+1, 13))423 a('\n'*l)424 names = (self.formatmonthname(theyear, k, colwidth, False)425 for k in months)426 a(formatstring(names, colwidth, c).rstrip())427 a('\n'*l)428 headers = (header for k in months)429 a(formatstring(headers, colwidth, c).rstrip())430 a('\n'*l)431 432 # max number of weeks for this row433 height = max(len(cal) for cal in row)434 for j in range(height):435 weeks = []436 for cal in row:437 if j >= len(cal):438 weeks.append('')439 else:440 weeks.append(self.formatweek(cal[j], w))441 a(formatstring(weeks, colwidth, c).rstrip())442 a('\n' * l)443 return ''.join(v)444 445 def pryear(self, theyear, w=0, l=0, c=6, m=3):446 """Print a year's calendar."""447 print(self.formatyear(theyear, w, l, c, m), end='')448 449 450class HTMLCalendar(Calendar):451 """452 This calendar returns complete HTML pages.453 """454 455 # CSS classes for the day <td>s456 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]457 458 # CSS classes for the day <th>s459 cssclasses_weekday_head = cssclasses460 461 # CSS class for the days before and after current month462 cssclass_noday = "noday"463 464 # CSS class for the month's head465 cssclass_month_head = "month"466 467 # CSS class for the month468 cssclass_month = "month"469 470 # CSS class for the year's table head471 cssclass_year_head = "year"472 473 # CSS class for the whole year table474 cssclass_year = "year"475 476 def formatday(self, day, weekday):477 """478 Return a day as a table cell.479 """480 if day == 0:481 # day outside month482 return '<td class="%s"> </td>' % self.cssclass_noday483 else:484 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)485 486 def formatweek(self, theweek):487 """488 Return a complete week as a table row.489 """490 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)491 return '<tr>%s</tr>' % s492 493 def formatweekday(self, day):494 """495 Return a weekday name as a table header.496 """497 return '<th class="%s">%s</th>' % (498 self.cssclasses_weekday_head[day], day_abbr[day])499 500 def formatweekheader(self):501 """502 Return a header for a week as a table row.503 """504 s = ''.join(self.formatweekday(i) for i in self.iterweekdays())505 return '<tr>%s</tr>' % s506 507 def formatmonthname(self, theyear, themonth, withyear=True):508 """509 Return a month name as a table row.510 """511 _validate_month(themonth)512 if withyear:513 s = '%s %s' % (month_name[themonth], theyear)514 else:515 s = '%s' % month_name[themonth]516 return '<tr><th colspan="7" class="%s">%s</th></tr>' % (517 self.cssclass_month_head, s)518 519 def formatmonth(self, theyear, themonth, withyear=True):520 """521 Return a formatted month as a table.522 """523 v = []524 a = v.append525 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % (526 self.cssclass_month))527 a('\n')528 a(self.formatmonthname(theyear, themonth, withyear=withyear))529 a('\n')530 a(self.formatweekheader())531 a('\n')532 for week in self.monthdays2calendar(theyear, themonth):533 a(self.formatweek(week))534 a('\n')535 a('</table>')536 a('\n')537 return ''.join(v)538 539 def formatyear(self, theyear, width=3):540 """541 Return a formatted year as a table of tables.542 """543 v = []544 a = v.append545 width = max(width, 1)546 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' %547 self.cssclass_year)548 a('\n')549 a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (550 width, self.cssclass_year_head, theyear))551 for i in range(JANUARY, JANUARY+12, width):552 # months in this row553 months = range(i, min(i+width, 13))554 a('<tr>')555 for m in months:556 a('<td>')557 a(self.formatmonth(theyear, m, withyear=False))558 a('</td>')559 a('</tr>')560 a('</table>')561 return ''.join(v)562 563 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):564 """565 Return a formatted year as a complete HTML page.566 """567 if encoding is None:568 encoding = sys.getdefaultencoding()569 v = []570 a = v.append571 a('<?xml version="1.0" encoding="%s"?>\n' % encoding)572 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')573 a('<html>\n')574 a('<head>\n')575 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)576 if css is not None:577 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)578 a('<title>Calendar for %d</title>\n' % theyear)579 a('</head>\n')580 a('<body>\n')581 a(self.formatyear(theyear, width))582 a('</body>\n')583 a('</html>\n')584 return ''.join(v).encode(encoding, "xmlcharrefreplace")585 586 587class different_locale:588 def __init__(self, locale):589 self.locale = locale590 self.oldlocale = None591 592 def __enter__(self):593 self.oldlocale = _locale.setlocale(_locale.LC_TIME, None)594 _locale.setlocale(_locale.LC_TIME, self.locale)595 596 def __exit__(self, *args):597 _locale.setlocale(_locale.LC_TIME, self.oldlocale)598 599 600def _get_default_locale():601 locale = _locale.setlocale(_locale.LC_TIME, None)602 if locale == "C":603 with different_locale(""):604 # The LC_TIME locale does not seem to be configured:605 # get the user preferred locale.606 locale = _locale.setlocale(_locale.LC_TIME, None)607 return locale608 609 610class LocaleTextCalendar(TextCalendar):611 """612 This class can be passed a locale name in the constructor and will return613 month and weekday names in the specified locale.614 """615 616 def __init__(self, firstweekday=0, locale=None):617 TextCalendar.__init__(self, firstweekday)618 if locale is None:619 locale = _get_default_locale()620 self.locale = locale621 622 def formatweekday(self, day, width):623 with different_locale(self.locale):624 return super().formatweekday(day, width)625 626 def formatmonthname(self, theyear, themonth, width, withyear=True):627 with different_locale(self.locale):628 return super().formatmonthname(theyear, themonth, width, withyear)629 630 631class LocaleHTMLCalendar(HTMLCalendar):632 """633 This class can be passed a locale name in the constructor and will return634 month and weekday names in the specified locale.635 """636 def __init__(self, firstweekday=0, locale=None):637 HTMLCalendar.__init__(self, firstweekday)638 if locale is None:639 locale = _get_default_locale()640 self.locale = locale641 642 def formatweekday(self, day):643 with different_locale(self.locale):644 return super().formatweekday(day)645 646 def formatmonthname(self, theyear, themonth, withyear=True):647 with different_locale(self.locale):648 return super().formatmonthname(theyear, themonth, withyear)649 650 651class _CLIDemoCalendar(TextCalendar):652 def __init__(self, highlight_day=None, *args, **kwargs):653 super().__init__(*args, **kwargs)654 self.highlight_day = highlight_day655 656 def formatweek(self, theweek, width, *, highlight_day=None):657 """658 Returns a single week in a string (no newline).659 """660 if highlight_day:661 from _colorize import get_colors662 663 ansi = get_colors()664 highlight = f"{ansi.BLACK}{ansi.BACKGROUND_YELLOW}"665 reset = ansi.RESET666 else:667 highlight = reset = ""668 669 return ' '.join(670 (671 f"{highlight}{self.formatday(d, wd, width)}{reset}"672 if d == highlight_day673 else self.formatday(d, wd, width)674 )675 for (d, wd) in theweek676 )677 678 def formatmonth(self, theyear, themonth, w=0, l=0):679 """680 Return a month's calendar string (multi-line).681 """682 if (683 self.highlight_day684 and self.highlight_day.year == theyear685 and self.highlight_day.month == themonth686 ):687 highlight_day = self.highlight_day.day688 else:689 highlight_day = None690 w = max(2, w)691 l = max(1, l)692 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)693 s = s.rstrip()694 s += '\n' * l695 s += self.formatweekheader(w).rstrip()696 s += '\n' * l697 for week in self.monthdays2calendar(theyear, themonth):698 s += self.formatweek(week, w, highlight_day=highlight_day).rstrip()699 s += '\n' * l700 return s701 702 def formatyear(self, theyear, w=2, l=1, c=6, m=3):703 """704 Returns a year's calendar as a multi-line string.705 """706 w = max(2, w)707 l = max(1, l)708 c = max(2, c)709 colwidth = (w + 1) * 7 - 1710 v = []711 a = v.append712 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())713 a('\n'*l)714 header = self.formatweekheader(w)715 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):716 # months in this row717 months = range(m*i+1, min(m*(i+1)+1, 13))718 a('\n'*l)719 names = (self.formatmonthname(theyear, k, colwidth, False)720 for k in months)721 a(formatstring(names, colwidth, c).rstrip())722 a('\n'*l)723 headers = (header for k in months)724 a(formatstring(headers, colwidth, c).rstrip())725 a('\n'*l)726 727 if (728 self.highlight_day729 and self.highlight_day.year == theyear730 and self.highlight_day.month in months731 ):732 month_pos = months.index(self.highlight_day.month)733 else:734 month_pos = None735 736 # max number of weeks for this row737 height = max(len(cal) for cal in row)738 for j in range(height):739 weeks = []740 for k, cal in enumerate(row):741 if j >= len(cal):742 weeks.append('')743 else:744 day = (745 self.highlight_day.day if k == month_pos else None746 )747 weeks.append(748 self.formatweek(cal[j], w, highlight_day=day)749 )750 a(formatstring(weeks, colwidth, c).rstrip())751 a('\n' * l)752 return ''.join(v)753 754 755class _CLIDemoLocaleCalendar(LocaleTextCalendar, _CLIDemoCalendar):756 def __init__(self, highlight_day=None, *args, **kwargs):757 super().__init__(*args, **kwargs)758 self.highlight_day = highlight_day759 760 761# Support for old module level interface762c = TextCalendar()763 764firstweekday = c.getfirstweekday765 766def setfirstweekday(firstweekday):767 if not MONDAY <= firstweekday <= SUNDAY:768 raise IllegalWeekdayError(firstweekday)769 c.firstweekday = firstweekday770 771monthcalendar = c.monthdayscalendar772prweek = c.prweek773week = c.formatweek774weekheader = c.formatweekheader775prmonth = c.prmonth776month = c.formatmonth777calendar = c.formatyear778prcal = c.pryear779 780 781# Spacing of month columns for multi-column year calendar782_colwidth = 7*3 - 1 # Amount printed by prweek()783_spacing = 6 # Number of spaces between columns784 785 786def format(cols, colwidth=_colwidth, spacing=_spacing):787 """Prints multi-column formatting for year calendars"""788 print(formatstring(cols, colwidth, spacing))789 790 791def formatstring(cols, colwidth=_colwidth, spacing=_spacing):792 """Returns a string formatted from n strings, centered within n columns."""793 spacing *= ' '794 return spacing.join(c.center(colwidth) for c in cols)795 796 797EPOCH = 1970798_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()799 800 801def timegm(tuple):802 """Unrelated but handy function to calculate Unix timestamp from GMT."""803 year, month, day, hour, minute, second = tuple[:6]804 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1805 hours = days*24 + hour806 minutes = hours*60 + minute807 seconds = minutes*60 + second808 return seconds809 810 811def main(args=None):812 import argparse813 parser = argparse.ArgumentParser(color=True)814 textgroup = parser.add_argument_group('text only arguments')815 htmlgroup = parser.add_argument_group('html only arguments')816 textgroup.add_argument(817 "-w", "--width",818 type=int, default=2,819 help="width of date column (default 2)"820 )821 textgroup.add_argument(822 "-l", "--lines",823 type=int, default=1,824 help="number of lines for each week (default 1)"825 )826 textgroup.add_argument(827 "-s", "--spacing",828 type=int, default=6,829 help="spacing between months (default 6)"830 )831 textgroup.add_argument(832 "-m", "--months",833 type=int, default=3,834 help="months per row (default 3)"835 )836 htmlgroup.add_argument(837 "-c", "--css",838 default="calendar.css",839 help="CSS to use for page"840 )841 parser.add_argument(842 "-L", "--locale",843 default=None,844 help="locale to use for month and weekday names"845 )846 parser.add_argument(847 "-e", "--encoding",848 default=None,849 help="encoding to use for output"850 )851 parser.add_argument(852 "-t", "--type",853 default="text",854 choices=("text", "html"),855 help="output type (text or html)"856 )857 parser.add_argument(858 "-f", "--first-weekday",859 type=int, default=0,860 help="weekday (0 is Monday, 6 is Sunday) to start each week (default 0)"861 )862 parser.add_argument(863 "year",864 nargs='?', type=int,865 help="year number"866 )867 parser.add_argument(868 "month",869 nargs='?', type=int,870 help="month number (1-12, text only)"871 )872 873 options = parser.parse_args(args)874 875 if options.locale and not options.encoding:876 parser.error("if --locale is specified --encoding is required")877 sys.exit(1)878 879 locale = options.locale, options.encoding880 today = datetime.date.today()881 882 if options.type == "html":883 if options.month:884 parser.error("incorrect number of arguments")885 sys.exit(1)886 if options.locale:887 cal = LocaleHTMLCalendar(locale=locale)888 else:889 cal = HTMLCalendar()890 cal.setfirstweekday(options.first_weekday)891 encoding = options.encoding892 if encoding is None:893 encoding = sys.getdefaultencoding()894 optdict = dict(encoding=encoding, css=options.css)895 write = sys.stdout.buffer.write896 if options.year is None:897 write(cal.formatyearpage(today.year, **optdict))898 else:899 write(cal.formatyearpage(options.year, **optdict))900 else:901 if options.locale:902 cal = _CLIDemoLocaleCalendar(highlight_day=today, locale=locale)903 else:904 cal = _CLIDemoCalendar(highlight_day=today)905 cal.setfirstweekday(options.first_weekday)906 optdict = dict(w=options.width, l=options.lines)907 if options.month is None:908 optdict["c"] = options.spacing909 optdict["m"] = options.months910 else:911 _validate_month(options.month)912 if options.year is None:913 result = cal.formatyear(today.year, **optdict)914 elif options.month is None:915 result = cal.formatyear(options.year, **optdict)916 else:917 result = cal.formatmonth(options.year, options.month, **optdict)918 write = sys.stdout.write919 if options.encoding:920 result = result.encode(options.encoding)921 write = sys.stdout.buffer.write922 write(result)923 924 925if __name__ == "__main__":926 main()927 