From 278932b01e7e0641d7cf23f10125b04bfd4ea23a Mon Sep 17 00:00:00 2001 From: Vyas Ramasubramani Date: Sun, 6 Sep 2026 11:14:46 -0700 Subject: [PATCH] TEST: Target timezone DST edge cases --- python/cudf/cudf/tests/conftest.py | 47 ++++++++++++++++++- .../cudf/tests/series/accessors/test_dt.py | 24 ++++++---- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/python/cudf/cudf/tests/conftest.py b/python/cudf/cudf/tests/conftest.py index 91ecb3247bf0..5a1009571159 100644 --- a/python/cudf/cudf/tests/conftest.py +++ b/python/cudf/cudf/tests/conftest.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import datetime @@ -98,6 +98,19 @@ def _get_all_zones(): return sorted(zones) +def _get_transition_zones(timestamps, always_include): + zones = set(always_include) + for zone in _get_all_zones(): + timezone = zoneinfo.ZoneInfo(zone) + if any( + timestamp.replace(tzinfo=timezone, fold=0).utcoffset() + != timestamp.replace(tzinfo=timezone, fold=1).utcoffset() + for timestamp in timestamps + ): + zones.add(zone) + return sorted(zones) + + # NOTE: _get_all_zones is a very large list; we likely do NOT want to # use it for more than a handful of tests @pytest.fixture(params=_get_all_zones()) @@ -105,6 +118,38 @@ def all_timezones(request): return request.param +@pytest.fixture( + params=_get_transition_zones( + [ + datetime.datetime(2018, 11, 4, 0, 30), + datetime.datetime(2018, 11, 4, 1), + datetime.datetime(2018, 11, 4, 1, 30), + datetime.datetime(2018, 11, 4, 2), + datetime.datetime(2018, 11, 4, 2, 30), + ], + {"America/Metlakatla", "UTC"}, + ) +) +def ambiguous_timezones(request): + return request.param + + +@pytest.fixture( + params=_get_transition_zones( + [ + datetime.datetime(2018, 3, 11, 1, 30), + datetime.datetime(2018, 3, 11, 2), + datetime.datetime(2018, 3, 11, 2, 30), + datetime.datetime(2018, 3, 11, 3), + datetime.datetime(2018, 3, 11, 3, 30), + ], + {"America/Grand_Turk", "UTC"}, + ) +) +def nonexistent_timezones(request): + return request.param + + @pytest.fixture( params=["America/New_York", "Asia/Tokyo", "CET", "Etc/GMT+1", "UTC"] ) diff --git a/python/cudf/cudf/tests/series/accessors/test_dt.py b/python/cudf/cudf/tests/series/accessors/test_dt.py index ba4fc2ffe22e..791e86f46a7a 100644 --- a/python/cudf/cudf/tests/series/accessors/test_dt.py +++ b/python/cudf/cudf/tests/series/accessors/test_dt.py @@ -753,10 +753,12 @@ def test_tz_localize(datetime_types_as_str, all_timezones): assert str(s.dtype.tz) == all_timezones -def test_localize_ambiguous(request, datetime_types_as_str, all_timezones): +def test_localize_ambiguous( + request, datetime_types_as_str, ambiguous_timezones +): request.applymarker( pytest.mark.xfail( - condition=(all_timezones == "America/Metlakatla"), + condition=(ambiguous_timezones == "America/Metlakatla"), reason="https://www.timeanddate.com/news/time/metlakatla-quits-dst.html", ) ) @@ -772,16 +774,20 @@ def test_localize_ambiguous(request, datetime_types_as_str, all_timezones): dtype=datetime_types_as_str, ) expect = s.to_pandas().dt.tz_localize( - zoneinfo.ZoneInfo(all_timezones), ambiguous="NaT", nonexistent="NaT" + zoneinfo.ZoneInfo(ambiguous_timezones), + ambiguous="NaT", + nonexistent="NaT", ) - got = s.dt.tz_localize(all_timezones) + got = s.dt.tz_localize(ambiguous_timezones) assert_eq(expect, got) -def test_localize_nonexistent(request, datetime_types_as_str, all_timezones): +def test_localize_nonexistent( + request, datetime_types_as_str, nonexistent_timezones +): request.applymarker( pytest.mark.xfail( - condition=all_timezones == "America/Grand_Turk", + condition=nonexistent_timezones == "America/Grand_Turk", reason="https://www.worldtimezone.com/dst_news/dst_news_turkscaicos03.html", ) ) @@ -797,9 +803,11 @@ def test_localize_nonexistent(request, datetime_types_as_str, all_timezones): dtype=datetime_types_as_str, ) expect = s.to_pandas().dt.tz_localize( - zoneinfo.ZoneInfo(all_timezones), ambiguous="NaT", nonexistent="NaT" + zoneinfo.ZoneInfo(nonexistent_timezones), + ambiguous="NaT", + nonexistent="NaT", ) - got = s.dt.tz_localize(all_timezones) + got = s.dt.tz_localize(nonexistent_timezones) assert_eq(expect, got)