https://github.com/python/cpython/commit/07912f86323dc5a13f4d070b7e3f2c3cb2850d8b
commit: 07912f86323dc5a13f4d070b7e3f2c3cb2850d8b
branch: main
author: Pål Grønås Drange <[email protected]>
committer: hugovk <[email protected]>
date: 2025-10-31T17:28:53+02:00
summary:

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

Co-authored-by: Hugo van Kemenade <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst
M Doc/library/calendar.rst
M Doc/whatsnew/3.15.rst
M Lib/calendar.py
M Lib/test/test_calendar.py

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.
 
 
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index 1294415a220b97..f70345dd2b8d62 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -336,6 +336,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 <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
 -----------
diff --git a/Lib/calendar.py b/Lib/calendar.py
index ed6b74b248042e..d80c3fd9524776 100644
--- a/Lib/calendar.py
+++ b/Lib/calendar.py
@@ -574,9 +574,9 @@ def formatyear(self, theyear, width=3):
         a('</table>')
         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:
             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('</head>\n')
         a('<body>\n')
-        a(self.formatyear(theyear, width))
+        a(content)
         a('</body>\n')
         a('</html>\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):
@@ -886,7 +900,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)
@@ -899,9 +913,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 +923,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 @@
 </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 = [
     [[[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:
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
new file mode 100644
index 00000000000000..5563d07717117e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-10-16-22-49-16.gh-issue-140212.llBNd0.rst
@@ -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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to