Skip to content

BUG: Timestamp fails when fold is passed with positional args #52221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,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
Expand Down
7 changes: 6 additions & 1 deletion pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1581,7 +1581,12 @@ class Timestamp(_Timestamp):
"Valid values for the fold argument are None, 0, or 1."
)

if (ts_input is not _no_input and not (
# GH#52117 note that currently we end up with the year in the ts_input
# variable when using positional arguments. We should fix this.
from_positional = (ts_input is not None and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the ts_input check be for _no_input?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually, yes. We have changed the code since pandas 1.5. [Before] (

if tzinfo is not None:
), we were building a datetime where we now only raise:

        if tzinfo is not None:
            # GH#17690 tzinfo must be a datetime.tzinfo object, ensured
            #  by the cython annotation.
            if tz is not None:
                if (is_integer_object(tz)
                    and is_integer_object(ts_input)
                    and is_integer_object(freq)
                ):
                    # GH#31929 e.g. Timestamp(2019, 3, 4, 5, 6, tzinfo=foo)
                    # TODO(GH#45307): this will still be fragile to
                    #  mixed-and-matched positional/keyword arguments
                    ts_input = datetime(
                        ts_input,
                        freq,
                        tz,
                        unit or 0,
                        year or 0,
                        month or 0,
                        day or 0,
                        fold=fold or 0,
                    )
                    nanosecond = hour
                    tz = tzinfo
                    return cls(ts_input, nanosecond=nanosecond, tz=tz)

                raise ValueError('Can provide at most one of tz, tzinfo')

            # User passed tzinfo instead of tz; avoid silently ignoring
            tz, tzinfo = tzinfo, None

This is what it we do now instead:

        if tzinfo is not None:
            # GH#17690 tzinfo must be a datetime.tzinfo object, ensured
            #  by the cython annotation.
            if tz is not None:
                raise ValueError("Can provide at most one of tz, tzinfo")

            # User passed tzinfo instead of tz; avoid silently ignoring
            tz, tzinfo = tzinfo, None

Let me try to fix this by shifting the args one to the right instead.

year is not None and month is not None)

if (ts_input is not _no_input and not from_positional and not (
PyDateTime_Check(ts_input) and
getattr(ts_input, "tzinfo", None) is None)):
raise ValueError(
Expand Down
10 changes: 10 additions & 0 deletions pandas/tests/scalar/timestamp/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,3 +899,13 @@ 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