Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 7 additions & 4 deletions Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,20 @@ def __len__(self):
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
else:
# _localized_month only stores the format; strftime is called lazily when
# the names are first read, so systems that reject '%OB' raise ValueError
# here rather than above.
#
# Some systems that do not support '%OB' will keep it as-is (i.e.,
# we get [..., '%OB', '%OB', '%OB']), so for non-distinct names,
# we fall back to month_name/month_abbr.
if len(set(standalone_month_name)) != len(set(month_name)):
standalone_month_name = month_name
if len(set(standalone_month_abbr)) != len(set(month_abbr)):
standalone_month_abbr = month_abbr
except ValueError:
standalone_month_name = month_name
standalone_month_abbr = month_abbr


def isleap(year):
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import unittest

from test import support
from test.support import import_helper
from test.support.script_helper import assert_python_ok, assert_python_failure
import contextlib
import datetime
Expand All @@ -11,6 +12,7 @@
import platform
import sys
import time
from unittest import mock

# From https://en.wikipedia.org/wiki/Leap_year_starting_on_Saturday
result_0_02_text = """\
Expand Down Expand Up @@ -644,6 +646,26 @@ def test_standalone_month_name_and_abbr_C_locale(self):
self.assertListEqual(list(calendar.month_abbr),
list(calendar.standalone_month_abbr))

def test_standalone_month_fallback_when_specifier_rejected(self):
# gh-155245: _localized_month stores the format string and only calls
# strftime when a name is first read, so a platform that rejects
# '%OB' raises ValueError while the fallback below is being computed,
# not while the object is being built. Importing calendar has to fall
# back to the regular names instead of failing outright.
class _RejectsStandalone(datetime.date):
def strftime(self, format):
if 'O' in format:
raise ValueError(f'Invalid format string: {format}')
return super().strftime(format)

with mock.patch.object(datetime, 'date', _RejectsStandalone):
fresh_calendar = import_helper.import_fresh_module('calendar')

self.assertListEqual(list(fresh_calendar.standalone_month_name),
list(fresh_calendar.month_name))
self.assertListEqual(list(fresh_calendar.standalone_month_abbr),
list(fresh_calendar.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,5 @@
Fix :mod:`calendar` failing to import on platforms whose ``strftime``
rejects the ``%OB`` and ``%Ob`` format specifiers. The names are computed
lazily, so the resulting :exc:`ValueError` was raised outside the ``try``
block that was meant to catch it, instead of falling back to the regular
month names.
Loading