Skip to content

Commit 48d5e50

Browse files
committed
Bug #947906: Add classes LocaleTextCalendar and LocaleHTMLCalendar,
that output localized month and weekday names and can cope with encodings.
1 parent 1c5a59f commit 48d5e50

File tree

3 files changed

+177
-35
lines changed

3 files changed

+177
-35
lines changed

Doc/lib/libcalendar.tex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,24 @@ \section{\module{calendar} ---
176176
\end{methoddesc}
177177

178178

179+
\begin{classdesc}{LocaleTextCalendar}{\optional{firstweekday\optional{, locale}}}
180+
This subclass of \class{TextCalendar} can be passed a locale name in the
181+
constructor and will return month and weekday names in the specified locale.
182+
If this locale includes an encoding all strings containing month and weekday
183+
names will be returned as unicode.
184+
\versionadded{2.5}
185+
\end{classdesc}
186+
187+
188+
\begin{classdesc}{LocaleHTMLCalendar}{\optional{firstweekday\optional{, locale}}}
189+
This subclass of \class{HTMLCalendar} can be passed a locale name in the
190+
constructor and will return month and weekday names in the specified locale.
191+
If this locale includes an encoding all strings containing month and weekday
192+
names will be returned as unicode.
193+
\versionadded{2.5}
194+
\end{classdesc}
195+
196+
179197
For simple text calendars this module provides the following functions.
180198

181199
\begin{funcdesc}{setfirstweekday}{weekday}

Lib/calendar.py

Lines changed: 156 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Sunday as the last (the European convention). Use setfirstweekday() to
66
set the first day of the week (0=Monday, 6=Sunday)."""
77

8-
import sys, datetime
8+
import sys, datetime, locale
99

1010
__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
1111
"firstweekday", "isleap", "leapdays", "weekday", "monthrange",
@@ -297,11 +297,13 @@ def formatweekheader(self, width):
297297
"""
298298
return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
299299

300-
def formatmonthname(self, theyear, themonth, width):
300+
def formatmonthname(self, theyear, themonth, width, withyear=True):
301301
"""
302302
Return a formatted month name.
303303
"""
304-
s = "%s %r" % (month_name[themonth], theyear)
304+
s = month_name[themonth]
305+
if withyear:
306+
s = "%s %r" % (s, theyear)
305307
return s.center(width)
306308

307309
def prmonth(self, theyear, themonth, w=0, l=0):
@@ -343,9 +345,12 @@ def formatyear(self, theyear, w=2, l=1, c=6, m=3):
343345
# months in this row
344346
months = xrange(m*i+1, min(m*(i+1)+1, 13))
345347
a('\n'*l)
346-
a(formatstring((month_name[k] for k in months), colwidth, c).rstrip())
348+
names = (self.formatmonthname(theyear, k, colwidth, False)
349+
for k in months)
350+
a(formatstring(names, colwidth, c).rstrip())
347351
a('\n'*l)
348-
a(formatstring((header for k in months), colwidth, c).rstrip())
352+
headers = (header for k in months)
353+
a(formatstring(headers, colwidth, c).rstrip())
349354
a('\n'*l)
350355
# max number of weeks for this row
351356
height = max(len(cal) for cal in row)
@@ -474,7 +479,92 @@ def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
474479
a(self.formatyear(theyear, width))
475480
a('</body>\n')
476481
a('</html>\n')
477-
return ''.join(v).encode(encoding)
482+
return ''.join(v).encode(encoding, "xmlcharrefreplace")
483+
484+
485+
class LocaleTextCalendar(TextCalendar):
486+
"""
487+
This class can be passed a locale name in the constructor and will return
488+
month and weekday names in the specified locale. If this locale includes
489+
an encoding all strings containing month and weekday names will be returned
490+
as unicode.
491+
"""
492+
493+
def __init__(self, firstweekday=0, locale=None):
494+
TextCalendar.__init__(self, firstweekday)
495+
if locale is None:
496+
locale = locale.getdefaultlocale()
497+
self.locale = locale
498+
499+
def formatweekday(self, day, width):
500+
oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
501+
try:
502+
encoding = locale.getlocale(locale.LC_TIME)[1]
503+
if width >= 9:
504+
names = day_name
505+
else:
506+
names = day_abbr
507+
name = names[day]
508+
if encoding is not None:
509+
name = name.decode(encoding)
510+
result = name[:width].center(width)
511+
finally:
512+
locale.setlocale(locale.LC_TIME, oldlocale)
513+
return result
514+
515+
def formatmonthname(self, theyear, themonth, width, withyear=True):
516+
oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
517+
try:
518+
encoding = locale.getlocale(locale.LC_TIME)[1]
519+
s = month_name[themonth]
520+
if encoding is not None:
521+
s = s.decode(encoding)
522+
if withyear:
523+
s = "%s %r" % (s, theyear)
524+
result = s.center(width)
525+
finally:
526+
locale.setlocale(locale.LC_TIME, oldlocale)
527+
return result
528+
529+
530+
class LocaleHTMLCalendar(HTMLCalendar):
531+
"""
532+
This class can be passed a locale name in the constructor and will return
533+
month and weekday names in the specified locale. If this locale includes
534+
an encoding all strings containing month and weekday names will be returned
535+
as unicode.
536+
"""
537+
def __init__(self, firstweekday=0, locale=None):
538+
HTMLCalendar.__init__(self, firstweekday)
539+
if locale is None:
540+
locale = locale.getdefaultlocale()
541+
self.locale = locale
542+
543+
def formatweekday(self, day):
544+
oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
545+
try:
546+
encoding = locale.getlocale(locale.LC_TIME)[1]
547+
s = day_abbr[day]
548+
if encoding is not None:
549+
s = s.decode(encoding)
550+
result = '<th class="%s">%s</th>' % (self.cssclasses[day], s)
551+
finally:
552+
locale.setlocale(locale.LC_TIME, oldlocale)
553+
return result
554+
555+
def formatmonthname(self, theyear, themonth, withyear=True):
556+
oldlocale = locale.setlocale(locale.LC_TIME, self.locale)
557+
try:
558+
encoding = locale.getlocale(locale.LC_TIME)[1]
559+
s = month_name[themonth]
560+
if encoding is not None:
561+
s = s.decode(encoding)
562+
if withyear:
563+
s = '%s %s' % (s, theyear)
564+
result = '<tr><th colspan="7" class="month">%s</th></tr>' % s
565+
finally:
566+
locale.setlocale(locale.LC_TIME, oldlocale)
567+
return result
478568

479569

480570
# Support for old module level interface
@@ -524,34 +614,60 @@ def timegm(tuple):
524614

525615
def main(args):
526616
import optparse
527-
parser = optparse.OptionParser(usage="usage: %prog [options] [year] [month]")
528-
parser.add_option("-w", "--width",
529-
dest="width", type="int", default=2,
530-
help="width of date column (default 2, text only)")
531-
parser.add_option("-l", "--lines",
532-
dest="lines", type="int", default=1,
533-
help="number of lines for each week (default 1, text only)")
534-
parser.add_option("-s", "--spacing",
535-
dest="spacing", type="int", default=6,
536-
help="spacing between months (default 6, text only)")
537-
parser.add_option("-m", "--months",
538-
dest="months", type="int", default=3,
539-
help="months per row (default 3, text only)")
540-
parser.add_option("-c", "--css",
541-
dest="css", default="calendar.css",
542-
help="CSS to use for page (html only)")
543-
parser.add_option("-e", "--encoding",
544-
dest="encoding", default=None,
545-
help="Encoding to use for CSS output (html only)")
546-
parser.add_option("-t", "--type",
547-
dest="type", default="text",
548-
choices=("text", "html"),
549-
help="output type (text or html)")
617+
parser = optparse.OptionParser(usage="usage: %prog [options] [year [month]]")
618+
parser.add_option(
619+
"-w", "--width",
620+
dest="width", type="int", default=2,
621+
help="width of date column (default 2, text only)"
622+
)
623+
parser.add_option(
624+
"-l", "--lines",
625+
dest="lines", type="int", default=1,
626+
help="number of lines for each week (default 1, text only)"
627+
)
628+
parser.add_option(
629+
"-s", "--spacing",
630+
dest="spacing", type="int", default=6,
631+
help="spacing between months (default 6, text only)"
632+
)
633+
parser.add_option(
634+
"-m", "--months",
635+
dest="months", type="int", default=3,
636+
help="months per row (default 3, text only)"
637+
)
638+
parser.add_option(
639+
"-c", "--css",
640+
dest="css", default="calendar.css",
641+
help="CSS to use for page (html only)"
642+
)
643+
parser.add_option(
644+
"-L", "--locale",
645+
dest="locale", default=None,
646+
help="locale to be used from month and weekday names"
647+
)
648+
parser.add_option(
649+
"-e", "--encoding",
650+
dest="encoding", default=None,
651+
help="Encoding to use for output"
652+
)
653+
parser.add_option(
654+
"-t", "--type",
655+
dest="type", default="text",
656+
choices=("text", "html"),
657+
help="output type (text or html)"
658+
)
550659

551660
(options, args) = parser.parse_args(args)
552661

662+
if options.locale and not options.encoding:
663+
parser.error("if --locale is specified --encoding is required")
664+
sys.exit(1)
665+
553666
if options.type == "html":
554-
cal = HTMLCalendar()
667+
if options.locale:
668+
cal = LocaleHTMLCalendar(locale=options.locale)
669+
else:
670+
cal = HTMLCalendar()
555671
encoding = options.encoding
556672
if encoding is None:
557673
encoding = sys.getdefaultencoding()
@@ -564,20 +680,26 @@ def main(args):
564680
parser.error("incorrect number of arguments")
565681
sys.exit(1)
566682
else:
567-
cal = TextCalendar()
683+
if options.locale:
684+
cal = LocaleTextCalendar(locale=options.locale)
685+
else:
686+
cal = TextCalendar()
568687
optdict = dict(w=options.width, l=options.lines)
569688
if len(args) != 3:
570689
optdict["c"] = options.spacing
571690
optdict["m"] = options.months
572691
if len(args) == 1:
573-
print cal.formatyear(datetime.date.today().year, **optdict)
692+
result = cal.formatyear(datetime.date.today().year, **optdict)
574693
elif len(args) == 2:
575-
print cal.formatyear(int(args[1]), **optdict)
694+
result = cal.formatyear(int(args[1]), **optdict)
576695
elif len(args) == 3:
577-
print cal.formatmonth(int(args[1]), int(args[2]), **optdict)
696+
result = cal.formatmonth(int(args[1]), int(args[2]), **optdict)
578697
else:
579698
parser.error("incorrect number of arguments")
580699
sys.exit(1)
700+
if options.encoding:
701+
result = result.encode(options.encoding)
702+
print result
581703

582704

583705
if __name__ == "__main__":

Misc/NEWS

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,9 @@ Library
920920

921921
- Bug #947906: An object oriented interface has been added to the calendar
922922
module. It's possible to generate HTML calendar now and the module can be
923-
called as a script (e.g. via ``python -mcalendar``).
923+
called as a script (e.g. via ``python -mcalendar``). Localized month and
924+
weekday names can be ouput (even if an exotic encoding is used) using
925+
special classes that use unicode.
924926

925927
Build
926928
-----

0 commit comments

Comments
 (0)