@@ -1154,10 +1154,16 @@ def nonsingular(self, vmin, vmax):
11541154class RRuleLocator (DateLocator ):
11551155 # use the dateutil rrule instance
11561156
1157- def __init__ (self , o , tz = None ):
1157+ def __init__ (self , o , tz = None , interval_multiples = False ):
11581158 super ().__init__ (tz )
11591159 self .rule = o
11601160
1161+ # rrule cannot create interval multiples for YEARLY, this
1162+ # needs to be handled separately
1163+ self .mult_base = None
1164+ if interval_multiples and o ._rrule ._freq == YEARLY :
1165+ self .mult_base = ticker ._Edge_integer (self ._get_interval (), 0 )
1166+
11611167 def __call__ (self ):
11621168 # if no data have been set, this will tank with a ValueError
11631169 try :
@@ -1168,22 +1174,28 @@ def __call__(self):
11681174 return self .tick_values (dmin , dmax )
11691175
11701176 def tick_values (self , vmin , vmax ):
1171- delta = relativedelta (vmax , vmin )
1177+ if self .mult_base :
1178+ # start and stop need to be multiples of the interval
1179+ start , stop = self ._get_intmult_limits (vmin , vmax )
1180+ vmin , vmax = start , stop
11721181
1173- # We need to cap at the endpoints of valid datetime
1174- try :
1175- start = vmin - delta
1176- except (ValueError , OverflowError ):
1177- # cap
1178- start = datetime .datetime (1 , 1 , 1 , 0 , 0 , 0 ,
1179- tzinfo = datetime .timezone .utc )
1182+ else :
1183+ delta = relativedelta (vmax , vmin )
11801184
1181- try :
1182- stop = vmax + delta
1183- except (ValueError , OverflowError ):
1184- # cap
1185- stop = datetime .datetime (9999 , 12 , 31 , 23 , 59 , 59 ,
1186- tzinfo = datetime .timezone .utc )
1185+ # We need to cap at the endpoints of valid datetime
1186+ try :
1187+ start = vmin - delta
1188+ except (ValueError , OverflowError ):
1189+ # cap
1190+ start = datetime .datetime (1 , 1 , 1 , 0 , 0 , 0 ,
1191+ tzinfo = datetime .timezone .utc )
1192+
1193+ try :
1194+ stop = vmax + delta
1195+ except (ValueError , OverflowError ):
1196+ # cap
1197+ stop = datetime .datetime (9999 , 12 , 31 , 23 , 59 , 59 ,
1198+ tzinfo = datetime .timezone .utc )
11871199
11881200 self .rule .set (dtstart = start , until = stop )
11891201
@@ -1192,6 +1204,23 @@ def tick_values(self, vmin, vmax):
11921204 return date2num ([vmin , vmax ])
11931205 return self .raise_if_exceeds (date2num (dates ))
11941206
1207+ def _get_intmult_limits (self , vmin , vmax ):
1208+ # used for interval multiples when freq == YEARLY; make start and stop
1209+ # multiples of the interval but cap at the endpoints of valid datetime
1210+ ymin = max (self .mult_base .le (vmin .year ) * self .mult_base .step , 1 )
1211+ ymax = min (self .mult_base .ge (vmax .year ) * self .mult_base .step , 9999 )
1212+
1213+ c = self .rule ._construct
1214+ replace = {'year' : ymin ,
1215+ 'month' : getattr (c , 'bymonth' , 1 ),
1216+ 'day' : getattr (c , 'bymonthday' , 1 ),
1217+ 'hour' : 0 , 'minute' : 0 , 'second' : 0 }
1218+
1219+ start = vmin .replace (** replace )
1220+ stop = vmax .replace (year = ymax )
1221+
1222+ return start , stop
1223+
11951224 def _get_unit (self ):
11961225 # docstring inherited
11971226 freq = self .rule ._rrule ._freq
@@ -1430,17 +1459,16 @@ def get_locator(self, dmin, dmax):
14301459 else :
14311460 interval = 1
14321461
1433- if (freq == YEARLY ) and self .interval_multiples :
1434- locator = YearLocator (interval , tz = self .tz )
1435- elif use_rrule_locator [i ]:
1462+ if use_rrule_locator [i ]:
14361463 _ , bymonth , bymonthday , byhour , byminute , bysecond , _ = byranges
14371464 rrule = rrulewrapper (self ._freq , interval = interval ,
14381465 dtstart = dmin , until = dmax ,
14391466 bymonth = bymonth , bymonthday = bymonthday ,
14401467 byhour = byhour , byminute = byminute ,
14411468 bysecond = bysecond )
14421469
1443- locator = RRuleLocator (rrule , self .tz )
1470+ locator = RRuleLocator (rrule , self .tz ,
1471+ interval_multiples = self .interval_multiples )
14441472 else :
14451473 locator = MicrosecondLocator (interval , tz = self .tz )
14461474 if date2num (dmin ) > 70 * 365 and interval < 1000 :
@@ -1454,7 +1482,7 @@ def get_locator(self, dmin, dmax):
14541482 return locator
14551483
14561484
1457- class YearLocator (DateLocator ):
1485+ class YearLocator (RRuleLocator ):
14581486 """
14591487 Make ticks on a given day of each year that is a multiple of base.
14601488
@@ -1471,52 +1499,9 @@ def __init__(self, base=1, month=1, day=1, tz=None):
14711499 Mark years that are multiple of base on a given month and day
14721500 (default jan 1).
14731501 """
1474- super ().__init__ (tz )
1475- self .base = ticker ._Edge_integer (base , 0 )
1476- self .replaced = {'month' : month ,
1477- 'day' : day ,
1478- 'hour' : 0 ,
1479- 'minute' : 0 ,
1480- 'second' : 0 ,
1481- }
1482- if not hasattr (tz , 'localize' ):
1483- # if tz is pytz, we need to do this w/ the localize fcn,
1484- # otherwise datetime.replace works fine...
1485- self .replaced ['tzinfo' ] = tz
1486-
1487- def __call__ (self ):
1488- # if no data have been set, this will tank with a ValueError
1489- try :
1490- dmin , dmax = self .viewlim_to_dt ()
1491- except ValueError :
1492- return []
1493-
1494- return self .tick_values (dmin , dmax )
1495-
1496- def tick_values (self , vmin , vmax ):
1497- ymin = self .base .le (vmin .year ) * self .base .step
1498- ymax = self .base .ge (vmax .year ) * self .base .step
1499-
1500- vmin = vmin .replace (year = ymin , ** self .replaced )
1501- if hasattr (self .tz , 'localize' ):
1502- # look after pytz
1503- if not vmin .tzinfo :
1504- vmin = self .tz .localize (vmin , is_dst = True )
1505-
1506- ticks = [vmin ]
1507-
1508- while True :
1509- dt = ticks [- 1 ]
1510- if dt .year >= ymax :
1511- return date2num (ticks )
1512- year = dt .year + self .base .step
1513- dt = dt .replace (year = year , ** self .replaced )
1514- if hasattr (self .tz , 'localize' ):
1515- # look after pytz
1516- if not dt .tzinfo :
1517- dt = self .tz .localize (dt , is_dst = True )
1518-
1519- ticks .append (dt )
1502+ rule = rrulewrapper (YEARLY , interval = base , bymonth = month ,
1503+ bymonthday = day , ** self .hms0d )
1504+ super ().__init__ (rule , tz , interval_multiples = True )
15201505
15211506
15221507class MonthLocator (RRuleLocator ):
0 commit comments