diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 9e4bba1cf3544..4dcb3db0c2d72 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1626,6 +1626,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, @@ -1646,6 +1670,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 5fca577ff28d1..d9f73b632d5b9 100644 --- a/pandas/tests/scalar/timestamp/test_constructors.py +++ b/pandas/tests/scalar/timestamp/test_constructors.py @@ -25,6 +25,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_weekday_but_no_day_raises(self): # GH#52659 msg = "Parsing datetimes with weekday but no day information is not supported"