|
| 1 | +import re |
| 2 | + |
1 | 3 | cimport numpy as cnp
|
2 | 4 | from cpython.object cimport (
|
3 | 5 | Py_EQ,
|
@@ -2591,20 +2593,30 @@ class Period(_Period):
|
2591 | 2593 | value = value.upper()
|
2592 | 2594 |
|
2593 | 2595 | freqstr = freq.rule_code if freq is not None else None
|
2594 |
| - dt, reso = parse_datetime_string_with_reso(value, freqstr) |
2595 |
| - if reso == "nanosecond": |
2596 |
| - nanosecond = dt.nanosecond |
2597 |
| - if dt is NaT: |
2598 |
| - ordinal = NPY_NAT |
2599 | 2596 |
|
2600 |
| - if freq is None and ordinal != NPY_NAT: |
2601 |
| - # Skip NaT, since it doesn't have a resolution |
2602 |
| - try: |
2603 |
| - freq = attrname_to_abbrevs[reso] |
2604 |
| - except KeyError: |
2605 |
| - raise ValueError(f"Invalid frequency or could not " |
2606 |
| - f"infer: {reso}") |
2607 |
| - freq = to_offset(freq) |
| 2597 | + try: |
| 2598 | + dt, reso = parse_datetime_string_with_reso(value, freqstr) |
| 2599 | + except ValueError: |
| 2600 | + match = re.search(r"^\d{4}-\d{2}-\d{2}/\d{4}-\d{2}-\d{2}", value) |
| 2601 | + if not match: |
| 2602 | + raise |
| 2603 | + # Case that cannot be parsed (correctly) by our datetime |
| 2604 | + # parsing logic |
| 2605 | + dt, freq = parse_weekly_str(value, freq) |
| 2606 | + else: |
| 2607 | + if reso == "nanosecond": |
| 2608 | + nanosecond = dt.nanosecond |
| 2609 | + if dt is NaT: |
| 2610 | + ordinal = NPY_NAT |
| 2611 | + |
| 2612 | + if freq is None and ordinal != NPY_NAT: |
| 2613 | + # Skip NaT, since it doesn't have a resolution |
| 2614 | + try: |
| 2615 | + freq = attrname_to_abbrevs[reso] |
| 2616 | + except KeyError: |
| 2617 | + raise ValueError(f"Invalid frequency or could not " |
| 2618 | + f"infer: {reso}") |
| 2619 | + freq = to_offset(freq) |
2608 | 2620 |
|
2609 | 2621 | elif PyDateTime_Check(value):
|
2610 | 2622 | dt = value
|
@@ -2664,3 +2676,31 @@ def validate_end_alias(how: str) -> str: # Literal["E", "S"]
|
2664 | 2676 | if how not in {"S", "E"}:
|
2665 | 2677 | raise ValueError("How must be one of S or E")
|
2666 | 2678 | return how
|
| 2679 | + |
| 2680 | + |
| 2681 | +cdef parse_weekly_str(value, BaseOffset freq): |
| 2682 | + """ |
| 2683 | + Parse e.g. "2017-01-23/2017-01-29", which cannot be parsed by the general |
| 2684 | + datetime-parsing logic. This ensures that we can round-trip with |
| 2685 | + Period.__str__ with weekly freq. |
| 2686 | + """ |
| 2687 | + match = re.search(r"^\d{4}-\d{2}-\d{2}/\d{4}-\d{2}-\d{2}", value) |
| 2688 | + if not match: |
| 2689 | + raise ValueError("Could not parse as weekly-freq Period") |
| 2690 | + |
| 2691 | + start, end = value.split("/") |
| 2692 | + start = Timestamp(start) |
| 2693 | + end = Timestamp(end) |
| 2694 | + |
| 2695 | + if (end - start).days != 6: |
| 2696 | + # We are interested in cases where this is str(period) |
| 2697 | + # of a Week-freq period |
| 2698 | + raise ValueError("Could not parse as weekly-freq Period") |
| 2699 | + |
| 2700 | + if freq is None: |
| 2701 | + day_name = end.day_name()[:3].upper() |
| 2702 | + freqstr = f"W-{day_name}" |
| 2703 | + freq = to_offset(freqstr) |
| 2704 | + # We _should_ have freq.is_on_offset(end) |
| 2705 | + |
| 2706 | + return end, freq |
0 commit comments