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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
53 changes: 48 additions & 5 deletions Doc/library/calendar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +496 to +498
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a formatting change and should not be included in this PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's to limit the line length to 80 characters in accordance with the reStructuredText markup guideline

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, you did not modify this section, please revert it. Existing violations do not have to be fixed, I assure you there are hundreds in the file.


>>> 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.
Comment on lines +516 to +517
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again unrelated formatting changes

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's to limit the line length to 80 characters in accordance with the reStructuredText markup guideline.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You did not modify this section, it is unrelated to this pr, please revert it.


>>> 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']

Comment on lines +537 to +540
Copy link
Member

@StanFromIreland StanFromIreland Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is pointless in the English locale, and it links to month_name with the exact same example.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, it's not relevant for English. But which locale should I use here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not have to provide an example.

.. 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']

Comment on lines +551 to +554
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

.. versionadded:: next


.. data:: JANUARY
FEBRUARY
MARCH
Expand Down
22 changes: 17 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,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
Comment on lines +146 to +152
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
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

The previous comment covers this IMO



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

Expand Down
13 changes: 12 additions & 1 deletion Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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='')
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Comment on lines +1 to +3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both the NEWS entries are for the same issue, they could just be merged. "Fix ... by adding :data:calendar.standalone_month_name and :data:calendar.standalone_month_abbr. These provide ... "

Original file line number Diff line number Diff line change
@@ -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.
Loading