From 8e347dfd0eb805bc8933b9e6a1f99403243ef259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dea=20Mar=C3=ADa=20L=C3=A9on?= Date: Thu, 15 Jun 2023 18:07:48 +0200 Subject: [PATCH 1/6] Added Timestamp examples --- ci/code_checks.sh | 4 -- pandas/_libs/tslibs/nattype.pyx | 53 +++++++++++++++++++-- pandas/_libs/tslibs/timestamps.pyx | 74 ++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/ci/code_checks.sh b/ci/code_checks.sh index adda422296396..0dbe3fe0fdb9a 100755 --- a/ci/code_checks.sh +++ b/ci/code_checks.sh @@ -105,10 +105,6 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then pandas.errors.UnsupportedFunctionCall \ pandas.test \ pandas.NaT \ - pandas.Timestamp.date \ - pandas.Timestamp.dst \ - pandas.Timestamp.isocalendar \ - pandas.Timestamp.isoweekday \ pandas.Timestamp.strptime \ pandas.Timestamp.time \ pandas.Timestamp.timetuple \ diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index ea859a5f7d53d..513b1f79ca2d2 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -438,6 +438,14 @@ class NaTType(_NaT): Return the day of the week represented by the date. Monday == 1 ... Sunday == 7. + + Examples + -------- + >>> ts = pd.Timestamp('2023-01-01 10:00:00') + >>> ts + Timestamp('2023-01-01 10:00:00') + >>> ts.isoweekday() + 7 """, ) total_seconds = _make_nan_func("total_seconds", timedelta.total_seconds.__doc__) @@ -494,13 +502,9 @@ class NaTType(_NaT): """, ) # _nat_methods - date = _make_nat_func("date", datetime.date.__doc__) - utctimetuple = _make_error_func("utctimetuple", datetime) timetz = _make_error_func("timetz", datetime) timetuple = _make_error_func("timetuple", datetime) - isocalendar = _make_error_func("isocalendar", datetime) - dst = _make_error_func("dst", datetime) time = _make_error_func("time", datetime) toordinal = _make_error_func("toordinal", datetime) tzname = _make_error_func("tzname", datetime) @@ -512,6 +516,47 @@ class NaTType(_NaT): # ---------------------------------------------------------------------- # The remaining methods have docstrings copy/pasted from the analogous # Timestamp methods. + isocalendar = _make_error_func( + "isocalendar", + """ + Return a named tuple containing ISO year, week number, and weekday. + + Examples + -------- + >>> ts = pd.Timestamp('2023-01-01 10:00:00') + >>> ts + Timestamp('2023-01-01 10:00:00') + >>> ts.isocalendar() + datetime.IsoCalendarDate(year=2022, week=52, weekday=7) + """ + ) + dst = _make_error_func( + "dst", + """ + Return self.tzinfo.dst(self). + + Examples + -------- + >>> ts = pd.Timestamp('2000-06-01 00:00:00', + ... tz='Europe/Brussels').dst() + >>> ts + datetime.timedelta(seconds=3600) + """ + ) + date = _make_nat_func( + "date", + """ + Return date object with same year, month and day. + + Examples + -------- + >>> ts = pd.Timestamp('2023-01-01 10:00:00.00') + >>> ts + Timestamp('2023-01-01 10:00:00') + >>> ts.date() + datetime.date(2023, 1, 1) + """ + ) ctime = _make_error_func( "ctime", diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index d66e856ef77cf..cccada93412c7 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1531,6 +1531,72 @@ class Timestamp(_Timestamp): ) from err return _dt.ctime() + def date(self): + """ + Return date object with same year, month and day. + + Examples + -------- + >>> ts = pd.Timestamp('2023-01-01 10:00:00.00') + >>> ts + Timestamp('2023-01-01 10:00:00') + >>> ts.date() + datetime.date(2023, 1, 1) + """ + try: + _dt = datetime(self.year, self.month, self.day, + self.hour, self.minute, self.second, + self.microsecond, self.tzinfo, fold=self.fold) + except ValueError as err: + raise NotImplementedError( + "date not yet supported on Timestamps which " + "are outside the range of Python's standard library. " + "For now, please call the components you need (such as `.year` " + "and `.month`) and construct your string from there." + ) from err + return _dt.date() + + def dst(self): + """ + Return self.tzinfo.dst(self). + + Examples + -------- + >>> ts = pd.Timestamp('2000-06-01 00:00:00', + ... tz='Europe/Brussels').dst() + >>> ts + datetime.timedelta(seconds=3600) + """ + _dt = datetime(self.year, self.month, self.day, + self.hour, self.minute, self.second, + self.microsecond, self.tzinfo, fold=self.fold) + return _dt.dst() + + def isocalendar(self): + """ + Return a named tuple containing ISO year, week number, and weekday. + + Examples + -------- + >>> ts = pd.Timestamp('2023-01-01 10:00:00') + >>> ts + Timestamp('2023-01-01 10:00:00') + >>> ts.isocalendar() + datetime.IsoCalendarDate(year=2022, week=52, weekday=7) + """ + try: + _dt = datetime(self.year, self.month, self.day, + self.hour, self.minute, self.second, + self.microsecond, self.tzinfo, fold=self.fold) + except ValueError as err: + raise NotImplementedError( + "isocalendar not yet supported on Timestamps which " + "are outside the range of Python's standard library. " + "For now, please call the components you need (such as `.year` " + "and `.month`) and construct your string from there." + ) from err + return _dt.isocalendar() + # Issue 25016. @classmethod def strptime(cls, date_string, format): @@ -2384,6 +2450,14 @@ default 'raise' Return the day of the week represented by the date. Monday == 1 ... Sunday == 7. + + Examples + -------- + >>> ts = pd.Timestamp('2023-01-01 10:00:00') + >>> ts + Timestamp('2023-01-01 10:00:00') + >>> ts.isoweekday() + 7 """ # same as super().isoweekday(), but that breaks because of how # we have overridden year, see note in create_timestamp_from_ts From d18c825d70e4019ba0fde75150113967d8292624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dea=20Mar=C3=ADa=20L=C3=A9on?= Date: Fri, 16 Jun 2023 15:40:57 +0200 Subject: [PATCH 2/6] Added test and corrected msg --- pandas/_libs/tslibs/timestamps.pyx | 13 +++++++++--- .../tests/scalar/timestamp/test_timestamp.py | 20 +++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index cccada93412c7..a6e484cf6406b 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1592,8 +1592,6 @@ class Timestamp(_Timestamp): raise NotImplementedError( "isocalendar not yet supported on Timestamps which " "are outside the range of Python's standard library. " - "For now, please call the components you need (such as `.year` " - "and `.month`) and construct your string from there." ) from err return _dt.isocalendar() @@ -2461,7 +2459,16 @@ default 'raise' """ # same as super().isoweekday(), but that breaks because of how # we have overridden year, see note in create_timestamp_from_ts - return self.weekday() + 1 + try: + _dt = datetime(self.year, self.month, self.day, + self.hour, self.minute, self.second, + self.microsecond, self.tzinfo, fold=self.fold) + except ValueError as err: + raise NotImplementedError( + "isocalendar not yet supported on Timestamps which " + "are outside the range of Python's standard library. " + ) from err + return _dt.weekday() + 1 def weekday(self): """ diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index afb4dd7422114..8dd85fcf01805 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -1124,9 +1124,25 @@ def test_negative_dates(): # https://github.com/pandas-dev/pandas/issues/50787 ts = Timestamp("-2000-01-01") msg = ( - "^strftime not yet supported on Timestamps which are outside the range of " + " not yet supported on Timestamps which are outside the range of " "Python's standard library. For now, please call the components you need " r"\(such as `.year` and `.month`\) and construct your string from there.$" ) - with pytest.raises(NotImplementedError, match=msg): + func = "^strftime" + with pytest.raises(NotImplementedError, match=func + msg): ts.strftime("%Y") + func = "^date" + with pytest.raises(NotImplementedError, match=func + msg): + ts.date() + + msg = ( + "isocalendar not yet supported on Timestamps which " + "are outside the range of Python's standard library. " + ) + with pytest.raises(NotImplementedError, match=msg): + ts.isocalendar() + with pytest.raises(NotImplementedError, match=msg): + ts.isoweekday() + + with pytest.raises(pytz.NonExistentTimeError, match="-2000-01-01 00:00:00"): + Timestamp("-2000", tz="Europe/Brussels").dst() From fa41d006c7d6e981c52ef89124f597a5d7229d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dea=20Mar=C3=ADa=20L=C3=A9on?= Date: Fri, 16 Jun 2023 19:53:28 +0200 Subject: [PATCH 3/6] Correcting error on isoweekday --- pandas/_libs/tslibs/timestamps.pyx | 2 +- pandas/tests/scalar/timestamp/test_timestamp.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index a6e484cf6406b..8076f61aba3d8 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -2465,7 +2465,7 @@ default 'raise' self.microsecond, self.tzinfo, fold=self.fold) except ValueError as err: raise NotImplementedError( - "isocalendar not yet supported on Timestamps which " + "isoweekday not yet supported on Timestamps which " "are outside the range of Python's standard library. " ) from err return _dt.weekday() + 1 diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 8dd85fcf01805..b2652de3c0c54 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -1136,12 +1136,14 @@ def test_negative_dates(): ts.date() msg = ( - "isocalendar not yet supported on Timestamps which " + " not yet supported on Timestamps which " "are outside the range of Python's standard library. " ) - with pytest.raises(NotImplementedError, match=msg): + func = "^isocalendar" + with pytest.raises(NotImplementedError, match=func + msg): ts.isocalendar() - with pytest.raises(NotImplementedError, match=msg): + func = "^isoweekday" + with pytest.raises(NotImplementedError, match=func + msg): ts.isoweekday() with pytest.raises(pytz.NonExistentTimeError, match="-2000-01-01 00:00:00"): From 81c14c729ede615d547ef49e9340905e21bc6c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dea=20Mar=C3=ADa=20L=C3=A9on?= Date: Mon, 19 Jun 2023 19:12:59 +0200 Subject: [PATCH 4/6] Added requested corrections --- doc/source/whatsnew/v2.1.0.rst | 1 + pandas/_libs/tslibs/nattype.pyx | 7 ++++--- pandas/_libs/tslibs/timestamps.pyx | 20 +++++++------------ .../tests/scalar/timestamp/test_timestamp.py | 6 +++--- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index e2f0904a78cf9..dcd1b7283fa42 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -349,6 +349,7 @@ Datetimelike ^^^^^^^^^^^^ - :meth:`DatetimeIndex.map` with ``na_action="ignore"`` now works as expected. (:issue:`51644`) - Bug in :func:`date_range` when ``freq`` was a :class:`DateOffset` with ``nanoseconds`` (:issue:`46877`) +- Bug in :meth:`Timestamp.date`, :meth:`Timestamp.isocalendar`, and :meth:`Timestamp.isoweekday` were returning incorrect results for inputs outside those supported by the Python standard library's datetime module (:issue:`53668`) - Bug in :meth:`Timestamp.round` with values close to the implementation bounds returning incorrect results instead of raising ``OutOfBoundsDatetime`` (:issue:`51494`) - 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 constructing a :class:`Series` or :class:`DataFrame` from a datetime or timedelta scalar always inferring nanosecond resolution instead of inferring from the input (:issue:`52212`) diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index a201093a6c0e7..a179aa225f845 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -545,13 +545,14 @@ class NaTType(_NaT): dst = _make_error_func( "dst", """ - Return self.tzinfo.dst(self). + Return the daylight saving time (DST) adjustment. Examples -------- - >>> ts = pd.Timestamp('2000-06-01 00:00:00', - ... tz='Europe/Brussels').dst() + >>> ts = pd.Timestamp('2000-06-01 00:00:00', tz='Europe/Brussels') >>> ts + Timestamp('2000-06-01 00:00:00+0200', tz='Europe/Brussels') + >>> ts.dst() datetime.timedelta(seconds=3600) """ ) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 8076f61aba3d8..885d953faf28e 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1544,33 +1544,27 @@ class Timestamp(_Timestamp): datetime.date(2023, 1, 1) """ try: - _dt = datetime(self.year, self.month, self.day, - self.hour, self.minute, self.second, - self.microsecond, self.tzinfo, fold=self.fold) + _dt = date(self.year, self.month, self.day) except ValueError as err: raise NotImplementedError( "date not yet supported on Timestamps which " "are outside the range of Python's standard library. " - "For now, please call the components you need (such as `.year` " - "and `.month`) and construct your string from there." ) from err - return _dt.date() + return _dt def dst(self): """ - Return self.tzinfo.dst(self). + Return the daylight saving time (DST) adjustment. Examples -------- - >>> ts = pd.Timestamp('2000-06-01 00:00:00', - ... tz='Europe/Brussels').dst() + >>> ts = pd.Timestamp('2000-06-01 00:00:00', tz='Europe/Brussels') >>> ts + Timestamp('2000-06-01 00:00:00+0200', tz='Europe/Brussels') + >>> ts.dst() datetime.timedelta(seconds=3600) """ - _dt = datetime(self.year, self.month, self.day, - self.hour, self.minute, self.second, - self.microsecond, self.tzinfo, fold=self.fold) - return _dt.dst() + return super().dst() def isocalendar(self): """ diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index b2652de3c0c54..6ef0f761f0dcb 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -1131,14 +1131,14 @@ def test_negative_dates(): func = "^strftime" with pytest.raises(NotImplementedError, match=func + msg): ts.strftime("%Y") - func = "^date" - with pytest.raises(NotImplementedError, match=func + msg): - ts.date() msg = ( " not yet supported on Timestamps which " "are outside the range of Python's standard library. " ) + func = "^date" + with pytest.raises(NotImplementedError, match=func + msg): + ts.date() func = "^isocalendar" with pytest.raises(NotImplementedError, match=func + msg): ts.isocalendar() From 5febc4ebedf6dc2297bf26abc8e7bf1fd2244a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dea=20Mar=C3=ADa=20L=C3=A9on?= Date: Mon, 19 Jun 2023 19:18:37 +0200 Subject: [PATCH 5/6] added import datetime --- pandas/_libs/tslibs/timestamps.pyx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 885d953faf28e..b5918818ea756 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -45,6 +45,8 @@ from cpython.object cimport ( import_datetime() +import datetime as dt + from pandas._libs.tslibs cimport ccalendar from pandas._libs.tslibs.base cimport ABCTimestamp @@ -1544,7 +1546,7 @@ class Timestamp(_Timestamp): datetime.date(2023, 1, 1) """ try: - _dt = date(self.year, self.month, self.day) + _dt = dt.date(self.year, self.month, self.day) except ValueError as err: raise NotImplementedError( "date not yet supported on Timestamps which " From 533fe55cb7ec414cc50fcd32836100bc526fc765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dea=20Mar=C3=ADa=20L=C3=A9on?= Date: Tue, 20 Jun 2023 12:29:56 +0200 Subject: [PATCH 6/6] Removed isoweekday raise error, corrected time and timetz --- doc/source/whatsnew/v2.1.0.rst | 2 +- pandas/_libs/tslibs/timestamps.pyx | 12 ++---------- pandas/tests/scalar/timestamp/test_timestamp.py | 6 ------ 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 5c18583b23177..085e2bb1de55b 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -350,7 +350,7 @@ Datetimelike ^^^^^^^^^^^^ - :meth:`DatetimeIndex.map` with ``na_action="ignore"`` now works as expected. (:issue:`51644`) - Bug in :func:`date_range` when ``freq`` was a :class:`DateOffset` with ``nanoseconds`` (:issue:`46877`) -- Bug in :meth:`Timestamp.date`, :meth:`Timestamp.isocalendar`, and :meth:`Timestamp.isoweekday` were returning incorrect results for inputs outside those supported by the Python standard library's datetime module (:issue:`53668`) +- Bug in :meth:`Timestamp.date`, :meth:`Timestamp.isocalendar` were returning incorrect results for inputs outside those supported by the Python standard library's datetime module (:issue:`53668`) - Bug in :meth:`Timestamp.round` with values close to the implementation bounds returning incorrect results instead of raising ``OutOfBoundsDatetime`` (:issue:`51494`) - 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 constructing a :class:`Series` or :class:`DataFrame` from a datetime or timedelta scalar always inferring nanosecond resolution instead of inferring from the input (:issue:`52212`) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index b5918818ea756..54f0dac459990 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -2455,16 +2455,8 @@ default 'raise' """ # same as super().isoweekday(), but that breaks because of how # we have overridden year, see note in create_timestamp_from_ts - try: - _dt = datetime(self.year, self.month, self.day, - self.hour, self.minute, self.second, - self.microsecond, self.tzinfo, fold=self.fold) - except ValueError as err: - raise NotImplementedError( - "isoweekday not yet supported on Timestamps which " - "are outside the range of Python's standard library. " - ) from err - return _dt.weekday() + 1 + + return self.weekday() + 1 def weekday(self): """ diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 6ef0f761f0dcb..2e4b41e06bbd6 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -1142,9 +1142,3 @@ def test_negative_dates(): func = "^isocalendar" with pytest.raises(NotImplementedError, match=func + msg): ts.isocalendar() - func = "^isoweekday" - with pytest.raises(NotImplementedError, match=func + msg): - ts.isoweekday() - - with pytest.raises(pytz.NonExistentTimeError, match="-2000-01-01 00:00:00"): - Timestamp("-2000", tz="Europe/Brussels").dst()