Skip to content

DEPR: Timestamp.freq #41586

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

Merged
merged 20 commits into from
Jun 7, 2021
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 2 additions & 2 deletions pandas/_libs/tslibs/timestamps.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ cdef object create_timestamp_from_ts(int64_t value,
cdef class _Timestamp(ABCTimestamp):
cdef readonly:
int64_t value, nanosecond
object freq
object _freq

cdef bint _get_start_end_field(self, str field, freq)
cdef _get_date_name_field(self, str field, object locale)
Expand All @@ -27,4 +27,4 @@ cdef class _Timestamp(ABCTimestamp):
cdef bint _compare_outside_nanorange(_Timestamp self, datetime other,
int op) except -1
cpdef void _set_freq(self, freq)
cpdef bint _needs_field_deprecation_warning(_Timestamp self, freq)
cdef _warn_on_field_deprecation(_Timestamp self, freq, str field)
3 changes: 3 additions & 0 deletions pandas/_libs/tslibs/timestamps.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ from typing import (
import numpy as np

from pandas._libs.tslibs import (
BaseOffset,
NaT,
NaTType,
Period,
Expand Down Expand Up @@ -57,6 +58,8 @@ class Timestamp(datetime):
fold: int | None= ...,
) -> _S | NaTType: ...

def _set_freq(self, freq: BaseOffset | None) -> None: ...

@property
def year(self) -> int: ...
@property
Expand Down
143 changes: 68 additions & 75 deletions pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ cdef inline object create_timestamp_from_ts(int64_t value,
dts.day, dts.hour, dts.min,
dts.sec, dts.us, tz, fold=fold)
ts_base.value = value
ts_base.freq = freq
ts_base._freq = freq
ts_base.nanosecond = dts.ps // 1000

return ts_base
Expand Down Expand Up @@ -156,10 +156,19 @@ cdef class _Timestamp(ABCTimestamp):
dayofyear = _Timestamp.day_of_year

cpdef void _set_freq(self, freq):
# set the .freq attribute without going through the constructor,
# set the ._freq attribute without going through the constructor,
# which would issue a warning
# Caller is responsible for validation
self.freq = freq
self._freq = freq

@property
def freq(self):
warnings.warn(
"Timestamp.freq is deprecated and will be removed in a future version",
FutureWarning,
stacklevel=1,
)
return self._freq

def __hash__(_Timestamp self):
if self.nanosecond:
Expand Down Expand Up @@ -271,7 +280,7 @@ cdef class _Timestamp(ABCTimestamp):
nanos = delta_to_nanoseconds(other)
result = type(self)(self.value + nanos, tz=self.tzinfo)
if result is not NaT:
result._set_freq(self.freq) # avoid warning in constructor
result._set_freq(self._freq) # avoid warning in constructor
return result

elif is_integer_object(other):
Expand Down Expand Up @@ -379,7 +388,7 @@ cdef class _Timestamp(ABCTimestamp):
if freq:
kwds = freq.kwds
month_kw = kwds.get('startingMonth', kwds.get('month', 12))
freqstr = self.freqstr
freqstr = self._freqstr
else:
month_kw = 12
freqstr = None
Expand All @@ -389,19 +398,30 @@ cdef class _Timestamp(ABCTimestamp):
field, freqstr, month_kw)
return out[0]

cpdef bint _needs_field_deprecation_warning(self, freq):
cdef _warn_on_field_deprecation(self, freq, str field):
"""
Will the removal of .freq change the value of start/end properties?
Warn if the removal of .freq change the value of start/end properties.
"""
cdef:
bint needs = False

if freq is not None:
kwds = freq.kwds
month_kw = kwds.get("startingMonth", kwds.get("month", 12))
freqstr = self.freqstr
freqstr = self._freqstr
if month_kw != 12:
return True
needs = True
if freqstr.startswith("B"):
return True
return False
needs = True

if needs:
warnings.warn(
"Timestamp.freq is deprecated and will be removed in a future "
"version. When you have a freq, use "
f"freq.{field}(timestamp) instead",
FutureWarning,
stacklevel=1,
)

@property
def is_month_start(self) -> bool:
Expand All @@ -418,17 +438,11 @@ cdef class _Timestamp(ABCTimestamp):
>>> ts.is_month_start
True
"""
if self.freq is None:
if self._freq is None:
# fast-path for non-business frequencies
return self.day == 1
if self._needs_field_deprecation_warning(self.freq):
warnings.warn(
"Timestamp.freq is deprecated and will be removed in a future version. "
"When you have a freq, use freq.is_month_start(timestamp) instead",
FutureWarning,
stacklevel=2,
)
return self._get_start_end_field("is_month_start", self.freq)
self._warn_on_field_deprecation(self._freq, "is_month_start")
return self._get_start_end_field("is_month_start", self._freq)

@property
def is_month_end(self) -> bool:
Expand All @@ -445,17 +459,11 @@ cdef class _Timestamp(ABCTimestamp):
>>> ts.is_month_end
True
"""
if self.freq is None:
if self._freq is None:
# fast-path for non-business frequencies
return self.day == self.days_in_month
if self._needs_field_deprecation_warning(self.freq):
warnings.warn(
"Timestamp.freq is deprecated and will be removed in a future version. "
"When you have a freq, use freq.is_month_end(timestamp) instead",
FutureWarning,
stacklevel=2,
)
return self._get_start_end_field("is_month_end", self.freq)
self._warn_on_field_deprecation(self._freq, "is_month_end")
return self._get_start_end_field("is_month_end", self._freq)

@property
def is_quarter_start(self) -> bool:
Expand All @@ -472,17 +480,11 @@ cdef class _Timestamp(ABCTimestamp):
>>> ts.is_quarter_start
True
"""
if self.freq is None:
if self._freq is None:
# fast-path for non-business frequencies
return self.day == 1 and self.month % 3 == 1
if self._needs_field_deprecation_warning(self.freq):
warnings.warn(
"Timestamp.freq is deprecated and will be removed in a future version. "
"When you have a freq, use freq.is_quarter_start(timestamp) instead",
FutureWarning,
stacklevel=2,
)
return self._get_start_end_field("is_quarter_start", self.freq)
self._warn_on_field_deprecation(self._freq, "is_quarter_start")
return self._get_start_end_field("is_quarter_start", self._freq)

@property
def is_quarter_end(self) -> bool:
Expand All @@ -499,17 +501,11 @@ cdef class _Timestamp(ABCTimestamp):
>>> ts.is_quarter_end
True
"""
if self.freq is None:
if self._freq is None:
# fast-path for non-business frequencies
return (self.month % 3) == 0 and self.day == self.days_in_month
if self._needs_field_deprecation_warning(self.freq):
warnings.warn(
"Timestamp.freq is deprecated and will be removed in a future version. "
"When you have a freq, use freq.is_quarter_end(timestamp) instead",
FutureWarning,
stacklevel=2,
)
return self._get_start_end_field("is_quarter_end", self.freq)
self._warn_on_field_deprecation(self._freq, "is_quarter_end")
return self._get_start_end_field("is_quarter_end", self._freq)

@property
def is_year_start(self) -> bool:
Expand All @@ -526,17 +522,11 @@ cdef class _Timestamp(ABCTimestamp):
>>> ts.is_year_start
True
"""
if self.freq is None:
if self._freq is None:
# fast-path for non-business frequencies
return self.day == self.month == 1
if self._needs_field_deprecation_warning(self.freq):
warnings.warn(
"Timestamp.freq is deprecated and will be removed in a future version. "
"When you have a freq, use freq.is_year_start(timestamp) instead",
FutureWarning,
stacklevel=2,
)
return self._get_start_end_field("is_year_start", self.freq)
self._warn_on_field_deprecation(self._freq, "is_year_start")
return self._get_start_end_field("is_year_start", self._freq)

@property
def is_year_end(self) -> bool:
Expand All @@ -553,17 +543,11 @@ cdef class _Timestamp(ABCTimestamp):
>>> ts.is_year_end
True
"""
if self.freq is None:
if self._freq is None:
# fast-path for non-business frequencies
return self.month == 12 and self.day == 31
if self._needs_field_deprecation_warning(self.freq):
warnings.warn(
"Timestamp.freq is deprecated and will be removed in a future version. "
"When you have a freq, use freq.is_year_end(timestamp) instead",
FutureWarning,
stacklevel=2,
)
return self._get_start_end_field("is_year_end", self.freq)
self._warn_on_field_deprecation(self._freq, "is_year_end")
return self._get_start_end_field("is_year_end", self._freq)

cdef _get_date_name_field(self, str field, object locale):
cdef:
Expand Down Expand Up @@ -736,11 +720,11 @@ cdef class _Timestamp(ABCTimestamp):

def __setstate__(self, state):
self.value = state[0]
self.freq = state[1]
self._freq = state[1]
self.tzinfo = state[2]

def __reduce__(self):
object_state = self.value, self.freq, self.tzinfo
object_state = self.value, self._freq, self.tzinfo
return (Timestamp, object_state)

# -----------------------------------------------------------------
Expand Down Expand Up @@ -782,7 +766,7 @@ cdef class _Timestamp(ABCTimestamp):
pass

tz = f", tz='{zone}'" if zone is not None else ""
freq = "" if self.freq is None else f", freq='{self.freqstr}'"
freq = "" if self._freq is None else f", freq='{self._freqstr}'"

return f"Timestamp('{stamp}'{tz}{freq})"

Expand Down Expand Up @@ -940,7 +924,7 @@ cdef class _Timestamp(ABCTimestamp):
)

