diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index bac567b537edc..4486d9217bfae 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -154,6 +154,7 @@ Datetimelike - Bug in :meth:`Timestamp.round` with values close to the implementation bounds returning incorrect results instead of raising ``OutOfBoundsDatetime`` (:issue:`51494`) - :meth:`arrays.DatetimeArray.map` can now take a ``na_action`` argument. :meth:`DatetimeIndex.map` with ``na_action="ignore"`` now works as expected. (:issue:`51644`) - Bug in :meth:`arrays.DatetimeArray.map` and :meth:`DatetimeIndex.map`, where the supplied callable operated array-wise instead of element-wise (:issue:`51977`) +- Bug in :class:`Timestamp` raising an error when passing fold when constructing from positional arguments. - Timedelta diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 10a331f302cc4..6307323d42f2f 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1269,7 +1269,23 @@ cdef class _Timestamp(ABCTimestamp): # Python front end to C extension type _Timestamp # This serves as the box for datetime64 +def _fix_positional_arguments(cls): + original_new = cls.__new__ + + def updated_new(cls, *args, **kwargs): + # GH#52117 If we passed positional args, then _ts_input + # is now set to year and the other positional args + # are shifted to the left. Let's shift back + if len(args) > 2 and all(isinstance(arg, int) for arg in args[:3]): + return original_new(cls, _no_input, *args, **kwargs) + else: + return original_new(cls, *args, **kwargs) + + cls.__new__ = updated_new + return cls + +@_fix_positional_arguments class Timestamp(_Timestamp): """ Pandas replacement for python datetime.datetime object. diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py index 02244c1686cab..8c88ceac7d8c9 100644 --- a/pandas/tests/scalar/timestamp/test_constructors.py +++ b/pandas/tests/scalar/timestamp/test_constructors.py @@ -340,12 +340,7 @@ def test_constructor_positional_with_tzinfo(self): @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.node.add_marker(mark) + # GH#52221 makes a mix of positional and keyword arguments behave consistently kwargs = {kwd: 4} ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc, **kwargs) @@ -899,3 +894,21 @@ def test_timestamp_constructor_adjust_value_for_fold(tz, ts_input, fold, value_o result = ts._value expected = value_out assert result == expected + + +@pytest.mark.parametrize("tz", ["dateutil/Europe/London"]) +def test_timestamp_constructor_positional_with_fold(tz): + # Check that we build an object successfully + # if we pass positional arguments and fold + ts = Timestamp(2019, 10, 27, 1, 30, tz=tz, fold=0) + result = ts._value + expected = 1572136200000000 + assert result == expected + + +def test_timestamp_constructor_arg_shift(): + # Check that passing a positional argument as keyword + # does not change the value + result = Timestamp(2019, 10, 27, minute=30) + expected = Timestamp(2019, 10, 27, 0, 30) + assert result == expected