Skip to content

Warn when creating Period with a string that includes timezone information #47990

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
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.5.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Bug fixes
- Bug in :meth:`.Styler.to_excel` leading to error when unrecognized ``border-style`` (e.g. ``"hair"``) provided to Excel writers (:issue:`48649`)
- Bug when chaining several :meth:`.Styler.concat` calls, only the last styler was concatenated (:issue:`49207`)
- Fixed bug when instantiating a :class:`DataFrame` subclass inheriting from ``typing.Generic`` that triggered a ``UserWarning`` on python 3.11 (:issue:`49649`)
- :class:`Period` now raises a warning when created with data that contains timezone information. This is necessary because :class:`Period`, :class:`PeriodArray` and :class:`PeriodIndex` do not support timezones and hence drop any timezone information used when creating them. (:issue:`47005`)
-

.. ---------------------------------------------------------------------------
Expand Down
9 changes: 9 additions & 0 deletions pandas/_libs/tslibs/period.pyx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import warnings

cimport numpy as cnp
from cpython.object cimport (
Py_EQ,
Expand Down Expand Up @@ -2613,6 +2615,13 @@ class Period(_Period):
raise ValueError(msg)

if ordinal is None:
if dt.tzinfo:
# GH 47005
warnings.warn(
"The pandas.Period class does not support timezones. "
f"The timezone given in '{value}' will be ignored.",
UserWarning
)
base = freq_to_dtype_code(freq)
ordinal = period_ordinal(dt.year, dt.month, dt.day,
dt.hour, dt.minute, dt.second,
Expand Down
6 changes: 5 additions & 1 deletion pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,11 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: dt.datetime):
-------
lower, upper: pd.Timestamp
"""
per = Period(parsed, freq=reso.attr_abbrev)
with warnings.catch_warnings():
# Period looses tzinfo. We ignore the corresponding warning here,
# and add the lost tzinfo below.
warnings.simplefilter("ignore", UserWarning)
per = Period(parsed, freq=reso.attr_abbrev)
start, end = per.start_time, per.end_time

# GH 24076
Expand Down
5 changes: 4 additions & 1 deletion pandas/plotting/_matplotlib/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Generator,
cast,
)
import warnings

from dateutil.relativedelta import relativedelta
import matplotlib.dates as mdates
Expand Down Expand Up @@ -262,7 +263,9 @@ def get_datevalue(date, freq):
if isinstance(date, Period):
return date.asfreq(freq).ordinal
elif isinstance(date, (str, datetime, pydt.date, pydt.time, np.datetime64)):
return Period(date, freq).ordinal
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
return Period(date, freq).ordinal
elif (
is_integer(date)
or is_float(date)
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/arrays/test_datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ class TestPeriodArray(SharedTests):
index_cls = PeriodIndex
array_cls = PeriodArray
scalar_type = Period
example_dtype = PeriodIndex([], freq="W").dtype
example_dtype = PeriodIndex([], freq="Q").dtype

@pytest.fixture
def arr1d(self, period_index):
Expand Down
16 changes: 10 additions & 6 deletions pandas/tests/indexes/datetimes/methods/test_to_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,13 @@ def test_to_period_millisecond(self):
)

with tm.assert_produces_warning(UserWarning):
# warning that timezone info will be lost
# GH 21333 - warning that timezone info will be lost
period = index.to_period(freq="L")
assert 2 == len(period)
assert period[0] == Period("2007-01-01 10:11:12.123Z", "L")
assert period[1] == Period("2007-01-01 10:11:13.789Z", "L")
with tm.assert_produces_warning(UserWarning):
# GH 47005 - warning that timezone info will be lost
assert period[0] == Period("2007-01-01 10:11:12.123Z", "L")
assert period[1] == Period("2007-01-01 10:11:13.789Z", "L")

def test_to_period_microsecond(self):
index = DatetimeIndex(
Expand All @@ -132,11 +134,13 @@ def test_to_period_microsecond(self):
)

with tm.assert_produces_warning(UserWarning):
# warning that timezone info will be lost
# GH 21333 - warning that timezone info will be lost
period = index.to_period(freq="U")
assert 2 == len(period)
assert period[0] == Period("2007-01-01 10:11:12.123456Z", "U")
assert period[1] == Period("2007-01-01 10:11:13.789123Z", "U")
with tm.assert_produces_warning(UserWarning):
# GH 47005 - warning that timezone info will be lost
assert period[0] == Period("2007-01-01 10:11:12.123456Z", "U")
assert period[1] == Period("2007-01-01 10:11:13.789123Z", "U")

@pytest.mark.parametrize(
"tz",
Expand Down
22 changes: 21 additions & 1 deletion pandas/tests/indexes/period/test_period_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,17 @@ def test_required_arguments(self):
with pytest.raises(ValueError, match=msg):
period_range("2011-1-1", "2012-1-1", "B")

@pytest.mark.parametrize("freq", ["D", "W", "M", "Q", "A"])
@pytest.mark.parametrize(
"freq",
[
"D",
# Parsing week strings is not fully supported. See GH 48000.
pytest.param("W", marks=pytest.mark.filterwarnings("ignore:.*timezone")),
"M",
"Q",
"A",
],
)
def test_construction_from_string(self, freq):
# non-empty
expected = date_range(
Expand Down Expand Up @@ -119,3 +129,13 @@ def test_errors(self):
msg = "periods must be a number, got foo"
with pytest.raises(TypeError, match=msg):
period_range(start="2017Q1", periods="foo")


def test_range_tz():
# GH 47005 Time zone should be ignored with warning.
with tm.assert_produces_warning(UserWarning):
pi_tz = period_range(
"2022-01-01 06:00:00+02:00", "2022-01-01 09:00:00+02:00", freq="H"
)
pi_naive = period_range("2022-01-01 06:00:00", "2022-01-01 09:00:00", freq="H")
tm.assert_index_equal(pi_tz, pi_naive)
8 changes: 8 additions & 0 deletions pandas/tests/indexing/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,11 @@ def test_getitem_str_slice_millisecond_resolution(self, frame_or_series):
],
)
tm.assert_equal(result, expected)


def test_slice_with_datestring_tz():
# GH 24076
# GH 16785
df = DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))
sliced = df["2019-01-01 12:00:00+04:00":"2019-01-01 13:00:00+04:00"]
tm.assert_frame_equal(sliced, df)
39 changes: 24 additions & 15 deletions pandas/tests/resample/test_period_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,16 @@ def test_with_local_timezone_pytz(self):

series = Series(1, index=index)
series = series.tz_convert(local_timezone)
result = series.resample("D", kind="period").mean()

# Create the expected series
# Index is moved back a day with the timezone conversion from UTC to
# Pacific
expected_index = period_range(start=start, end=end, freq="D") - offsets.Day()
# see gh-47005
with tm.assert_produces_warning(UserWarning):
result = series.resample("D", kind="period").mean()

# Create the expected series
# Index is moved back a day with the timezone conversion from UTC to
# Pacific
expected_index = (
period_range(start=start, end=end, freq="D") - offsets.Day()
)
expected = Series(1.0, index=expected_index)
tm.assert_series_equal(result, expected)

Expand Down Expand Up @@ -304,14 +308,16 @@ def test_with_local_timezone_dateutil(self):

series = Series(1, index=index)
series = series.tz_convert(local_timezone)
result = series.resample("D", kind="period").mean()

# Create the expected series
# Index is moved back a day with the timezone conversion from UTC to
# Pacific
expected_index = (
period_range(start=start, end=end, freq="D", name="idx") - offsets.Day()
)
# see gh-47005
with tm.assert_produces_warning(UserWarning):
result = series.resample("D", kind="period").mean()

# Create the expected series
# Index is moved back a day with the timezone conversion from UTC to
# Pacific
expected_index = (
period_range(start=start, end=end, freq="D", name="idx") - offsets.Day()
)
expected = Series(1.0, index=expected_index)
tm.assert_series_equal(result, expected)

Expand Down Expand Up @@ -504,7 +510,10 @@ def test_resample_tz_localized(self):
tm.assert_series_equal(result, expected)

# for good measure
result = s.resample("D", kind="period").mean()
# see gh-47005
with tm.assert_produces_warning(UserWarning):
result = s.resample("D", kind="period").mean()

ex_index = period_range("2001-09-20", periods=1, freq="D")
expected = Series([1.5], index=ex_index)
tm.assert_series_equal(result, expected)
Expand Down
15 changes: 15 additions & 0 deletions pandas/tests/scalar/period/test_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -1555,3 +1555,18 @@ def test_invalid_frequency_error_message():
msg = "Invalid frequency: <WeekOfMonth: week=0, weekday=0>"
with pytest.raises(ValueError, match=msg):
Period("2012-01-02", freq="WOM-1MON")


@pytest.mark.parametrize(
"val",
[
("20220101T123456", "Z"),
("2012-12-12T06:06:06", "-06:00"),
],
)
def test_period_with_timezone(val):
# GH 47005 Time zone should be ignored with warning.
with tm.assert_produces_warning(UserWarning):
p_tz = Period("".join(val), freq="s")
p_naive = Period(val[0], freq="s")
assert p_tz == p_naive