From 0fc33f8e683579d658bcd8b5b5eae2450ca0f6bf Mon Sep 17 00:00:00 2001 From: jcaoun Date: Tue, 22 Nov 2022 23:48:04 -0500 Subject: [PATCH 01/11] fixed bug in tz_localize to throw an error if the argument to the ambiguos parameter is not one of the 4 correct options --- floor_timestamp_tests.py | 45 ++++++++++++++++++++++++++++++ pandas/_libs/tslibs/timestamps.pyx | 7 +++-- 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 floor_timestamp_tests.py diff --git a/floor_timestamp_tests.py b/floor_timestamp_tests.py new file mode 100644 index 0000000000000..7bab251fa822d --- /dev/null +++ b/floor_timestamp_tests.py @@ -0,0 +1,45 @@ +import pandas as pd +import unittest + +class MyTestCase(unittest.TestCase): + + def test_1_infer(self): + with self.assertRaises(ValueError): + ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') + # This works: + ts.floor('H') + # But this gives an error: + ts.floor('H', ambiguous='infer') + + def test_2_True(self): + ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') + actual = ts.floor('H', ambiguous=True) + expected = ts.floor('H') + self.assertEqual(actual, expected) + + def test_3_False(self): + ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') + actual = ts.floor('H', ambiguous=False) + expected = ts.floor('H') + self.assertEqual(actual, expected) + + def test_4_NaT(self): + ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') + actual = ts.floor('H', ambiguous='NaT') + expected = ts.floor('H') + self.assertEqual(actual, expected) + + def test_5_None(self): + ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') + actual = str(ts.floor('H')) + expected = '2020-10-25 00:00:00+02:00' + self.assertEqual(actual, expected) + + def test_6_other(self): + with self.assertRaises(ValueError): + ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') + ts.floor('H', ambiguous='other') + + +if __name__ == '__main__': + unittest.main() diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index ac8a6738a816c..f861a7c8031a9 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -6,8 +6,8 @@ construction requirements, we need to do object instantiation in python (see Timestamp class below). This will serve as a C extension type that shadows the python class, where we do any heavy lifting. """ -import warnings +import warnings cimport cython import numpy as np @@ -1926,8 +1926,9 @@ default 'raise' >>> pd.NaT.tz_localize() NaT """ - if ambiguous == 'infer': - raise ValueError('Cannot infer offset with only one time.') + + if ambiguous != True and ambiguous != False and ambiguous != 'NaT' and ambiguous != 'raise': + raise ValueError('ambiguous parameter must be one of: True, False, 'NaT', 'raise' (default)) nonexistent_options = ('raise', 'NaT', 'shift_forward', 'shift_backward') if nonexistent not in nonexistent_options and not PyDelta_Check(nonexistent): From 4650eb428dbbc4ea7f2270f23c49caa9f81922fd Mon Sep 17 00:00:00 2001 From: jcaoun Date: Wed, 30 Nov 2022 20:36:48 -0500 Subject: [PATCH 02/11] fixed ambiguous check and timestamp test cases --- pandas/_libs/tslibs/timestamps.pyx | 4 +- .../tests/scalar/timestamp/test_timezones.py | 81 ++++++++----------- 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 725494d55948f..e6929ba237a52 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1940,8 +1940,8 @@ default 'raise' NaT """ - if ambiguous != True and ambiguous != False and ambiguous != 'NaT' and ambiguous != 'raise': - raise ValueError('ambiguous parameter must be one of: True, False, 'NaT', 'raise' (default)) + if not isinstance(ambiguous, bool) and ambiguous not in {'NaT', 'raise'}: + raise ValueError("ambiguous parameter must be one of: True, False, 'NaT', 'raise' (default)") nonexistent_options = ('raise', 'NaT', 'shift_forward', 'shift_backward') if nonexistent not in nonexistent_options and not PyDelta_Check(nonexistent): diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index 3ebffaad23910..b20dc27adeeb8 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -7,7 +7,9 @@ timedelta, ) -import dateutil +# import dateutil +import re + from dateutil.tz import ( gettz, tzoffset, @@ -22,17 +24,13 @@ from pandas._libs.tslibs import timezones from pandas._libs.tslibs.dtypes import NpyDatetimeUnit from pandas.errors import OutOfBoundsDatetime -import pandas.util._test_decorators as td from pandas import ( NaT, Timestamp, ) -try: - from zoneinfo import ZoneInfo -except ImportError: - ZoneInfo = None +# import pandas.util._test_decorators as td class TestTimestampTZOperations: @@ -67,7 +65,7 @@ def test_tz_localize_pushes_out_of_bounds(self): def test_tz_localize_ambiguous_bool(self, unit): # make sure that we are correctly accepting bool values as ambiguous # GH#14402 - ts = Timestamp("2015-11-01 01:00:03").as_unit(unit) + ts = Timestamp("2015-11-01 01:00:03")._as_unit(unit) expected0 = Timestamp("2015-11-01 01:00:03-0500", tz="US/Central") expected1 = Timestamp("2015-11-01 01:00:03-0600", tz="US/Central") @@ -75,19 +73,6 @@ def test_tz_localize_ambiguous_bool(self, unit): with pytest.raises(pytz.AmbiguousTimeError, match=msg): ts.tz_localize("US/Central") - with pytest.raises(pytz.AmbiguousTimeError, match=msg): - ts.tz_localize("dateutil/US/Central") - - if ZoneInfo is not None: - try: - tz = ZoneInfo("US/Central") - except KeyError: - # no tzdata - pass - else: - with pytest.raises(pytz.AmbiguousTimeError, match=msg): - ts.tz_localize(tz) - result = ts.tz_localize("US/Central", ambiguous=True) assert result == expected0 assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value @@ -102,7 +87,9 @@ def test_tz_localize_ambiguous(self): ts_no_dst = ts.tz_localize("US/Eastern", ambiguous=False) assert (ts_no_dst.value - ts_dst.value) / 1e9 == 3600 - msg = "Cannot infer offset with only one time" + msg = re.escape( + "ambiguous parameter must be one of: True, False, 'NaT', 'raise' (default)" + ) with pytest.raises(ValueError, match=msg): ts.tz_localize("US/Eastern", ambiguous="infer") @@ -141,9 +128,9 @@ def test_tz_localize_ambiguous_raise(self): with pytest.raises(AmbiguousTimeError, match=msg): ts.tz_localize("US/Pacific", ambiguous="raise") - def test_tz_localize_nonexistent_invalid_arg(self, warsaw): + def test_tz_localize_nonexistent_invalid_arg(self): # GH 22644 - tz = warsaw + tz = "Europe/Warsaw" ts = Timestamp("2015-03-29 02:00:00") msg = ( "The nonexistent argument must be one of 'raise', 'NaT', " @@ -179,11 +166,10 @@ def test_tz_localize_ambiguous_compat(self): # validate that pytz and dateutil are compat for dst # when the transition happens naive = Timestamp("2013-10-27 01:00:00") - pytz_zone = "Europe/London" dateutil_zone = "dateutil/Europe/London" - result_pytz = naive.tz_localize(pytz_zone, ambiguous=0) - result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=0) + result_pytz = naive.tz_localize(pytz_zone, ambiguous=False) + result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=False) assert result_pytz.value == result_dateutil.value assert result_pytz.value == 1382835600000000000 @@ -194,8 +180,8 @@ def test_tz_localize_ambiguous_compat(self): assert str(result_pytz) == str(result_dateutil) # 1 hour difference - result_pytz = naive.tz_localize(pytz_zone, ambiguous=1) - result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=1) + result_pytz = naive.tz_localize(pytz_zone, ambiguous=True) + result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=True) assert result_pytz.value == result_dateutil.value assert result_pytz.value == 1382832000000000000 @@ -275,7 +261,7 @@ def test_timestamp_tz_localize_nonexistent_shift( tz = tz_type + tz if isinstance(shift, str): shift = "shift_" + shift - ts = Timestamp(start_ts).as_unit(unit) + ts = Timestamp(start_ts)._as_unit(unit) result = ts.tz_localize(tz, nonexistent=shift) expected = Timestamp(end_ts).tz_localize(tz) @@ -291,27 +277,28 @@ def test_timestamp_tz_localize_nonexistent_shift( assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value @pytest.mark.parametrize("offset", [-1, 1]) - def test_timestamp_tz_localize_nonexistent_shift_invalid(self, offset, warsaw): + @pytest.mark.parametrize("tz_type", ["", "dateutil/"]) + def test_timestamp_tz_localize_nonexistent_shift_invalid(self, offset, tz_type): # GH 8917, 24466 - tz = warsaw + tz = tz_type + "Europe/Warsaw" ts = Timestamp("2015-03-29 02:20:00") msg = "The provided timedelta will relocalize on a nonexistent time" with pytest.raises(ValueError, match=msg): ts.tz_localize(tz, nonexistent=timedelta(seconds=offset)) + @pytest.mark.parametrize("tz", ["Europe/Warsaw", "dateutil/Europe/Warsaw"]) @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) - def test_timestamp_tz_localize_nonexistent_NaT(self, warsaw, unit): + def test_timestamp_tz_localize_nonexistent_NaT(self, tz, unit): # GH 8917 - tz = warsaw - ts = Timestamp("2015-03-29 02:20:00").as_unit(unit) + ts = Timestamp("2015-03-29 02:20:00")._as_unit(unit) result = ts.tz_localize(tz, nonexistent="NaT") assert result is NaT + @pytest.mark.parametrize("tz", ["Europe/Warsaw", "dateutil/Europe/Warsaw"]) @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) - def test_timestamp_tz_localize_nonexistent_raise(self, warsaw, unit): + def test_timestamp_tz_localize_nonexistent_raise(self, tz, unit): # GH 8917 - tz = warsaw - ts = Timestamp("2015-03-29 02:20:00").as_unit(unit) + ts = Timestamp("2015-03-29 02:20:00")._as_unit(unit) msg = "2015-03-29 02:20:00" with pytest.raises(pytz.NonExistentTimeError, match=msg): ts.tz_localize(tz, nonexistent="raise") @@ -355,18 +342,18 @@ def test_astimezone(self, tzstr): assert expected == result assert isinstance(result, Timestamp) - @td.skip_if_windows - def test_tz_convert_utc_with_system_utc(self): + # @td.skip_if_windows + # def test_tz_convert_utc_with_system_utc(self): - # from system utc to real utc - ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) - # check that the time hasn't changed. - assert ts == ts.tz_convert(dateutil.tz.tzutc()) + # # from system utc to real utc + # ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) + # # check that the time hasn't changed. + # assert ts == ts.tz_convert(dateutil.tz.tzutc()) - # from system utc to real utc - ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) - # check that the time hasn't changed. - assert ts == ts.tz_convert(dateutil.tz.tzutc()) + # # from system utc to real utc + # ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) + # # check that the time hasn't changed. + # assert ts == ts.tz_convert(dateutil.tz.tzutc()) # ------------------------------------------------------------------ # Timestamp.__init__ with tz str or tzinfo From 4a762d080f25e49a0e323940e42385b11a64401c Mon Sep 17 00:00:00 2001 From: Julia Aoun Date: Thu, 1 Dec 2022 12:13:20 -0500 Subject: [PATCH 03/11] Delete floor_timestamp_tests.py --- floor_timestamp_tests.py | 45 ---------------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 floor_timestamp_tests.py diff --git a/floor_timestamp_tests.py b/floor_timestamp_tests.py deleted file mode 100644 index 7bab251fa822d..0000000000000 --- a/floor_timestamp_tests.py +++ /dev/null @@ -1,45 +0,0 @@ -import pandas as pd -import unittest - -class MyTestCase(unittest.TestCase): - - def test_1_infer(self): - with self.assertRaises(ValueError): - ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') - # This works: - ts.floor('H') - # But this gives an error: - ts.floor('H', ambiguous='infer') - - def test_2_True(self): - ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') - actual = ts.floor('H', ambiguous=True) - expected = ts.floor('H') - self.assertEqual(actual, expected) - - def test_3_False(self): - ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') - actual = ts.floor('H', ambiguous=False) - expected = ts.floor('H') - self.assertEqual(actual, expected) - - def test_4_NaT(self): - ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') - actual = ts.floor('H', ambiguous='NaT') - expected = ts.floor('H') - self.assertEqual(actual, expected) - - def test_5_None(self): - ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') - actual = str(ts.floor('H')) - expected = '2020-10-25 00:00:00+02:00' - self.assertEqual(actual, expected) - - def test_6_other(self): - with self.assertRaises(ValueError): - ts = pd.Timestamp("2020-10-25 00:00", tz='Europe/Berlin') - ts.floor('H', ambiguous='other') - - -if __name__ == '__main__': - unittest.main() From 296320a0f09719b5207bca0b6d66942ee9886215 Mon Sep 17 00:00:00 2001 From: jcaoun Date: Thu, 1 Dec 2022 12:33:22 -0500 Subject: [PATCH 04/11] fixed error in test cases --- pandas/tests/scalar/timestamp/test_timezones.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index b20dc27adeeb8..687a84c12b6d3 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -65,7 +65,7 @@ def test_tz_localize_pushes_out_of_bounds(self): def test_tz_localize_ambiguous_bool(self, unit): # make sure that we are correctly accepting bool values as ambiguous # GH#14402 - ts = Timestamp("2015-11-01 01:00:03")._as_unit(unit) + ts = Timestamp("2015-11-01 01:00:03").as_unit(unit) expected0 = Timestamp("2015-11-01 01:00:03-0500", tz="US/Central") expected1 = Timestamp("2015-11-01 01:00:03-0600", tz="US/Central") @@ -261,7 +261,7 @@ def test_timestamp_tz_localize_nonexistent_shift( tz = tz_type + tz if isinstance(shift, str): shift = "shift_" + shift - ts = Timestamp(start_ts)._as_unit(unit) + ts = Timestamp(start_ts).as_unit(unit) result = ts.tz_localize(tz, nonexistent=shift) expected = Timestamp(end_ts).tz_localize(tz) @@ -290,7 +290,7 @@ def test_timestamp_tz_localize_nonexistent_shift_invalid(self, offset, tz_type): @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) def test_timestamp_tz_localize_nonexistent_NaT(self, tz, unit): # GH 8917 - ts = Timestamp("2015-03-29 02:20:00")._as_unit(unit) + ts = Timestamp("2015-03-29 02:20:00").as_unit(unit) result = ts.tz_localize(tz, nonexistent="NaT") assert result is NaT @@ -298,7 +298,7 @@ def test_timestamp_tz_localize_nonexistent_NaT(self, tz, unit): @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) def test_timestamp_tz_localize_nonexistent_raise(self, tz, unit): # GH 8917 - ts = Timestamp("2015-03-29 02:20:00")._as_unit(unit) + ts = Timestamp("2015-03-29 02:20:00").as_unit(unit) msg = "2015-03-29 02:20:00" with pytest.raises(pytz.NonExistentTimeError, match=msg): ts.tz_localize(tz, nonexistent="raise") From 62ec2a94bb03b207b8edf4c8f6eae5d0396b8374 Mon Sep 17 00:00:00 2001 From: jcaoun Date: Fri, 2 Dec 2022 12:11:26 -0500 Subject: [PATCH 05/11] re-ran with pre-commit --- pandas/_libs/tslibs/timestamps.pyx | 5 ++++- .../tests/scalar/timestamp/test_timezones.py | 20 ------------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index e6929ba237a52..54ed1ffa1ac9d 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1941,7 +1941,10 @@ default 'raise' """ if not isinstance(ambiguous, bool) and ambiguous not in {'NaT', 'raise'}: - raise ValueError("ambiguous parameter must be one of: True, False, 'NaT', 'raise' (default)") + raise ValueError( + "ambiguous parameter must be one of: " + "True, False, 'NaT', 'raise' (default)" + ) nonexistent_options = ('raise', 'NaT', 'shift_forward', 'shift_backward') if nonexistent not in nonexistent_options and not PyDelta_Check(nonexistent): diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index 687a84c12b6d3..b66b7161dc99c 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -6,8 +6,6 @@ datetime, timedelta, ) - -# import dateutil import re from dateutil.tz import ( @@ -30,8 +28,6 @@ Timestamp, ) -# import pandas.util._test_decorators as td - class TestTimestampTZOperations: # -------------------------------------------------------------- @@ -342,22 +338,6 @@ def test_astimezone(self, tzstr): assert expected == result assert isinstance(result, Timestamp) - # @td.skip_if_windows - # def test_tz_convert_utc_with_system_utc(self): - - # # from system utc to real utc - # ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) - # # check that the time hasn't changed. - # assert ts == ts.tz_convert(dateutil.tz.tzutc()) - - # # from system utc to real utc - # ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) - # # check that the time hasn't changed. - # assert ts == ts.tz_convert(dateutil.tz.tzutc()) - - # ------------------------------------------------------------------ - # Timestamp.__init__ with tz str or tzinfo - def test_timestamp_constructor_tz_utc(self): utc_stamp = Timestamp("3/11/2012 05:00", tz="utc") assert utc_stamp.tzinfo is pytz.utc From db967a362bedf4e17130d2f32f68fbdd691ffb36 Mon Sep 17 00:00:00 2001 From: jcaoun Date: Fri, 2 Dec 2022 13:58:02 -0500 Subject: [PATCH 06/11] merged with pandas/dev main branch and re-added previous changes --- pandas/_libs/tslibs/timestamps.pyx | 7 ++-- .../tests/scalar/timestamp/test_timezones.py | 36 ++++++++++++++----- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index e0c7c0dce7867..05990ca13abfe 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1946,8 +1946,11 @@ default 'raise' >>> pd.NaT.tz_localize() NaT """ - if ambiguous == "infer": - raise ValueError("Cannot infer offset with only one time.") + if not isinstance(ambiguous, bool) and ambiguous not in {"NaT", "raise"}: + raise ValueError( + "ambiguous parameter must be one of: " + "True, False, 'NaT', 'raise' (default)" + ) nonexistent_options = ("raise", "NaT", "shift_forward", "shift_backward") if nonexistent not in nonexistent_options and not PyDelta_Check(nonexistent): diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index b66b7161dc99c..e14eddb11ffb5 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -28,6 +28,11 @@ Timestamp, ) +try: + from zoneinfo import ZoneInfo +except ImportError: + ZoneInfo = None + class TestTimestampTZOperations: # -------------------------------------------------------------- @@ -69,6 +74,19 @@ def test_tz_localize_ambiguous_bool(self, unit): with pytest.raises(pytz.AmbiguousTimeError, match=msg): ts.tz_localize("US/Central") + with pytest.raises(pytz.AmbiguousTimeError, match=msg): + ts.tz_localize("dateutil/US/Central") + + if ZoneInfo is not None: + try: + tz = ZoneInfo("US/Central") + except KeyError: + # no tzdata + pass + else: + with pytest.raises(pytz.AmbiguousTimeError, match=msg): + ts.tz_localize(tz) + result = ts.tz_localize("US/Central", ambiguous=True) assert result == expected0 assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value @@ -124,9 +142,9 @@ def test_tz_localize_ambiguous_raise(self): with pytest.raises(AmbiguousTimeError, match=msg): ts.tz_localize("US/Pacific", ambiguous="raise") - def test_tz_localize_nonexistent_invalid_arg(self): + def test_tz_localize_nonexistent_invalid_arg(self, warsaw): # GH 22644 - tz = "Europe/Warsaw" + tz = warsaw ts = Timestamp("2015-03-29 02:00:00") msg = ( "The nonexistent argument must be one of 'raise', 'NaT', " @@ -162,6 +180,7 @@ def test_tz_localize_ambiguous_compat(self): # validate that pytz and dateutil are compat for dst # when the transition happens naive = Timestamp("2013-10-27 01:00:00") + pytz_zone = "Europe/London" dateutil_zone = "dateutil/Europe/London" result_pytz = naive.tz_localize(pytz_zone, ambiguous=False) @@ -273,27 +292,26 @@ def test_timestamp_tz_localize_nonexistent_shift( assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value @pytest.mark.parametrize("offset", [-1, 1]) - @pytest.mark.parametrize("tz_type", ["", "dateutil/"]) - def test_timestamp_tz_localize_nonexistent_shift_invalid(self, offset, tz_type): + def test_timestamp_tz_localize_nonexistent_shift_invalid(self, offset, warsaw): # GH 8917, 24466 - tz = tz_type + "Europe/Warsaw" + tz = warsaw ts = Timestamp("2015-03-29 02:20:00") msg = "The provided timedelta will relocalize on a nonexistent time" with pytest.raises(ValueError, match=msg): ts.tz_localize(tz, nonexistent=timedelta(seconds=offset)) - @pytest.mark.parametrize("tz", ["Europe/Warsaw", "dateutil/Europe/Warsaw"]) @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) - def test_timestamp_tz_localize_nonexistent_NaT(self, tz, unit): + def test_timestamp_tz_localize_nonexistent_NaT(self, warsaw, unit): # GH 8917 + tz = warsaw ts = Timestamp("2015-03-29 02:20:00").as_unit(unit) result = ts.tz_localize(tz, nonexistent="NaT") assert result is NaT - @pytest.mark.parametrize("tz", ["Europe/Warsaw", "dateutil/Europe/Warsaw"]) @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) - def test_timestamp_tz_localize_nonexistent_raise(self, tz, unit): + def test_timestamp_tz_localize_nonexistent_raise(self, warsaw, unit): # GH 8917 + tz = warsaw ts = Timestamp("2015-03-29 02:20:00").as_unit(unit) msg = "2015-03-29 02:20:00" with pytest.raises(pytz.NonExistentTimeError, match=msg): From c26031513d654512ea302871023bdb7b78c131b5 Mon Sep 17 00:00:00 2001 From: jcaoun Date: Fri, 2 Dec 2022 14:38:46 -0500 Subject: [PATCH 07/11] added quotes --- pandas/_libs/tslibs/timestamps.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 05990ca13abfe..f25114c273bcf 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1948,7 +1948,7 @@ default 'raise' """ if not isinstance(ambiguous, bool) and ambiguous not in {"NaT", "raise"}: raise ValueError( - "ambiguous parameter must be one of: " + "'ambiguous' parameter must be one of: " "True, False, 'NaT', 'raise' (default)" ) From f38e138b1c6a4924dd87ee9f000b152f4b704e48 Mon Sep 17 00:00:00 2001 From: jcaoun Date: Fri, 2 Dec 2022 14:59:25 -0500 Subject: [PATCH 08/11] added quotes to test case --- pandas/tests/scalar/timestamp/test_timezones.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index e14eddb11ffb5..a0d8818f3729d 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -102,7 +102,8 @@ def test_tz_localize_ambiguous(self): assert (ts_no_dst.value - ts_dst.value) / 1e9 == 3600 msg = re.escape( - "ambiguous parameter must be one of: True, False, 'NaT', 'raise' (default)" + "'ambiguous' parameter must be one of: " + "True, False, 'NaT', 'raise' (default)" ) with pytest.raises(ValueError, match=msg): ts.tz_localize("US/Eastern", ambiguous="infer") From 241233c3b9b76a3cd60c3352daf09c90b0505d08 Mon Sep 17 00:00:00 2001 From: jcaoun Date: Tue, 6 Dec 2022 16:55:38 -0500 Subject: [PATCH 09/11] added removed test case + whatsnew notes --- doc/source/whatsnew/v1.5.3.rst | 5 +++-- pandas/tests/scalar/timestamp/test_timezones.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v1.5.3.rst b/doc/source/whatsnew/v1.5.3.rst index b6c1c857717c7..5955ff3c9df0a 100644 --- a/doc/source/whatsnew/v1.5.3.rst +++ b/doc/source/whatsnew/v1.5.3.rst @@ -25,14 +25,14 @@ Fixed regressions Bug fixes ~~~~~~~~~ - Bug in :meth:`.Styler.to_excel` leading to error when unrecognized ``border-style`` (e.g. ``"hair"``) provided to Excel writers (:issue:`48649`) -- +- Bug in :meth:`.Timestamp.floor` fixed tz_localize so 'ambiguous' parameter can only be True, False, 'NaT' or 'raise' and all other invalid arguments to ambiguous have same error, including 'infer' (:issue:`49565`) .. --------------------------------------------------------------------------- .. _whatsnew_153.other: Other ~~~~~ -- +- Modified test cases in test_timezones.py to reflect new error message from bug fix for bug: (:issue:`49565`) - .. --------------------------------------------------------------------------- @@ -40,5 +40,6 @@ Other Contributors ~~~~~~~~~~~~ +- Julia Chaker Aoun .. contributors:: v1.5.2..v1.5.3|HEAD diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index a0d8818f3729d..7bdc6dc33996f 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -8,6 +8,7 @@ ) import re +import dateutil from dateutil.tz import ( gettz, tzoffset, @@ -22,6 +23,7 @@ from pandas._libs.tslibs import timezones from pandas._libs.tslibs.dtypes import NpyDatetimeUnit from pandas.errors import OutOfBoundsDatetime +import pandas.util._test_decorators as td from pandas import ( NaT, @@ -357,6 +359,18 @@ def test_astimezone(self, tzstr): assert expected == result assert isinstance(result, Timestamp) + @td.skip_if_windows + def test_tz_convert_utc_with_system_utc(self): + # from system utc to real utc + ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) + # check that the time hasn't changed. + assert ts == ts.tz_convert(dateutil.tz.tzutc()) + + # from system utc to real utc + ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) + # check that the time hasn't changed. + assert ts == ts.tz_convert(dateutil.tz.tzutc()) + def test_timestamp_constructor_tz_utc(self): utc_stamp = Timestamp("3/11/2012 05:00", tz="utc") assert utc_stamp.tzinfo is pytz.utc From 3e5944bcca9298ed86ad4086014b588765f1bc07 Mon Sep 17 00:00:00 2001 From: jcaoun Date: Tue, 6 Dec 2022 17:36:13 -0500 Subject: [PATCH 10/11] fixed whatsnew note --- doc/source/whatsnew/v2.0.0.rst | 2 +- pandas/tests/scalar/timestamp/test_timezones.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 62b0ea5307e41..d0eed405c944c 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -671,7 +671,7 @@ Timezones ^^^^^^^^^ - Bug in :meth:`Series.astype` and :meth:`DataFrame.astype` with object-dtype containing multiple timezone-aware ``datetime`` objects with heterogeneous timezones to a :class:`DatetimeTZDtype` incorrectly raising (:issue:`32581`) - Bug in :func:`to_datetime` was failing to parse date strings with timezone name when ``format`` was specified with ``%Z`` (:issue:`49748`) -- +- Better error message when passing invalid values to ``ambiguous`` parameter in :meth:`Timestamp.tz_localize` (:issue:`49565`) Numeric ^^^^^^^ diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index 7bdc6dc33996f..d7db99333cd03 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -371,6 +371,9 @@ def test_tz_convert_utc_with_system_utc(self): # check that the time hasn't changed. assert ts == ts.tz_convert(dateutil.tz.tzutc()) + # ------------------------------------------------------------------ + # Timestamp.__init__ with tz str or tzinfo + def test_timestamp_constructor_tz_utc(self): utc_stamp = Timestamp("3/11/2012 05:00", tz="utc") assert utc_stamp.tzinfo is pytz.utc From c7cd6f161d46ad4ab116cabd2e4aa505fa3299ec Mon Sep 17 00:00:00 2001 From: jcaoun Date: Sun, 11 Dec 2022 12:27:19 -0500 Subject: [PATCH 11/11] removed notes from v1.5.3 and added to v2.0.0 --- doc/source/whatsnew/v1.5.3.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v1.5.3.rst b/doc/source/whatsnew/v1.5.3.rst index 5955ff3c9df0a..763f9f87194d5 100644 --- a/doc/source/whatsnew/v1.5.3.rst +++ b/doc/source/whatsnew/v1.5.3.rst @@ -25,14 +25,13 @@ Fixed regressions Bug fixes ~~~~~~~~~ - Bug in :meth:`.Styler.to_excel` leading to error when unrecognized ``border-style`` (e.g. ``"hair"``) provided to Excel writers (:issue:`48649`) -- Bug in :meth:`.Timestamp.floor` fixed tz_localize so 'ambiguous' parameter can only be True, False, 'NaT' or 'raise' and all other invalid arguments to ambiguous have same error, including 'infer' (:issue:`49565`) +- .. --------------------------------------------------------------------------- .. _whatsnew_153.other: Other ~~~~~ -- Modified test cases in test_timezones.py to reflect new error message from bug fix for bug: (:issue:`49565`) - .. --------------------------------------------------------------------------- @@ -40,6 +39,5 @@ Other Contributors ~~~~~~~~~~~~ -- Julia Chaker Aoun .. contributors:: v1.5.2..v1.5.3|HEAD