if freq is None:
freq = self.freq
freq = self._freq
warnings.warn(
"In a future version, calling 'Timestamp.to_period()' without "
"passing a 'freq' will raise an exception.",
Expand Down Expand Up @@ -1345,7 +1329,7 @@ class Timestamp(_Timestamp):

if freq is None:
# GH 22311: Try to extract the frequency of a given Timestamp input
freq = getattr(ts_input, 'freq', None)
freq = getattr(ts_input, '_freq', None)
else:
warnings.warn(
"The 'freq' argument in Timestamp is deprecated and will be "
Expand Down Expand Up @@ -1627,12 +1611,21 @@ timedelta}, default 'raise'
"Use tz_localize() or tz_convert() as appropriate"
)

@property
def _freqstr(self):
return getattr(self._freq, "freqstr", self._freq)

@property
def freqstr(self):
"""
Return the total number of days in the month.
"""
return getattr(self.freq, 'freqstr', self.freq)
warnings.warn(
"Timestamp.freqstr is deprecated and will be removed in a future version.",
FutureWarning,
stacklevel=1,
)
return self._freqstr

def tz_localize(self, tz, ambiguous='raise', nonexistent='raise'):
"""
Expand Down Expand Up @@ -1725,15 +1718,15 @@ default 'raise'
nonexistent=nonexistent)
out = Timestamp(value, tz=tz)
Copy link
Contributor

Choose a reason for hiding this comment

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

can we some just avoid setting this?

if out is not NaT:
out._set_freq(self.freq) # avoid warning in constructor
out._set_freq(self._freq) # avoid warning in constructor
return out
else:
if tz is None:
# reset tz
value = tz_convert_from_utc_single(self.value, self.tz)
out = Timestamp(value, tz=tz)
if out is not NaT:
out._set_freq(self.freq) # avoid warning in constructor
out._set_freq(self._freq) # avoid warning in constructor
return out
else:
raise TypeError(
Expand Down Expand Up @@ -1791,7 +1784,7 @@ default 'raise'
# Same UTC timestamp, different time zone
out = Timestamp(self.value, tz=tz)
if out is not NaT:
out._set_freq(self.freq) # avoid warning in constructor
out._set_freq(self._freq) # avoid warning in constructor
return out

astimezone = tz_convert
Expand Down Expand Up @@ -1925,7 +1918,7 @@ default 'raise'
if value != NPY_NAT:
check_dts_bounds(&dts)

return create_timestamp_from_ts(value, dts, tzobj, self.freq, fold)
return create_timestamp_from_ts(value, dts, tzobj, self._freq, fold)

def to_julian_date(self) -> np.float64:
"""
Expand Down
4 changes: 3 additions & 1 deletion pandas/core/arrays/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,9 @@ def _check_compatible_with(self, other, setitem: bool = False):

