diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index 39090e36ed9c0d..5fb6140ff1acbb 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -493,25 +493,68 @@ The :mod:`calendar` module exports the following data attributes: .. data:: month_name - A sequence that represents the months of the year in the current locale. This - follows normal convention of January being month number 1, so it has a length of - 13 and ``month_name[0]`` is the empty string. + A sequence that represents the months of the year in the current locale. + This follows normal convention of January being month number 1, so it has + a length of 13 and ``month_name[0]`` is the empty string. >>> import calendar >>> list(calendar.month_name) ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + .. caution:: + + In locales with alternative month names forms, the :data:`!month_name` sequence + may not be suitable when a month name stands by itself and not as part of a date. + For instance, in Greek and in many Slavic and Baltic languages, :data:`!month_name` + will produce the month in genitive case. Use :data:`standalone_month_name` for a form + suitable for standalone use. + .. data:: month_abbr A sequence that represents the abbreviated months of the year in the current - locale. This follows normal convention of January being month number 1, so it - has a length of 13 and ``month_abbr[0]`` is the empty string. + locale. This follows normal convention of January being month number 1, so + it has a length of 13 and ``month_abbr[0]`` is the empty string. >>> import calendar >>> list(calendar.month_abbr) ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + .. caution:: + + In locales with alternative month names forms, the :data:`!month_abbr` sequence + may not be suitable when a month name stands by itself and not as part of a date. + Use :data:`standalone_month_abbr` for a form suitable for standalone use. + + +.. data:: standalone_month_name + + A sequence that represents the months of the year in the current locale + in the grammatical form used when a month name stands by itself if + the locale provides one. If the locale does not supply a standalone form, + it is equal to :data:`month_name`. + + >>> import calendar + >>> list(calendar.standalone_month_name) + ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + + .. versionadded:: next + + +.. data:: standalone_month_abbr + + A sequence that represents the abbreviated months of the year in the current + locale in the grammatical form used when a month name stands by itself if + the locale provides one. If the locale does not supply a standalone form + it is equal to :data:`month_abbr`. + + >>> import calendar + >>> list(calendar.standalone_month_abbr) + ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + + .. versionadded:: next + + .. data:: JANUARY FEBRUARY MARCH diff --git a/Lib/calendar.py b/Lib/calendar.py index 01a76ff8e78c45..f3cb92b1289a15 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -14,8 +14,9 @@ __all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday", "firstweekday", "isleap", "leapdays", "weekday", "monthrange", "monthcalendar", "prmonth", "month", "prcal", "calendar", - "timegm", "month_name", "month_abbr", "day_name", "day_abbr", - "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar", + "timegm", "month_name", "month_abbr", "standalone_month_name", + "standalone_month_abbr", "day_name", "day_abbr", "Calendar", + "TextCalendar", "HTMLCalendar", "LocaleTextCalendar", "LocaleHTMLCalendar", "weekheader", "Day", "Month", "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", @@ -139,6 +140,17 @@ def __len__(self): month_name = _localized_month('%B') month_abbr = _localized_month('%b') +# On platforms that support the %OB and %Ob specifiers, they are used +# to get the standalone form of the month name. This is required for +# some languages such as Greek, Slavic, and Baltic languages. +try: + standalone_month_name = _localized_month('%OB') + standalone_month_abbr = _localized_month('%Ob') +except ValueError: + # The platform does not support the %OB and %Ob specifiers. + standalone_month_name = month_name + standalone_month_abbr = month_abbr + def isleap(year): """Return True for leap years, False for non-leap years.""" @@ -377,7 +389,7 @@ def formatmonthname(self, theyear, themonth, width, withyear=True): """ _validate_month(themonth) - s = month_name[themonth] + s = standalone_month_name[themonth] if withyear: s = "%s %r" % (s, theyear) return s.center(width) @@ -510,9 +522,9 @@ def formatmonthname(self, theyear, themonth, withyear=True): """ _validate_month(themonth) if withyear: - s = '%s %s' % (month_name[themonth], theyear) + s = '%s %s' % (standalone_month_name[themonth], theyear) else: - s = '%s' % month_name[themonth] + s = standalone_month_name[themonth] return '%s' % ( self.cssclass_month_head, s) diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py index 073df310bb49eb..f2c6c6686ee203 100644 --- a/Lib/test/test_calendar.py +++ b/Lib/test/test_calendar.py @@ -546,7 +546,8 @@ def test_days(self): self.assertEqual(value[::-1], list(reversed(value))) def test_months(self): - for attr in "month_name", "month_abbr": + for attr in ("month_name", "month_abbr", "standalone_month_name", + "standalone_month_abbr"): value = getattr(calendar, attr) self.assertEqual(len(value), 13) self.assertEqual(len(value[:]), 13) @@ -556,6 +557,16 @@ def test_months(self): # verify it "acts like a sequence" in two forms of iteration self.assertEqual(value[::-1], list(reversed(value))) + def test_standalone_month_name_and_abbr(self): + # Ensure that the standalone month names and abbreviations are + # equal to the regular month names and abbreviations for + # the "C" locale. + with calendar.different_locale("C"): + self.assertListEqual(list(calendar.month_name), + list(calendar.standalone_month_name)) + self.assertListEqual(list(calendar.month_abbr), + list(calendar.standalone_month_abbr)) + def test_locale_text_calendar(self): try: cal = calendar.LocaleTextCalendar(locale='') diff --git a/Misc/NEWS.d/next/Library/2025-03-17-21-15-04.gh-issue-131146.P9Cdx0.rst b/Misc/NEWS.d/next/Library/2025-03-17-21-15-04.gh-issue-131146.P9Cdx0.rst new file mode 100644 index 00000000000000..6284f2140bc0a0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-17-21-15-04.gh-issue-131146.P9Cdx0.rst @@ -0,0 +1,3 @@ +Add :data:`calendar.standalone_month_name` and :data:`calendar.standalone_month_abbr` to +provide month names and abbreviations in a grammatical form used when a month name stands +by itself if the locale provides one. diff --git a/Misc/NEWS.d/next/Library/2025-03-17-21-21-06.gh-issue-131146.A5Obgv.rst b/Misc/NEWS.d/next/Library/2025-03-17-21-21-06.gh-issue-131146.A5Obgv.rst new file mode 100644 index 00000000000000..705b082972ae13 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-17-21-21-06.gh-issue-131146.A5Obgv.rst @@ -0,0 +1,3 @@ +Fix :class:`calendar.TextCalendar` and :class:`calendar.HTMLCalendar` and +:mod:`calendar` CLI to display month names in the nominative case if +provided by the locale.