Skip to content

gh-131146: Fix month names in a genitive case in calendar module #131147

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jul 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Doc/library/calendar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,14 @@ The :mod:`calendar` module exports the following data attributes:
>>> 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

Expand All @@ -512,6 +520,31 @@ The :mod:`calendar` module exports the following data attributes:
>>> 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 standalone form if the locale provides one. Else it is equivalent
to :data:`month_name`.

.. versionadded:: next


.. data:: standalone_month_abbr

A sequence that represents the abbreviated months of the year in the current
locale in the standalone form if the locale provides one. Else it is
equivalent to :data:`month_abbr`.

.. versionadded:: next


.. data:: JANUARY
FEBRUARY
MARCH
Expand Down
21 changes: 16 additions & 5 deletions Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -139,6 +140,16 @@ 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:
standalone_month_name = month_name
standalone_month_abbr = month_abbr


def isleap(year):
"""Return True for leap years, False for non-leap years."""
Expand Down Expand Up @@ -377,7 +388,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)
Expand Down Expand Up @@ -510,9 +521,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 '<tr><th colspan="7" class="%s">%s</th></tr>' % (
self.cssclass_month_head, s)

Expand Down
36 changes: 35 additions & 1 deletion Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io
import locale
import os
import platform
import sys
import time

Expand Down Expand Up @@ -546,7 +547,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)
Expand All @@ -556,6 +558,38 @@ def test_months(self):
# verify it "acts like a sequence" in two forms of iteration
self.assertEqual(value[::-1], list(reversed(value)))

@support.run_with_locale('LC_ALL', 'pl_PL')
@unittest.skipUnless(sys.platform == 'darwin' or platform.libc_ver()[0] == 'glibc',
"Guaranteed to work with glibc and macOS")
def test_standalone_month_name_and_abbr_pl_locale(self):
expected_standalone_month_names = [
"", "styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec",
"lipiec", "sierpień", "wrzesień", "październik", "listopad",
"grudzień"
]
expected_standalone_month_abbr = [
"", "sty", "lut", "mar", "kwi", "maj", "cze",
"lip", "sie", "wrz", "paź", "lis", "gru"
]
self.assertEqual(
list(calendar.standalone_month_name),
expected_standalone_month_names
)
self.assertEqual(
list(calendar.standalone_month_abbr),
expected_standalone_month_abbr
)

def test_standalone_month_name_and_abbr_C_locale(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='')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Fix :class:`calendar.TextCalendar`, :class:`calendar.HTMLCalendar`,
and the :mod:`calendar` CLI to display month names in the nominative
case by adding :data:`calendar.standalone_month_name` and
:data:`calendar.standalone_month_abbr`, which provide month names and
abbreviations in the grammatical form used when a month name stands by
itself, if the locale supports it.
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy