diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index b55f526e758e..bd22b8dc68ba 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -1417,7 +1417,7 @@ def __init__(self, tz=None, minticks=5, maxticks=None, locator.intervald[HOURLY] = [3] # only show every 3 hours """ DateLocator.__init__(self, tz) - self._locator = YearLocator() + self._locator = YearLocator(tz=tz) self._freq = YEARLY self._freqs = [YEARLY, MONTHLY, DAILY, HOURLY, MINUTELY, SECONDLY, MICROSECONDLY] @@ -1574,7 +1574,7 @@ def get_locator(self, dmin, dmax): 'AutoDateLocator.') if (freq == YEARLY) and self.interval_multiples: - locator = YearLocator(interval) + locator = YearLocator(interval, tz=self.tz) elif use_rrule_locator[i]: _, bymonth, bymonthday, byhour, byminute, bysecond, _ = byranges rrule = rrulewrapper(self._freq, interval=interval, @@ -1623,8 +1623,11 @@ def __init__(self, base=1, month=1, day=1, tz=None): 'hour': 0, 'minute': 0, 'second': 0, - 'tzinfo': tz } + if not hasattr(tz, 'localize'): + # if tz is pytz, we need to do this w/ the localize fcn, + # otherwise datetime.replace works fine... + self.replaced['tzinfo'] = tz def __call__(self): # if no data have been set, this will tank with a ValueError @@ -1639,13 +1642,26 @@ def tick_values(self, vmin, vmax): ymin = self.base.le(vmin.year) * self.base.step ymax = self.base.ge(vmax.year) * self.base.step - ticks = [vmin.replace(year=ymin, **self.replaced)] + vmin = vmin.replace(year=ymin, **self.replaced) + if hasattr(self.tz, 'localize'): + # look after pytz + if not vmin.tzinfo: + vmin = self.tz.localize(vmin, is_dst=True) + + ticks = [vmin] + while True: dt = ticks[-1] if dt.year >= ymax: return date2num(ticks) year = dt.year + self.base.step - ticks.append(dt.replace(year=year, **self.replaced)) + dt = dt.replace(year=year, **self.replaced) + if hasattr(self.tz, 'localize'): + # look after pytz + if not dt.tzinfo: + dt = self.tz.localize(dt, is_dst=True) + + ticks.append(dt) def autoscale(self): """ @@ -1656,7 +1672,9 @@ def autoscale(self): ymin = self.base.le(dmin.year) ymax = self.base.ge(dmax.year) vmin = dmin.replace(year=ymin, **self.replaced) + vmin = vmin.astimezone(self.tz) vmax = dmax.replace(year=ymax, **self.replaced) + vmax = vmax.astimezone(self.tz) vmin = date2num(vmin) vmax = date2num(vmax) diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 9365a51d37a2..4a232c45e87a 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -12,6 +12,7 @@ from matplotlib.cbook import MatplotlibDeprecationWarning import matplotlib.dates as mdates import matplotlib.ticker as mticker +from matplotlib import rc_context def __has_pytz(): @@ -439,7 +440,6 @@ def _create_auto_date_locator(date1, date2): mdates.date2num(date2)) return locator - d1 = datetime.datetime(1997, 1, 1) results = ([datetime.timedelta(weeks=52 * 200), ['1980-01-01 00:00:00+00:00', '2000-01-01 00:00:00+00:00', '2020-01-01 00:00:00+00:00', '2040-01-01 00:00:00+00:00', @@ -500,6 +500,7 @@ def _create_auto_date_locator(date1, date2): ], ) + d1 = datetime.datetime(1997, 1, 1) for t_delta, expected in results: d2 = d1 + t_delta locator = _create_auto_date_locator(d1, d2) @@ -557,6 +558,77 @@ def _create_auto_date_locator(date1, date2): assert strings == expected +def test_auto_date_locator_intmult_tz(): + def _create_auto_date_locator(date1, date2, tz): + locator = mdates.AutoDateLocator(interval_multiples=True, tz=tz) + locator.create_dummy_axis() + locator.set_view_interval(mdates.date2num(date1), + mdates.date2num(date2)) + return locator + + results = ([datetime.timedelta(weeks=52*200), + ['1980-01-01 00:00:00-08:00', '2000-01-01 00:00:00-08:00', + '2020-01-01 00:00:00-08:00', '2040-01-01 00:00:00-08:00', + '2060-01-01 00:00:00-08:00', '2080-01-01 00:00:00-08:00', + '2100-01-01 00:00:00-08:00', '2120-01-01 00:00:00-08:00', + '2140-01-01 00:00:00-08:00', '2160-01-01 00:00:00-08:00', + '2180-01-01 00:00:00-08:00', '2200-01-01 00:00:00-08:00'] + ], + [datetime.timedelta(weeks=52), + ['1997-01-01 00:00:00-08:00', '1997-02-01 00:00:00-08:00', + '1997-03-01 00:00:00-08:00', '1997-04-01 00:00:00-08:00', + '1997-05-01 00:00:00-07:00', '1997-06-01 00:00:00-07:00', + '1997-07-01 00:00:00-07:00', '1997-08-01 00:00:00-07:00', + '1997-09-01 00:00:00-07:00', '1997-10-01 00:00:00-07:00', + '1997-11-01 00:00:00-08:00', '1997-12-01 00:00:00-08:00'] + ], + [datetime.timedelta(days=141), + ['1997-01-01 00:00:00-08:00', '1997-01-22 00:00:00-08:00', + '1997-02-01 00:00:00-08:00', '1997-02-22 00:00:00-08:00', + '1997-03-01 00:00:00-08:00', '1997-03-22 00:00:00-08:00', + '1997-04-01 00:00:00-08:00', '1997-04-22 00:00:00-07:00', + '1997-05-01 00:00:00-07:00', '1997-05-22 00:00:00-07:00'] + ], + [datetime.timedelta(days=40), + ['1997-01-01 00:00:00-08:00', '1997-01-05 00:00:00-08:00', + '1997-01-09 00:00:00-08:00', '1997-01-13 00:00:00-08:00', + '1997-01-17 00:00:00-08:00', '1997-01-21 00:00:00-08:00', + '1997-01-25 00:00:00-08:00', '1997-01-29 00:00:00-08:00', + '1997-02-01 00:00:00-08:00', '1997-02-05 00:00:00-08:00', + '1997-02-09 00:00:00-08:00'] + ], + [datetime.timedelta(hours=40), + ['1997-01-01 00:00:00-08:00', '1997-01-01 04:00:00-08:00', + '1997-01-01 08:00:00-08:00', '1997-01-01 12:00:00-08:00', + '1997-01-01 16:00:00-08:00', '1997-01-01 20:00:00-08:00', + '1997-01-02 00:00:00-08:00', '1997-01-02 04:00:00-08:00', + '1997-01-02 08:00:00-08:00', '1997-01-02 12:00:00-08:00', + '1997-01-02 16:00:00-08:00'] + ], + [datetime.timedelta(minutes=20), + ['1997-01-01 00:00:00-08:00', '1997-01-01 00:05:00-08:00', + '1997-01-01 00:10:00-08:00', '1997-01-01 00:15:00-08:00', + '1997-01-01 00:20:00-08:00'] + ], + [datetime.timedelta(seconds=40), + ['1997-01-01 00:00:00-08:00', '1997-01-01 00:00:05-08:00', + '1997-01-01 00:00:10-08:00', '1997-01-01 00:00:15-08:00', + '1997-01-01 00:00:20-08:00', '1997-01-01 00:00:25-08:00', + '1997-01-01 00:00:30-08:00', '1997-01-01 00:00:35-08:00', + '1997-01-01 00:00:40-08:00'] + ] + ) + + tz = dateutil.tz.gettz('Canada/Pacific') + d1 = datetime.datetime(1997, 1, 1, tzinfo=tz) + for t_delta, expected in results: + with rc_context({'_internal.classic_mode': False}): + d2 = d1 + t_delta + locator = _create_auto_date_locator(d1, d2, tz) + st = list(map(str, mdates.num2date(locator(), tz=tz))) + assert st == expected + + @image_comparison(baseline_images=['date_inverted_limit'], extensions=['png']) def test_date_inverted_limit(): @@ -701,6 +773,30 @@ def attach_tz(dt, zi): _test_rrulewrapper(attach_tz, pytz.timezone) +@pytest.mark.pytz +@pytest.mark.skipif(not __has_pytz(), reason="Requires pytz") +def test_yearlocator_pytz(): + import pytz + + tz = pytz.timezone('America/New_York') + x = [tz.localize(datetime.datetime(2010, 1, 1)) + + datetime.timedelta(i) for i in range(2000)] + locator = mdates.AutoDateLocator(interval_multiples=True, tz=tz) + locator.create_dummy_axis() + locator.set_view_interval(mdates.date2num(x[0])-1.0, + mdates.date2num(x[-1])+1.0) + + np.testing.assert_allclose([733408.208333, 733773.208333, 734138.208333, + 734503.208333, 734869.208333, + 735234.208333, 735599.208333], locator()) + expected = ['2009-01-01 00:00:00-05:00', + '2010-01-01 00:00:00-05:00', '2011-01-01 00:00:00-05:00', + '2012-01-01 00:00:00-05:00', '2013-01-01 00:00:00-05:00', + '2014-01-01 00:00:00-05:00', '2015-01-01 00:00:00-05:00'] + st = list(map(str, mdates.num2date(locator(), tz=tz))) + assert st == expected + + def test_DayLocator(): with pytest.raises(ValueError): mdates.DayLocator(interval=-1)