From 91a7c7eb53cecafc51d94d458491ffa197f48841 Mon Sep 17 00:00:00 2001 From: OlivierLuG Date: Thu, 11 Jun 2020 23:17:32 +0200 Subject: [PATCH 1/7] BUG #34621 added nanosecond support to class Period --- pandas/_libs/tslibs/period.pyx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 47ebf139ed496..c911566bd5510 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -2355,6 +2355,7 @@ class Period(_Period): if freq is not None: freq = cls._maybe_convert_freq(freq) + nanosecond = 0 if ordinal is not None and value is not None: raise ValueError("Only value or ordinal but not both should be " @@ -2404,6 +2405,13 @@ class Period(_Period): value = str(value) value = value.upper() dt, reso = parse_time_string(value, freq) + try: + ts = Timestamp(value, freq=freq) + nanosecond = ts.nanosecond + if nanosecond != 0: + reso = 'nanosecond' + except: + pass if dt is NaT: ordinal = NPY_NAT @@ -2435,7 +2443,7 @@ class Period(_Period): base = freq_to_dtype_code(freq) ordinal = period_ordinal(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, - dt.microsecond, 0, base) + dt.microsecond, nanosecond, base) return cls._from_ordinal(ordinal, freq) From 73a6eab7b5c9953929a95b696b7d025d40d3fee4 Mon Sep 17 00:00:00 2001 From: OlivierLuG Date: Fri, 12 Jun 2020 23:43:05 +0200 Subject: [PATCH 2/7] BUG #34621 added tests --- pandas/_libs/tslibs/period.pyx | 7 ++++--- pandas/tests/scalar/period/test_period.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index c911566bd5510..80a2839c1dffe 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -32,6 +32,7 @@ from pandas._libs.tslibs.np_datetime cimport ( NPY_FR_D, NPY_FR_us, ) +from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime cdef extern from "src/datetime/np_datetime.h": int64_t npy_datetimestruct_to_datetime(NPY_DATETIMEUNIT fr, @@ -2410,8 +2411,8 @@ class Period(_Period): nanosecond = ts.nanosecond if nanosecond != 0: reso = 'nanosecond' - except: - pass + except OutOfBoundsDatetime: + nanosecond = 0 if dt is NaT: ordinal = NPY_NAT @@ -2443,7 +2444,7 @@ class Period(_Period): base = freq_to_dtype_code(freq) ordinal = period_ordinal(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, - dt.microsecond, nanosecond, base) + dt.microsecond, 1000*nanosecond, base) return cls._from_ordinal(ordinal, freq) diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index 702899f163e06..2bad9b6c7bedb 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -484,6 +484,23 @@ def test_period_cons_combined(self): with pytest.raises(ValueError, match=msg): Period("2011-01", freq="1D1W") + #@pytest.mark.xfail + @pytest.mark.parametrize("day_", ["1970/01/01 ", "2020-12-31 ", "1981/09/13 "]) + @pytest.mark.parametrize("hour_", ["00:00:00", "00:00:01", "23:59:59", "12:00:59"]) + @pytest.mark.parametrize( + "floating_sec_, expected", + [ + (".000000001", 1), + (".000000999", 999), + (".123456789", 789), + (".999999999", 999), + ], + ) + def test_period_constructor_nanosecond(self, day_, hour_, floating_sec_, expected): + # GH 34621 + result = Period(day_ + hour_ + floating_sec_).start_time.nanosecond + assert result == expected + class TestPeriodMethods: def test_round_trip(self): From ea9f69ce144a815c151a2f60ac458d3d5f2dd297 Mon Sep 17 00:00:00 2001 From: OlivierLuG Date: Fri, 12 Jun 2020 23:52:37 +0200 Subject: [PATCH 3/7] BUG #34621 added tests (black pandas passed) --- pandas/tests/scalar/period/test_period.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index 2bad9b6c7bedb..52f46a2767b45 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -484,7 +484,6 @@ def test_period_cons_combined(self): with pytest.raises(ValueError, match=msg): Period("2011-01", freq="1D1W") - #@pytest.mark.xfail @pytest.mark.parametrize("day_", ["1970/01/01 ", "2020-12-31 ", "1981/09/13 "]) @pytest.mark.parametrize("hour_", ["00:00:00", "00:00:01", "23:59:59", "12:00:59"]) @pytest.mark.parametrize( From ab5640f6ef9160ba86028ccd25e96d076d80f528 Mon Sep 17 00:00:00 2001 From: OlivierLuG Date: Sat, 13 Jun 2020 15:23:58 +0200 Subject: [PATCH 4/7] #34131 fix one test failure --- pandas/_libs/tslibs/period.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 80a2839c1dffe..6b9fcbfbdd2f4 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -2411,7 +2411,7 @@ class Period(_Period): nanosecond = ts.nanosecond if nanosecond != 0: reso = 'nanosecond' - except OutOfBoundsDatetime: + except (ValueError, OutOfBoundsDatetime): nanosecond = 0 if dt is NaT: ordinal = NPY_NAT From 95d740e5fd9e2be58b6004baf22ab9e154a2678e Mon Sep 17 00:00:00 2001 From: OlivierLuG Date: Mon, 15 Jun 2020 21:58:33 +0200 Subject: [PATCH 5/7] #34621 review taken into account --- pandas/_libs/tslibs/period.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 6b9fcbfbdd2f4..c4be74e5e2bcd 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -2407,11 +2407,11 @@ class Period(_Period): value = value.upper() dt, reso = parse_time_string(value, freq) try: - ts = Timestamp(value, freq=freq) + ts = Timestamp(value) nanosecond = ts.nanosecond if nanosecond != 0: reso = 'nanosecond' - except (ValueError, OutOfBoundsDatetime): + except ValueError: nanosecond = 0 if dt is NaT: ordinal = NPY_NAT @@ -2498,7 +2498,7 @@ def quarter_to_myear(year: int, quarter: int, freq): mnum = c_MONTH_NUMBERS[get_rule_month(freq)] + 1 month = (mnum + (quarter - 1) * 3) % 12 + 1 if month > mnum: - year -= 1 + year -= 1ough to only use ValueError, as that would already cover the OutOfBoundsDatet return year, month # TODO: This whole func is really similar to parsing.pyx L434-L450 From abb09363c9ff6e2741ad31aac81cb781499d484e Mon Sep 17 00:00:00 2001 From: OlivierLuG Date: Mon, 15 Jun 2020 22:09:30 +0200 Subject: [PATCH 6/7] #34621 review taken into account, and suppress a bad copy/paste on line 2501 --- pandas/_libs/tslibs/period.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index c4be74e5e2bcd..0c89de5a071a6 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -2498,7 +2498,7 @@ def quarter_to_myear(year: int, quarter: int, freq): mnum = c_MONTH_NUMBERS[get_rule_month(freq)] + 1 month = (mnum + (quarter - 1) * 3) % 12 + 1 if month > mnum: - year -= 1ough to only use ValueError, as that would already cover the OutOfBoundsDatet + year -= 1 return year, month # TODO: This whole func is really similar to parsing.pyx L434-L450 From 8a559cb07f2c731b0ab83fe08ccc20c59b5157b9 Mon Sep 17 00:00:00 2001 From: OlivierLuG Date: Tue, 16 Jun 2020 20:42:49 +0200 Subject: [PATCH 7/7] #34621 Added nanosecond support to Period constructor --- pandas/_libs/tslibs/period.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 0c89de5a071a6..cb0fd7d1aa380 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -32,7 +32,6 @@ from pandas._libs.tslibs.np_datetime cimport ( NPY_FR_D, NPY_FR_us, ) -from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime cdef extern from "src/datetime/np_datetime.h": int64_t npy_datetimestruct_to_datetime(NPY_DATETIMEUNIT fr, @@ -2408,11 +2407,12 @@ class Period(_Period): dt, reso = parse_time_string(value, freq) try: ts = Timestamp(value) + except ValueError: + nanosecond = 0 + else: nanosecond = ts.nanosecond if nanosecond != 0: reso = 'nanosecond' - except ValueError: - nanosecond = 0 if dt is NaT: ordinal = NPY_NAT