1"""Calendar printing functions 2 3Note when comparing these calendars to the ones printed by cal(1): By 4default, these calendars have Monday as the first day of the week, and 5Sunday as the last (the European convention). Use setfirstweekday() to 6set the first day of the week (0=Monday, 6=Sunday).""" 7 8import sys 9import datetime 10from enum import IntEnum, global_enum 11import locale as _locale 12from itertools import repeat 13 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 = ValueError 28 29# Exceptions raised for bad input 30# This is trick for backward compatibility. Since 3.13, we will raise IllegalMonthError instead of 31# 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 = month 35 def __str__(self): 36 return "bad month number %r; must be 1-12" % self.month 37 38 39class IllegalWeekdayError(ValueError): 40 def __init__(self, weekday): 41 self.weekday = weekday 42 def __str__(self): 43 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday 44 45 46def __getattr__(name): 47 if name in ('January', 'February'): 48 import warnings 49 warnings.warn(f"The '{name}' attribute is deprecated, use '{name.upper()}' instead", 50 DeprecationWarning, stacklevel=2) 51 if name == 'January': 52 return 1 53 else: 54 return 2 55 56 raise AttributeError(f"module '{__name__}' has no attribute '{name}'") 57 58 59# Constants for months 60@global_enum 61class Month(IntEnum): 62 JANUARY = 1 63 FEBRUARY = 2 64 MARCH = 3 65 APRIL = 4 66 MAY = 5 67 JUNE = 6 68 JULY = 7 69 AUGUST = 8 70 SEPTEMBER = 9 71 OCTOBER = 10 72 NOVEMBER = 11 73 DECEMBER = 12 74 75 76# Constants for days 77@global_enum 78class Day(IntEnum): 79 MONDAY = 0 80 TUESDAY = 1 81 WEDNESDAY = 2 82 THURSDAY = 3 83 FRIDAY = 4 84 SATURDAY = 5 85 SUNDAY = 6 86 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, as 92# English strings. The classes following emulate a read-only version of 93# that, but supply localized names. Note that the values are computed 94# 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 = format 103 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 13 113 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 = format 122 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 7 132 133 134# Full and abbreviated names of weekdays 135day_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 -= 1 152 y2 -= 1 153 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 % 400 160 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, ndays 174 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, 12 183 else: 184 return year, month-1 185 186 187def _nextmonth(year, month): 188 if month == 12: 189 return year+1, 1 190 else: 191 return year, month+1 192 193 194class Calendar(object): 195 """ 196 Base calendar class. This class doesn't do any formatting. It simply 197 provides data to subclasses. 198 """ 199 200 def __init__(self, firstweekday=0): 201 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday 202 203 def getfirstweekday(self): 204 return self._firstweekday % 7 205 206 def setfirstweekday(self, firstweekday): 207 self._firstweekday = firstweekday 208 209 firstweekday = property(getfirstweekday, setfirstweekday) 210 211 def iterweekdays(self): 212 """ 213 Return an iterator for one week of weekday numbers starting with the 214 configured first one. 215 """ 216 for i in range(self.firstweekday, self.firstweekday + 7): 217 yield i%7 218 219 def itermonthdates(self, year, month): 220 """ 221 Return an iterator for one month. The iterator will yield datetime.date 222 values and will always iterate through complete weeks, so it will yield 223 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 outside 231 the specified month the day number is 0. 232 """ 233 day1, ndays = monthrange(year, month) 234 days_before = (day1 - self.firstweekday) % 7 235 yield from repeat(0, days_before) 236 yield from range(1, ndays + 1) 237 days_after = (self.firstweekday - day1 - ndays) % 7 238 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 % 7 247 248 def itermonthdays3(self, year, month): 249 """ 250 Like itermonthdates(), but will yield (year, month, day) tuples. Can be 251 used for dates outside of datetime.date range. 252 """ 253 day1, ndays = monthrange(year, month) 254 days_before = (day1 - self.firstweekday) % 7 255 days_after = (self.firstweekday - day1 - ndays) % 7 256 y, m = _prevmonth(year, month) 257 end = _monthlen(y, m) + 1 258 for d in range(end-days_before, end): 259 yield y, m, d 260 for d in range(1, ndays + 1): 261 yield year, month, d 262 y, m = _nextmonth(year, month) 263 for d in range(1, days_after + 1): 264 yield y, m, d 265 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) % 7 273 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 are 286 (day number, weekday number) tuples. Day numbers outside this month 287 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 return 303 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-7 305 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 to 313 yeardatescalendar()). Entries in the week lists are 314 (day number, weekday number) tuples. Day numbers outside this month are 315 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 to 323 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 text 333 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 days 350 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_name 364 else: 365 names = day_abbr 366 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' * l 400 s += self.formatweekheader(w).rstrip() 401 s += '\n' * l 402 for week in self.monthdays2calendar(theyear, themonth): 403 s += self.formatweek(week, w).rstrip() 404 s += '\n' * l 405 return s 406 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 - 1 415 v = [] 416 a = v.append 417 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 row 422 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 # max number of weeks for this row 432 height = max(len(cal) for cal in row) 433 for j in range(height): 434 weeks = [] 435 for cal in row: 436 if j >= len(cal): 437 weeks.append('') 438 else: 439 weeks.append(self.formatweek(cal[j], w)) 440 a(formatstring(weeks, colwidth, c).rstrip()) 441 a('\n' * l) 442 return ''.join(v) 443 444 def pryear(self, theyear, w=0, l=0, c=6, m=3): 445 """Print a year's calendar.""" 446 print(self.formatyear(theyear, w, l, c, m), end='') 447 448 449class HTMLCalendar(Calendar): 450 """ 451 This calendar returns complete HTML pages. 452 """ 453 454 # CSS classes for the day <td>s 455 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] 456 457 # CSS classes for the day <th>s 458 cssclasses_weekday_head = cssclasses 459 460 # CSS class for the days before and after current month 461 cssclass_noday = "noday" 462 463 # CSS class for the month's head 464 cssclass_month_head = "month" 465 466 # CSS class for the month 467 cssclass_month = "month" 468 469 # CSS class for the year's table head 470 cssclass_year_head = "year" 471 472 # CSS class for the whole year table 473 cssclass_year = "year" 474 475 def formatday(self, day, weekday): 476 """ 477 Return a day as a table cell. 478 """ 479 if day == 0: 480 # day outside month 481 return '<td class="%s"> </td>' % self.cssclass_noday 482 else: 483 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day) 484 485 def formatweek(self, theweek): 486 """ 487 Return a complete week as a table row. 488 """ 489 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek) 490 return '<tr>%s</tr>' % s 491 492 def formatweekday(self, day): 493 """ 494 Return a weekday name as a table header. 495 """ 496 return '<th class="%s">%s</th>' % ( 497 self.cssclasses_weekday_head[day], day_abbr[day]) 498 499 def formatweekheader(self): 500 """ 501 Return a header for a week as a table row. 502 """ 503 s = ''.join(self.formatweekday(i) for i in self.iterweekdays()) 504 return '<tr>%s</tr>' % s 505 506 def formatmonthname(self, theyear, themonth, withyear=True): 507 """ 508 Return a month name as a table row. 509 """ 510 _validate_month(themonth) 511 if withyear: 512 s = '%s %s' % (month_name[themonth], theyear) 513 else: 514 s = '%s' % month_name[themonth] 515 return '<tr><th colspan="7" class="%s">%s</th></tr>' % ( 516 self.cssclass_month_head, s) 517 518 def formatmonth(self, theyear, themonth, withyear=True): 519 """ 520 Return a formatted month as a table. 521 """ 522 v = [] 523 a = v.append 524 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % ( 525 self.cssclass_month)) 526 a('\n') 527 a(self.formatmonthname(theyear, themonth, withyear=withyear)) 528 a('\n') 529 a(self.formatweekheader()) 530 a('\n') 531 for week in self.monthdays2calendar(theyear, themonth): 532 a(self.formatweek(week)) 533 a('\n') 534 a('</table>') 535 a('\n') 536 return ''.join(v) 537 538 def formatyear(self, theyear, width=3): 539 """ 540 Return a formatted year as a table of tables. 541 """ 542 v = [] 543 a = v.append 544 width = max(width, 1) 545 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % 546 self.cssclass_year) 547 a('\n') 548 a('<tr><th colspan="%d" class="%s">%s</th></tr>' % ( 549 width, self.cssclass_year_head, theyear)) 550 for i in range(JANUARY, JANUARY+12, width): 551 # months in this row 552 months = range(i, min(i+width, 13)) 553 a('<tr>') 554 for m in months: 555 a('<td>') 556 a(self.formatmonth(theyear, m, withyear=False)) 557 a('</td>') 558 a('</tr>') 559 a('</table>') 560 return ''.join(v) 561 562 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None): 563 """ 564 Return a formatted year as a complete HTML page. 565 """ 566 if encoding is None: 567 encoding = sys.getdefaultencoding() 568 v = [] 569 a = v.append 570 a('<?xml version="1.0" encoding="%s"?>\n' % encoding) 571 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n') 572 a('<html>\n') 573 a('<head>\n') 574 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding) 575 if css is not None: 576 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css) 577 a('<title>Calendar for %d</title>\n' % theyear) 578 a('</head>\n') 579 a('<body>\n') 580 a(self.formatyear(theyear, width)) 581 a('</body>\n') 582 a('</html>\n') 583 return ''.join(v).encode(encoding, "xmlcharrefreplace") 584 585 586class different_locale: 587 def __init__(self, locale): 588 self.locale = locale 589 self.oldlocale = None 590 591 def __enter__(self): 592 self.oldlocale = _locale.setlocale(_locale.LC_TIME, None) 593 _locale.setlocale(_locale.LC_TIME, self.locale) 594 595 def __exit__(self, *args): 596 _locale.setlocale(_locale.LC_TIME, self.oldlocale) 597 598 599def _get_default_locale(): 600 locale = _locale.setlocale(_locale.LC_TIME, None) 601 if locale == "C": 602 with different_locale(""): 603 # The LC_TIME locale does not seem to be configured: 604 # get the user preferred locale. 605 locale = _locale.setlocale(_locale.LC_TIME, None) 606 return locale 607 608 609class LocaleTextCalendar(TextCalendar): 610 """ 611 This class can be passed a locale name in the constructor and will return 612 month and weekday names in the specified locale. 613 """ 614 615 def __init__(self, firstweekday=0, locale=None): 616 TextCalendar.__init__(self, firstweekday) 617 if locale is None: 618 locale = _get_default_locale() 619 self.locale = locale 620 621 def formatweekday(self, day, width): 622 with different_locale(self.locale): 623 return super().formatweekday(day, width) 624 625 def formatmonthname(self, theyear, themonth, width, withyear=True): 626 with different_locale(self.locale): 627 return super().formatmonthname(theyear, themonth, width, withyear) 628 629 630class LocaleHTMLCalendar(HTMLCalendar): 631 """ 632 This class can be passed a locale name in the constructor and will return 633 month and weekday names in the specified locale. 634 """ 635 def __init__(self, firstweekday=0, locale=None): 636 HTMLCalendar.__init__(self, firstweekday) 637 if locale is None: 638 locale = _get_default_locale() 639 self.locale = locale 640 641 def formatweekday(self, day): 642 with different_locale(self.locale): 643 return super().formatweekday(day) 644 645 def formatmonthname(self, theyear, themonth, withyear=True): 646 with different_locale(self.locale): 647 return super().formatmonthname(theyear, themonth, withyear) 648 649# Support for old module level interface 650c = TextCalendar() 651 652firstweekday = c.getfirstweekday 653 654def setfirstweekday(firstweekday): 655 if not MONDAY <= firstweekday <= SUNDAY: 656 raise IllegalWeekdayError(firstweekday) 657 c.firstweekday = firstweekday 658 659monthcalendar = c.monthdayscalendar 660prweek = c.prweek 661week = c.formatweek 662weekheader = c.formatweekheader 663prmonth = c.prmonth 664month = c.formatmonth 665calendar = c.formatyear 666prcal = c.pryear 667 668 669# Spacing of month columns for multi-column year calendar 670_colwidth = 7*3 - 1 # Amount printed by prweek() 671_spacing = 6 # Number of spaces between columns 672 673 674def format(cols, colwidth=_colwidth, spacing=_spacing): 675 """Prints multi-column formatting for year calendars""" 676 print(formatstring(cols, colwidth, spacing)) 677 678 679def formatstring(cols, colwidth=_colwidth, spacing=_spacing): 680 """Returns a string formatted from n strings, centered within n columns.""" 681 spacing *= ' ' 682 return spacing.join(c.center(colwidth) for c in cols) 683 684 685EPOCH = 1970 686_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal() 687 688 689def timegm(tuple): 690 """Unrelated but handy function to calculate Unix timestamp from GMT.""" 691 year, month, day, hour, minute, second = tuple[:6] 692 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1 693 hours = days*24 + hour 694 minutes = hours*60 + minute 695 seconds = minutes*60 + second 696 return seconds 697 698 699def main(args=None): 700 import argparse 701 parser = argparse.ArgumentParser() 702 textgroup = parser.add_argument_group('text only arguments') 703 htmlgroup = parser.add_argument_group('html only arguments') 704 textgroup.add_argument( 705 "-w", "--width", 706 type=int, default=2, 707 help="width of date column (default 2)" 708 ) 709 textgroup.add_argument( 710 "-l", "--lines", 711 type=int, default=1, 712 help="number of lines for each week (default 1)" 713 ) 714 textgroup.add_argument( 715 "-s", "--spacing", 716 type=int, default=6, 717 help="spacing between months (default 6)" 718 ) 719 textgroup.add_argument( 720 "-m", "--months", 721 type=int, default=3, 722 help="months per row (default 3)" 723 ) 724 htmlgroup.add_argument( 725 "-c", "--css", 726 default="calendar.css", 727 help="CSS to use for page" 728 ) 729 parser.add_argument( 730 "-L", "--locale", 731 default=None, 732 help="locale to use for month and weekday names" 733 ) 734 parser.add_argument( 735 "-e", "--encoding", 736 default=None, 737 help="encoding to use for output" 738 ) 739 parser.add_argument( 740 "-t", "--type", 741 default="text", 742 choices=("text", "html"), 743 help="output type (text or html)" 744 ) 745 parser.add_argument( 746 "-f", "--first-weekday", 747 type=int, default=0, 748 help="weekday (0 is Monday, 6 is Sunday) to start each week (default 0)" 749 ) 750 parser.add_argument( 751 "year", 752 nargs='?', type=int, 753 help="year number" 754 ) 755 parser.add_argument( 756 "month", 757 nargs='?', type=int, 758 help="month number (1-12, text only)" 759 ) 760 761 options = parser.parse_args(args) 762 763 if options.locale and not options.encoding: 764 parser.error("if --locale is specified --encoding is required") 765 sys.exit(1) 766 767 locale = options.locale, options.encoding 768 769 if options.type == "html": 770 if options.month: 771 parser.error("incorrect number of arguments") 772 sys.exit(1) 773 if options.locale: 774 cal = LocaleHTMLCalendar(locale=locale) 775 else: 776 cal = HTMLCalendar() 777 cal.setfirstweekday(options.first_weekday) 778 encoding = options.encoding 779 if encoding is None: 780 encoding = sys.getdefaultencoding() 781 optdict = dict(encoding=encoding, css=options.css) 782 write = sys.stdout.buffer.write 783 if options.year is None: 784 write(cal.formatyearpage(datetime.date.today().year, **optdict)) 785 else: 786 write(cal.formatyearpage(options.year, **optdict)) 787 else: 788 if options.locale: 789 cal = LocaleTextCalendar(locale=locale) 790 else: 791 cal = TextCalendar() 792 cal.setfirstweekday(options.first_weekday) 793 optdict = dict(w=options.width, l=options.lines) 794 if options.month is None: 795 optdict["c"] = options.spacing 796 optdict["m"] = options.months 797 if options.month is not None: 798 _validate_month(options.month) 799 if options.year is None: 800 result = cal.formatyear(datetime.date.today().year, **optdict) 801 elif options.month is None: 802 result = cal.formatyear(options.year, **optdict) 803 else: 804 result = cal.formatmonth(options.year, options.month, **optdict) 805 write = sys.stdout.write 806 if options.encoding: 807 result = result.encode(options.encoding) 808 write = sys.stdout.buffer.write 809 write(result) 810 811 812if __name__ == "__main__": 813 main() 814