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
21 changes: 14 additions & 7 deletions Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,20 @@ def __len__(self):
standalone_month_name = month_name
standalone_month_abbr = month_abbr
else:
# 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
# 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. The strftime() calls
# inside set() are evaluated lazily here, so on platforms that
# accept '%OB' at construction time but reject it later (e.g.
# under Wine), this can still raise ValueError.
try:
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
27 changes: 27 additions & 0 deletions Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,33 @@ 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_name_survives_lazy_OB_failure(self):
# gh-155245: some platforms accept '%OB'/'%Ob' when a
# _localized_month object is constructed, but the underlying
# strftime() call -- which only happens lazily, inside set() --
# can still raise ValueError once it actually runs (e.g. under
# Wine). Reloading the calendar module used to let that
# ValueError propagate instead of falling back to
# month_name/month_abbr.
import importlib

real_date = datetime.date

class FakeDate(real_date):
def strftime(self, fmt):
if '%O' in fmt:
raise ValueError('Invalid format string')
return super().strftime(fmt)

datetime.date = FakeDate
try:
importlib.reload(calendar)
finally:
datetime.date = real_date
importlib.reload(calendar)

self.assertEqual(calendar.standalone_month_name[1], calendar.month_name[1])

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 @@
Fixed a bug where :mod:`calendar` could raise an uncaught :exc:`ValueError` on import when falling back from :data:`calendar.standalone_month_name`/:data:`calendar.standalone_month_abbr` to :data:`calendar.month_name`/:data:`calendar.month_abbr`, on platforms where ``strftime()`` accepts ``%OB``/``%Ob`` when a name object is constructed but rejects them once the value is actually formatted (e.g. under Wine).
Loading