Skip to content

Commit 07912f8

Browse files
pgdrhugovk
andauthored
gh-140212: Add html for year-month option in Calendar (#140230)
Co-authored-by: Hugo van Kemenade <[email protected]>
1 parent dcf3cc5 commit 07912f8

File tree

5 files changed

+68
-11
lines changed

5 files changed

+68
-11
lines changed

Doc/library/calendar.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,8 +710,7 @@ The following options are accepted:
710710
.. option:: month
711711

712712
The month of the specified :option:`year` to print the calendar for.
713-
Must be a number between 1 and 12,
714-
and may only be used in text mode.
713+
Must be a number between 1 and 12.
715714
Defaults to printing a calendar for the full year.
716715

717716

Doc/whatsnew/3.15.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,10 @@ calendar
336336
dark mode and have been migrated to the HTML5 standard for improved accessibility.
337337
(Contributed by Jiahao Li and Hugo van Kemenade in :gh:`137634`.)
338338

339+
* The :mod:`calendar`'s :ref:`command-line <calendar-cli>` HTML output now
340+
accepts the year-month option: ``python -m calendar -t html 2009 06``.
341+
(Contributed by Pål Grønås Drange in :gh:`140212`.)
342+
339343

340344
collections
341345
-----------

Lib/calendar.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -574,9 +574,9 @@ def formatyear(self, theyear, width=3):
574574
a('</table>')
575575
return ''.join(v)
576576

577-
def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
577+
def _format_html_page(self, theyear, content, css, encoding):
578578
"""
579-
Return a formatted year as a complete HTML page.
579+
Return a complete HTML page with the given content.
580580
"""
581581
if encoding is None:
582582
encoding = 'utf-8'
@@ -597,11 +597,25 @@ def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
597597
a(f'<link rel="stylesheet" href="{css}">\n')
598598
a('</head>\n')
599599
a('<body>\n')
600-
a(self.formatyear(theyear, width))
600+
a(content)
601601
a('</body>\n')
602602
a('</html>\n')
603603
return ''.join(v).encode(encoding, "xmlcharrefreplace")
604604

605+
def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
606+
"""
607+
Return a formatted year as a complete HTML page.
608+
"""
609+
content = self.formatyear(theyear, width)
610+
return self._format_html_page(theyear, content, css, encoding)
611+
612+
def formatmonthpage(self, theyear, themonth, width=3, css='calendar.css', encoding=None):
613+
"""
614+
Return a formatted month as a complete HTML page.
615+
"""
616+
content = self.formatmonth(theyear, themonth, width)
617+
return self._format_html_page(theyear, content, css, encoding)
618+
605619

606620
class different_locale:
607621
def __init__(self, locale):
@@ -886,7 +900,7 @@ def main(args=None):
886900
parser.add_argument(
887901
"month",
888902
nargs='?', type=int,
889-
help="month number (1-12, text only)"
903+
help="month number (1-12)"
890904
)
891905

892906
options = parser.parse_args(args)
@@ -899,9 +913,6 @@ def main(args=None):
899913
today = datetime.date.today()
900914

901915
if options.type == "html":
902-
if options.month:
903-
parser.error("incorrect number of arguments")
904-
sys.exit(1)
905916
if options.locale:
906917
cal = LocaleHTMLCalendar(locale=locale)
907918
else:
@@ -912,10 +923,14 @@ def main(args=None):
912923
encoding = 'utf-8'
913924
optdict = dict(encoding=encoding, css=options.css)
914925
write = sys.stdout.buffer.write
926+
915927
if options.year is None:
916928
write(cal.formatyearpage(today.year, **optdict))
917929
else:
918-
write(cal.formatyearpage(options.year, **optdict))
930+
if options.month:
931+
write(cal.formatmonthpage(options.year, options.month, **optdict))
932+
else:
933+
write(cal.formatyearpage(options.year, **optdict))
919934
else:
920935
if options.locale:
921936
cal = _CLIDemoLocaleCalendar(highlight_day=today, locale=locale)

Lib/test/test_calendar.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,34 @@
245245
</html>
246246
"""
247247

248+
result_2009_6_html = """\
249+
<!DOCTYPE html>
250+
<html lang="en">
251+
<head>
252+
<meta charset="utf-8">
253+
<meta name="viewport" content="width=device-width, initial-scale=1">
254+
<title>Calendar for 2009</title>
255+
<style>
256+
:root { color-scheme: light dark; }
257+
table.year { border: solid; }
258+
table.year > tbody > tr > td { border: solid; vertical-align: top; }
259+
</style>
260+
<link rel="stylesheet" href="calendar.css">
261+
</head>
262+
<body>
263+
<table class="month">
264+
<tr><th colspan="7" class="month">June 2009</th></tr>
265+
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
266+
<tr><td class="mon">1</td><td class="tue">2</td><td class="wed">3</td><td class="thu">4</td><td class="fri">5</td><td class="sat">6</td><td class="sun">7</td></tr>
267+
<tr><td class="mon">8</td><td class="tue">9</td><td class="wed">10</td><td class="thu">11</td><td class="fri">12</td><td class="sat">13</td><td class="sun">14</td></tr>
268+
<tr><td class="mon">15</td><td class="tue">16</td><td class="wed">17</td><td class="thu">18</td><td class="fri">19</td><td class="sat">20</td><td class="sun">21</td></tr>
269+
<tr><td class="mon">22</td><td class="tue">23</td><td class="wed">24</td><td class="thu">25</td><td class="fri">26</td><td class="sat">27</td><td class="sun">28</td></tr>
270+
<tr><td class="mon">29</td><td class="tue">30</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>
271+
</table>
272+
</body>
273+
</html>
274+
"""
275+
248276
result_2004_days = [
249277
[[[0, 0, 0, 1, 2, 3, 4],
250278
[5, 6, 7, 8, 9, 10, 11],
@@ -506,6 +534,13 @@ def test_format(self):
506534
calendar.format(["1", "2", "3"], colwidth=3, spacing=1)
507535
self.assertEqual(out.getvalue().strip(), "1 2 3")
508536

537+
def test_format_html_year_with_month(self):
538+
self.assertEqual(
539+
calendar.HTMLCalendar().formatmonthpage(2009, 6).decode("ascii"),
540+
result_2009_6_html
541+
)
542+
543+
509544
class CalendarTestCase(unittest.TestCase):
510545

511546
def test_deprecation_warning(self):
@@ -1102,7 +1137,6 @@ def test_illegal_arguments(self):
11021137
self.assertFailure('2004', '1', 'spam')
11031138
self.assertFailure('2004', '1', '1')
11041139
self.assertFailure('2004', '1', '1', 'spam')
1105-
self.assertFailure('-t', 'html', '2004', '1')
11061140

11071141
def test_output_current_year(self):
11081142
for run in self.runners:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Calendar's HTML formatting now accepts year and month as options.
2+
Previously, running ``python -m calendar -t html 2025 10`` would result in an
3+
error message. It now generates an HTML document displaying the calendar for
4+
the specified month.
5+
Contributed by Pål Grønås Drange.

0 commit comments

Comments
 (0)