From 5eaaf29a328a8cdde7ed65a314d3b9d5e2663d99 Mon Sep 17 00:00:00 2001 From: Tekin Ertekin Date: Thu, 6 Aug 2026 13:03:35 +0300 Subject: [PATCH] gh-155245: Fix calendar failing to import when strftime rejects %OB _localized_month stores the format string and calls strftime lazily from __getitem__, so constructing _localized_month('%OB') never raises. The first strftime call happens further down, while comparing the standalone names against the regular ones to detect systems that keep '%OB' as-is -- and that comparison sits in the else branch, outside the try block meant to catch the failure. On a platform whose strftime rejects '%OB' the ValueError therefore escaped and importing calendar failed outright. Move the comparison into the try block so the intended fallback to month_name/month_abbr applies. --- Lib/calendar.py | 11 ++++++---- Lib/test/test_calendar.py | 22 +++++++++++++++++++ ...-08-06-13-05-42.gh-issue-155245.Rt4kQm.rst | 5 +++++ 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-06-13-05-42.gh-issue-155245.Rt4kQm.rst diff --git a/Lib/calendar.py b/Lib/calendar.py index 92fe6b7723fe26..88d250ff5006c0 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -146,10 +146,10 @@ 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. @@ -157,6 +157,9 @@ def __len__(self): 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): diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py index 8646cfcad58cea..0f2cfe1940fef0 100644 --- a/Lib/test/test_calendar.py +++ b/Lib/test/test_calendar.py @@ -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 @@ -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 = """\ @@ -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='') diff --git a/Misc/NEWS.d/next/Library/2026-08-06-13-05-42.gh-issue-155245.Rt4kQm.rst b/Misc/NEWS.d/next/Library/2026-08-06-13-05-42.gh-issue-155245.Rt4kQm.rst new file mode 100644 index 00000000000000..72e070765463c7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-06-13-05-42.gh-issue-155245.Rt4kQm.rst @@ -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.