From 1a9f1fb6dad6e86962ea7d2bbed2bd6c30fedadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Gr=C3=B8n=C3=A5s=20Drange?= Date: Thu, 16 Oct 2025 20:56:44 +0200 Subject: [PATCH 1/9] gh-140212: Add html for year-date option in Calendar --- Lib/calendar.py | 38 ++++++++++++++++++++++++++++++++++---- Lib/test/test_calendar.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/Lib/calendar.py b/Lib/calendar.py index ed6b74b248042e..29972b00b8715d 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -603,6 +603,35 @@ def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None): return ''.join(v).encode(encoding, "xmlcharrefreplace") + def formatmonthpage(self, theyear, themonth, width=3, css='calendar.css', encoding=None): + """ + Return a formatted month as a complete HTML page. + """ + if encoding is None: + encoding = 'utf-8' + v = [] + a = v.append + a('\n') + a('\n') + a('\n') + a(f'\n') + a('\n') + a(f'Calendar for {theyear}\n') + a('\n') + if css is not None: + a(f'\n') + a('\n') + a('\n') + a(self.formatmonth(theyear, themonth, width)) + a('\n') + a('\n') + return ''.join(v).encode(encoding, "xmlcharrefreplace") + + class different_locale: def __init__(self, locale): self.locale = locale @@ -899,9 +928,6 @@ def main(args=None): today = datetime.date.today() if options.type == "html": - if options.month: - parser.error("incorrect number of arguments") - sys.exit(1) if options.locale: cal = LocaleHTMLCalendar(locale=locale) else: @@ -912,10 +938,14 @@ def main(args=None): encoding = 'utf-8' optdict = dict(encoding=encoding, css=options.css) write = sys.stdout.buffer.write + if options.year is None: write(cal.formatyearpage(today.year, **optdict)) 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: if options.locale: cal = _CLIDemoLocaleCalendar(highlight_day=today, locale=locale) diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py index 020f9d61cae3cf..fe9a59d335b6b0 100644 --- a/Lib/test/test_calendar.py +++ b/Lib/test/test_calendar.py @@ -245,6 +245,34 @@ """ +result_2009_6_html = """\ + + + + + +Calendar for 2009 + + + + + + + + + + + + +
June 2009
MonTueWedThuFriSatSun
1234567
891011121314
15161718192021
22232425262728
2930     
+ + +""" + result_2004_days = [ [[[0, 0, 0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11], @@ -506,6 +534,13 @@ def test_format(self): calendar.format(["1", "2", "3"], colwidth=3, spacing=1) 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): def test_deprecation_warning(self): @@ -1102,7 +1137,6 @@ def test_illegal_arguments(self): self.assertFailure('2004', '1', 'spam') self.assertFailure('2004', '1', '1') self.assertFailure('2004', '1', '1', 'spam') - self.assertFailure('-t', 'html', '2004', '1') def test_output_current_year(self): for run in self.runners: From 4c3384b5da4e5279e7cd3e7b02d3363ebb883a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Gr=C3=B8n=C3=A5s=20Drange?= Date: Thu, 16 Oct 2025 22:51:04 +0200 Subject: [PATCH 2/9] gh-140212: Add blurb for calendar fix --- .../2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst new file mode 100644 index 00000000000000..a932cc0b28f2f2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst @@ -0,0 +1,4 @@ +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. From 1e2943bb9706d1a6a6600853655188d79711165c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Gr=C3=B8n=C3=A5s=20Drange?= Date: Thu, 16 Oct 2025 22:54:38 +0200 Subject: [PATCH 3/9] Update documentation for arguments to calendar --- Lib/calendar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/calendar.py b/Lib/calendar.py index 29972b00b8715d..7eb3e3f56f5959 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -915,7 +915,7 @@ def main(args=None): parser.add_argument( "month", nargs='?', type=int, - help="month number (1-12, text only)" + help="month number (1-12)" ) options = parser.parse_args(args) From b2f6f226e6ce8efcde51a774a0cdcc28baf26958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Gr=C3=B8n=C3=A5s=20Drange?= Date: Thu, 16 Oct 2025 22:59:16 +0200 Subject: [PATCH 4/9] Update documentation for Calendar's HTML format --- Doc/library/calendar.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index 3af01b132419a5..f76b1013dfbc66 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -710,8 +710,7 @@ The following options are accepted: .. option:: month The month of the specified :option:`year` to print the calendar for. - Must be a number between 1 and 12, - and may only be used in text mode. + Must be a number between 1 and 12. Defaults to printing a calendar for the full year. From 87248cb7003957bd7e4c855b30fa3650cc650712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Gr=C3=B8n=C3=A5s=20Drange?= Date: Mon, 20 Oct 2025 11:19:51 +0200 Subject: [PATCH 5/9] Move blurb to library --- .../2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Core_and_Builtins => Library}/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst (100%) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst b/Misc/NEWS.d/next/Library/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst similarity index 100% rename from Misc/NEWS.d/next/Core_and_Builtins/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst rename to Misc/NEWS.d/next/Library/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst From e0f9a9e12fb350beebb8ba8ac35ab8aa549bc6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Gr=C3=B8n=C3=A5s=20Drange?= Date: Wed, 22 Oct 2025 09:06:52 +0200 Subject: [PATCH 6/9] Fix backticks in blurb, add contributed --- .../Library/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst b/Misc/NEWS.d/next/Library/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst index a932cc0b28f2f2..5563d07717117e 100644 --- a/Misc/NEWS.d/next/Library/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst +++ b/Misc/NEWS.d/next/Library/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst @@ -1,4 +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 +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. From f54f75799e795ccf967e9a33da1aa730e098f67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Gr=C3=B8n=C3=A5s=20Drange?= Date: Wed, 22 Oct 2025 11:48:05 +0200 Subject: [PATCH 7/9] Refactor out html page Contributed by hugovk Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Lib/calendar.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Lib/calendar.py b/Lib/calendar.py index 7eb3e3f56f5959..621bae1e421132 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -603,9 +603,9 @@ def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None): return ''.join(v).encode(encoding, "xmlcharrefreplace") - def formatmonthpage(self, theyear, themonth, width=3, css='calendar.css', encoding=None): + def _format_html_page(self, theyear, content, css, encoding): """ - Return a formatted month as a complete HTML page. + Return a complete HTML page with the given content. """ if encoding is None: encoding = 'utf-8' @@ -626,11 +626,25 @@ def formatmonthpage(self, theyear, themonth, width=3, css='calendar.css', encodi a(f'\n') a('\n') a('\n') - a(self.formatmonth(theyear, themonth, width)) + a(content) a('\n') a('\n') 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: def __init__(self, locale): From 042034c92e89069c5580c8515fbbddd7f909d98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Gr=C3=B8n=C3=A5s=20Drange?= Date: Wed, 22 Oct 2025 12:42:25 +0200 Subject: [PATCH 8/9] Remove duplicate function --- Lib/calendar.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/Lib/calendar.py b/Lib/calendar.py index 621bae1e421132..d80c3fd9524776 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -574,35 +574,6 @@ def formatyear(self, theyear, width=3): a('') return ''.join(v) - def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None): - """ - Return a formatted year as a complete HTML page. - """ - if encoding is None: - encoding = 'utf-8' - v = [] - a = v.append - a('\n') - a('\n') - a('\n') - a(f'\n') - a('\n') - a(f'Calendar for {theyear}\n') - a('\n') - if css is not None: - a(f'\n') - a('\n') - a('\n') - a(self.formatyear(theyear, width)) - a('\n') - a('\n') - return ''.join(v).encode(encoding, "xmlcharrefreplace") - - def _format_html_page(self, theyear, content, css, encoding): """ Return a complete HTML page with the given content. From d1b9b2b663588d8525f39c8e2e91d3bbed88dc41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Gr=C3=B8n=C3=A5s=20Drange?= Date: Thu, 23 Oct 2025 13:59:06 +0200 Subject: [PATCH 9/9] Add What's new entry --- Doc/whatsnew/3.15.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d3ae7c21a0358b..384aaffb7da148 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -324,6 +324,10 @@ calendar dark mode and have been migrated to the HTML5 standard for improved accessibility. (Contributed by Jiahao Li and Hugo van Kemenade in :gh:`137634`.) +* The :mod:`calendar`'s :ref:`command-line ` 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 -----------