From f50328db45c4fcc4a32f94b96a516e5bca0af711 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 24 Mar 2023 16:38:15 -0700 Subject: [PATCH] BUG: construct Timestamp with year out of pydatetime range --- pandas/_libs/tslibs/timestamps.pyx | 47 +++++++++++++++++++ .../scalar/timestamp/test_constructors.py | 20 ++++++++ 2 files changed, 67 insertions(+) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 10a331f302cc4..7d58dd3615663 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1625,6 +1625,30 @@ class Timestamp(_Timestamp): # When year, month or day is not given, we call the datetime # constructor to make sure we get the same error message # since Timestamp inherits datetime + + if year is not None and not (0 < year < 10_000): + # GH#52091 + obj = cls( + year=1970, + month=month, + day=day, + hour=hour, + minute=minute, + second=second, + microsecond=microsecond, + nanosecond=nanosecond, + fold=fold, + tz=tz, + tzinfo=tzinfo, + unit=unit, + ) + if nanosecond is not None and nanosecond != 0: + raise OutOfBoundsDatetime( + f"Cannot construct a Timestamp with year={year} and " + "non-zero nanoseconds" + ) + return obj.as_unit("us").replace(year=year) + datetime_kwargs = { "hour": hour or 0, "minute": minute or 0, @@ -1645,6 +1669,29 @@ class Timestamp(_Timestamp): # User passed positional arguments: # Timestamp(year, month, day[, hour[, minute[, second[, # microsecond[, tzinfo]]]]]) + if not (0 < ts_input < 10_000): + # GH#52091 + obj = cls( + 1970, + year, + month, + day, + hour, + minute, + second, + microsecond, + nanosecond=nanosecond, + fold=fold, + tz=tz, + tzinfo=tzinfo, + unit=unit, + ) + if nanosecond is not None and nanosecond != 0: + raise OutOfBoundsDatetime( + f"Cannot construct a Timestamp with year={ts_input} and " + "non-zero nanoseconds" + ) + return obj.as_unit("us").replace(year=ts_input) ts_input = datetime(ts_input, year, month, day or 0, hour or 0, minute or 0, second or 0, fold=fold or 0) unit = None diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py index ca0796e55f28d..d5ac987f58183 100644 --- a/pandas/tests/scalar/timestamp/test_constructors.py +++ b/pandas/tests/scalar/timestamp/test_constructors.py @@ -30,6 +30,26 @@ class TestTimestampConstructors: + def test_construct_year_out_of_pydatetime_bounds(self): + # GH#52091 pass a year outside of pydatetime bounds either as positional + # or keyword argument + ts = Timestamp(year=21000, month=1, day=2) + assert ts.year == 21000 + assert ts.month == 1 + assert ts.day == 2 + assert ts.unit == "us" + + ts2 = Timestamp(21000, 1, 2) + assert ts2 == ts + assert ts2.unit == "us" + + msg = "Cannot construct a Timestamp with year=21000 and non-zero nanoseconds" + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp(year=21000, month=1, day=2, nanosecond=1) + + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp(21000, 1, 2, nanosecond=1) + 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)