From 621a840de0c932041041b3a70e17dcc0cab36d39 Mon Sep 17 00:00:00 2001 From: Paul G Date: Wed, 24 Dec 2014 09:24:40 -0600 Subject: [PATCH 01/23] Clean up docstrings (convert several single-quote string comments to triple-quote docstrings for consistency.) Signed-off-by: Paul G --- lib/matplotlib/dates.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index c2ade94c89fc..b487a7d0fb71 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -311,14 +311,18 @@ def date2num(d): def julian2num(j): - 'Convert a Julian date (or sequence) to a matplotlib date (or sequence).' + """ + Convert a Julian date (or sequence) to a matplotlib date (or sequence). + """ if cbook.iterable(j): j = np.asarray(j) return j - 1721424.5 def num2julian(n): - 'Convert a matplotlib date (or sequence) to a Julian date (or sequence).' + """ + Convert a matplotlib date (or sequence) to a Julian date (or sequence). + """ if cbook.iterable(n): n = np.asarray(n) return n + 1721424.5 @@ -1207,7 +1211,9 @@ def _get_interval(self): def _close_to_dt(d1, d2, epsilon=5): - 'Assert that datetimes *d1* and *d2* are within *epsilon* microseconds.' + """ + Assert that datetimes *d1* and *d2* are within *epsilon* microseconds. + """ delta = d2 - d1 mus = abs(delta.days * MUSECONDS_PER_DAY + delta.seconds * 1e6 + delta.microseconds) @@ -1300,22 +1306,30 @@ def date_ticker_factory(span, tz=None, numticks=5): def seconds(s): - 'Return seconds as days.' + """ + Return seconds as days. + """ return float(s) / SEC_PER_DAY def minutes(m): - 'Return minutes as days.' + """ + Return minutes as days. + """ return float(m) / MINUTES_PER_DAY def hours(h): - 'Return hours as days.' + """ + Return hours as days. + """ return h / 24. def weeks(w): - 'Return weeks as days.' + """ + Return weeks as days. + """ return w * 7. @@ -1360,7 +1374,9 @@ def convert(value, unit, axis): @staticmethod def default_units(x, axis): - 'Return the tzinfo instance of *x* or of its first element, or None' + """ + Return the tzinfo instance of *x* or of its first element, or None + """ if isinstance(x, np.ndarray): x = x.ravel() From 504044e4e94663bbe0a3878a3e539b4d765942ea Mon Sep 17 00:00:00 2001 From: Paul G Date: Wed, 24 Dec 2014 09:31:12 -0600 Subject: [PATCH 02/23] Small expansions to docstrings in the date library. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index b487a7d0fb71..8ad33e7dd33f 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -609,6 +609,9 @@ def __getattr__(self, name): class DateLocator(ticker.Locator): + """ + Determines the tick locations when plotting dates. + """ hms0d = {'byhour': 0, 'byminute': 0, 'bysecond': 0} def __init__(self, tz=None): @@ -620,13 +623,22 @@ def __init__(self, tz=None): self.tz = tz def set_tzinfo(self, tz): + """ + Set time zone info. + """ self.tz = tz def datalim_to_dt(self): + """ + Convert axis data interval to datetime objects. + """ dmin, dmax = self.axis.get_data_interval() return num2date(dmin, self.tz), num2date(dmax, self.tz) def viewlim_to_dt(self): + """ + Converts the view interval to datetime objects. + """ vmin, vmax = self.axis.get_view_interval() return num2date(vmin, self.tz), num2date(vmax, self.tz) From 390c1f4397f0a29f828664ae8724432601086f46 Mon Sep 17 00:00:00 2001 From: Paul G Date: Wed, 24 Dec 2014 17:28:43 -0600 Subject: [PATCH 03/23] Moved all the "magic numbers" into some centrally defined constants for the moment. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 102 ++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 40 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 8ad33e7dd33f..dd5f386de4d3 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -175,15 +175,28 @@ def _get_rc_timezone(): import pytz return pytz.timezone(s) +""" +Time-related constants. +""" +EPOCH_OFFSET = 719163. # Days between 0001-01-01 and epoch, +1. +JULIAN_OFFSET = 1721424.5 # Julian date at 0001-01-01 MICROSECONDLY = SECONDLY + 1 HOURS_PER_DAY = 24. -MINUTES_PER_DAY = 60. * HOURS_PER_DAY -SECONDS_PER_DAY = 60. * MINUTES_PER_DAY +MIN_PER_HOUR = 60. +SEC_PER_MIN = 60. +MONTHS_PER_YEAR = 12. + +DAYS_PER_WEEK = 7. +DAYS_PER_MONTH = 30. +DAYS_PER_YEAR = 365.0 + +MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY +SECONDS_PER_DAY = SEC_PER_MIN * MINUTES_PER_DAY MUSECONDS_PER_DAY = 1e6 * SECONDS_PER_DAY -SEC_PER_MIN = 60 -SEC_PER_HOUR = 3600 -SEC_PER_DAY = SEC_PER_HOUR * 24 -SEC_PER_WEEK = SEC_PER_DAY * 7 + +SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR +SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY +SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK # Days per week MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = ( MO, TU, WE, TH, FR, SA, SU) WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) @@ -224,9 +237,9 @@ def _from_ordinalf(x, tz=None): ix = int(x) dt = datetime.datetime.fromordinal(ix) remainder = float(x) - ix - hour, remainder = divmod(24 * remainder, 1) - minute, remainder = divmod(60 * remainder, 1) - second, remainder = divmod(60 * remainder, 1) + hour, remainder = divmod(HOURS_PER_DAY * remainder, 1) + minute, remainder = divmod(MIN_PER_HOUR * remainder, 1) + second, remainder = divmod(SEC_PER_MIN * remainder, 1) microsecond = int(1e6 * remainder) if microsecond < 10: microsecond = 0 # compensate for rounding errors @@ -316,7 +329,7 @@ def julian2num(j): """ if cbook.iterable(j): j = np.asarray(j) - return j - 1721424.5 + return j - JULIAN_OFFSET def num2julian(n): @@ -325,7 +338,7 @@ def num2julian(n): """ if cbook.iterable(n): n = np.asarray(n) - return n + 1721424.5 + return n + JULIAN_OFFSET def num2date(x, tz=None): @@ -436,8 +449,19 @@ def _findall(self, text, substr): # calendar. def strftime(self, dt, fmt): + """ + Prints `datetime.datetime` object `dt` using a C-like `strftime()` + format string. + + Currently `datetime.datetime.strftime()` is not supported for + years <= 1900 in Python 2.x and years <= 1000 in Python 3.x. This + function extends this functionality to + """ fmt = self.illegal_s.sub(r"\1", fmt) fmt = fmt.replace("%s", "s") + + # strftime is not supported on datetime for years <= 1900 in Python 2.x + # or years <= 1000 in Python 3.x if dt.year > 1900: return cbook.unicode_safe(dt.strftime(fmt)) @@ -564,11 +588,11 @@ def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'): self._tz = tz self.defaultfmt = defaultfmt self._formatter = DateFormatter(self.defaultfmt, tz) - self.scaled = {365.0: '%Y', - 30.: '%b %Y', + self.scaled = {DAYS_PER_YEAR: '%Y', + DAYS_PER_MONTH: '%b %Y', 1.0: '%b %d %Y', - 1. / 24.: '%H:%M:%S', - 1. / (24. * 60.): '%H:%M:%S.%f'} + 1. / HOURS_PER_DAY: '%H:%M:%S', + 1. / (MINUTES_PER_DAY): '%H:%M:%S.%f'} def __call__(self, x, pos=None): locator_unit_scale = float(self._locator._get_unit()) @@ -730,19 +754,19 @@ def _get_unit(self): @staticmethod def get_unit_generic(freq): if (freq == YEARLY): - return 365.0 + return DAYS_PER_YEAR elif (freq == MONTHLY): - return 30.0 + return DAYS_PER_MONTH elif (freq == WEEKLY): - return 7.0 + return DAYS_PER_WEEK elif (freq == DAILY): return 1.0 elif (freq == HOURLY): - return (1.0 / 24.0) + return (1.0 / HOURS_PER_DAY) elif (freq == MINUTELY): - return (1.0 / (24 * 60)) + return (1.0 / MINUTES_PER_DAY) elif (freq == SECONDLY): - return (1.0 / (24 * 3600)) + return (1.0 / SECONDS_PER_DAY) else: # error return -1 # or should this just return '1'? @@ -886,8 +910,8 @@ def nonsingular(self, vmin, vmax): # whatever is thrown at us, we can scale the unit. # But default nonsingular date plots at an ~4 year period. if vmin == vmax: - vmin = vmin - 365 * 2 - vmax = vmax + 365 * 2 + vmin = vmin - DAYS_PER_YEAR * 2 + vmax = vmax + DAYS_PER_YEAR * 2 return vmin, vmax def set_axis(self, axis): @@ -920,11 +944,11 @@ def get_locator(self, dmin, dmax): delta = -delta numYears = (delta.years * 1.0) - numMonths = (numYears * 12.0) + delta.months - numDays = (numMonths * 31.0) + delta.days - numHours = (numDays * 24.0) + delta.hours - numMinutes = (numHours * 60.0) + delta.minutes - numSeconds = (numMinutes * 60.0) + delta.seconds + numMonths = (numYears * MONTHS_PER_YEAR) + delta.months + numDays = (numMonths * DAYS_PER_MONTH) + delta.days + numHours = (numDays * HOURS_PER_DAY) + delta.hours + numMinutes = (numHours * MIN_PER_HOUR) + delta.minutes + numSeconds = (numMinutes * SEC_PER_MIN) + delta.seconds numMicroseconds = (numSeconds * 1e6) + delta.microseconds nums = [numYears, numMonths, numDays, numHours, numMinutes, @@ -1246,16 +1270,14 @@ def epoch2num(e): Convert an epoch or sequence of epochs to the new date format, that is days since 0001. """ - spd = 24. * 3600. - return 719163 + np.asarray(e) / spd + return EPOCH_OFFSET + np.asarray(e) / SEC_PER_DAY def num2epoch(d): """ Convert days since 0001 to epoch. *d* can be a number or sequence. """ - spd = 24. * 3600. - return (np.asarray(d) - 719163) * spd + return (np.asarray(d) - EPOCH_OFFSET) * SEC_PER_DAY def mx2num(mxdates): @@ -1281,14 +1303,14 @@ def date_ticker_factory(span, tz=None, numticks=5): """ if span == 0: - span = 1 / 24. + span = 1 / HOURS_PER_DAY - minutes = span * 24 * 60 - hours = span * 24 + minutes = span * MINUTES_PER_DAY + hours = span * HOURS_PER_DAY days = span - weeks = span / 7. - months = span / 31. # approx - years = span / 365. + weeks = span / DAYS_PER_WEEK + months = span / DAYS_PER_MONTH # Approx + years = span / DAYS_PER_YEAR # Approx if years > numticks: locator = YearLocator(int(years / numticks), tz=tz) # define @@ -1335,14 +1357,14 @@ def hours(h): """ Return hours as days. """ - return h / 24. + return h / HOURS_PER_DAY def weeks(w): """ Return weeks as days. """ - return w * 7. + return w * DAYS_PER_WEEK class DateConverter(units.ConversionInterface): From 5af753ccef87bdfd4ec7e1198273ebca83d9f7b1 Mon Sep 17 00:00:00 2001 From: Paul G Date: Wed, 24 Dec 2014 17:49:30 -0600 Subject: [PATCH 04/23] Replace hard-coded conversion logic in _to_ordinalf with functions from `datetime`. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index dd5f386de4d3..a79e92a93c0f 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -178,8 +178,8 @@ def _get_rc_timezone(): """ Time-related constants. """ -EPOCH_OFFSET = 719163. # Days between 0001-01-01 and epoch, +1. -JULIAN_OFFSET = 1721424.5 # Julian date at 0001-01-01 +EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1)) # Epoch in ordinal +JULIAN_OFFSET = 1721424.5 # Julian date at 0001-01-01 MICROSECONDLY = SECONDLY + 1 HOURS_PER_DAY = 24. MIN_PER_HOUR = 60. @@ -214,12 +214,13 @@ def _to_ordinalf(dt): if delta is not None: dt -= delta - base = float(dt.toordinal()) - if hasattr(dt, 'hour'): - base += (dt.hour / HOURS_PER_DAY + dt.minute / MINUTES_PER_DAY + - dt.second / SECONDS_PER_DAY + - dt.microsecond / MUSECONDS_PER_DAY - ) + base = dt.toordinal() + td_remainder = (dt-datetime.datetime.fromordinal(base)).total_seconds() + + base = float(base) + if td_remainder > 0: + base ++ td_remainder / SECONDS_PER_DAY + return base From ee7931cecddcf9f54eed5a399e6f4dd54f6c73bc Mon Sep 17 00:00:00 2001 From: Paul G Date: Wed, 24 Dec 2014 17:57:30 -0600 Subject: [PATCH 05/23] Make `from_ordinalf` use mostly functions from `datetime` for time conversion. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index a79e92a93c0f..b58f459386a7 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -191,12 +191,13 @@ def _get_rc_timezone(): DAYS_PER_YEAR = 365.0 MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY -SECONDS_PER_DAY = SEC_PER_MIN * MINUTES_PER_DAY -MUSECONDS_PER_DAY = 1e6 * SECONDS_PER_DAY SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY -SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK # Days per week +SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK + +MUSECONDS_PER_DAY = 1e6 * SEC_PER_DAY + MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = ( MO, TU, WE, TH, FR, SA, SU) WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) @@ -219,7 +220,7 @@ def _to_ordinalf(dt): base = float(base) if td_remainder > 0: - base ++ td_remainder / SECONDS_PER_DAY + base ++ td_remainder / SEC_PER_DAY return base @@ -238,18 +239,8 @@ def _from_ordinalf(x, tz=None): ix = int(x) dt = datetime.datetime.fromordinal(ix) remainder = float(x) - ix - hour, remainder = divmod(HOURS_PER_DAY * remainder, 1) - minute, remainder = divmod(MIN_PER_HOUR * remainder, 1) - second, remainder = divmod(SEC_PER_MIN * remainder, 1) - microsecond = int(1e6 * remainder) - if microsecond < 10: - microsecond = 0 # compensate for rounding errors - dt = datetime.datetime( - dt.year, dt.month, dt.day, int(hour), int(minute), int(second), - microsecond, tzinfo=UTC).astimezone(tz) - - if microsecond > 999990: # compensate for rounding errors - dt += datetime.timedelta(microseconds=1e6 - microsecond) + + dt += datetime.timedelta(seconds = remainder*SEC_PER_DAY) return dt @@ -374,7 +365,7 @@ def drange(dstart, dend, delta): *dend* are :class:`datetime` instances. *delta* is a :class:`datetime.timedelta` instance. """ - step = (delta.days + delta.seconds / SECONDS_PER_DAY + + step = (delta.days + delta.seconds / SEC_PER_DAY + delta.microseconds / MUSECONDS_PER_DAY) f1 = _to_ordinalf(dstart) f2 = _to_ordinalf(dend) @@ -767,7 +758,7 @@ def get_unit_generic(freq): elif (freq == MINUTELY): return (1.0 / MINUTES_PER_DAY) elif (freq == SECONDLY): - return (1.0 / SECONDS_PER_DAY) + return (1.0 / SEC_PER_DAY) else: # error return -1 # or should this just return '1'? From 4b0d94b446c978fc7f8ae3e46d33ff801adb3cc6 Mon Sep 17 00:00:00 2001 From: Paul G Date: Wed, 24 Dec 2014 18:38:54 -0600 Subject: [PATCH 06/23] Fix issue causing failed builds. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index b58f459386a7..e605a94106da 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -178,7 +178,7 @@ def _get_rc_timezone(): """ Time-related constants. """ -EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1)) # Epoch in ordinal +EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal()) JULIAN_OFFSET = 1721424.5 # Julian date at 0001-01-01 MICROSECONDLY = SECONDLY + 1 HOURS_PER_DAY = 24. From ba623a78f540cb696b60f7c83c91354aa945f9ac Mon Sep 17 00:00:00 2001 From: Paul G Date: Wed, 24 Dec 2014 21:17:35 -0600 Subject: [PATCH 07/23] Fix problem with inconsistent time zones. This now also fixes Issue #3896. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index e605a94106da..bd30a5defc72 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -215,12 +215,16 @@ def _to_ordinalf(dt): if delta is not None: dt -= delta - base = dt.toordinal() - td_remainder = (dt-datetime.datetime.fromordinal(base)).total_seconds() + # Get a datetime object at midnight in the same time zone as dt. + cdate = dt.date() + midnight_time = datetime.time(0, 0, 0, tzinfo=dt.tzinfo) - base = float(base) + rdt = datetime.datetime.combine(cdate, midnight_time) + td_remainder = (dt-rdt).total_seconds() + + base = float(dt.toordinal()) if td_remainder > 0: - base ++ td_remainder / SEC_PER_DAY + base += td_remainder / SEC_PER_DAY return base From 9f66ae2530d03e7ff102252a02e7c0cfd6f6419f Mon Sep 17 00:00:00 2001 From: Paul G Date: Thu, 25 Dec 2014 14:34:57 -0600 Subject: [PATCH 08/23] Didn't realize that `_to_ordinalf` needs to support datetime.date objects. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index bd30a5defc72..62f26313f209 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -205,11 +205,14 @@ def _get_rc_timezone(): def _to_ordinalf(dt): """ - Convert :mod:`datetime` to the Gregorian date as UTC float days, - preserving hours, minutes, seconds and microseconds. Return value + Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float + days, preserving hours, minutes, seconds and microseconds. Return value is a :func:`float`. """ + if isinstance(dt, datetime.date): + return float(datetime.datetime.toordinal(dt)) + if hasattr(dt, 'tzinfo') and dt.tzinfo is not None: delta = dt.tzinfo.utcoffset(dt) if delta is not None: From d61397da93470e12e6a167c10b065bb1bdff096b Mon Sep 17 00:00:00 2001 From: Paul G Date: Thu, 25 Dec 2014 14:36:42 -0600 Subject: [PATCH 09/23] PEP8 fix. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 62f26313f209..4a1a961448a9 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -247,7 +247,7 @@ def _from_ordinalf(x, tz=None): dt = datetime.datetime.fromordinal(ix) remainder = float(x) - ix - dt += datetime.timedelta(seconds = remainder*SEC_PER_DAY) + dt += datetime.timedelta(seconds = remainder * SEC_PER_DAY) return dt From 0a5fbc68a08cf9e6ed48b6342822da07b1f987e1 Mon Sep 17 00:00:00 2001 From: Paul G Date: Fri, 26 Dec 2014 15:07:06 -0600 Subject: [PATCH 10/23] Fix timezone issue, update code to coding standards. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 58 ++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 4a1a961448a9..f8bdf3563c16 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -169,6 +169,9 @@ def dst(self, dt): def _get_rc_timezone(): + """ + Retrieve the preferred timeszone from the rcParams dictionary. + """ s = matplotlib.rcParams['timezone'] if s == 'UTC': return UTC @@ -247,7 +250,8 @@ def _from_ordinalf(x, tz=None): dt = datetime.datetime.fromordinal(ix) remainder = float(x) - ix - dt += datetime.timedelta(seconds = remainder * SEC_PER_DAY) + dt += datetime.timedelta(seconds=remainder * SEC_PER_DAY) + dt = dt.astimezone(tz) return dt @@ -752,20 +756,20 @@ def _get_unit(self): @staticmethod def get_unit_generic(freq): - if (freq == YEARLY): + if freq == YEARLY: return DAYS_PER_YEAR - elif (freq == MONTHLY): + elif freq == MONTHLY: return DAYS_PER_MONTH - elif (freq == WEEKLY): + elif freq == WEEKLY: return DAYS_PER_WEEK - elif (freq == DAILY): + elif freq == DAILY: return 1.0 - elif (freq == HOURLY): - return (1.0 / HOURS_PER_DAY) - elif (freq == MINUTELY): - return (1.0 / MINUTES_PER_DAY) - elif (freq == SECONDLY): - return (1.0 / SEC_PER_DAY) + elif freq == HOURLY: + return 1.0 / HOURS_PER_DAY + elif freq == MINUTELY: + return 1.0 / MINUTES_PER_DAY + elif freq == SECONDLY: + return 1.0 / SEC_PER_DAY else: # error return -1 # or should this just return '1'? @@ -1083,7 +1087,7 @@ class MonthLocator(RRuleLocator): """ Make ticks on occurances of each month month, eg 1, 3, 12. """ - def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None): + def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None): """ Mark every month in *bymonth*; *bymonth* can be an int or sequence. Default is ``range(1,13)``, i.e. every month. @@ -1103,7 +1107,7 @@ class WeekdayLocator(RRuleLocator): Make ticks on occurances of each weekday. """ - def __init__(self, byweekday=1, interval=1, tz=None): + def __init__(self, byweekday=1, interval=1, tz=None): """ Mark every weekday in *byweekday*; *byweekday* can be a number or sequence. @@ -1125,7 +1129,7 @@ class DayLocator(RRuleLocator): Make ticks on occurances of each day of the month. For example, 1, 15, 30. """ - def __init__(self, bymonthday=None, interval=1, tz=None): + def __init__(self, bymonthday=None, interval=1, tz=None): """ Mark every day in *bymonthday*; *bymonthday* can be an int or sequence. @@ -1143,7 +1147,7 @@ class HourLocator(RRuleLocator): """ Make ticks on occurances of each hour. """ - def __init__(self, byhour=None, interval=1, tz=None): + def __init__(self, byhour=None, interval=1, tz=None): """ Mark every hour in *byhour*; *byhour* can be an int or sequence. Default is to tick every hour: ``byhour=range(24)`` @@ -1162,7 +1166,7 @@ class MinuteLocator(RRuleLocator): """ Make ticks on occurances of each minute. """ - def __init__(self, byminute=None, interval=1, tz=None): + def __init__(self, byminute=None, interval=1, tz=None): """ Mark every minute in *byminute*; *byminute* can be an int or sequence. Default is to tick every minute: ``byminute=range(60)`` @@ -1181,7 +1185,7 @@ class SecondLocator(RRuleLocator): """ Make ticks on occurances of each second. """ - def __init__(self, bysecond=None, interval=1, tz=None): + def __init__(self, bysecond=None, interval=1, tz=None): """ Mark every second in *bysecond*; *bysecond* can be an int or sequence. Default is to tick every second: ``bysecond = range(60)`` @@ -1252,7 +1256,7 @@ def _close_to_dt(d1, d2, epsilon=5): delta = d2 - d1 mus = abs(delta.days * MUSECONDS_PER_DAY + delta.seconds * 1e6 + delta.microseconds) - assert(mus < epsilon) + assert mus < epsilon def _close_to_num(o1, o2, epsilon=5): @@ -1261,7 +1265,7 @@ def _close_to_num(o1, o2, epsilon=5): microseconds. """ delta = abs((o2 - o1) * MUSECONDS_PER_DAY) - assert(delta < epsilon) + assert delta < epsilon def epoch2num(e): @@ -1304,10 +1308,10 @@ def date_ticker_factory(span, tz=None, numticks=5): if span == 0: span = 1 / HOURS_PER_DAY - minutes = span * MINUTES_PER_DAY - hours = span * HOURS_PER_DAY + mins = span * MINUTES_PER_DAY + hrs = span * HOURS_PER_DAY days = span - weeks = span / DAYS_PER_WEEK + wks = span / DAYS_PER_WEEK months = span / DAYS_PER_MONTH # Approx years = span / DAYS_PER_YEAR # Approx @@ -1317,17 +1321,17 @@ def date_ticker_factory(span, tz=None, numticks=5): elif months > numticks: locator = MonthLocator(tz=tz) fmt = '%b %Y' - elif weeks > numticks: + elif wks > numticks: locator = WeekdayLocator(tz=tz) fmt = '%a, %b %d' elif days > numticks: locator = DayLocator(interval=int(math.ceil(days / numticks)), tz=tz) fmt = '%b %d' - elif hours > numticks: - locator = HourLocator(interval=int(math.ceil(hours / numticks)), tz=tz) + elif hrs > numticks: + locator = HourLocator(interval=int(math.ceil(hrs / numticks)), tz=tz) fmt = '%H:%M\n%b %d' - elif minutes > numticks: - locator = MinuteLocator(interval=int(math.ceil(minutes / numticks)), + elif mins > numticks: + locator = MinuteLocator(interval=int(math.ceil(mins / numticks)), tz=tz) fmt = '%H:%M:%S' else: From 72ef748ad64aa449b4f185d8fb8dce33486266ca Mon Sep 17 00:00:00 2001 From: Paul G Date: Fri, 26 Dec 2014 16:35:06 -0600 Subject: [PATCH 11/23] Fix timezone problem, clarify docstring for timezones. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index f8bdf3563c16..6fca9dfc1299 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -208,7 +208,7 @@ def _get_rc_timezone(): def _to_ordinalf(dt): """ - Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float + Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float days, preserving hours, minutes, seconds and microseconds. Return value is a :func:`float`. """ @@ -226,7 +226,7 @@ def _to_ordinalf(dt): midnight_time = datetime.time(0, 0, 0, tzinfo=dt.tzinfo) rdt = datetime.datetime.combine(cdate, midnight_time) - td_remainder = (dt-rdt).total_seconds() + td_remainder = (dt - rdt).total_seconds() base = float(dt.toordinal()) if td_remainder > 0: @@ -243,15 +243,21 @@ def _from_ordinalf(x, tz=None): """ Convert Gregorian float of the date, preserving hours, minutes, seconds and microseconds. Return value is a :class:`datetime`. + + The input date `x` is a float in ordinal days at UTC, and the output will + be the specified :class:`datetime` object corresponding to that time in + timezone `tz`, or if `tz` is `None`, in the timezone specified in + `rcParams['timezone']`. """ if tz is None: tz = _get_rc_timezone() + ix = int(x) - dt = datetime.datetime.fromordinal(ix) + dt = datetime.datetime.fromordinal(ix).replace(tzinfo=UTC) + remainder = float(x) - ix dt += datetime.timedelta(seconds=remainder * SEC_PER_DAY) - dt = dt.astimezone(tz) return dt From 7fd2d36615e7d23b74e9e1c5303cdd991aa884de Mon Sep 17 00:00:00 2001 From: Paul G Date: Fri, 26 Dec 2014 17:43:15 -0600 Subject: [PATCH 12/23] Fixed issue with `_to_ordinalf()`, should work fine now. Build will still fail, but that's fine for now. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 6fca9dfc1299..ce061c20c33c 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -213,24 +213,22 @@ def _to_ordinalf(dt): is a :func:`float`. """ - if isinstance(dt, datetime.date): - return float(datetime.datetime.toordinal(dt)) - if hasattr(dt, 'tzinfo') and dt.tzinfo is not None: delta = dt.tzinfo.utcoffset(dt) if delta is not None: dt -= delta - # Get a datetime object at midnight in the same time zone as dt. - cdate = dt.date() - midnight_time = datetime.time(0, 0, 0, tzinfo=dt.tzinfo) + base = float(dt.toordinal()) + if isinstance(dt, datetime.datetime): + # Get a datetime object at midnight in the same time zone as dt. + cdate = dt.date() + midnight_time = datetime.time(0, 0, 0, tzinfo=dt.tzinfo) - rdt = datetime.datetime.combine(cdate, midnight_time) - td_remainder = (dt - rdt).total_seconds() + rdt = datetime.datetime.combine(cdate, midnight_time) + td_remainder = (dt - rdt).total_seconds() - base = float(dt.toordinal()) - if td_remainder > 0: - base += td_remainder / SEC_PER_DAY + if td_remainder > 0: + base += td_remainder / SEC_PER_DAY return base @@ -382,10 +380,9 @@ def drange(dstart, dend, delta): *dend* are :class:`datetime` instances. *delta* is a :class:`datetime.timedelta` instance. """ - step = (delta.days + delta.seconds / SEC_PER_DAY + - delta.microseconds / MUSECONDS_PER_DAY) f1 = _to_ordinalf(dstart) f2 = _to_ordinalf(dend) + step = delta.total_seconds() / SEC_PER_DAY # calculate the difference between dend and dstart in times of delta num = int(np.ceil((f2 - f1) / step)) From 2f809168fb601ff9b89f07d9700c0073248d64b1 Mon Sep 17 00:00:00 2001 From: Paul G Date: Fri, 26 Dec 2014 18:46:03 -0600 Subject: [PATCH 13/23] Change behavior of `get_locator` to accurately calculate number of days between two points. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index ce061c20c33c..e8e223e43b6c 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -257,6 +257,12 @@ def _from_ordinalf(x, tz=None): dt += datetime.timedelta(seconds=remainder * SEC_PER_DAY) + # Compensate for rounding errors + if dt.microsecond < 10: + dt = dt.replace(microsecond=0) + elif dt.microsecond > 999990: + dt += datetime.timedelta(microseconds=1e6 - dt.microsecond) + return dt @@ -944,14 +950,15 @@ def autoscale(self): def get_locator(self, dmin, dmax): 'Pick the best locator based on a distance.' delta = relativedelta(dmax, dmin) + tdelta = dmax - dmin # take absolute difference if dmin > dmax: delta = -delta - numYears = (delta.years * 1.0) + numYears = delta.years * 1.0 numMonths = (numYears * MONTHS_PER_YEAR) + delta.months - numDays = (numMonths * DAYS_PER_MONTH) + delta.days + numDays = tdelta.days # Avoid estimates of days/month, days/year numHours = (numDays * HOURS_PER_DAY) + delta.hours numMinutes = (numHours * MIN_PER_HOUR) + delta.minutes numSeconds = (numMinutes * SEC_PER_MIN) + delta.seconds From ca284d12f8055585966b9c03c1be49f4fc55a4c5 Mon Sep 17 00:00:00 2001 From: Paul G Date: Fri, 26 Dec 2014 18:59:35 -0600 Subject: [PATCH 14/23] Absolute value must apply to timedelta object as well as reslative delta object. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index e8e223e43b6c..36b63f204180 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -955,6 +955,7 @@ def get_locator(self, dmin, dmax): # take absolute difference if dmin > dmax: delta = -delta + tdelta = -tdelta numYears = delta.years * 1.0 numMonths = (numYears * MONTHS_PER_YEAR) + delta.months From 68f3018df966f2b3f5f255220b765f25651d65e5 Mon Sep 17 00:00:00 2001 From: Paul G Date: Fri, 26 Dec 2014 19:58:21 -0600 Subject: [PATCH 15/23] Adjust calculation of `numSeconds` and `numMicroseconds`, since the `timedelta` object actually calculates those for us as is. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 36b63f204180..10252107a06a 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -957,13 +957,17 @@ def get_locator(self, dmin, dmax): delta = -delta tdelta = -tdelta - numYears = delta.years * 1.0 + # The following uses a mix of calls to relativedelta and timedelta + # methods because there is incomplete overlap in the functionality of + # these similar functions, and it's best to avoid doing our own math + # whenever possible. + numYears = float(delta.years) numMonths = (numYears * MONTHS_PER_YEAR) + delta.months - numDays = tdelta.days # Avoid estimates of days/month, days/year + numDays = tdelta.days # Avoids estimates of days/month, days/year numHours = (numDays * HOURS_PER_DAY) + delta.hours numMinutes = (numHours * MIN_PER_HOUR) + delta.minutes - numSeconds = (numMinutes * SEC_PER_MIN) + delta.seconds - numMicroseconds = (numSeconds * 1e6) + delta.microseconds + numSeconds = np.floor(tdelta.total_seconds()) + numMicroseconds = np.floor(tdelta.total_seconds() * 1e6) nums = [numYears, numMonths, numDays, numHours, numMinutes, numSeconds, numMicroseconds] From 88e6e1e52b67057243f5bc2c379bc6ceec9d7cce Mon Sep 17 00:00:00 2001 From: Paul G Date: Sat, 27 Dec 2014 12:57:48 -0600 Subject: [PATCH 16/23] Another adjustment to allow external libraries to do the calcs for us. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 10252107a06a..d8a3e073b35d 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -632,6 +632,7 @@ class rrulewrapper(object): def __init__(self, freq, **kwargs): self._construct = kwargs.copy() self._construct["freq"] = freq + print(self._construct) self._rrule = rrule(**self._construct) def set(self, **kwargs): @@ -1153,9 +1154,9 @@ def __init__(self, bymonthday=None, interval=1, tz=None): """ if bymonthday is None: bymonthday = list(xrange(1, 32)) - o = rrulewrapper(DAILY, bymonthday=bymonthday, + rule = rrulewrapper(DAILY, bymonthday=bymonthday, interval=interval, **self.hms0d) - RRuleLocator.__init__(self, o, tz) + RRuleLocator.__init__(self, rule, tz) class HourLocator(RRuleLocator): From 0de1e848635a497b116e154742b647ae7fe8524a Mon Sep 17 00:00:00 2001 From: Paul G Date: Sat, 27 Dec 2014 18:56:03 -0600 Subject: [PATCH 17/23] Adjusted test_dates to account for the minor change in behavior of the date locator. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 4 +--- lib/matplotlib/tests/test_dates.py | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index d8a3e073b35d..3d78f6a3a4b5 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -632,7 +632,6 @@ class rrulewrapper(object): def __init__(self, freq, **kwargs): self._construct = kwargs.copy() self._construct["freq"] = freq - print(self._construct) self._rrule = rrule(**self._construct) def set(self, **kwargs): @@ -1270,8 +1269,7 @@ def _close_to_dt(d1, d2, epsilon=5): Assert that datetimes *d1* and *d2* are within *epsilon* microseconds. """ delta = d2 - d1 - mus = abs(delta.days * MUSECONDS_PER_DAY + delta.seconds * 1e6 + - delta.microseconds) + mus = abs(delta.total_seconds() * 1e6) assert mus < epsilon diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 7b847bb0df6d..79c20a85a07d 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -243,11 +243,11 @@ def _create_auto_date_locator(date1, date2): '1990-09-01 00:00:00+00:00', '1990-10-01 00:00:00+00:00', '1990-11-01 00:00:00+00:00', '1990-12-01 00:00:00+00:00'] ], - [datetime.timedelta(days=140), - ['1990-01-06 00:00:00+00:00', '1990-01-27 00:00:00+00:00', - '1990-02-17 00:00:00+00:00', '1990-03-10 00:00:00+00:00', - '1990-03-31 00:00:00+00:00', '1990-04-21 00:00:00+00:00', - '1990-05-12 00:00:00+00:00'] + [datetime.timedelta(days=141), + ['1990-01-05 00:00:00+00:00', '1990-01-26 00:00:00+00:00', + '1990-02-16 00:00:00+00:00', '1990-03-09 00:00:00+00:00', + '1990-03-30 00:00:00+00:00', '1990-04-20 00:00:00+00:00', + '1990-05-11 00:00:00+00:00'] ], [datetime.timedelta(days=40), ['1990-01-03 00:00:00+00:00', '1990-01-10 00:00:00+00:00', From f585a172e950de2fa581e90790ea3e169de83b9b Mon Sep 17 00:00:00 2001 From: Paul G Date: Sun, 28 Dec 2014 14:38:59 -0600 Subject: [PATCH 18/23] Adjusted rounding logic in `_from_ordinalf` to be consistent with previous versions. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 3d78f6a3a4b5..c3514f509069 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -255,7 +255,8 @@ def _from_ordinalf(x, tz=None): remainder = float(x) - ix - dt += datetime.timedelta(seconds=remainder * SEC_PER_DAY) + # Round down to the nearest microsecond. + dt += datetime.timedelta(microseconds=int(remainder * MUSECONDS_PER_DAY)) # Compensate for rounding errors if dt.microsecond < 10: From 2a0865b4b92a56e6b55ac15f4eb931b8815f0369 Mon Sep 17 00:00:00 2001 From: Paul G Date: Sun, 28 Dec 2014 17:10:48 -0600 Subject: [PATCH 19/23] Add alias for total_seconds() to support Python 2.6 Signed-off-by: Paul G --- lib/matplotlib/dates.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index c3514f509069..c8d4a41f62ae 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -225,7 +225,7 @@ def _to_ordinalf(dt): midnight_time = datetime.time(0, 0, 0, tzinfo=dt.tzinfo) rdt = datetime.datetime.combine(cdate, midnight_time) - td_remainder = (dt - rdt).total_seconds() + td_remainder = _total_seconds(dt - rdt) if td_remainder > 0: base += td_remainder / SEC_PER_DAY @@ -236,6 +236,27 @@ def _to_ordinalf(dt): # a version of _to_ordinalf that can operate on numpy arrays _to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf) +try: + datetime.timedelta(datetime.datetime(1970, 1, 1), + datetime.datetime(1970, 1, 2)).total_seconds() + + def _total_seconds(tdelta): + """ + Alias providing support for datetime.timedelta.total_seconds() function + calls even in Python < 2.7. + + The input `tdelta` is a datetime.timedelta object, and returns a float + containing the total number of seconds representing the `tdelta` + duration. For large durations (> 270 on most platforms), this loses + microsecond accuracy. + """ + + return tdelta.total_seconds() +except AttributeError: + def _total_seconds(tdelta): + return (tdelta.microseconds + + (tdelta.seconds + tdelta.days * SEC_PER_DAY) * 1e6) * 1e-6 + def _from_ordinalf(x, tz=None): """ @@ -389,7 +410,7 @@ def drange(dstart, dend, delta): """ f1 = _to_ordinalf(dstart) f2 = _to_ordinalf(dend) - step = delta.total_seconds() / SEC_PER_DAY + step = _total_seconds(delta) / SEC_PER_DAY # calculate the difference between dend and dstart in times of delta num = int(np.ceil((f2 - f1) / step)) @@ -967,8 +988,8 @@ def get_locator(self, dmin, dmax): numDays = tdelta.days # Avoids estimates of days/month, days/year numHours = (numDays * HOURS_PER_DAY) + delta.hours numMinutes = (numHours * MIN_PER_HOUR) + delta.minutes - numSeconds = np.floor(tdelta.total_seconds()) - numMicroseconds = np.floor(tdelta.total_seconds() * 1e6) + numSeconds = np.floor(_total_seconds(tdelta)) + numMicroseconds = np.floor(_total_seconds(tdelta) * 1e6) nums = [numYears, numMonths, numDays, numHours, numMinutes, numSeconds, numMicroseconds] @@ -1270,7 +1291,7 @@ def _close_to_dt(d1, d2, epsilon=5): Assert that datetimes *d1* and *d2* are within *epsilon* microseconds. """ delta = d2 - d1 - mus = abs(delta.total_seconds() * 1e6) + mus = abs(_total_seconds(delta) * 1e6) assert mus < epsilon From f1f6d8f234d9f77b5b9509d7b4956500c3b7f733 Mon Sep 17 00:00:00 2001 From: Paul G Date: Sun, 28 Dec 2014 17:13:25 -0600 Subject: [PATCH 20/23] Cleaner way to test for existence of `total_seconds()`. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index c8d4a41f62ae..e26f4c8759ef 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -237,8 +237,7 @@ def _to_ordinalf(dt): _to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf) try: - datetime.timedelta(datetime.datetime(1970, 1, 1), - datetime.datetime(1970, 1, 2)).total_seconds() + datetime.timedelta(0).total_seconds() # Fails if method doesn't exist def _total_seconds(tdelta): """ From 46a4a0a7fa1d9b0b0f5629582b5a6155a17e96a4 Mon Sep 17 00:00:00 2001 From: Paul G Date: Mon, 29 Dec 2014 01:36:23 -0600 Subject: [PATCH 21/23] Tighten up `_total_seconds()` alias. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index e26f4c8759ef..79e2b6086ee8 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -237,8 +237,9 @@ def _to_ordinalf(dt): _to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf) try: - datetime.timedelta(0).total_seconds() # Fails if method doesn't exist - + # Available as a native method in Python >= 2.7. + _total_seconds = datetime.timedelta.total_seconds +except AttributeError: def _total_seconds(tdelta): """ Alias providing support for datetime.timedelta.total_seconds() function @@ -249,10 +250,6 @@ def _total_seconds(tdelta): duration. For large durations (> 270 on most platforms), this loses microsecond accuracy. """ - - return tdelta.total_seconds() -except AttributeError: - def _total_seconds(tdelta): return (tdelta.microseconds + (tdelta.seconds + tdelta.days * SEC_PER_DAY) * 1e6) * 1e-6 From 35b93d268ddd12a144b6c7d45410521995b38676 Mon Sep 17 00:00:00 2001 From: Paul G Date: Tue, 30 Dec 2014 17:53:39 -0600 Subject: [PATCH 22/23] Support numpy arrays in DayLocator, WeekdayLocator and MonthLocator when dateutil <= 2.3 Signed-off-by: Paul G --- lib/matplotlib/dates.py | 48 +++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 79e2b6086ee8..1a55bc8672a1 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -927,9 +927,8 @@ def __init__(self, tz=None, minticks=5, maxticks=None, MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000]} - self._byranges = [None, list(xrange(1, 13)), list(xrange(1, 32)), - list(xrange(0, 24)), list(xrange(0, 60)), - list(xrange(0, 60)), None] + self._byranges = [None, range(1, 13), range(1, 32), + range(0, 24), range(0, 60), range(0, 60), None] def __call__(self): 'Return the locations of the ticks' @@ -1129,10 +1128,16 @@ def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None): example, if ``interval=2``, mark every second occurance. """ if bymonth is None: - bymonth = list(xrange(1, 13)) - o = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday, + bymonth = range(1, 13) + elif isinstance(bymonth, np.ndarray): + # This fixes a bug in dateutil <= 2.3 which prevents the use of + # numpy arrays in (among other things) the bymonthday, byweekday + # and bymonth parameters. + bymonth = [x.item() for x in bymonth.astype(int)] + + rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday, interval=interval, **self.hms0d) - RRuleLocator.__init__(self, o, tz) + RRuleLocator.__init__(self, rule, tz) class WeekdayLocator(RRuleLocator): @@ -1152,9 +1157,15 @@ def __init__(self, byweekday=1, interval=1, tz=None): *interval* specifies the number of weeks to skip. For example, ``interval=2`` plots every second week. """ - o = rrulewrapper(DAILY, byweekday=byweekday, - interval=interval, **self.hms0d) - RRuleLocator.__init__(self, o, tz) + if isinstance(byweekday, np.ndarray): + # This fixes a bug in dateutil <= 2.3 which prevents the use of + # numpy arrays in (among other things) the bymonthday, byweekday + # and bymonth parameters. + [x.item() for x in byweekday.astype(int)] + + rule = rrulewrapper(DAILY, byweekday=byweekday, + interval=interval, **self.hms0d) + RRuleLocator.__init__(self, rule, tz) class DayLocator(RRuleLocator): @@ -1170,9 +1181,15 @@ def __init__(self, bymonthday=None, interval=1, tz=None): Default is to tick every day of the month: ``bymonthday=range(1,32)`` """ if bymonthday is None: - bymonthday = list(xrange(1, 32)) + bymonthday = range(1, 32) + elif isinstance(bymonthday, np.ndarray): + # This fixes a bug in dateutil <= 2.3 which prevents the use of + # numpy arrays in (among other things) the bymonthday, byweekday + # and bymonth parameters. + bymonthday = [x.item() for x in bymonthday.astype(int)] + rule = rrulewrapper(DAILY, bymonthday=bymonthday, - interval=interval, **self.hms0d) + interval=interval, **self.hms0d) RRuleLocator.__init__(self, rule, tz) @@ -1189,7 +1206,8 @@ def __init__(self, byhour=None, interval=1, tz=None): example, if ``interval=2``, mark every second occurrence. """ if byhour is None: - byhour = list(xrange(24)) + byhour = range(24) + rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval, byminute=0, bysecond=0) RRuleLocator.__init__(self, rule, tz) @@ -1208,7 +1226,8 @@ def __init__(self, byminute=None, interval=1, tz=None): example, if ``interval=2``, mark every second occurrence. """ if byminute is None: - byminute = list(xrange(60)) + byminute = range(60) + rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval, bysecond=0) RRuleLocator.__init__(self, rule, tz) @@ -1228,7 +1247,8 @@ def __init__(self, bysecond=None, interval=1, tz=None): """ if bysecond is None: - bysecond = list(xrange(60)) + bysecond = range(60) + rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval) RRuleLocator.__init__(self, rule, tz) From ad552564d25059e283d488e014e857e8ab1cb7b1 Mon Sep 17 00:00:00 2001 From: Paul G Date: Wed, 7 Jan 2015 15:40:03 -0600 Subject: [PATCH 23/23] Modified strftime code so it only falls back to this other routine when necessary. Signed-off-by: Paul G --- lib/matplotlib/dates.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 1a55bc8672a1..22fc3aa99a5a 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -491,9 +491,11 @@ def strftime(self, dt, fmt): fmt = fmt.replace("%s", "s") # strftime is not supported on datetime for years <= 1900 in Python 2.x - # or years <= 1000 in Python 3.x - if dt.year > 1900: + # or years <= 1000 in Python 3.3. Raises ValueError on failure. + try: return cbook.unicode_safe(dt.strftime(fmt)) + except ValueError: + pass year = dt.year # For every non-leap year century, advance by