def _box_func(self, x) -> Timestamp | NaTType:
ts = Timestamp(x, tz=self.tz)
if ts is not NaT:
# Non-overlapping identity check (left operand type: "Timestamp",
# right operand type: "NaTType")
if ts is not NaT: # type: ignore[comparison-overlap]
# GH#41586
# do this instead of passing to the constructor to avoid FutureWarning
ts._set_freq(self.freq)
Expand Down
1 change: 1 addition & 0 deletions pandas/tests/frame/methods/test_first_valid_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def test_first_last_valid_all_nan(self, index_func):
assert ser.first_valid_index() is None
assert ser.last_valid_index() is None

@pytest.mark.filterwarnings("ignore:Timestamp.freq is deprecated:FutureWarning")
def test_first_last_valid_preserves_freq(self):
# GH#20499: its preserves freq with holes
index = date_range("20110101", periods=30, freq="B")
Expand Down
2 changes: 2 additions & 0 deletions pandas/tests/frame/methods/test_reset_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def test_set_index_reset_index_dt64tz(self):
df = result.set_index("foo")
tm.assert_index_equal(df.index, idx)

@pytest.mark.filterwarnings("ignore:Timestamp.freq is deprecated:FutureWarning")
Copy link
Member

Choose a reason for hiding this comment

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

What's causing this test to raise a warning?

Copy link
Member Author

Choose a reason for hiding this comment

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

looks like it doesnt after one of the more recent comments, good catch, will remove

def test_reset_index_tz(self, tz_aware_fixture):
# GH 3950
# reset_index with single level
Expand Down Expand Up @@ -421,6 +422,7 @@ def test_reset_index_multiindex_columns(self):
result = df2.rename_axis([("c", "ii")]).reset_index(col_level=1, col_fill="C")
tm.assert_frame_equal(result, expected)

@pytest.mark.filterwarnings("ignore:Timestamp.freq is deprecated:FutureWarning")
def test_reset_index_datetime(self, tz_naive_fixture):
Copy link
Member

Choose a reason for hiding this comment

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

Similar question as before: what's causing this test to raise a warning?

# GH#3950
tz = tz_naive_fixture
Expand Down
Loading