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