Skip to content

Commit 82e9f8f

Browse files
committed
BUG: Parsing week-freq Periods
1 parent d72e244 commit 82e9f8f

File tree

3 files changed

+69
-18
lines changed

3 files changed

+69
-18
lines changed

doc/source/whatsnew/v2.0.0.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,8 @@ Period
10281028
- Bug in :meth:`Period.strftime` and :meth:`PeriodIndex.strftime`, raising ``UnicodeDecodeError`` when a locale-specific directive was passed (:issue:`46319`)
10291029
- Bug in adding a :class:`Period` object to an array of :class:`DateOffset` objects incorrectly raising ``TypeError`` (:issue:`50162`)
10301030
- Bug in :class:`Period` where passing a string with finer resolution than nanosecond would result in a ``KeyError`` instead of dropping the extra precision (:issue:`50417`)
1031+
- Bug in parsing strings representing Week-periods e.g. "2017-01-23/2017-01-29" as minute-frequency instead of week-frequency (:issue:`??`)
1032+
-
10311033

10321034
Plotting
10331035
^^^^^^^^

pandas/_libs/tslibs/period.pyx

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import re
2+
13
cimport numpy as cnp
24
from cpython.object cimport (
35
Py_EQ,
@@ -2590,26 +2592,33 @@ class Period(_Period):
25902592
value = str(value)
25912593
value = value.upper()
25922594

2593-
freqstr = freq.rule_code if freq is not None else None
2594-
dt, reso = parse_datetime_string_with_reso(value, freqstr)
2595-
try:
2596-
ts = Timestamp(value)
2597-
except ValueError:
2598-
nanosecond = 0
2599-
else:
2600-
nanosecond = ts.nanosecond
2601-
if nanosecond != 0:
2602-
reso = "nanosecond"
2603-
if dt is NaT:
2604-
ordinal = NPY_NAT
2595+
match = re.search(r"^\d{4}-\d{2}-\d{2}/\d{4}-\d{2}-\d{2}", value)
2596+
if match:
2597+
# Case that cannot be parsed (correctly) by our datetime
2598+
# parsing logic
2599+
dt, freq = parse_weekly_str(value, freq)
26052600

2606-
if freq is None:
2601+
else:
2602+
freqstr = freq.rule_code if freq is not None else None
2603+
dt, reso = parse_datetime_string_with_reso(value, freqstr)
26072604
try:
2608-
freq = attrname_to_abbrevs[reso]
2609-
except KeyError:
2610-
raise ValueError(f"Invalid frequency or could not "
2611-
f"infer: {reso}")
2612-
freq = to_offset(freq)
2605+
ts = Timestamp(value)
2606+
except ValueError:
2607+
nanosecond = 0
2608+
else:
2609+
nanosecond = ts.nanosecond
2610+
if nanosecond != 0:
2611+
reso = "nanosecond"
2612+
if dt is NaT:
2613+
ordinal = NPY_NAT
2614+
2615+
if freq is None:
2616+
try:
2617+
freq = attrname_to_abbrevs[reso]
2618+
except KeyError:
2619+
raise ValueError(f"Invalid frequency or could not "
2620+
f"infer: {reso}")
2621+
freq = to_offset(freq)
26132622

26142623
elif PyDateTime_Check(value):
26152624
dt = value
@@ -2669,3 +2678,31 @@ def validate_end_alias(how: str) -> str: # Literal["E", "S"]
26692678
if how not in {"S", "E"}:
26702679
raise ValueError("How must be one of S or E")
26712680
return how
2681+
2682+
2683+
cdef parse_weekly_str(value, BaseOffset freq):
2684+
"""
2685+
Parse e.g. "2017-01-23/2017-01-29", which cannot be parsed by the general
2686+
datetime-parsing logic. This ensures that we can round-trip with
2687+
Period.__str__ with weekly freq.
2688+
"""
2689+
match = re.search(r"^\d{4}-\d{2}-\d{2}/\d{4}-\d{2}-\d{2}", value)
2690+
if not match:
2691+
raise ValueError("Could not parse as weekly-freq Period")
2692+
2693+
start, end = value.split("/")
2694+
start = Timestamp(start)
2695+
end = Timestamp(end)
2696+
2697+
if (end - start).days != 6:
2698+
# We are interested in cases where this is str(period)
2699+
# of a Week-freq period
2700+
raise ValueError("Could not parse as weekly-freq Period")
2701+
2702+
if freq is None:
2703+
day_name = end.day_name()[:3].upper()
2704+
freqstr = f"W-{day_name}"
2705+
freq = to_offset(freqstr)
2706+
# We _should_ have freq.is_on_offset(end)
2707+
2708+
return end, freq

pandas/tests/scalar/period/test_period.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,18 @@ def test_period_cons_weekly(self, num, day):
399399
assert result == expected
400400
assert isinstance(result, Period)
401401

402+
def test_parse_week_str_roundstrip(self):
403+
per = Period("2017-01-23/2017-01-29")
404+
assert per.freq.freqstr == "W-SUN"
405+
406+
per = Period("2017-01-24/2017-01-30")
407+
assert per.freq.freqstr == "W-MON"
408+
409+
msg = "Could not parse as weekly-freq Period"
410+
with pytest.raises(ValueError, match=msg):
411+
# not 6 days apart
412+
Period("2016-01-23/2017-01-29")
413+
402414
def test_period_from_ordinal(self):
403415
p = Period("2011-01", freq="M")
404416
res = Period._from_ordinal(p.ordinal, freq="M")

0 commit comments

Comments
 (0)