diff --git a/babel/dates.py b/babel/dates.py index 69610a7f0..ae4059f1b 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -119,6 +119,33 @@ def _get_tz_name(dt_or_tzinfo: _DtOrTzinfo) -> str: return tzinfo.tzname(dt or datetime.datetime.now(UTC)) +@lru_cache(maxsize=1) +def _get_reverse_tz_aliases() -> dict[str, tuple[str, ...]]: + zone_aliases = get_global('zone_aliases') + reverse_aliases: dict[str, list[str]] = {} + for alias, canonical_zone in zone_aliases.items(): + reverse_aliases.setdefault(canonical_zone, []).append(alias) + return {canonical_zone: tuple(aliases) for canonical_zone, aliases in reverse_aliases.items()} + + +def _get_display_tz_name(zone: str, locale: Locale, *, allow_territory: bool = False) -> str: + zone_aliases = get_global('zone_aliases') + candidates = [zone_aliases.get(zone, zone)] + for reverse_zone in _get_reverse_tz_aliases().get(zone, ()): + if reverse_zone not in candidates: + candidates.append(reverse_zone) + + for candidate in candidates: + if locale.time_zones.get(candidate, {}): + return candidate + if get_global('meta_zones').get(candidate): + return candidate + if allow_territory and get_global('zone_territories').get(candidate): + return candidate + + return candidates[0] + + def _get_datetime(instant: _Instant) -> datetime.datetime: """ Get a datetime out of an "instant" (date, time, datetime, number). @@ -518,8 +545,8 @@ def get_timezone_location( zone = _get_tz_name(dt_or_tzinfo) - # Get the canonical time-zone code - zone = get_global('zone_aliases').get(zone, zone) + # Prefer the zone name that actually has locale data attached to it. + zone = _get_display_tz_name(zone, locale, allow_territory=True) info = locale.time_zones.get(zone, {}) @@ -655,8 +682,8 @@ def get_timezone_name( if zone_variant not in ('generic', 'standard', 'daylight'): raise ValueError('Invalid zone variation') - # Get the canonical time-zone code - zone = get_global('zone_aliases').get(zone, zone) + # Prefer the zone name that actually has locale data attached to it. + zone = _get_display_tz_name(zone, locale) if return_zone: return zone info = locale.time_zones.get(zone, {}) diff --git a/tests/test_dates.py b/tests/test_dates.py index 12bb23433..ab86ad5a0 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -16,6 +16,14 @@ import freezegun import pytest +try: + import zoneinfo +except ModuleNotFoundError: + try: + from backports import zoneinfo + except ImportError: + zoneinfo = None + from babel import Locale, dates from babel.dates import NO_INHERITANCE_MARKER, UTC, _localize, parse_pattern from babel.util import FixedOffsetTimezone @@ -552,6 +560,16 @@ def test_get_timezone_name_misc(timezone_getter): assert (dates.get_timezone_name(time(16, 20), locale='en_US', width='short') == "UTC") +@pytest.mark.skipif(zoneinfo is None, reason="zoneinfo not available") +def test_get_timezone_name_indianapolis_zoneinfo_regression(): + tz = zoneinfo.ZoneInfo('America/Indiana/Indianapolis') + dt = datetime(2025, 7, 1, 12, 0, tzinfo=tz) + + assert dates.get_timezone_name(dt, locale='en_US', width='long') == 'Eastern Daylight Time' + assert dates.get_timezone_name(dt, locale='en_US', width='short') == 'EDT' + assert dates.get_timezone_name(tz, locale='en_US', width='long') == 'Eastern Time' + + def test_format_date(): d = date(2007, 4, 1) assert dates.format_date(d, locale='en_US') == 'Apr 1, 2007'