gh-140212: Add html for year-month option in Calendar (#140230)

Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
This commit is contained in:
Pål Grønås Drange 2025-10-31 16:28:53 +01:00 committed by GitHub
parent dcf3cc5796
commit 07912f8632
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 68 additions and 11 deletions

View file

@ -710,8 +710,7 @@ The following options are accepted:
.. option:: month .. option:: month
The month of the specified :option:`year` to print the calendar for. The month of the specified :option:`year` to print the calendar for.
Must be a number between 1 and 12, Must be a number between 1 and 12.
and may only be used in text mode.
Defaults to printing a calendar for the full year. Defaults to printing a calendar for the full year.

View file

@ -336,6 +336,10 @@ calendar
dark mode and have been migrated to the HTML5 standard for improved accessibility. dark mode and have been migrated to the HTML5 standard for improved accessibility.
(Contributed by Jiahao Li and Hugo van Kemenade in :gh:`137634`.) (Contributed by Jiahao Li and Hugo van Kemenade in :gh:`137634`.)
* The :mod:`calendar`'s :ref:`command-line <calendar-cli>` HTML output now
accepts the year-month option: ``python -m calendar -t html 2009 06``.
(Contributed by Pål Grønås Drange in :gh:`140212`.)
collections collections
----------- -----------

View file

@ -574,9 +574,9 @@ def formatyear(self, theyear, width=3):
a('</table>') a('</table>')
return ''.join(v) return ''.join(v)
def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None): def _format_html_page(self, theyear, content, css, encoding):
""" """
Return a formatted year as a complete HTML page. Return a complete HTML page with the given content.
""" """
if encoding is None: if encoding is None:
encoding = 'utf-8' encoding = 'utf-8'
@ -597,11 +597,25 @@ def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
a(f'<link rel="stylesheet" href="{css}">\n') a(f'<link rel="stylesheet" href="{css}">\n')
a('</head>\n') a('</head>\n')
a('<body>\n') a('<body>\n')
a(self.formatyear(theyear, width)) a(content)
a('</body>\n') a('</body>\n')
a('</html>\n') a('</html>\n')
return ''.join(v).encode(encoding, "xmlcharrefreplace") return ''.join(v).encode(encoding, "xmlcharrefreplace")
def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
"""
Return a formatted year as a complete HTML page.
"""
content = self.formatyear(theyear, width)
return self._format_html_page(theyear, content, css, encoding)
def formatmonthpage(self, theyear, themonth, width=3, css='calendar.css', encoding=None):
"""
Return a formatted month as a complete HTML page.
"""
content = self.formatmonth(theyear, themonth, width)
return self._format_html_page(theyear, content, css, encoding)
class different_locale: class different_locale:
def __init__(self, locale): def __init__(self, locale):
@ -886,7 +900,7 @@ def main(args=None):
parser.add_argument( parser.add_argument(
"month", "month",
nargs='?', type=int, nargs='?', type=int,
help="month number (1-12, text only)" help="month number (1-12)"
) )
options = parser.parse_args(args) options = parser.parse_args(args)
@ -899,9 +913,6 @@ def main(args=None):
today = datetime.date.today() today = datetime.date.today()
if options.type == "html": if options.type == "html":
if options.month:
parser.error("incorrect number of arguments")
sys.exit(1)
if options.locale: if options.locale:
cal = LocaleHTMLCalendar(locale=locale) cal = LocaleHTMLCalendar(locale=locale)
else: else:
@ -912,10 +923,14 @@ def main(args=None):
encoding = 'utf-8' encoding = 'utf-8'
optdict = dict(encoding=encoding, css=options.css) optdict = dict(encoding=encoding, css=options.css)
write = sys.stdout.buffer.write write = sys.stdout.buffer.write
if options.year is None: if options.year is None:
write(cal.formatyearpage(today.year, **optdict)) write(cal.formatyearpage(today.year, **optdict))
else: else:
write(cal.formatyearpage(options.year, **optdict)) if options.month:
write(cal.formatmonthpage(options.year, options.month, **optdict))
else:
write(cal.formatyearpage(options.year, **optdict))
else: else:
if options.locale: if options.locale:
cal = _CLIDemoLocaleCalendar(highlight_day=today, locale=locale) cal = _CLIDemoLocaleCalendar(highlight_day=today, locale=locale)

View file

@ -245,6 +245,34 @@
</html> </html>
""" """
result_2009_6_html = """\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar for 2009</title>
<style>
:root { color-scheme: light dark; }
table.year { border: solid; }
table.year > tbody > tr > td { border: solid; vertical-align: top; }
</style>
<link rel="stylesheet" href="calendar.css">
</head>
<body>
<table class="month">
<tr><th colspan="7" class="month">June 2009</th></tr>
<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>
<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>
<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>
<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>
<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>
<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>
</table>
</body>
</html>
"""
result_2004_days = [ result_2004_days = [
[[[0, 0, 0, 1, 2, 3, 4], [[[0, 0, 0, 1, 2, 3, 4],
[5, 6, 7, 8, 9, 10, 11], [5, 6, 7, 8, 9, 10, 11],
@ -506,6 +534,13 @@ def test_format(self):
calendar.format(["1", "2", "3"], colwidth=3, spacing=1) calendar.format(["1", "2", "3"], colwidth=3, spacing=1)
self.assertEqual(out.getvalue().strip(), "1 2 3") self.assertEqual(out.getvalue().strip(), "1 2 3")
def test_format_html_year_with_month(self):
self.assertEqual(
calendar.HTMLCalendar().formatmonthpage(2009, 6).decode("ascii"),
result_2009_6_html
)
class CalendarTestCase(unittest.TestCase): class CalendarTestCase(unittest.TestCase):
def test_deprecation_warning(self): def test_deprecation_warning(self):
@ -1102,7 +1137,6 @@ def test_illegal_arguments(self):
self.assertFailure('2004', '1', 'spam') self.assertFailure('2004', '1', 'spam')
self.assertFailure('2004', '1', '1') self.assertFailure('2004', '1', '1')
self.assertFailure('2004', '1', '1', 'spam') self.assertFailure('2004', '1', '1', 'spam')
self.assertFailure('-t', 'html', '2004', '1')
def test_output_current_year(self): def test_output_current_year(self):
for run in self.runners: for run in self.runners:

View file

@ -0,0 +1,5 @@
Calendar's HTML formatting now accepts year and month as options.
Previously, running ``python -m calendar -t html 2025 10`` would result in an
error message. It now generates an HTML document displaying the calendar for
the specified month.
Contributed by Pål Grønås Drange.