11
11
from typing import (
12
12
TYPE_CHECKING ,
13
13
Any ,
14
- Final ,
15
14
cast ,
16
15
)
17
16
30
29
Timestamp ,
31
30
to_offset ,
32
31
)
33
- from pandas ._libs .tslibs .dtypes import FreqGroup
34
- from pandas ._typing import F
32
+ from pandas ._libs .tslibs .dtypes import (
33
+ FreqGroup ,
34
+ periods_per_day ,
35
+ )
36
+ from pandas ._typing import (
37
+ F ,
38
+ npt ,
39
+ )
35
40
36
41
from pandas .core .dtypes .common import (
37
42
is_float ,
60
65
61
66
from pandas ._libs .tslibs .offsets import BaseOffset
62
67
63
- # constants
64
- HOURS_PER_DAY : Final = 24.0
65
- MIN_PER_HOUR : Final = 60.0
66
- SEC_PER_MIN : Final = 60.0
67
-
68
- SEC_PER_HOUR : Final = SEC_PER_MIN * MIN_PER_HOUR
69
- SEC_PER_DAY : Final = SEC_PER_HOUR * HOURS_PER_DAY
70
-
71
- MUSEC_PER_DAY : Final = 10 ** 6 * SEC_PER_DAY
72
68
73
69
_mpl_units = {} # Cache for units overwritten by us
74
70
@@ -495,7 +491,7 @@ def _get_default_annual_spacing(nyears) -> tuple[int, int]:
495
491
return (min_spacing , maj_spacing )
496
492
497
493
498
- def period_break (dates : PeriodIndex , period : str ) -> np .ndarray :
494
+ def _period_break (dates : PeriodIndex , period : str ) -> npt . NDArray [ np .intp ] :
499
495
"""
500
496
Returns the indices where the given period changes.
501
497
@@ -506,12 +502,17 @@ def period_break(dates: PeriodIndex, period: str) -> np.ndarray:
506
502
period : str
507
503
Name of the period to monitor.
508
504
"""
505
+ mask = _period_break_mask (dates , period )
506
+ return np .nonzero (mask )[0 ]
507
+
508
+
509
+ def _period_break_mask (dates : PeriodIndex , period : str ) -> npt .NDArray [np .bool_ ]:
509
510
current = getattr (dates , period )
510
511
previous = getattr (dates - 1 * dates .freq , period )
511
- return np . nonzero ( current - previous )[ 0 ]
512
+ return current != previous
512
513
513
514
514
- def has_level_label (label_flags : np .ndarray , vmin : float ) -> bool :
515
+ def has_level_label (label_flags : npt . NDArray [ np .intp ] , vmin : float ) -> bool :
515
516
"""
516
517
Returns true if the ``label_flags`` indicate there is at least one label
517
518
for this level.
@@ -527,54 +528,59 @@ def has_level_label(label_flags: np.ndarray, vmin: float) -> bool:
527
528
return True
528
529
529
530
530
- def _daily_finder ( vmin , vmax , freq : BaseOffset ):
531
+ def _get_periods_per_ymd ( freq : BaseOffset ) -> tuple [ int , int , int ] :
531
532
# error: "BaseOffset" has no attribute "_period_dtype_code"
532
533
dtype_code = freq ._period_dtype_code # type: ignore[attr-defined]
533
534
freq_group = FreqGroup .from_period_dtype_code (dtype_code )
534
535
535
- periodsperday = - 1
536
+ ppd = - 1 # placeholder for above-day freqs
536
537
537
538
if dtype_code >= FreqGroup .FR_HR .value :
538
- if freq_group == FreqGroup .FR_NS :
539
- periodsperday = 24 * 60 * 60 * 1000000000
540
- elif freq_group == FreqGroup .FR_US :
541
- periodsperday = 24 * 60 * 60 * 1000000
542
- elif freq_group == FreqGroup .FR_MS :
543
- periodsperday = 24 * 60 * 60 * 1000
544
- elif freq_group == FreqGroup .FR_SEC :
545
- periodsperday = 24 * 60 * 60
546
- elif freq_group == FreqGroup .FR_MIN :
547
- periodsperday = 24 * 60
548
- elif freq_group == FreqGroup .FR_HR :
549
- periodsperday = 24
550
- else : # pragma: no cover
551
- raise ValueError (f"unexpected frequency: { dtype_code } " )
552
- periodsperyear = 365 * periodsperday
553
- periodspermonth = 28 * periodsperday
554
-
539
+ # error: "BaseOffset" has no attribute "_creso"
540
+ ppd = periods_per_day (freq ._creso ) # type: ignore[attr-defined]
541
+ ppm = 28 * ppd
542
+ ppy = 365 * ppd
555
543
elif freq_group == FreqGroup .FR_BUS :
556
- periodsperyear = 261
557
- periodspermonth = 19
544
+ ppm = 19
545
+ ppy = 261
558
546
elif freq_group == FreqGroup .FR_DAY :
559
- periodsperyear = 365
560
- periodspermonth = 28
547
+ ppm = 28
548
+ ppy = 365
561
549
elif freq_group == FreqGroup .FR_WK :
562
- periodsperyear = 52
563
- periodspermonth = 3
564
- else : # pragma: no cover
565
- raise ValueError ("unexpected frequency" )
550
+ ppm = 3
551
+ ppy = 52
552
+ elif freq_group == FreqGroup .FR_MTH :
553
+ ppm = 1
554
+ ppy = 12
555
+ elif freq_group == FreqGroup .FR_QTR :
556
+ ppm = - 1 # placerholder
557
+ ppy = 4
558
+ elif freq_group == FreqGroup .FR_ANN :
559
+ ppm = - 1 # placeholder
560
+ ppy = 1
561
+ else :
562
+ raise NotImplementedError (f"Unsupported frequency: { dtype_code } " )
563
+
564
+ return ppd , ppm , ppy
565
+
566
+
567
+ def _daily_finder (vmin , vmax , freq : BaseOffset ) -> np .ndarray :
568
+ # error: "BaseOffset" has no attribute "_period_dtype_code"
569
+ dtype_code = freq ._period_dtype_code # type: ignore[attr-defined]
570
+
571
+ periodsperday , periodspermonth , periodsperyear = _get_periods_per_ymd (freq )
566
572
567
573
# save this for later usage
568
574
vmin_orig = vmin
575
+ (vmin , vmax ) = (int (vmin ), int (vmax ))
576
+ span = vmax - vmin + 1
569
577
570
- (vmin , vmax ) = (
571
- Period (ordinal = int (vmin ), freq = freq ),
572
- Period (ordinal = int (vmax ), freq = freq ),
578
+ dates_ = period_range (
579
+ start = Period (ordinal = vmin , freq = freq ),
580
+ end = Period (ordinal = vmax , freq = freq ),
581
+ freq = freq ,
573
582
)
574
- assert isinstance (vmin , Period )
575
- assert isinstance (vmax , Period )
576
- span = vmax .ordinal - vmin .ordinal + 1
577
- dates_ = period_range (start = vmin , end = vmax , freq = freq )
583
+
578
584
# Initialize the output
579
585
info = np .zeros (
580
586
span , dtype = [("val" , np .int64 ), ("maj" , bool ), ("min" , bool ), ("fmt" , "|S20" )]
@@ -595,45 +601,38 @@ def first_label(label_flags):
595
601
596
602
# Case 1. Less than a month
597
603
if span <= periodspermonth :
598
- day_start = period_break (dates_ , "day" )
599
- month_start = period_break (dates_ , "month" )
604
+ day_start = _period_break (dates_ , "day" )
605
+ month_start = _period_break (dates_ , "month" )
606
+ year_start = _period_break (dates_ , "year" )
600
607
601
- def _hour_finder (label_interval , force_year_start ) -> None :
602
- _hour = dates_ .hour
603
- _prev_hour = (dates_ - 1 * dates_ .freq ).hour
604
- hour_start = (_hour - _prev_hour ) != 0
608
+ def _hour_finder (label_interval : int , force_year_start : bool ) -> None :
609
+ target = dates_ .hour
610
+ mask = _period_break_mask (dates_ , "hour" )
605
611
info_maj [day_start ] = True
606
- info_min [hour_start & (_hour % label_interval == 0 )] = True
607
- year_start = period_break (dates_ , "year" )
608
- info_fmt [hour_start & (_hour % label_interval == 0 )] = "%H:%M"
612
+ info_min [mask & (target % label_interval == 0 )] = True
613
+ info_fmt [mask & (target % label_interval == 0 )] = "%H:%M"
609
614
info_fmt [day_start ] = "%H:%M\n %d-%b"
610
615
info_fmt [year_start ] = "%H:%M\n %d-%b\n %Y"
611
616
if force_year_start and not has_level_label (year_start , vmin_orig ):
612
617
info_fmt [first_label (day_start )] = "%H:%M\n %d-%b\n %Y"
613
618
614
- def _minute_finder (label_interval ) -> None :
615
- hour_start = period_break (dates_ , "hour" )
616
- _minute = dates_ .minute
617
- _prev_minute = (dates_ - 1 * dates_ .freq ).minute
618
- minute_start = (_minute - _prev_minute ) != 0
619
+ def _minute_finder (label_interval : int ) -> None :
620
+ target = dates_ .minute
621
+ hour_start = _period_break (dates_ , "hour" )
622
+ mask = _period_break_mask (dates_ , "minute" )
619
623
info_maj [hour_start ] = True
620
- info_min [minute_start & (_minute % label_interval == 0 )] = True
621
- year_start = period_break (dates_ , "year" )
622
- info_fmt = info ["fmt" ]
623
- info_fmt [minute_start & (_minute % label_interval == 0 )] = "%H:%M"
624
+ info_min [mask & (target % label_interval == 0 )] = True
625
+ info_fmt [mask & (target % label_interval == 0 )] = "%H:%M"
624
626
info_fmt [day_start ] = "%H:%M\n %d-%b"
625
627
info_fmt [year_start ] = "%H:%M\n %d-%b\n %Y"
626
628
627
- def _second_finder (label_interval ) -> None :
628
- minute_start = period_break (dates_ , "minute" )
629
- _second = dates_ .second
630
- _prev_second = (dates_ - 1 * dates_ .freq ).second
631
- second_start = (_second - _prev_second ) != 0
632
- info ["maj" ][minute_start ] = True
633
- info ["min" ][second_start & (_second % label_interval == 0 )] = True
634
- year_start = period_break (dates_ , "year" )
635
- info_fmt = info ["fmt" ]
636
- info_fmt [second_start & (_second % label_interval == 0 )] = "%H:%M:%S"
629
+ def _second_finder (label_interval : int ) -> None :
630
+ target = dates_ .second
631
+ minute_start = _period_break (dates_ , "minute" )
632
+ mask = _period_break_mask (dates_ , "second" )
633
+ info_maj [minute_start ] = True
634
+ info_min [mask & (target % label_interval == 0 )] = True
635
+ info_fmt [mask & (target % label_interval == 0 )] = "%H:%M:%S"
637
636
info_fmt [day_start ] = "%H:%M:%S\n %d-%b"
638
637
info_fmt [year_start ] = "%H:%M:%S\n %d-%b\n %Y"
639
638
@@ -672,8 +671,6 @@ def _second_finder(label_interval) -> None:
672
671
else :
673
672
info_maj [month_start ] = True
674
673
info_min [day_start ] = True
675
- year_start = period_break (dates_ , "year" )
676
- info_fmt = info ["fmt" ]
677
674
info_fmt [day_start ] = "%d"
678
675
info_fmt [month_start ] = "%d\n %b"
679
676
info_fmt [year_start ] = "%d\n %b\n %Y"
@@ -685,15 +682,15 @@ def _second_finder(label_interval) -> None:
685
682
686
683
# Case 2. Less than three months
687
684
elif span <= periodsperyear // 4 :
688
- month_start = period_break (dates_ , "month" )
685
+ month_start = _period_break (dates_ , "month" )
689
686
info_maj [month_start ] = True
690
687
if dtype_code < FreqGroup .FR_HR .value :
691
688
info ["min" ] = True
692
689
else :
693
- day_start = period_break (dates_ , "day" )
690
+ day_start = _period_break (dates_ , "day" )
694
691
info ["min" ][day_start ] = True
695
- week_start = period_break (dates_ , "week" )
696
- year_start = period_break (dates_ , "year" )
692
+ week_start = _period_break (dates_ , "week" )
693
+ year_start = _period_break (dates_ , "year" )
697
694
info_fmt [week_start ] = "%d"
698
695
info_fmt [month_start ] = "\n \n %b"
699
696
info_fmt [year_start ] = "\n \n %b\n %Y"
@@ -704,9 +701,9 @@ def _second_finder(label_interval) -> None:
704
701
info_fmt [first_label (month_start )] = "\n \n %b\n %Y"
705
702
# Case 3. Less than 14 months ...............
706
703
elif span <= 1.15 * periodsperyear :
707
- year_start = period_break (dates_ , "year" )
708
- month_start = period_break (dates_ , "month" )
709
- week_start = period_break (dates_ , "week" )
704
+ year_start = _period_break (dates_ , "year" )
705
+ month_start = _period_break (dates_ , "month" )
706
+ week_start = _period_break (dates_ , "week" )
710
707
info_maj [month_start ] = True
711
708
info_min [week_start ] = True
712
709
info_min [year_start ] = False
@@ -717,17 +714,17 @@ def _second_finder(label_interval) -> None:
717
714
info_fmt [first_label (month_start )] = "%b\n %Y"
718
715
# Case 4. Less than 2.5 years ...............
719
716
elif span <= 2.5 * periodsperyear :
720
- year_start = period_break (dates_ , "year" )
721
- quarter_start = period_break (dates_ , "quarter" )
722
- month_start = period_break (dates_ , "month" )
717
+ year_start = _period_break (dates_ , "year" )
718
+ quarter_start = _period_break (dates_ , "quarter" )
719
+ month_start = _period_break (dates_ , "month" )
723
720
info_maj [quarter_start ] = True
724
721
info_min [month_start ] = True
725
722
info_fmt [quarter_start ] = "%b"
726
723
info_fmt [year_start ] = "%b\n %Y"
727
724
# Case 4. Less than 4 years .................
728
725
elif span <= 4 * periodsperyear :
729
- year_start = period_break (dates_ , "year" )
730
- month_start = period_break (dates_ , "month" )
726
+ year_start = _period_break (dates_ , "year" )
727
+ month_start = _period_break (dates_ , "month" )
731
728
info_maj [year_start ] = True
732
729
info_min [month_start ] = True
733
730
info_min [year_start ] = False
@@ -738,15 +735,15 @@ def _second_finder(label_interval) -> None:
738
735
info_fmt [year_start ] = "%b\n %Y"
739
736
# Case 5. Less than 11 years ................
740
737
elif span <= 11 * periodsperyear :
741
- year_start = period_break (dates_ , "year" )
742
- quarter_start = period_break (dates_ , "quarter" )
738
+ year_start = _period_break (dates_ , "year" )
739
+ quarter_start = _period_break (dates_ , "quarter" )
743
740
info_maj [year_start ] = True
744
741
info_min [quarter_start ] = True
745
742
info_min [year_start ] = False
746
743
info_fmt [year_start ] = "%Y"
747
744
# Case 6. More than 12 years ................
748
745
else :
749
- year_start = period_break (dates_ , "year" )
746
+ year_start = _period_break (dates_ , "year" )
750
747
year_break = dates_ [year_start ].year
751
748
nyears = span / periodsperyear
752
749
(min_anndef , maj_anndef ) = _get_default_annual_spacing (nyears )
@@ -759,8 +756,8 @@ def _second_finder(label_interval) -> None:
759
756
return info
760
757
761
758
762
- def _monthly_finder (vmin , vmax , freq ) :
763
- periodsperyear = 12
759
+ def _monthly_finder (vmin , vmax , freq : BaseOffset ) -> np . ndarray :
760
+ _ , _ , periodsperyear = _get_periods_per_ymd ( freq )
764
761
765
762
vmin_orig = vmin
766
763
(vmin , vmax ) = (int (vmin ), int (vmax ))
@@ -795,6 +792,7 @@ def _monthly_finder(vmin, vmax, freq):
795
792
quarter_start = (dates_ % 3 == 0 ).nonzero ()
796
793
info_maj [year_start ] = True
797
794
# TODO: Check the following : is it really info['fmt'] ?
795
+ # 2023-09-15 this is reached in test_finder_monthly
798
796
info ["fmt" ][quarter_start ] = True
799
797
info ["min" ] = True
800
798
@@ -829,8 +827,8 @@ def _monthly_finder(vmin, vmax, freq):
829
827
return info
830
828
831
829
832
- def _quarterly_finder (vmin , vmax , freq ) :
833
- periodsperyear = 4
830
+ def _quarterly_finder (vmin , vmax , freq : BaseOffset ) -> np . ndarray :
831
+ _ , _ , periodsperyear = _get_periods_per_ymd ( freq )
834
832
vmin_orig = vmin
835
833
(vmin , vmax ) = (int (vmin ), int (vmax ))
836
834
span = vmax - vmin + 1
@@ -876,7 +874,8 @@ def _quarterly_finder(vmin, vmax, freq):
876
874
return info
877
875
878
876
879
- def _annual_finder (vmin , vmax , freq ):
877
+ def _annual_finder (vmin , vmax , freq : BaseOffset ) -> np .ndarray :
878
+ # Note: small difference here vs other finders in adding 1 to vmax
880
879
(vmin , vmax ) = (int (vmin ), int (vmax + 1 ))
881
880
span = vmax - vmin + 1
882
881
@@ -889,8 +888,9 @@ def _annual_finder(vmin, vmax, freq):
889
888
890
889
(min_anndef , maj_anndef ) = _get_default_annual_spacing (span )
891
890
major_idx = dates_ % maj_anndef == 0
891
+ minor_idx = dates_ % min_anndef == 0
892
892
info ["maj" ][major_idx ] = True
893
- info ["min" ][( dates_ % min_anndef == 0 ) ] = True
893
+ info ["min" ][minor_idx ] = True
894
894
info ["fmt" ][major_idx ] = "%Y"
895
895
896
896
return info
@@ -1087,7 +1087,7 @@ def format_timedelta_ticks(x, pos, n_decimals: int) -> str:
1087
1087
"""
1088
1088
Convert seconds to 'D days HH:MM:SS.F'
1089
1089
"""
1090
- s , ns = divmod (x , 10 ** 9 )
1090
+ s , ns = divmod (x , 10 ** 9 ) # TODO(non-nano): this looks like it assumes ns
1091
1091
m , s = divmod (s , 60 )
1092
1092
h , m = divmod (m , 60 )
1093
1093
d , h = divmod (h , 24 )
0 commit comments