diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py index 5eddb4b83f44c..b0019d2381e47 100644 --- a/pandas/tests/scalar/timestamp/test_constructors.py +++ b/pandas/tests/scalar/timestamp/test_constructors.py @@ -8,7 +8,11 @@ import zoneinfo import dateutil.tz -from dateutil.tz import tzutc +from dateutil.tz import ( + gettz, + tzoffset, + tzutc, +) import numpy as np import pytest import pytz @@ -26,28 +30,397 @@ ) -class TestTimestampConstructors: +class TestTimestampConstructorUnitKeyword: + @pytest.mark.parametrize("typ", [int, float]) + def test_constructor_int_float_with_YM_unit(self, typ): + # GH#47266 avoid the conversions in cast_from_unit + val = typ(150) + + ts = Timestamp(val, unit="Y") + expected = Timestamp("2120-01-01") + assert ts == expected + + ts = Timestamp(val, unit="M") + expected = Timestamp("1982-07-01") + assert ts == expected + + @pytest.mark.parametrize("typ", [int, float]) + def test_construct_from_int_float_with_unit_out_of_bound_raises(self, typ): + # GH#50870 make sure we get a OutOfBoundsDatetime instead of OverflowError + val = typ(150000000000000) + + msg = f"cannot convert input {val} with the unit 'D'" + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp(val, unit="D") + + def test_constructor_float_not_round_with_YM_unit_raises(self): + # GH#47267 avoid the conversions in cast_from-unit + + msg = "Conversion of non-round float with unit=[MY] is ambiguous" + with pytest.raises(ValueError, match=msg): + Timestamp(150.5, unit="Y") + + with pytest.raises(ValueError, match=msg): + Timestamp(150.5, unit="M") + + @pytest.mark.parametrize( + "value, check_kwargs", + [ + [946688461000000000, {}], + [946688461000000000 / 1000, {"unit": "us"}], + [946688461000000000 / 1_000_000, {"unit": "ms"}], + [946688461000000000 / 1_000_000_000, {"unit": "s"}], + [10957, {"unit": "D", "h": 0}], + [ + (946688461000000000 + 500000) / 1000000000, + {"unit": "s", "us": 499, "ns": 964}, + ], + [ + (946688461000000000 + 500000000) / 1000000000, + {"unit": "s", "us": 500000}, + ], + [(946688461000000000 + 500000) / 1000000, {"unit": "ms", "us": 500}], + [(946688461000000000 + 500000) / 1000, {"unit": "us", "us": 500}], + [(946688461000000000 + 500000000) / 1000000, {"unit": "ms", "us": 500000}], + [946688461000000000 / 1000.0 + 5, {"unit": "us", "us": 5}], + [946688461000000000 / 1000.0 + 5000, {"unit": "us", "us": 5000}], + [946688461000000000 / 1000000.0 + 0.5, {"unit": "ms", "us": 500}], + [946688461000000000 / 1000000.0 + 0.005, {"unit": "ms", "us": 5, "ns": 5}], + [946688461000000000 / 1000000000.0 + 0.5, {"unit": "s", "us": 500000}], + [10957 + 0.5, {"unit": "D", "h": 12}], + ], + ) + def test_construct_with_unit(self, value, check_kwargs): + def check(value, unit=None, h=1, s=1, us=0, ns=0): + stamp = Timestamp(value, unit=unit) + assert stamp.year == 2000 + assert stamp.month == 1 + assert stamp.day == 1 + assert stamp.hour == h + if unit != "D": + assert stamp.minute == 1 + assert stamp.second == s + assert stamp.microsecond == us + else: + assert stamp.minute == 0 + assert stamp.second == 0 + assert stamp.microsecond == 0 + assert stamp.nanosecond == ns + + check(value, **check_kwargs) + + +class TestTimestampConstructorFoldKeyword: + def test_timestamp_constructor_invalid_fold_raise(self): + # Test for GH#25057 + # Valid fold values are only [None, 0, 1] + msg = "Valid values for the fold argument are None, 0, or 1." + with pytest.raises(ValueError, match=msg): + Timestamp(123, fold=2) + + def test_timestamp_constructor_pytz_fold_raise(self): + # Test for GH#25057 + # pytz doesn't support fold. Check that we raise + # if fold is passed with pytz + msg = "pytz timezones do not support fold. Please use dateutil timezones." + tz = pytz.timezone("Europe/London") + with pytest.raises(ValueError, match=msg): + Timestamp(datetime(2019, 10, 27, 0, 30, 0, 0), tz=tz, fold=0) + + @pytest.mark.parametrize("fold", [0, 1]) + @pytest.mark.parametrize( + "ts_input", + [ + 1572136200000000000, + 1572136200000000000.0, + np.datetime64(1572136200000000000, "ns"), + "2019-10-27 01:30:00+01:00", + datetime(2019, 10, 27, 0, 30, 0, 0, tzinfo=timezone.utc), + ], + ) + def test_timestamp_constructor_fold_conflict(self, ts_input, fold): + # Test for GH#25057 + # Check that we raise on fold conflict + msg = ( + "Cannot pass fold with possibly unambiguous input: int, float, " + "numpy.datetime64, str, or timezone-aware datetime-like. " + "Pass naive datetime-like or build Timestamp from components." + ) + with pytest.raises(ValueError, match=msg): + Timestamp(ts_input=ts_input, fold=fold) + + @pytest.mark.parametrize("tz", ["dateutil/Europe/London", None]) + @pytest.mark.parametrize("fold", [0, 1]) + def test_timestamp_constructor_retain_fold(self, tz, fold): + # Test for GH#25057 + # Check that we retain fold + ts = Timestamp(year=2019, month=10, day=27, hour=1, minute=30, tz=tz, fold=fold) + result = ts.fold + expected = fold + assert result == expected + + try: + _tzs = [ + "dateutil/Europe/London", + zoneinfo.ZoneInfo("Europe/London"), + ] + except zoneinfo.ZoneInfoNotFoundError: + _tzs = ["dateutil/Europe/London"] + + @pytest.mark.parametrize("tz", _tzs) + @pytest.mark.parametrize( + "ts_input,fold_out", + [ + (1572136200000000000, 0), + (1572139800000000000, 1), + ("2019-10-27 01:30:00+01:00", 0), + ("2019-10-27 01:30:00+00:00", 1), + (datetime(2019, 10, 27, 1, 30, 0, 0, fold=0), 0), + (datetime(2019, 10, 27, 1, 30, 0, 0, fold=1), 1), + ], + ) + def test_timestamp_constructor_infer_fold_from_value(self, tz, ts_input, fold_out): + # Test for GH#25057 + # Check that we infer fold correctly based on timestamps since utc + # or strings + ts = Timestamp(ts_input, tz=tz) + result = ts.fold + expected = fold_out + assert result == expected + + @pytest.mark.parametrize("tz", ["dateutil/Europe/London"]) + @pytest.mark.parametrize( + "ts_input,fold,value_out", + [ + (datetime(2019, 10, 27, 1, 30, 0, 0), 0, 1572136200000000), + (datetime(2019, 10, 27, 1, 30, 0, 0), 1, 1572139800000000), + ], + ) + def test_timestamp_constructor_adjust_value_for_fold( + self, tz, ts_input, fold, value_out + ): + # Test for GH#25057 + # Check that we adjust value for fold correctly + # based on timestamps since utc + ts = Timestamp(ts_input, tz=tz, fold=fold) + result = ts._value + expected = value_out + assert result == expected + + +class TestTimestampConstructorPositionalAndKeywordSupport: + def test_constructor_positional(self): + # see GH#10758 + msg = ( + "'NoneType' object cannot be interpreted as an integer" + if PY310 + else "an integer is required" + ) + with pytest.raises(TypeError, match=msg): + Timestamp(2000, 1) + + msg = "month must be in 1..12" + with pytest.raises(ValueError, match=msg): + Timestamp(2000, 0, 1) + with pytest.raises(ValueError, match=msg): + Timestamp(2000, 13, 1) + + msg = "day is out of range for month" + with pytest.raises(ValueError, match=msg): + Timestamp(2000, 1, 0) + with pytest.raises(ValueError, match=msg): + Timestamp(2000, 1, 32) + + # see gh-11630 + assert repr(Timestamp(2015, 11, 12)) == repr(Timestamp("20151112")) + assert repr(Timestamp(2015, 11, 12, 1, 2, 3, 999999)) == repr( + Timestamp("2015-11-12 01:02:03.999999") + ) + + def test_constructor_keyword(self): + # GH#10758 + msg = "function missing required argument 'day'|Required argument 'day'" + with pytest.raises(TypeError, match=msg): + Timestamp(year=2000, month=1) + + msg = "month must be in 1..12" + with pytest.raises(ValueError, match=msg): + Timestamp(year=2000, month=0, day=1) + with pytest.raises(ValueError, match=msg): + Timestamp(year=2000, month=13, day=1) + + msg = "day is out of range for month" + with pytest.raises(ValueError, match=msg): + Timestamp(year=2000, month=1, day=0) + with pytest.raises(ValueError, match=msg): + Timestamp(year=2000, month=1, day=32) + + assert repr(Timestamp(year=2015, month=11, day=12)) == repr( + Timestamp("20151112") + ) + + assert repr( + Timestamp( + year=2015, + month=11, + day=12, + hour=1, + minute=2, + second=3, + microsecond=999999, + ) + ) == repr(Timestamp("2015-11-12 01:02:03.999999")) + + @pytest.mark.parametrize( + "arg", + [ + "year", + "month", + "day", + "hour", + "minute", + "second", + "microsecond", + "nanosecond", + ], + ) + def test_invalid_date_kwarg_with_string_input(self, arg): + kwarg = {arg: 1} + msg = "Cannot pass a date attribute keyword argument" + with pytest.raises(ValueError, match=msg): + Timestamp("2010-10-10 12:59:59.999999999", **kwarg) + + @pytest.mark.parametrize("kwargs", [{}, {"year": 2020}, {"year": 2020, "month": 1}]) + def test_constructor_missing_keyword(self, kwargs): + # GH#31200 + + # The exact error message of datetime() depends on its version + msg1 = r"function missing required argument '(year|month|day)' \(pos [123]\)" + msg2 = r"Required argument '(year|month|day)' \(pos [123]\) not found" + msg = "|".join([msg1, msg2]) + + with pytest.raises(TypeError, match=msg): + Timestamp(**kwargs) + + def test_constructor_positional_with_tzinfo(self): + # GH#31929 + ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc) + expected = Timestamp("2020-12-31", tzinfo=timezone.utc) + assert ts == expected + + @pytest.mark.parametrize("kwd", ["nanosecond", "microsecond", "second", "minute"]) + def test_constructor_positional_keyword_mixed_with_tzinfo(self, kwd, request): + # TODO: if we passed microsecond with a keyword we would mess up + # xref GH#45307 + if kwd != "nanosecond": + # nanosecond is keyword-only as of 2.0, others are not + mark = pytest.mark.xfail(reason="GH#45307") + request.applymarker(mark) + + kwargs = {kwd: 4} + ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc, **kwargs) + + td_kwargs = {kwd + "s": 4} + td = Timedelta(**td_kwargs) + expected = Timestamp("2020-12-31", tz=timezone.utc) + td + assert ts == expected + + +class TestTimestampClassMethodConstructors: + # Timestamp constructors other than __new__ + + def test_constructor_strptime(self): + # GH#25016 + # Test support for Timestamp.strptime + fmt = "%Y%m%d-%H%M%S-%f%z" + ts = "20190129-235348-000001+0000" + msg = r"Timestamp.strptime\(\) is not implemented" + with pytest.raises(NotImplementedError, match=msg): + Timestamp.strptime(ts, fmt) + + def test_constructor_fromisocalendar(self): + # GH#30395 + expected_timestamp = Timestamp("2000-01-03 00:00:00") + expected_stdlib = datetime.fromisocalendar(2000, 1, 1) + result = Timestamp.fromisocalendar(2000, 1, 1) + assert result == expected_timestamp + assert result == expected_stdlib + assert isinstance(result, Timestamp) + + def test_constructor_fromordinal(self): + base = datetime(2000, 1, 1) + + ts = Timestamp.fromordinal(base.toordinal()) + assert base == ts + assert base.toordinal() == ts.toordinal() + + ts = Timestamp.fromordinal(base.toordinal(), tz="US/Eastern") + assert Timestamp("2000-01-01", tz="US/Eastern") == ts + assert base.toordinal() == ts.toordinal() + + # GH#3042 + dt = datetime(2011, 4, 16, 0, 0) + ts = Timestamp.fromordinal(dt.toordinal()) + assert ts.to_pydatetime() == dt + + # with a tzinfo + stamp = Timestamp("2011-4-16", tz="US/Eastern") + dt_tz = stamp.to_pydatetime() + ts = Timestamp.fromordinal(dt_tz.toordinal(), tz="US/Eastern") + assert ts.to_pydatetime() == dt_tz + + def test_now(self): + # GH#9000 + ts_from_string = Timestamp("now") + ts_from_method = Timestamp.now() + ts_datetime = datetime.now() + + ts_from_string_tz = Timestamp("now", tz="US/Eastern") + ts_from_method_tz = Timestamp.now(tz="US/Eastern") + + # Check that the delta between the times is less than 1s (arbitrarily + # small) + delta = Timedelta(seconds=1) + assert abs(ts_from_method - ts_from_string) < delta + assert abs(ts_datetime - ts_from_method) < delta + assert abs(ts_from_method_tz - ts_from_string_tz) < delta + assert ( + abs( + ts_from_string_tz.tz_localize(None) + - ts_from_method_tz.tz_localize(None) + ) + < delta + ) + + def test_today(self): + ts_from_string = Timestamp("today") + ts_from_method = Timestamp.today() + ts_datetime = datetime.today() + + ts_from_string_tz = Timestamp("today", tz="US/Eastern") + ts_from_method_tz = Timestamp.today(tz="US/Eastern") + + # Check that the delta between the times is less than 1s (arbitrarily + # small) + delta = Timedelta(seconds=1) + assert abs(ts_from_method - ts_from_string) < delta + assert abs(ts_datetime - ts_from_method) < delta + assert abs(ts_from_method_tz - ts_from_string_tz) < delta + assert ( + abs( + ts_from_string_tz.tz_localize(None) + - ts_from_method_tz.tz_localize(None) + ) + < delta + ) + + +class TestTimestampResolutionInference: def test_construct_from_time_unit(self): # GH#54097 only passing a time component, no date ts = Timestamp("01:01:01.111") assert ts.unit == "ms" - def test_weekday_but_no_day_raises(self): - # GH#52659 - msg = "Parsing datetimes with weekday but no day information is not supported" - with pytest.raises(ValueError, match=msg): - Timestamp("2023 Sept Thu") - - def test_construct_from_string_invalid_raises(self): - # dateutil (weirdly) parses "200622-12-31" as - # datetime(2022, 6, 20, 12, 0, tzinfo=tzoffset(None, -111600) - # which besides being mis-parsed, is a tzoffset that will cause - # str(ts) to raise ValueError. Ensure we raise in the constructor - # instead. - # see test_to_datetime_malformed_raise for analogous to_datetime test - with pytest.raises(ValueError, match="gives an invalid tzoffset"): - Timestamp("200622-12-31") - def test_constructor_str_infer_reso(self): # non-iso8601 path @@ -72,58 +445,44 @@ def test_constructor_str_infer_reso(self): ts = Timestamp("300 June 1:30:01.300") assert ts.unit == "ms" - def test_constructor_from_iso8601_str_with_offset_reso(self): - # GH#49737 - ts = Timestamp("2016-01-01 04:05:06-01:00") - assert ts.unit == "s" - - ts = Timestamp("2016-01-01 04:05:06.000-01:00") - assert ts.unit == "ms" - - ts = Timestamp("2016-01-01 04:05:06.000000-01:00") - assert ts.unit == "us" - - ts = Timestamp("2016-01-01 04:05:06.000000001-01:00") - assert ts.unit == "ns" - - def test_constructor_from_date_second_reso(self): - # GH#49034 constructing from a pydate object gets lowest supported - # reso, i.e. seconds - obj = date(2012, 9, 1) - ts = Timestamp(obj) - assert ts.unit == "s" - - @pytest.mark.parametrize("typ", [int, float]) - def test_construct_from_int_float_with_unit_out_of_bound_raises(self, typ): - # GH#50870 make sure we get a OutOfBoundsDatetime instead of OverflowError - val = typ(150000000000000) - - msg = f"cannot convert input {val} with the unit 'D'" - with pytest.raises(OutOfBoundsDatetime, match=msg): - Timestamp(val, unit="D") - @pytest.mark.parametrize("typ", [int, float]) - def test_constructor_int_float_with_YM_unit(self, typ): - # GH#47266 avoid the conversions in cast_from_unit - val = typ(150) +class TestTimestampConstructors: + def test_weekday_but_no_day_raises(self): + # GH#52659 + msg = "Parsing datetimes with weekday but no day information is not supported" + with pytest.raises(ValueError, match=msg): + Timestamp("2023 Sept Thu") - ts = Timestamp(val, unit="Y") - expected = Timestamp("2120-01-01") - assert ts == expected + def test_construct_from_string_invalid_raises(self): + # dateutil (weirdly) parses "200622-12-31" as + # datetime(2022, 6, 20, 12, 0, tzinfo=tzoffset(None, -111600) + # which besides being mis-parsed, is a tzoffset that will cause + # str(ts) to raise ValueError. Ensure we raise in the constructor + # instead. + # see test_to_datetime_malformed_raise for analogous to_datetime test + with pytest.raises(ValueError, match="gives an invalid tzoffset"): + Timestamp("200622-12-31") - ts = Timestamp(val, unit="M") - expected = Timestamp("1982-07-01") - assert ts == expected + def test_constructor_from_iso8601_str_with_offset_reso(self): + # GH#49737 + ts = Timestamp("2016-01-01 04:05:06-01:00") + assert ts.unit == "s" - def test_constructor_float_not_round_with_YM_unit_deprecated(self): - # GH#47267 avoid the conversions in cast_from-unit + ts = Timestamp("2016-01-01 04:05:06.000-01:00") + assert ts.unit == "ms" - msg = "Conversion of non-round float with unit=[MY] is ambiguous" - with pytest.raises(ValueError, match=msg): - Timestamp(150.5, unit="Y") + ts = Timestamp("2016-01-01 04:05:06.000000-01:00") + assert ts.unit == "us" - with pytest.raises(ValueError, match=msg): - Timestamp(150.5, unit="M") + ts = Timestamp("2016-01-01 04:05:06.000000001-01:00") + assert ts.unit == "ns" + + def test_constructor_from_date_second_reso(self): + # GH#49034 constructing from a pydate object gets lowest supported + # reso, i.e. seconds + obj = date(2012, 9, 1) + ts = Timestamp(obj) + assert ts.unit == "s" def test_constructor_datetime64_with_tz(self): # GH#42288, GH#24559 @@ -319,15 +678,6 @@ def test_constructor_invalid_tz(self): # interpreted as `year` Timestamp("2012-01-01", "US/Pacific") - def test_constructor_strptime(self): - # GH25016 - # Test support for Timestamp.strptime - fmt = "%Y%m%d-%H%M%S-%f%z" - ts = "20190129-235348-000001+0000" - msg = r"Timestamp.strptime\(\) is not implemented" - with pytest.raises(NotImplementedError, match=msg): - Timestamp.strptime(ts, fmt) - def test_constructor_tz_or_tzinfo(self): # GH#17943, GH#17690, GH#5168 stamps = [ @@ -340,113 +690,6 @@ def test_constructor_tz_or_tzinfo(self): ] assert all(ts == stamps[0] for ts in stamps) - def test_constructor_positional_with_tzinfo(self): - # GH#31929 - ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc) - expected = Timestamp("2020-12-31", tzinfo=timezone.utc) - assert ts == expected - - @pytest.mark.parametrize("kwd", ["nanosecond", "microsecond", "second", "minute"]) - def test_constructor_positional_keyword_mixed_with_tzinfo(self, kwd, request): - # TODO: if we passed microsecond with a keyword we would mess up - # xref GH#45307 - if kwd != "nanosecond": - # nanosecond is keyword-only as of 2.0, others are not - mark = pytest.mark.xfail(reason="GH#45307") - request.applymarker(mark) - - kwargs = {kwd: 4} - ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc, **kwargs) - - td_kwargs = {kwd + "s": 4} - td = Timedelta(**td_kwargs) - expected = Timestamp("2020-12-31", tz=timezone.utc) + td - assert ts == expected - - def test_constructor_positional(self): - # see gh-10758 - msg = ( - "'NoneType' object cannot be interpreted as an integer" - if PY310 - else "an integer is required" - ) - with pytest.raises(TypeError, match=msg): - Timestamp(2000, 1) - - msg = "month must be in 1..12" - with pytest.raises(ValueError, match=msg): - Timestamp(2000, 0, 1) - with pytest.raises(ValueError, match=msg): - Timestamp(2000, 13, 1) - - msg = "day is out of range for month" - with pytest.raises(ValueError, match=msg): - Timestamp(2000, 1, 0) - with pytest.raises(ValueError, match=msg): - Timestamp(2000, 1, 32) - - # see gh-11630 - assert repr(Timestamp(2015, 11, 12)) == repr(Timestamp("20151112")) - assert repr(Timestamp(2015, 11, 12, 1, 2, 3, 999999)) == repr( - Timestamp("2015-11-12 01:02:03.999999") - ) - - def test_constructor_keyword(self): - # GH 10758 - msg = "function missing required argument 'day'|Required argument 'day'" - with pytest.raises(TypeError, match=msg): - Timestamp(year=2000, month=1) - - msg = "month must be in 1..12" - with pytest.raises(ValueError, match=msg): - Timestamp(year=2000, month=0, day=1) - with pytest.raises(ValueError, match=msg): - Timestamp(year=2000, month=13, day=1) - - msg = "day is out of range for month" - with pytest.raises(ValueError, match=msg): - Timestamp(year=2000, month=1, day=0) - with pytest.raises(ValueError, match=msg): - Timestamp(year=2000, month=1, day=32) - - assert repr(Timestamp(year=2015, month=11, day=12)) == repr( - Timestamp("20151112") - ) - - assert repr( - Timestamp( - year=2015, - month=11, - day=12, - hour=1, - minute=2, - second=3, - microsecond=999999, - ) - ) == repr(Timestamp("2015-11-12 01:02:03.999999")) - - def test_constructor_fromordinal(self): - base = datetime(2000, 1, 1) - - ts = Timestamp.fromordinal(base.toordinal()) - assert base == ts - assert base.toordinal() == ts.toordinal() - - ts = Timestamp.fromordinal(base.toordinal(), tz="US/Eastern") - assert Timestamp("2000-01-01", tz="US/Eastern") == ts - assert base.toordinal() == ts.toordinal() - - # GH#3042 - dt = datetime(2011, 4, 16, 0, 0) - ts = Timestamp.fromordinal(dt.toordinal()) - assert ts.to_pydatetime() == dt - - # with a tzinfo - stamp = Timestamp("2011-4-16", tz="US/Eastern") - dt_tz = stamp.to_pydatetime() - ts = Timestamp.fromordinal(dt_tz.toordinal(), tz="US/Eastern") - assert ts.to_pydatetime() == dt_tz - @pytest.mark.parametrize( "result", [ @@ -490,25 +733,6 @@ def test_constructor_invalid_Z0_isostring(self, z): with pytest.raises(ValueError, match=msg): Timestamp(f"2014-11-02 01:00{z}") - @pytest.mark.parametrize( - "arg", - [ - "year", - "month", - "day", - "hour", - "minute", - "second", - "microsecond", - "nanosecond", - ], - ) - def test_invalid_date_kwarg_with_string_input(self, arg): - kwarg = {arg: 1} - msg = "Cannot pass a date attribute keyword argument" - with pytest.raises(ValueError, match=msg): - Timestamp("2010-10-10 12:59:59.999999999", **kwarg) - def test_out_of_bounds_integer_value(self): # GH#26651 check that we raise OutOfBoundsDatetime, not OverflowError msg = str(Timestamp.max._value * 2) @@ -623,59 +847,6 @@ def test_max_valid(self): # Ensure that Timestamp.max is a valid Timestamp Timestamp(Timestamp.max) - def test_now(self): - # GH#9000 - ts_from_string = Timestamp("now") - ts_from_method = Timestamp.now() - ts_datetime = datetime.now() - - ts_from_string_tz = Timestamp("now", tz="US/Eastern") - ts_from_method_tz = Timestamp.now(tz="US/Eastern") - - # Check that the delta between the times is less than 1s (arbitrarily - # small) - delta = Timedelta(seconds=1) - assert abs(ts_from_method - ts_from_string) < delta - assert abs(ts_datetime - ts_from_method) < delta - assert abs(ts_from_method_tz - ts_from_string_tz) < delta - assert ( - abs( - ts_from_string_tz.tz_localize(None) - - ts_from_method_tz.tz_localize(None) - ) - < delta - ) - - def test_today(self): - ts_from_string = Timestamp("today") - ts_from_method = Timestamp.today() - ts_datetime = datetime.today() - - ts_from_string_tz = Timestamp("today", tz="US/Eastern") - ts_from_method_tz = Timestamp.today(tz="US/Eastern") - - # Check that the delta between the times is less than 1s (arbitrarily - # small) - delta = Timedelta(seconds=1) - assert abs(ts_from_method - ts_from_string) < delta - assert abs(ts_datetime - ts_from_method) < delta - assert abs(ts_from_method_tz - ts_from_string_tz) < delta - assert ( - abs( - ts_from_string_tz.tz_localize(None) - - ts_from_method_tz.tz_localize(None) - ) - < delta - ) - - @pytest.mark.parametrize("tz", [None, pytz.timezone("US/Pacific")]) - def test_disallow_setting_tz(self, tz): - # GH 3746 - ts = Timestamp("2010") - msg = "Cannot directly set timezone" - with pytest.raises(AttributeError, match=msg): - ts.tz = tz - @pytest.mark.parametrize("offset", ["+0300", "+0200"]) def test_construct_timestamp_near_dst(self, offset): # GH 20854 @@ -720,14 +891,88 @@ class SubDatetime(datetime): expected = Timestamp(2000, 1, 1) assert result == expected - def test_constructor_fromisocalendar(self): - # GH 30395 - expected_timestamp = Timestamp("2000-01-03 00:00:00") - expected_stdlib = datetime.fromisocalendar(2000, 1, 1) - result = Timestamp.fromisocalendar(2000, 1, 1) - assert result == expected_timestamp - assert result == expected_stdlib - assert isinstance(result, Timestamp) + def test_timestamp_constructor_tz_utc(self): + utc_stamp = Timestamp("3/11/2012 05:00", tz="utc") + assert utc_stamp.tzinfo is timezone.utc + assert utc_stamp.hour == 5 + + utc_stamp = Timestamp("3/11/2012 05:00").tz_localize("utc") + assert utc_stamp.hour == 5 + + def test_timestamp_to_datetime_tzoffset(self): + tzinfo = tzoffset(None, 7200) + expected = Timestamp("3/11/2012 04:00", tz=tzinfo) + result = Timestamp(expected.to_pydatetime()) + assert expected == result + + def test_timestamp_constructor_near_dst_boundary(self): + # GH#11481 & GH#15777 + # Naive string timestamps were being localized incorrectly + # with tz_convert_from_utc_single instead of tz_localize_to_utc + + for tz in ["Europe/Brussels", "Europe/Prague"]: + result = Timestamp("2015-10-25 01:00", tz=tz) + expected = Timestamp("2015-10-25 01:00").tz_localize(tz) + assert result == expected + + msg = "Cannot infer dst time from 2015-10-25 02:00:00" + with pytest.raises(pytz.AmbiguousTimeError, match=msg): + Timestamp("2015-10-25 02:00", tz=tz) + + result = Timestamp("2017-03-26 01:00", tz="Europe/Paris") + expected = Timestamp("2017-03-26 01:00").tz_localize("Europe/Paris") + assert result == expected + + msg = "2017-03-26 02:00" + with pytest.raises(pytz.NonExistentTimeError, match=msg): + Timestamp("2017-03-26 02:00", tz="Europe/Paris") + + # GH#11708 + naive = Timestamp("2015-11-18 10:00:00") + result = naive.tz_localize("UTC").tz_convert("Asia/Kolkata") + expected = Timestamp("2015-11-18 15:30:00+0530", tz="Asia/Kolkata") + assert result == expected + + # GH#15823 + result = Timestamp("2017-03-26 00:00", tz="Europe/Paris") + expected = Timestamp("2017-03-26 00:00:00+0100", tz="Europe/Paris") + assert result == expected + + result = Timestamp("2017-03-26 01:00", tz="Europe/Paris") + expected = Timestamp("2017-03-26 01:00:00+0100", tz="Europe/Paris") + assert result == expected + + msg = "2017-03-26 02:00" + with pytest.raises(pytz.NonExistentTimeError, match=msg): + Timestamp("2017-03-26 02:00", tz="Europe/Paris") + + result = Timestamp("2017-03-26 02:00:00+0100", tz="Europe/Paris") + naive = Timestamp(result.as_unit("ns")._value) + expected = naive.tz_localize("UTC").tz_convert("Europe/Paris") + assert result == expected + + result = Timestamp("2017-03-26 03:00", tz="Europe/Paris") + expected = Timestamp("2017-03-26 03:00:00+0200", tz="Europe/Paris") + assert result == expected + + @pytest.mark.parametrize( + "tz", + [ + pytz.timezone("US/Eastern"), + gettz("US/Eastern"), + "US/Eastern", + "dateutil/US/Eastern", + ], + ) + def test_timestamp_constructed_by_date_and_tz(self, tz): + # GH#2993, Timestamp cannot be constructed by datetime.date + # and tz correctly + + result = Timestamp(date(2012, 3, 11), tz=tz) + + expected = Timestamp("3/11/2012", tz=tz) + assert result.hour == expected.hour + assert result == expected def test_constructor_ambiguous_dst(): @@ -761,19 +1006,6 @@ def test_timestamp_constructor_identity(): assert result is expected -@pytest.mark.parametrize("kwargs", [{}, {"year": 2020}, {"year": 2020, "month": 1}]) -def test_constructor_missing_keyword(kwargs): - # GH 31200 - - # The exact error message of datetime() depends on its version - msg1 = r"function missing required argument '(year|month|day)' \(pos [123]\)" - msg2 = r"Required argument '(year|month|day)' \(pos [123]\) not found" - msg = "|".join([msg1, msg2]) - - with pytest.raises(TypeError, match=msg): - Timestamp(**kwargs) - - @pytest.mark.parametrize("nano", [-1, 1000]) def test_timestamp_nano_range(nano): # GH 48255 @@ -801,107 +1033,6 @@ def test_non_nano_value(): assert result == -52700112000 -def test_timestamp_constructor_invalid_fold_raise(): - # Test forGH #25057 - # Valid fold values are only [None, 0, 1] - msg = "Valid values for the fold argument are None, 0, or 1." - with pytest.raises(ValueError, match=msg): - Timestamp(123, fold=2) - - -def test_timestamp_constructor_pytz_fold_raise(): - # Test for GH#25057 - # pytz doesn't support fold. Check that we raise - # if fold is passed with pytz - msg = "pytz timezones do not support fold. Please use dateutil timezones." - tz = pytz.timezone("Europe/London") - with pytest.raises(ValueError, match=msg): - Timestamp(datetime(2019, 10, 27, 0, 30, 0, 0), tz=tz, fold=0) - - -@pytest.mark.parametrize("fold", [0, 1]) -@pytest.mark.parametrize( - "ts_input", - [ - 1572136200000000000, - 1572136200000000000.0, - np.datetime64(1572136200000000000, "ns"), - "2019-10-27 01:30:00+01:00", - datetime(2019, 10, 27, 0, 30, 0, 0, tzinfo=timezone.utc), - ], -) -def test_timestamp_constructor_fold_conflict(ts_input, fold): - # Test for GH#25057 - # Check that we raise on fold conflict - msg = ( - "Cannot pass fold with possibly unambiguous input: int, float, " - "numpy.datetime64, str, or timezone-aware datetime-like. " - "Pass naive datetime-like or build Timestamp from components." - ) - with pytest.raises(ValueError, match=msg): - Timestamp(ts_input=ts_input, fold=fold) - - -@pytest.mark.parametrize("tz", ["dateutil/Europe/London", None]) -@pytest.mark.parametrize("fold", [0, 1]) -def test_timestamp_constructor_retain_fold(tz, fold): - # Test for GH#25057 - # Check that we retain fold - ts = Timestamp(year=2019, month=10, day=27, hour=1, minute=30, tz=tz, fold=fold) - result = ts.fold - expected = fold - assert result == expected - - -try: - _tzs = [ - "dateutil/Europe/London", - zoneinfo.ZoneInfo("Europe/London"), - ] -except zoneinfo.ZoneInfoNotFoundError: - _tzs = ["dateutil/Europe/London"] - - -@pytest.mark.parametrize("tz", _tzs) -@pytest.mark.parametrize( - "ts_input,fold_out", - [ - (1572136200000000000, 0), - (1572139800000000000, 1), - ("2019-10-27 01:30:00+01:00", 0), - ("2019-10-27 01:30:00+00:00", 1), - (datetime(2019, 10, 27, 1, 30, 0, 0, fold=0), 0), - (datetime(2019, 10, 27, 1, 30, 0, 0, fold=1), 1), - ], -) -def test_timestamp_constructor_infer_fold_from_value(tz, ts_input, fold_out): - # Test for GH#25057 - # Check that we infer fold correctly based on timestamps since utc - # or strings - ts = Timestamp(ts_input, tz=tz) - result = ts.fold - expected = fold_out - assert result == expected - - -@pytest.mark.parametrize("tz", ["dateutil/Europe/London"]) -@pytest.mark.parametrize( - "ts_input,fold,value_out", - [ - (datetime(2019, 10, 27, 1, 30, 0, 0), 0, 1572136200000000), - (datetime(2019, 10, 27, 1, 30, 0, 0), 1, 1572139800000000), - ], -) -def test_timestamp_constructor_adjust_value_for_fold(tz, ts_input, fold, value_out): - # Test for GH#25057 - # Check that we adjust value for fold correctly - # based on timestamps since utc - ts = Timestamp(ts_input, tz=tz, fold=fold) - result = ts._value - expected = value_out - assert result == expected - - @pytest.mark.parametrize("na_value", [None, np.nan, np.datetime64("NaT"), NaT, NA]) def test_timestamp_constructor_na_value(na_value): # GH45481 diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index ded32a77cbaf7..685630cf2ed5f 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -262,6 +262,14 @@ def test_dow_parametric(self, ts, sign): class TestTimestamp: + @pytest.mark.parametrize("tz", [None, pytz.timezone("US/Pacific")]) + def test_disallow_setting_tz(self, tz): + # GH#3746 + ts = Timestamp("2010") + msg = "Cannot directly set timezone" + with pytest.raises(AttributeError, match=msg): + ts.tz = tz + def test_default_to_stdlib_utc(self): assert Timestamp.utcnow().tz is timezone.utc assert Timestamp.now("UTC").tz is timezone.utc @@ -379,52 +387,6 @@ def test_basics_nanos(self): assert stamp.microsecond == 145224 assert stamp.nanosecond == 192 - @pytest.mark.parametrize( - "value, check_kwargs", - [ - [946688461000000000, {}], - [946688461000000000 / 1000, {"unit": "us"}], - [946688461000000000 / 1_000_000, {"unit": "ms"}], - [946688461000000000 / 1_000_000_000, {"unit": "s"}], - [10957, {"unit": "D", "h": 0}], - [ - (946688461000000000 + 500000) / 1000000000, - {"unit": "s", "us": 499, "ns": 964}, - ], - [ - (946688461000000000 + 500000000) / 1000000000, - {"unit": "s", "us": 500000}, - ], - [(946688461000000000 + 500000) / 1000000, {"unit": "ms", "us": 500}], - [(946688461000000000 + 500000) / 1000, {"unit": "us", "us": 500}], - [(946688461000000000 + 500000000) / 1000000, {"unit": "ms", "us": 500000}], - [946688461000000000 / 1000.0 + 5, {"unit": "us", "us": 5}], - [946688461000000000 / 1000.0 + 5000, {"unit": "us", "us": 5000}], - [946688461000000000 / 1000000.0 + 0.5, {"unit": "ms", "us": 500}], - [946688461000000000 / 1000000.0 + 0.005, {"unit": "ms", "us": 5, "ns": 5}], - [946688461000000000 / 1000000000.0 + 0.5, {"unit": "s", "us": 500000}], - [10957 + 0.5, {"unit": "D", "h": 12}], - ], - ) - def test_unit(self, value, check_kwargs): - def check(value, unit=None, h=1, s=1, us=0, ns=0): - stamp = Timestamp(value, unit=unit) - assert stamp.year == 2000 - assert stamp.month == 1 - assert stamp.day == 1 - assert stamp.hour == h - if unit != "D": - assert stamp.minute == 1 - assert stamp.second == s - assert stamp.microsecond == us - else: - assert stamp.minute == 0 - assert stamp.second == 0 - assert stamp.microsecond == 0 - assert stamp.nanosecond == ns - - check(value, **check_kwargs) - def test_roundtrip(self): # test value to string and back conversions # further test accessors diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index 3a6953af4337e..7d5b953c2ab16 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -2,18 +2,13 @@ Tests for Timestamp timezone-related methods """ from datetime import ( - date, datetime, timedelta, - timezone, ) import re import dateutil -from dateutil.tz import ( - gettz, - tzoffset, -) +from dateutil.tz import gettz import pytest import pytz from pytz.exceptions import ( @@ -374,90 +369,6 @@ def test_tz_convert_utc_with_system_utc(self): 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 timezone.utc - assert utc_stamp.hour == 5 - - utc_stamp = Timestamp("3/11/2012 05:00").tz_localize("utc") - assert utc_stamp.hour == 5 - - def test_timestamp_to_datetime_tzoffset(self): - tzinfo = tzoffset(None, 7200) - expected = Timestamp("3/11/2012 04:00", tz=tzinfo) - result = Timestamp(expected.to_pydatetime()) - assert expected == result - - def test_timestamp_constructor_near_dst_boundary(self): - # GH#11481 & GH#15777 - # Naive string timestamps were being localized incorrectly - # with tz_convert_from_utc_single instead of tz_localize_to_utc - - for tz in ["Europe/Brussels", "Europe/Prague"]: - result = Timestamp("2015-10-25 01:00", tz=tz) - expected = Timestamp("2015-10-25 01:00").tz_localize(tz) - assert result == expected - - msg = "Cannot infer dst time from 2015-10-25 02:00:00" - with pytest.raises(pytz.AmbiguousTimeError, match=msg): - Timestamp("2015-10-25 02:00", tz=tz) - - result = Timestamp("2017-03-26 01:00", tz="Europe/Paris") - expected = Timestamp("2017-03-26 01:00").tz_localize("Europe/Paris") - assert result == expected - - msg = "2017-03-26 02:00" - with pytest.raises(pytz.NonExistentTimeError, match=msg): - Timestamp("2017-03-26 02:00", tz="Europe/Paris") - - # GH#11708 - naive = Timestamp("2015-11-18 10:00:00") - result = naive.tz_localize("UTC").tz_convert("Asia/Kolkata") - expected = Timestamp("2015-11-18 15:30:00+0530", tz="Asia/Kolkata") - assert result == expected - - # GH#15823 - result = Timestamp("2017-03-26 00:00", tz="Europe/Paris") - expected = Timestamp("2017-03-26 00:00:00+0100", tz="Europe/Paris") - assert result == expected - - result = Timestamp("2017-03-26 01:00", tz="Europe/Paris") - expected = Timestamp("2017-03-26 01:00:00+0100", tz="Europe/Paris") - assert result == expected - - msg = "2017-03-26 02:00" - with pytest.raises(pytz.NonExistentTimeError, match=msg): - Timestamp("2017-03-26 02:00", tz="Europe/Paris") - - result = Timestamp("2017-03-26 02:00:00+0100", tz="Europe/Paris") - naive = Timestamp(result.as_unit("ns")._value) - expected = naive.tz_localize("UTC").tz_convert("Europe/Paris") - assert result == expected - - result = Timestamp("2017-03-26 03:00", tz="Europe/Paris") - expected = Timestamp("2017-03-26 03:00:00+0200", tz="Europe/Paris") - assert result == expected - - @pytest.mark.parametrize( - "tz", - [ - pytz.timezone("US/Eastern"), - gettz("US/Eastern"), - "US/Eastern", - "dateutil/US/Eastern", - ], - ) - def test_timestamp_constructed_by_date_and_tz(self, tz): - # GH#2993, Timestamp cannot be constructed by datetime.date - # and tz correctly - - result = Timestamp(date(2012, 3, 11), tz=tz) - - expected = Timestamp("3/11/2012", tz=tz) - assert result.hour == expected.hour - assert result == expected @pytest.mark.parametrize( "tz",