diff --git a/doc/source/reference/offset_frequency.rst b/doc/source/reference/offset_frequency.rst index e6271a7806706..f0e531cd81f84 100644 --- a/doc/source/reference/offset_frequency.rst +++ b/doc/source/reference/offset_frequency.rst @@ -26,6 +26,8 @@ Properties DateOffset.normalize DateOffset.rule_code DateOffset.n + DateOffset.is_month_start + DateOffset.is_month_end Methods ~~~~~~~ @@ -40,6 +42,12 @@ Methods DateOffset.is_anchored DateOffset.is_on_offset DateOffset.__call__ + DateOffset.is_month_start + DateOffset.is_month_end + DateOffset.is_quarter_start + DateOffset.is_quarter_end + DateOffset.is_year_start + DateOffset.is_year_end BusinessDay ----------- @@ -86,6 +94,12 @@ Methods BusinessDay.is_anchored BusinessDay.is_on_offset BusinessDay.__call__ + BusinessDay.is_month_start + BusinessDay.is_month_end + BusinessDay.is_quarter_start + BusinessDay.is_quarter_end + BusinessDay.is_year_start + BusinessDay.is_year_end BusinessHour ------------ @@ -125,6 +139,12 @@ Methods BusinessHour.is_anchored BusinessHour.is_on_offset BusinessHour.__call__ + BusinessHour.is_month_start + BusinessHour.is_month_end + BusinessHour.is_quarter_start + BusinessHour.is_quarter_end + BusinessHour.is_year_start + BusinessHour.is_year_end CustomBusinessDay ----------------- @@ -171,6 +191,12 @@ Methods CustomBusinessDay.is_anchored CustomBusinessDay.is_on_offset CustomBusinessDay.__call__ + CustomBusinessDay.is_month_start + CustomBusinessDay.is_month_end + CustomBusinessDay.is_quarter_start + CustomBusinessDay.is_quarter_end + CustomBusinessDay.is_year_start + CustomBusinessDay.is_year_end CustomBusinessHour ------------------ @@ -210,6 +236,12 @@ Methods CustomBusinessHour.is_anchored CustomBusinessHour.is_on_offset CustomBusinessHour.__call__ + CustomBusinessHour.is_month_start + CustomBusinessHour.is_month_end + CustomBusinessHour.is_quarter_start + CustomBusinessHour.is_quarter_end + CustomBusinessHour.is_year_start + CustomBusinessHour.is_year_end MonthEnd -------- @@ -244,6 +276,12 @@ Methods MonthEnd.is_anchored MonthEnd.is_on_offset MonthEnd.__call__ + MonthEnd.is_month_start + MonthEnd.is_month_end + MonthEnd.is_quarter_start + MonthEnd.is_quarter_end + MonthEnd.is_year_start + MonthEnd.is_year_end MonthBegin ---------- @@ -278,6 +316,12 @@ Methods MonthBegin.is_anchored MonthBegin.is_on_offset MonthBegin.__call__ + MonthBegin.is_month_start + MonthBegin.is_month_end + MonthBegin.is_quarter_start + MonthBegin.is_quarter_end + MonthBegin.is_year_start + MonthBegin.is_year_end BusinessMonthEnd ---------------- @@ -321,6 +365,12 @@ Methods BusinessMonthEnd.is_anchored BusinessMonthEnd.is_on_offset BusinessMonthEnd.__call__ + BusinessMonthEnd.is_month_start + BusinessMonthEnd.is_month_end + BusinessMonthEnd.is_quarter_start + BusinessMonthEnd.is_quarter_end + BusinessMonthEnd.is_year_start + BusinessMonthEnd.is_year_end BusinessMonthBegin ------------------ @@ -364,6 +414,12 @@ Methods BusinessMonthBegin.is_anchored BusinessMonthBegin.is_on_offset BusinessMonthBegin.__call__ + BusinessMonthBegin.is_month_start + BusinessMonthBegin.is_month_end + BusinessMonthBegin.is_quarter_start + BusinessMonthBegin.is_quarter_end + BusinessMonthBegin.is_year_start + BusinessMonthBegin.is_year_end CustomBusinessMonthEnd ---------------------- @@ -411,6 +467,12 @@ Methods CustomBusinessMonthEnd.is_anchored CustomBusinessMonthEnd.is_on_offset CustomBusinessMonthEnd.__call__ + CustomBusinessMonthEnd.is_month_start + CustomBusinessMonthEnd.is_month_end + CustomBusinessMonthEnd.is_quarter_start + CustomBusinessMonthEnd.is_quarter_end + CustomBusinessMonthEnd.is_year_start + CustomBusinessMonthEnd.is_year_end CustomBusinessMonthBegin ------------------------ @@ -458,6 +520,12 @@ Methods CustomBusinessMonthBegin.is_anchored CustomBusinessMonthBegin.is_on_offset CustomBusinessMonthBegin.__call__ + CustomBusinessMonthBegin.is_month_start + CustomBusinessMonthBegin.is_month_end + CustomBusinessMonthBegin.is_quarter_start + CustomBusinessMonthBegin.is_quarter_end + CustomBusinessMonthBegin.is_year_start + CustomBusinessMonthBegin.is_year_end SemiMonthEnd ------------ @@ -493,6 +561,12 @@ Methods SemiMonthEnd.is_anchored SemiMonthEnd.is_on_offset SemiMonthEnd.__call__ + SemiMonthEnd.is_month_start + SemiMonthEnd.is_month_end + SemiMonthEnd.is_quarter_start + SemiMonthEnd.is_quarter_end + SemiMonthEnd.is_year_start + SemiMonthEnd.is_year_end SemiMonthBegin -------------- @@ -528,6 +602,12 @@ Methods SemiMonthBegin.is_anchored SemiMonthBegin.is_on_offset SemiMonthBegin.__call__ + SemiMonthBegin.is_month_start + SemiMonthBegin.is_month_end + SemiMonthBegin.is_quarter_start + SemiMonthBegin.is_quarter_end + SemiMonthBegin.is_year_start + SemiMonthBegin.is_year_end Week ---- @@ -563,6 +643,12 @@ Methods Week.is_anchored Week.is_on_offset Week.__call__ + Week.is_month_start + Week.is_month_end + Week.is_quarter_start + Week.is_quarter_end + Week.is_year_start + Week.is_year_end WeekOfMonth ----------- @@ -599,6 +685,12 @@ Methods WeekOfMonth.is_on_offset WeekOfMonth.__call__ WeekOfMonth.weekday + WeekOfMonth.is_month_start + WeekOfMonth.is_month_end + WeekOfMonth.is_quarter_start + WeekOfMonth.is_quarter_end + WeekOfMonth.is_year_start + WeekOfMonth.is_year_end LastWeekOfMonth --------------- @@ -635,6 +727,12 @@ Methods LastWeekOfMonth.is_anchored LastWeekOfMonth.is_on_offset LastWeekOfMonth.__call__ + LastWeekOfMonth.is_month_start + LastWeekOfMonth.is_month_end + LastWeekOfMonth.is_quarter_start + LastWeekOfMonth.is_quarter_end + LastWeekOfMonth.is_year_start + LastWeekOfMonth.is_year_end BQuarterEnd ----------- @@ -670,6 +768,12 @@ Methods BQuarterEnd.is_anchored BQuarterEnd.is_on_offset BQuarterEnd.__call__ + BQuarterEnd.is_month_start + BQuarterEnd.is_month_end + BQuarterEnd.is_quarter_start + BQuarterEnd.is_quarter_end + BQuarterEnd.is_year_start + BQuarterEnd.is_year_end BQuarterBegin ------------- @@ -705,6 +809,12 @@ Methods BQuarterBegin.is_anchored BQuarterBegin.is_on_offset BQuarterBegin.__call__ + BQuarterBegin.is_month_start + BQuarterBegin.is_month_end + BQuarterBegin.is_quarter_start + BQuarterBegin.is_quarter_end + BQuarterBegin.is_year_start + BQuarterBegin.is_year_end QuarterEnd ---------- @@ -740,6 +850,12 @@ Methods QuarterEnd.is_anchored QuarterEnd.is_on_offset QuarterEnd.__call__ + QuarterEnd.is_month_start + QuarterEnd.is_month_end + QuarterEnd.is_quarter_start + QuarterEnd.is_quarter_end + QuarterEnd.is_year_start + QuarterEnd.is_year_end QuarterBegin ------------ @@ -775,6 +891,12 @@ Methods QuarterBegin.is_anchored QuarterBegin.is_on_offset QuarterBegin.__call__ + QuarterBegin.is_month_start + QuarterBegin.is_month_end + QuarterBegin.is_quarter_start + QuarterBegin.is_quarter_end + QuarterBegin.is_year_start + QuarterBegin.is_year_end BYearEnd -------- @@ -810,6 +932,12 @@ Methods BYearEnd.is_anchored BYearEnd.is_on_offset BYearEnd.__call__ + BYearEnd.is_month_start + BYearEnd.is_month_end + BYearEnd.is_quarter_start + BYearEnd.is_quarter_end + BYearEnd.is_year_start + BYearEnd.is_year_end BYearBegin ---------- @@ -845,6 +973,12 @@ Methods BYearBegin.is_anchored BYearBegin.is_on_offset BYearBegin.__call__ + BYearBegin.is_month_start + BYearBegin.is_month_end + BYearBegin.is_quarter_start + BYearBegin.is_quarter_end + BYearBegin.is_year_start + BYearBegin.is_year_end YearEnd ------- @@ -880,6 +1014,12 @@ Methods YearEnd.is_anchored YearEnd.is_on_offset YearEnd.__call__ + YearEnd.is_month_start + YearEnd.is_month_end + YearEnd.is_quarter_start + YearEnd.is_quarter_end + YearEnd.is_year_start + YearEnd.is_year_end YearBegin --------- @@ -915,6 +1055,12 @@ Methods YearBegin.is_anchored YearBegin.is_on_offset YearBegin.__call__ + YearBegin.is_month_start + YearBegin.is_month_end + YearBegin.is_quarter_start + YearBegin.is_quarter_end + YearBegin.is_year_start + YearBegin.is_year_end FY5253 ------ @@ -954,6 +1100,12 @@ Methods FY5253.is_anchored FY5253.is_on_offset FY5253.__call__ + FY5253.is_month_start + FY5253.is_month_end + FY5253.is_quarter_start + FY5253.is_quarter_end + FY5253.is_year_start + FY5253.is_year_end FY5253Quarter ------------- @@ -995,6 +1147,12 @@ Methods FY5253Quarter.is_on_offset FY5253Quarter.year_has_extra_week FY5253Quarter.__call__ + FY5253Quarter.is_month_start + FY5253Quarter.is_month_end + FY5253Quarter.is_quarter_start + FY5253Quarter.is_quarter_end + FY5253Quarter.is_year_start + FY5253Quarter.is_year_end Easter ------ @@ -1029,6 +1187,12 @@ Methods Easter.is_anchored Easter.is_on_offset Easter.__call__ + Easter.is_month_start + Easter.is_month_end + Easter.is_quarter_start + Easter.is_quarter_end + Easter.is_year_start + Easter.is_year_end Tick ---- @@ -1064,6 +1228,12 @@ Methods Tick.__call__ Tick.apply Tick.apply_index + Tick.is_month_start + Tick.is_month_end + Tick.is_quarter_start + Tick.is_quarter_end + Tick.is_year_start + Tick.is_year_end Day --- @@ -1099,6 +1269,12 @@ Methods Day.__call__ Day.apply Day.apply_index + Day.is_month_start + Day.is_month_end + Day.is_quarter_start + Day.is_quarter_end + Day.is_year_start + Day.is_year_end Hour ---- @@ -1134,6 +1310,12 @@ Methods Hour.__call__ Hour.apply Hour.apply_index + Hour.is_month_start + Hour.is_month_end + Hour.is_quarter_start + Hour.is_quarter_end + Hour.is_year_start + Hour.is_year_end Minute ------ @@ -1169,6 +1351,12 @@ Methods Minute.__call__ Minute.apply Minute.apply_index + Minute.is_month_start + Minute.is_month_end + Minute.is_quarter_start + Minute.is_quarter_end + Minute.is_year_start + Minute.is_year_end Second ------ @@ -1204,6 +1392,12 @@ Methods Second.__call__ Second.apply Second.apply_index + Second.is_month_start + Second.is_month_end + Second.is_quarter_start + Second.is_quarter_end + Second.is_year_start + Second.is_year_end Milli ----- @@ -1239,6 +1433,12 @@ Methods Milli.__call__ Milli.apply Milli.apply_index + Milli.is_month_start + Milli.is_month_end + Milli.is_quarter_start + Milli.is_quarter_end + Milli.is_year_start + Milli.is_year_end Micro ----- @@ -1274,6 +1474,12 @@ Methods Micro.__call__ Micro.apply Micro.apply_index + Micro.is_month_start + Micro.is_month_end + Micro.is_quarter_start + Micro.is_quarter_end + Micro.is_year_start + Micro.is_year_end Nano ---- @@ -1309,6 +1515,12 @@ Methods Nano.__call__ Nano.apply Nano.apply_index + Nano.is_month_start + Nano.is_month_end + Nano.is_quarter_start + Nano.is_quarter_end + Nano.is_year_start + Nano.is_year_end .. _api.frequencies: diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 1b2a16244d606..e80b43a5ecad6 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -690,6 +690,7 @@ Deprecations - Deprecated special treatment of lists with first element a Categorical in the :class:`DataFrame` constructor; pass as ``pd.DataFrame({col: categorical, ...})`` instead (:issue:`38845`) - Deprecated behavior of :class:`DataFrame` constructor when a ``dtype`` is passed and the data cannot be cast to that dtype. In a future version, this will raise instead of being silently ignored (:issue:`24435`) - Deprecated passing arguments as positional (except for ``"method"``) in :meth:`DataFrame.interpolate` and :meth:`Series.interpolate` (:issue:`41485`) +- Deprecated the :attr:`Timestamp.freq` attribute. For the properties that use it (``is_month_start``, ``is_month_end``, ``is_quarter_start``, ``is_quarter_end``, ``is_year_start``, ``is_year_end``), when you have a ``freq``, use e.g. ``freq.is_month_start(ts)`` (:issue:`15146`) - Deprecated passing arguments as positional in :meth:`DataFrame.ffill`, :meth:`Series.ffill`, :meth:`DataFrame.bfill`, and :meth:`Series.bfill` (:issue:`41485`) - Deprecated passing arguments as positional in :meth:`DataFrame.sort_values` (other than ``"by"``) and :meth:`Series.sort_values` (:issue:`41485`) - Deprecated passing arguments as positional in :meth:`DataFrame.dropna` and :meth:`Series.dropna` (:issue:`41485`) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 4e6e5485b2ade..1b1a497df4ca7 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -716,6 +716,26 @@ cdef class BaseOffset: # if there were a canonical docstring for what is_anchored means. return self.n == 1 + # ------------------------------------------------------------------ + + def is_month_start(self, _Timestamp ts): + return ts._get_start_end_field("is_month_start", self) + + def is_month_end(self, _Timestamp ts): + return ts._get_start_end_field("is_month_end", self) + + def is_quarter_start(self, _Timestamp ts): + return ts._get_start_end_field("is_quarter_start", self) + + def is_quarter_end(self, _Timestamp ts): + return ts._get_start_end_field("is_quarter_end", self) + + def is_year_start(self, _Timestamp ts): + return ts._get_start_end_field("is_year_start", self) + + def is_year_end(self, _Timestamp ts): + return ts._get_start_end_field("is_year_end", self) + cdef class SingleConstructorOffset(BaseOffset): @classmethod diff --git a/pandas/_libs/tslibs/timestamps.pxd b/pandas/_libs/tslibs/timestamps.pxd index eadd7c7022acb..8833a611b0722 100644 --- a/pandas/_libs/tslibs/timestamps.pxd +++ b/pandas/_libs/tslibs/timestamps.pxd @@ -16,9 +16,9 @@ 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) + cdef bint _get_start_end_field(self, str field, freq) cdef _get_date_name_field(self, str field, object locale) cdef int64_t _maybe_convert_value_to_local(self) cdef bint _can_compare(self, datetime other) @@ -26,3 +26,5 @@ cdef class _Timestamp(ABCTimestamp): cpdef datetime to_pydatetime(_Timestamp self, bint warn=*) cdef bint _compare_outside_nanorange(_Timestamp self, datetime other, int op) except -1 + cpdef void _set_freq(self, freq) + cdef _warn_on_field_deprecation(_Timestamp self, freq, str field) diff --git a/pandas/_libs/tslibs/timestamps.pyi b/pandas/_libs/tslibs/timestamps.pyi index 8728b700a1f6d..1c06538c7399e 100644 --- a/pandas/_libs/tslibs/timestamps.pyi +++ b/pandas/_libs/tslibs/timestamps.pyi @@ -18,6 +18,7 @@ from typing import ( import numpy as np from pandas._libs.tslibs import ( + BaseOffset, NaT, NaTType, Period, @@ -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 diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index a4f764878d19e..7b03522d56d76 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -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 @@ -155,6 +155,21 @@ cdef class _Timestamp(ABCTimestamp): dayofweek = _Timestamp.day_of_week dayofyear = _Timestamp.day_of_year + cpdef void _set_freq(self, freq): + # set the ._freq attribute without going through the constructor, + # which would issue a warning + # Caller is responsible for validation + 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: return hash(self.value) @@ -263,7 +278,9 @@ cdef class _Timestamp(ABCTimestamp): if is_any_td_scalar(other): nanos = delta_to_nanoseconds(other) - result = type(self)(self.value + nanos, tz=self.tzinfo, freq=self.freq) + result = type(self)(self.value + nanos, tz=self.tzinfo) + if result is not NaT: + result._set_freq(self._freq) # avoid warning in constructor return result elif is_integer_object(other): @@ -361,18 +378,17 @@ cdef class _Timestamp(ABCTimestamp): val = self.value return val - cdef bint _get_start_end_field(self, str field): + cdef bint _get_start_end_field(self, str field, freq): cdef: int64_t val dict kwds ndarray[uint8_t, cast=True] out int month_kw - freq = self.freq 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 @@ -382,6 +398,31 @@ cdef class _Timestamp(ABCTimestamp): field, freqstr, month_kw) return out[0] + cdef _warn_on_field_deprecation(self, freq, str field): + """ + 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 + if month_kw != 12: + needs = True + if freqstr.startswith("B"): + 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: """ @@ -397,10 +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 - return self._get_start_end_field("is_month_start") + 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: @@ -417,10 +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 - return self._get_start_end_field("is_month_end") + 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: @@ -437,10 +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 - return self._get_start_end_field("is_quarter_start") + 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: @@ -457,10 +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 - return self._get_start_end_field("is_quarter_end") + 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: @@ -477,10 +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 - return self._get_start_end_field("is_year_start") + 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: @@ -497,10 +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 - return self._get_start_end_field("is_year_end") + 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: @@ -673,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) # ----------------------------------------------------------------- @@ -719,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})" @@ -877,7 +924,13 @@ 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.", + FutureWarning, + stacklevel=2, + ) return Period(self, freq=freq) @@ -1147,7 +1200,7 @@ class Timestamp(_Timestamp): nanosecond=None, tzinfo_type tzinfo=None, *, - fold=None + fold=None, ): # The parameter list folds together legacy parameter names (the first # four) and positional and keyword parameter names from pydatetime. @@ -1276,9 +1329,16 @@ 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) - elif not is_offset_object(freq): - freq = to_offset(freq) + freq = getattr(ts_input, '_freq', None) + else: + warnings.warn( + "The 'freq' argument in Timestamp is deprecated and will be " + "removed in a future version.", + FutureWarning, + stacklevel=1, + ) + if not is_offset_object(freq): + freq = to_offset(freq) return create_timestamp_from_ts(ts.value, ts.dts, ts.tzinfo, freq, ts.fold) @@ -1551,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'): """ @@ -1647,12 +1716,18 @@ default 'raise' value = tz_localize_to_utc_single(self.value, tz, ambiguous=ambiguous, nonexistent=nonexistent) - return Timestamp(value, tz=tz, freq=self.freq) + out = Timestamp(value, tz=tz) + if out is not NaT: + 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) - return Timestamp(value, tz=tz, freq=self.freq) + out = Timestamp(value, tz=tz) + if out is not NaT: + out._set_freq(self._freq) # avoid warning in constructor + return out else: raise TypeError( "Cannot localize tz-aware Timestamp, use tz_convert for conversions" @@ -1707,7 +1782,10 @@ default 'raise' ) else: # Same UTC timestamp, different time zone - return Timestamp(self.value, tz=tz, freq=self.freq) + out = Timestamp(self.value, tz=tz) + if out is not NaT: + out._set_freq(self._freq) # avoid warning in constructor + return out astimezone = tz_convert @@ -1840,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: """ diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index ba5be03b93490..df33caab913bd 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1189,7 +1189,11 @@ def _addsub_object_array(self, other: np.ndarray, op): # Caller is responsible for broadcasting if necessary assert self.shape == other.shape, (self.shape, other.shape) - res_values = op(self.astype("O"), np.asarray(other)) + with warnings.catch_warnings(): + # filter out warnings about Timestamp.freq + warnings.filterwarnings("ignore", category=FutureWarning) + res_values = op(self.astype("O"), np.asarray(other)) + result = pd_array(res_values.ravel()) # error: Item "ExtensionArray" of "Union[Any, ExtensionArray]" has no attribute # "reshape" diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 020f708606353..7867471da6b94 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -510,7 +510,14 @@ def _check_compatible_with(self, other, setitem: bool = False): # Descriptive Properties def _box_func(self, x) -> Timestamp | NaTType: - return Timestamp(x, freq=self.freq, tz=self.tz) + ts = Timestamp(x, tz=self.tz) + # 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) + return ts @property # error: Return type "Union[dtype, DatetimeTZDtype]" of "dtype" @@ -603,13 +610,18 @@ def __iter__(self): length = len(self) chunksize = 10000 chunks = (length // chunksize) + 1 - for i in range(chunks): - start_i = i * chunksize - end_i = min((i + 1) * chunksize, length) - converted = ints_to_pydatetime( - data[start_i:end_i], tz=self.tz, freq=self.freq, box="timestamp" - ) - yield from converted + + with warnings.catch_warnings(): + # filter out warnings about Timestamp.freq + warnings.filterwarnings("ignore", category=FutureWarning) + + for i in range(chunks): + start_i = i * chunksize + end_i = min((i + 1) * chunksize, length) + converted = ints_to_pydatetime( + data[start_i:end_i], tz=self.tz, freq=self.freq, box="timestamp" + ) + yield from converted def astype(self, dtype, copy: bool = True): # We handle diff --git a/pandas/tests/frame/methods/test_first_valid_index.py b/pandas/tests/frame/methods/test_first_valid_index.py index c2de1a6bb7b14..e4cbd892de38e 100644 --- a/pandas/tests/frame/methods/test_first_valid_index.py +++ b/pandas/tests/frame/methods/test_first_valid_index.py @@ -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") diff --git a/pandas/tests/frame/methods/test_reset_index.py b/pandas/tests/frame/methods/test_reset_index.py index 91f354fecca63..76d259707787d 100644 --- a/pandas/tests/frame/methods/test_reset_index.py +++ b/pandas/tests/frame/methods/test_reset_index.py @@ -421,6 +421,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): # GH#3950 tz = tz_naive_fixture @@ -509,9 +510,7 @@ def test_reset_index_datetime(self, tz_naive_fixture): }, columns=["level_0", "level_1", "a"], ) - expected["level_1"] = expected["level_1"].apply( - lambda d: Timestamp(d, freq="D", tz=tz) - ) + expected["level_1"] = expected["level_1"].apply(lambda d: Timestamp(d, tz=tz)) result = df.reset_index() tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/indexes/datetimes/methods/test_repeat.py b/pandas/tests/indexes/datetimes/methods/test_repeat.py index 81768622fd3d5..c18109a23b6e8 100644 --- a/pandas/tests/indexes/datetimes/methods/test_repeat.py +++ b/pandas/tests/indexes/datetimes/methods/test_repeat.py @@ -62,10 +62,10 @@ def test_repeat(self, tz_naive_fixture): expected_rng = DatetimeIndex( [ - Timestamp("2016-01-01 00:00:00", tz=tz, freq="30T"), - Timestamp("2016-01-01 00:00:00", tz=tz, freq="30T"), - Timestamp("2016-01-01 00:30:00", tz=tz, freq="30T"), - Timestamp("2016-01-01 00:30:00", tz=tz, freq="30T"), + Timestamp("2016-01-01 00:00:00", tz=tz), + Timestamp("2016-01-01 00:00:00", tz=tz), + Timestamp("2016-01-01 00:30:00", tz=tz), + Timestamp("2016-01-01 00:30:00", tz=tz), ] ) diff --git a/pandas/tests/indexes/datetimes/methods/test_to_period.py b/pandas/tests/indexes/datetimes/methods/test_to_period.py index 51cc6af2eed08..f6a598bd2a1ed 100644 --- a/pandas/tests/indexes/datetimes/methods/test_to_period.py +++ b/pandas/tests/indexes/datetimes/methods/test_to_period.py @@ -147,6 +147,9 @@ def test_to_period_tz(self, tz): with tm.assert_produces_warning(UserWarning): # GH#21333 warning that timezone info will be lost + # filter warning about freq deprecation + warnings.filterwarnings("ignore", category=FutureWarning) + result = ts.to_period()[0] expected = ts[0].to_period() @@ -165,6 +168,8 @@ def test_to_period_tz_utc_offset_consistency(self, tz): # GH#22905 ts = date_range("1/1/2000", "2/1/2000", tz="Etc/GMT-1") with tm.assert_produces_warning(UserWarning): + warnings.filterwarnings("ignore", category=FutureWarning) + result = ts.to_period()[0] expected = ts[0].to_period() assert result == expected diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py index 935e6afec246e..03cfeb245c11d 100644 --- a/pandas/tests/indexes/datetimes/test_date_range.py +++ b/pandas/tests/indexes/datetimes/test_date_range.py @@ -49,21 +49,21 @@ def test_date_range_timestamp_equiv(self): rng = date_range("20090415", "20090519", tz="US/Eastern") stamp = rng[0] - ts = Timestamp("20090415", tz="US/Eastern", freq="D") + ts = Timestamp("20090415", tz="US/Eastern") assert ts == stamp def test_date_range_timestamp_equiv_dateutil(self): rng = date_range("20090415", "20090519", tz="dateutil/US/Eastern") stamp = rng[0] - ts = Timestamp("20090415", tz="dateutil/US/Eastern", freq="D") + ts = Timestamp("20090415", tz="dateutil/US/Eastern") assert ts == stamp def test_date_range_timestamp_equiv_explicit_pytz(self): rng = date_range("20090415", "20090519", tz=pytz.timezone("US/Eastern")) stamp = rng[0] - ts = Timestamp("20090415", tz=pytz.timezone("US/Eastern"), freq="D") + ts = Timestamp("20090415", tz=pytz.timezone("US/Eastern")) assert ts == stamp @td.skip_if_windows_python_3 @@ -73,7 +73,7 @@ def test_date_range_timestamp_equiv_explicit_dateutil(self): rng = date_range("20090415", "20090519", tz=gettz("US/Eastern")) stamp = rng[0] - ts = Timestamp("20090415", tz=gettz("US/Eastern"), freq="D") + ts = Timestamp("20090415", tz=gettz("US/Eastern")) assert ts == stamp def test_date_range_timestamp_equiv_from_datetime_instance(self): @@ -82,12 +82,12 @@ def test_date_range_timestamp_equiv_from_datetime_instance(self): # addition/subtraction of integers timestamp_instance = date_range(datetime_instance, periods=1, freq="D")[0] - ts = Timestamp(datetime_instance, freq="D") + ts = Timestamp(datetime_instance) assert ts == timestamp_instance def test_date_range_timestamp_equiv_preserve_frequency(self): timestamp_instance = date_range("2014-03-05", periods=1, freq="D")[0] - ts = Timestamp("2014-03-05", freq="D") + ts = Timestamp("2014-03-05") assert timestamp_instance == ts @@ -400,9 +400,7 @@ def test_range_misspecified(self): def test_compat_replace(self): # https://github.com/statsmodels/statsmodels/issues/3349 # replace should take ints/longs for compat - result = date_range( - Timestamp("1960-04-01 00:00:00", freq="QS-JAN"), periods=76, freq="QS-JAN" - ) + result = date_range(Timestamp("1960-04-01 00:00:00"), periods=76, freq="QS-JAN") assert len(result) == 76 def test_catch_infinite_loop(self): diff --git a/pandas/tests/indexes/datetimes/test_misc.py b/pandas/tests/indexes/datetimes/test_misc.py index c3736d588601b..fe84699a89bc5 100644 --- a/pandas/tests/indexes/datetimes/test_misc.py +++ b/pandas/tests/indexes/datetimes/test_misc.py @@ -268,40 +268,43 @@ def test_datetimeindex_accessors4(self): assert dti.is_month_start[0] == 1 def test_datetimeindex_accessors5(self): - tests = [ - (Timestamp("2013-06-01", freq="M").is_month_start, 1), - (Timestamp("2013-06-01", freq="BM").is_month_start, 0), - (Timestamp("2013-06-03", freq="M").is_month_start, 0), - (Timestamp("2013-06-03", freq="BM").is_month_start, 1), - (Timestamp("2013-02-28", freq="Q-FEB").is_month_end, 1), - (Timestamp("2013-02-28", freq="Q-FEB").is_quarter_end, 1), - (Timestamp("2013-02-28", freq="Q-FEB").is_year_end, 1), - (Timestamp("2013-03-01", freq="Q-FEB").is_month_start, 1), - (Timestamp("2013-03-01", freq="Q-FEB").is_quarter_start, 1), - (Timestamp("2013-03-01", freq="Q-FEB").is_year_start, 1), - (Timestamp("2013-03-31", freq="QS-FEB").is_month_end, 1), - (Timestamp("2013-03-31", freq="QS-FEB").is_quarter_end, 0), - (Timestamp("2013-03-31", freq="QS-FEB").is_year_end, 0), - (Timestamp("2013-02-01", freq="QS-FEB").is_month_start, 1), - (Timestamp("2013-02-01", freq="QS-FEB").is_quarter_start, 1), - (Timestamp("2013-02-01", freq="QS-FEB").is_year_start, 1), - (Timestamp("2013-06-30", freq="BQ").is_month_end, 0), - (Timestamp("2013-06-30", freq="BQ").is_quarter_end, 0), - (Timestamp("2013-06-30", freq="BQ").is_year_end, 0), - (Timestamp("2013-06-28", freq="BQ").is_month_end, 1), - (Timestamp("2013-06-28", freq="BQ").is_quarter_end, 1), - (Timestamp("2013-06-28", freq="BQ").is_year_end, 0), - (Timestamp("2013-06-30", freq="BQS-APR").is_month_end, 0), - (Timestamp("2013-06-30", freq="BQS-APR").is_quarter_end, 0), - (Timestamp("2013-06-30", freq="BQS-APR").is_year_end, 0), - (Timestamp("2013-06-28", freq="BQS-APR").is_month_end, 1), - (Timestamp("2013-06-28", freq="BQS-APR").is_quarter_end, 1), - (Timestamp("2013-03-29", freq="BQS-APR").is_year_end, 1), - (Timestamp("2013-11-01", freq="AS-NOV").is_year_start, 1), - (Timestamp("2013-10-31", freq="AS-NOV").is_year_end, 1), - (Timestamp("2012-02-01").days_in_month, 29), - (Timestamp("2013-02-01").days_in_month, 28), - ] + with tm.assert_produces_warning( + FutureWarning, match="The 'freq' argument", check_stacklevel=False + ): + tests = [ + (Timestamp("2013-06-01", freq="M").is_month_start, 1), + (Timestamp("2013-06-01", freq="BM").is_month_start, 0), + (Timestamp("2013-06-03", freq="M").is_month_start, 0), + (Timestamp("2013-06-03", freq="BM").is_month_start, 1), + (Timestamp("2013-02-28", freq="Q-FEB").is_month_end, 1), + (Timestamp("2013-02-28", freq="Q-FEB").is_quarter_end, 1), + (Timestamp("2013-02-28", freq="Q-FEB").is_year_end, 1), + (Timestamp("2013-03-01", freq="Q-FEB").is_month_start, 1), + (Timestamp("2013-03-01", freq="Q-FEB").is_quarter_start, 1), + (Timestamp("2013-03-01", freq="Q-FEB").is_year_start, 1), + (Timestamp("2013-03-31", freq="QS-FEB").is_month_end, 1), + (Timestamp("2013-03-31", freq="QS-FEB").is_quarter_end, 0), + (Timestamp("2013-03-31", freq="QS-FEB").is_year_end, 0), + (Timestamp("2013-02-01", freq="QS-FEB").is_month_start, 1), + (Timestamp("2013-02-01", freq="QS-FEB").is_quarter_start, 1), + (Timestamp("2013-02-01", freq="QS-FEB").is_year_start, 1), + (Timestamp("2013-06-30", freq="BQ").is_month_end, 0), + (Timestamp("2013-06-30", freq="BQ").is_quarter_end, 0), + (Timestamp("2013-06-30", freq="BQ").is_year_end, 0), + (Timestamp("2013-06-28", freq="BQ").is_month_end, 1), + (Timestamp("2013-06-28", freq="BQ").is_quarter_end, 1), + (Timestamp("2013-06-28", freq="BQ").is_year_end, 0), + (Timestamp("2013-06-30", freq="BQS-APR").is_month_end, 0), + (Timestamp("2013-06-30", freq="BQS-APR").is_quarter_end, 0), + (Timestamp("2013-06-30", freq="BQS-APR").is_year_end, 0), + (Timestamp("2013-06-28", freq="BQS-APR").is_month_end, 1), + (Timestamp("2013-06-28", freq="BQS-APR").is_quarter_end, 1), + (Timestamp("2013-03-29", freq="BQS-APR").is_year_end, 1), + (Timestamp("2013-11-01", freq="AS-NOV").is_year_start, 1), + (Timestamp("2013-10-31", freq="AS-NOV").is_year_end, 1), + (Timestamp("2012-02-01").days_in_month, 29), + (Timestamp("2013-02-01").days_in_month, 28), + ] for ts, value in tests: assert ts == value diff --git a/pandas/tests/indexes/datetimes/test_scalar_compat.py b/pandas/tests/indexes/datetimes/test_scalar_compat.py index 40a03396cf98e..da18cc44d5365 100644 --- a/pandas/tests/indexes/datetimes/test_scalar_compat.py +++ b/pandas/tests/indexes/datetimes/test_scalar_compat.py @@ -62,7 +62,12 @@ def test_dti_timestamp_fields(self, field): # extra fields from DatetimeIndex like quarter and week idx = tm.makeDateIndex(100) expected = getattr(idx, field)[-1] - result = getattr(Timestamp(idx[-1]), field) + + warn = FutureWarning if field.startswith("is_") else None + with tm.assert_produces_warning( + warn, match="Timestamp.freq is deprecated", check_stacklevel=False + ): + result = getattr(Timestamp(idx[-1]), field) assert result == expected def test_dti_timestamp_isocalendar_fields(self): @@ -75,8 +80,17 @@ def test_dti_timestamp_freq_fields(self): # extra fields from DatetimeIndex like quarter and week idx = tm.makeDateIndex(100) - assert idx.freq == Timestamp(idx[-1], idx.freq).freq - assert idx.freqstr == Timestamp(idx[-1], idx.freq).freqstr + msg = "The 'freq' argument in Timestamp is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + ts = Timestamp(idx[-1], idx.freq) + + msg2 = "Timestamp.freq is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg2): + assert idx.freq == ts.freq + + msg3 = "Timestamp.freqstr is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg3): + assert idx.freqstr == ts.freqstr # ---------------------------------------------------------------- # DatetimeIndex.round @@ -116,11 +130,11 @@ def test_round(self, tz_naive_fixture): expected_rng = DatetimeIndex( [ - Timestamp("2016-01-01 00:00:00", tz=tz, freq="30T"), - Timestamp("2016-01-01 00:00:00", tz=tz, freq="30T"), - Timestamp("2016-01-01 01:00:00", tz=tz, freq="30T"), - Timestamp("2016-01-01 02:00:00", tz=tz, freq="30T"), - Timestamp("2016-01-01 02:00:00", tz=tz, freq="30T"), + Timestamp("2016-01-01 00:00:00", tz=tz), + Timestamp("2016-01-01 00:00:00", tz=tz), + Timestamp("2016-01-01 01:00:00", tz=tz), + Timestamp("2016-01-01 02:00:00", tz=tz), + Timestamp("2016-01-01 02:00:00", tz=tz), ] ) expected_elt = expected_rng[1] @@ -170,11 +184,11 @@ def test_no_rounding_occurs(self, tz_naive_fixture): expected_rng = DatetimeIndex( [ - Timestamp("2016-01-01 00:00:00", tz=tz, freq="2T"), - Timestamp("2016-01-01 00:02:00", tz=tz, freq="2T"), - Timestamp("2016-01-01 00:04:00", tz=tz, freq="2T"), - Timestamp("2016-01-01 00:06:00", tz=tz, freq="2T"), - Timestamp("2016-01-01 00:08:00", tz=tz, freq="2T"), + Timestamp("2016-01-01 00:00:00", tz=tz), + Timestamp("2016-01-01 00:02:00", tz=tz), + Timestamp("2016-01-01 00:04:00", tz=tz), + Timestamp("2016-01-01 00:06:00", tz=tz), + Timestamp("2016-01-01 00:08:00", tz=tz), ] ) diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 8bba11786e3e5..a12f4c9676d9b 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -591,8 +591,8 @@ def test_dti_construction_ambiguous_endpoint(self, tz): times = date_range( "2013-10-26 23:00", "2013-10-27 01:00", freq="H", tz=tz, ambiguous="infer" ) - assert times[0] == Timestamp("2013-10-26 23:00", tz=tz, freq="H") - assert times[-1] == Timestamp("2013-10-27 01:00:00+0000", tz=tz, freq="H") + assert times[0] == Timestamp("2013-10-26 23:00", tz=tz) + assert times[-1] == Timestamp("2013-10-27 01:00:00+0000", tz=tz) @pytest.mark.parametrize( "tz, option, expected", @@ -615,7 +615,7 @@ def test_dti_construction_nonexistent_endpoint(self, tz, option, expected): times = date_range( "2019-03-10 00:00", "2019-03-10 02:00", freq="H", tz=tz, nonexistent=option ) - assert times[-1] == Timestamp(expected, tz=tz, freq="H") + assert times[-1] == Timestamp(expected, tz=tz) def test_dti_tz_localize_bdate_range(self): dr = bdate_range("1/1/2009", "1/1/2010") diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index dcccd42c52c8c..a8a2055ffb093 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -2069,8 +2069,8 @@ def test_loc_getitem_label_slice_across_dst(self): ) series2 = Series([0, 1, 2, 3, 4], index=idx) - t_1 = Timestamp("2017-10-29 02:30:00+02:00", tz="Europe/Berlin", freq="30min") - t_2 = Timestamp("2017-10-29 02:00:00+01:00", tz="Europe/Berlin", freq="30min") + t_1 = Timestamp("2017-10-29 02:30:00+02:00", tz="Europe/Berlin") + t_2 = Timestamp("2017-10-29 02:00:00+01:00", tz="Europe/Berlin") result = series2.loc[t_1:t_2] expected = Series([2, 3], index=idx[2:4]) tm.assert_series_equal(result, expected) diff --git a/pandas/tests/io/test_pickle.py b/pandas/tests/io/test_pickle.py index bb3c7afd8ec34..7cf9d7e9a1925 100644 --- a/pandas/tests/io/test_pickle.py +++ b/pandas/tests/io/test_pickle.py @@ -23,6 +23,7 @@ import shutil from warnings import ( catch_warnings, + filterwarnings, simplefilter, ) import zipfile @@ -55,7 +56,10 @@ # TODO(ArrayManager) pickling -pytestmark = td.skip_array_manager_not_yet_implemented +pytestmark = [ + td.skip_array_manager_not_yet_implemented, + pytest.mark.filterwarnings("ignore:Timestamp.freq is deprecated:FutureWarning"), +] @pytest.fixture(scope="module") @@ -63,7 +67,12 @@ def current_pickle_data(): # our current version pickle data from pandas.tests.io.generate_legacy_storage_files import create_pickle_data - return create_pickle_data() + with catch_warnings(): + filterwarnings( + "ignore", "The 'freq' argument in Timestamp", category=FutureWarning + ) + + return create_pickle_data() # --------------------- @@ -205,6 +214,7 @@ def python_unpickler(path): ), ], ) +@pytest.mark.filterwarnings("ignore:The 'freq' argument in Timestamp:FutureWarning") def test_round_trip_current(current_pickle_data, pickle_writer): data = current_pickle_data for typ, dv in data.items(): diff --git a/pandas/tests/reductions/test_reductions.py b/pandas/tests/reductions/test_reductions.py index a6d7d78ff3af2..2f698a82bac49 100644 --- a/pandas/tests/reductions/test_reductions.py +++ b/pandas/tests/reductions/test_reductions.py @@ -451,8 +451,8 @@ def test_numpy_minmax_range(self): def test_numpy_minmax_datetime64(self): dr = date_range(start="2016-01-15", end="2016-01-20") - assert np.min(dr) == Timestamp("2016-01-15 00:00:00", freq="D") - assert np.max(dr) == Timestamp("2016-01-20 00:00:00", freq="D") + assert np.min(dr) == Timestamp("2016-01-15 00:00:00") + assert np.max(dr) == Timestamp("2016-01-20 00:00:00") errmsg = "the 'out' parameter is not supported" with pytest.raises(ValueError, match=errmsg): diff --git a/pandas/tests/resample/test_datetime_index.py b/pandas/tests/resample/test_datetime_index.py index abe834b9fff17..5594659fb4b03 100644 --- a/pandas/tests/resample/test_datetime_index.py +++ b/pandas/tests/resample/test_datetime_index.py @@ -1739,8 +1739,8 @@ def test_get_timestamp_range_edges(first, last, freq, exp_first, exp_last): last = Period(last) last = last.to_timestamp(last.freq) - exp_first = Timestamp(exp_first, freq=freq) - exp_last = Timestamp(exp_last, freq=freq) + exp_first = Timestamp(exp_first) + exp_last = Timestamp(exp_last) freq = pd.tseries.frequencies.to_offset(freq) result = _get_timestamp_range_edges(first, last, freq) diff --git a/pandas/tests/reshape/concat/test_datetimes.py b/pandas/tests/reshape/concat/test_datetimes.py index 0a1ba17949ddb..c4fe16b43313a 100644 --- a/pandas/tests/reshape/concat/test_datetimes.py +++ b/pandas/tests/reshape/concat/test_datetimes.py @@ -403,6 +403,7 @@ def test_concat_multiple_tzs(self): expected = DataFrame({"time": [ts2, ts3]}) tm.assert_frame_equal(results, expected) + @pytest.mark.filterwarnings("ignore:Timestamp.freq is deprecated:FutureWarning") def test_concat_multiindex_with_tz(self): # GH 6606 df = DataFrame( diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py index 63e277c1580af..97e933e9821af 100644 --- a/pandas/tests/reshape/test_pivot.py +++ b/pandas/tests/reshape/test_pivot.py @@ -516,6 +516,7 @@ def test_pivot_index_with_nan(self, method): result = pd.pivot(df, "b", "a", "c") tm.assert_frame_equal(result, pv.T) + @pytest.mark.filterwarnings("ignore:Timestamp.freq is deprecated:FutureWarning") @pytest.mark.parametrize("method", [True, False]) def test_pivot_with_tz(self, method): # GH 5878 diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py index e178b31b9ae93..fd46954fd4c71 100644 --- a/pandas/tests/scalar/timestamp/test_arithmetic.py +++ b/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -36,7 +36,7 @@ def test_overflow_offset_raises(self): # xref https://github.com/statsmodels/statsmodels/issues/3374 # ends up multiplying really large numbers which overflow - stamp = Timestamp("2017-01-13 00:00:00", freq="D") + stamp = Timestamp("2017-01-13 00:00:00") offset_overflow = 20169940 * offsets.Day(1) msg = ( "the add operation between " @@ -116,7 +116,9 @@ def test_addition_subtraction_types(self): td = timedelta(seconds=1) # build a timestamp with a frequency, since then it supports # addition/subtraction of integers - ts = Timestamp(dt, freq="D") + with tm.assert_produces_warning(FutureWarning, match="The 'freq' argument"): + # freq deprecated + ts = Timestamp(dt, freq="D") msg = "Addition/subtraction of integers" with pytest.raises(TypeError, match=msg): @@ -148,6 +150,8 @@ def test_addition_subtraction_types(self): ("M", None, np.timedelta64(1, "M")), ], ) + @pytest.mark.filterwarnings("ignore:Timestamp.freq is deprecated:FutureWarning") + @pytest.mark.filterwarnings("ignore:The 'freq' argument:FutureWarning") def test_addition_subtraction_preserve_frequency(self, freq, td, td64): ts = Timestamp("2014-03-05 00:00:00", freq=freq) original_freq = ts.freq @@ -189,8 +193,8 @@ def test_timestamp_add_timedelta64_unit(self, other, expected_difference): @pytest.mark.parametrize( "ts", [ - Timestamp("1776-07-04", freq="D"), - Timestamp("1776-07-04", tz="UTC", freq="D"), + Timestamp("1776-07-04"), + Timestamp("1776-07-04", tz="UTC"), ], ) @pytest.mark.parametrize( diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py index 83e40aa5cb96b..16ce51a88340e 100644 --- a/pandas/tests/scalar/timestamp/test_constructors.py +++ b/pandas/tests/scalar/timestamp/test_constructors.py @@ -19,6 +19,7 @@ Timestamp, compat, ) +import pandas._testing as tm from pandas.tseries import offsets @@ -195,11 +196,13 @@ def test_constructor_invalid_tz(self): Timestamp("2017-10-22", tzinfo=pytz.utc, tz="UTC") msg = "Invalid frequency:" + msg2 = "The 'freq' argument" with pytest.raises(ValueError, match=msg): # GH#5168 # case where user tries to pass tz as an arg, not kwarg, gets # interpreted as a `freq` - Timestamp("2012-01-01", "US/Pacific") + with tm.assert_produces_warning(FutureWarning, match=msg2): + Timestamp("2012-01-01", "US/Pacific") def test_constructor_strptime(self): # GH25016 @@ -287,6 +290,8 @@ def test_constructor_keyword(self): == repr(Timestamp("2015-11-12 01:02:03.999999")) ) + @pytest.mark.filterwarnings("ignore:Timestamp.freq is:FutureWarning") + @pytest.mark.filterwarnings("ignore:The 'freq' argument:FutureWarning") def test_constructor_fromordinal(self): base = datetime(2000, 1, 1) @@ -523,15 +528,18 @@ def test_construct_with_different_string_format(self, arg): def test_construct_timestamp_preserve_original_frequency(self): # GH 22311 - result = Timestamp(Timestamp("2010-08-08", freq="D")).freq + with tm.assert_produces_warning(FutureWarning, match="The 'freq' argument"): + result = Timestamp(Timestamp("2010-08-08", freq="D")).freq expected = offsets.Day() assert result == expected def test_constructor_invalid_frequency(self): # GH 22311 msg = "Invalid frequency:" + msg2 = "The 'freq' argument" with pytest.raises(ValueError, match=msg): - Timestamp("2012-01-01", freq=[]) + with tm.assert_produces_warning(FutureWarning, match=msg2): + Timestamp("2012-01-01", freq=[]) @pytest.mark.parametrize("box", [datetime, Timestamp]) def test_raise_tz_and_tzinfo_in_datetime_input(self, box): diff --git a/pandas/tests/scalar/timestamp/test_rendering.py b/pandas/tests/scalar/timestamp/test_rendering.py index a27d233d5ab88..2f88f96b6bbea 100644 --- a/pandas/tests/scalar/timestamp/test_rendering.py +++ b/pandas/tests/scalar/timestamp/test_rendering.py @@ -4,6 +4,7 @@ import pytz # noqa # a test below uses pytz but only inside a `eval` call from pandas import Timestamp +import pandas._testing as tm class TestTimestampRendering: @@ -35,17 +36,26 @@ def test_repr(self, date, freq, tz): assert freq_repr not in repr(date_tz) assert date_tz == eval(repr(date_tz)) - date_freq = Timestamp(date, freq=freq) + msg = "The 'freq' argument in Timestamp" + with tm.assert_produces_warning(FutureWarning, match=msg): + date_freq = Timestamp(date, freq=freq) assert date in repr(date_freq) assert tz_repr not in repr(date_freq) assert freq_repr in repr(date_freq) - assert date_freq == eval(repr(date_freq)) + with tm.assert_produces_warning( + FutureWarning, match=msg, check_stacklevel=False + ): + assert date_freq == eval(repr(date_freq)) - date_tz_freq = Timestamp(date, tz=tz, freq=freq) + with tm.assert_produces_warning(FutureWarning, match=msg): + date_tz_freq = Timestamp(date, tz=tz, freq=freq) assert date in repr(date_tz_freq) assert tz_repr in repr(date_tz_freq) assert freq_repr in repr(date_tz_freq) - assert date_tz_freq == eval(repr(date_tz_freq)) + with tm.assert_produces_warning( + FutureWarning, match=msg, check_stacklevel=False + ): + assert date_tz_freq == eval(repr(date_tz_freq)) def test_repr_utcoffset(self): # This can cause the tz field to be populated, but it's redundant to diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 54f3f21dc9f6f..e13242e60e3a3 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -35,13 +35,44 @@ class TestTimestampProperties: + def test_freq_deprecation(self): + # GH#41586 + msg = "The 'freq' argument in Timestamp is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + # warning issued at construction + ts = Timestamp("2021-06-01", freq="D") + ts2 = Timestamp("2021-06-01", freq="B") + + msg = "Timestamp.freq is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + # warning issued at attribute lookup + ts.freq + + for per in ["month", "quarter", "year"]: + for side in ["start", "end"]: + attr = f"is_{per}_{side}" + + with tm.assert_produces_warning(FutureWarning, match=msg): + getattr(ts2, attr) + + # is_(month|quarter|year)_(start|end) does _not_ issue a warning + # with freq="D" bc the result will be unaffected by the deprecation + with tm.assert_produces_warning(None): + getattr(ts, attr) + + @pytest.mark.filterwarnings("ignore:The 'freq' argument:FutureWarning") + @pytest.mark.filterwarnings("ignore:Timestamp.freq is deprecated:FutureWarning") def test_properties_business(self): ts = Timestamp("2017-10-01", freq="B") control = Timestamp("2017-10-01") assert ts.dayofweek == 6 assert ts.day_of_week == 6 assert not ts.is_month_start # not a weekday + assert not ts.freq.is_month_start(ts) + assert ts.freq.is_month_start(ts + Timedelta(days=1)) assert not ts.is_quarter_start # not a weekday + assert not ts.freq.is_quarter_start(ts) + assert ts.freq.is_quarter_start(ts + Timedelta(days=1)) # Control case: non-business is month/qtr start assert control.is_month_start assert control.is_quarter_start @@ -51,7 +82,11 @@ def test_properties_business(self): assert ts.dayofweek == 5 assert ts.day_of_week == 5 assert not ts.is_month_end # not a weekday + assert not ts.freq.is_month_end(ts) + assert ts.freq.is_month_end(ts - Timedelta(days=1)) assert not ts.is_quarter_end # not a weekday + assert not ts.freq.is_quarter_end(ts) + assert ts.freq.is_quarter_end(ts - Timedelta(days=1)) # Control case: non-business is month/qtr start assert control.is_month_end assert control.is_quarter_end @@ -398,10 +433,12 @@ def test_hash_equivalent(self): def test_tz_conversion_freq(self, tz_naive_fixture): # GH25241 - t1 = Timestamp("2019-01-01 10:00", freq="H") - assert t1.tz_localize(tz=tz_naive_fixture).freq == t1.freq - t2 = Timestamp("2019-01-02 12:00", tz="UTC", freq="T") - assert t2.tz_convert(tz="UTC").freq == t2.freq + with tm.assert_produces_warning(FutureWarning, match="freq"): + t1 = Timestamp("2019-01-01 10:00", freq="H") + assert t1.tz_localize(tz=tz_naive_fixture).freq == t1.freq + with tm.assert_produces_warning(FutureWarning, match="freq"): + t2 = Timestamp("2019-01-02 12:00", tz="UTC", freq="T") + assert t2.tz_convert(tz="UTC").freq == t2.freq class TestTimestampNsOperations: @@ -513,26 +550,26 @@ def test_to_pydatetime_nonzero_nano(self): assert result == expected def test_timestamp_to_datetime(self): - stamp = Timestamp("20090415", tz="US/Eastern", freq="D") + stamp = Timestamp("20090415", tz="US/Eastern") dtval = stamp.to_pydatetime() assert stamp == dtval assert stamp.tzinfo == dtval.tzinfo def test_timestamp_to_datetime_dateutil(self): - stamp = Timestamp("20090415", tz="dateutil/US/Eastern", freq="D") + stamp = Timestamp("20090415", tz="dateutil/US/Eastern") dtval = stamp.to_pydatetime() assert stamp == dtval assert stamp.tzinfo == dtval.tzinfo def test_timestamp_to_datetime_explicit_pytz(self): - stamp = Timestamp("20090415", tz=pytz.timezone("US/Eastern"), freq="D") + stamp = Timestamp("20090415", tz=pytz.timezone("US/Eastern")) dtval = stamp.to_pydatetime() assert stamp == dtval assert stamp.tzinfo == dtval.tzinfo @td.skip_if_windows_python_3 def test_timestamp_to_datetime_explicit_dateutil(self): - stamp = Timestamp("20090415", tz=gettz("US/Eastern"), freq="D") + stamp = Timestamp("20090415", tz=gettz("US/Eastern")) dtval = stamp.to_pydatetime() assert stamp == dtval assert stamp.tzinfo == dtval.tzinfo diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index fed9c72d62fa0..56af003c59bf5 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -987,13 +987,9 @@ def test_constructor_with_datetime_tz(self): # indexing result = s.iloc[0] - assert result == Timestamp( - "2013-01-01 00:00:00-0500", tz="US/Eastern", freq="D" - ) + assert result == Timestamp("2013-01-01 00:00:00-0500", tz="US/Eastern") result = s[0] - assert result == Timestamp( - "2013-01-01 00:00:00-0500", tz="US/Eastern", freq="D" - ) + assert result == Timestamp("2013-01-01 00:00:00-0500", tz="US/Eastern") result = s[Series([True, True, False], index=s.index)] tm.assert_series_equal(result, s[0:2])