def strftimeEx(fmt, t, timetuple=None): """ Extends time.strftime() to format milliseconds and microseconds. Expects input to be a floating-point number of seconds since epoch. The additional formats are: - ``%(ms)``: milliseconds (uses round()) - ``%(ms_)``: milliseconds (uses floor()) - ``%(us)``: microseconds (uses round()) The format may also be a callable which will bypass time.strftime() entirely. """ if callable(fmt): return fmt(t) if "%(ms)" in fmt: # Assume that fmt does not also contain %(ms_) and %(us). # (It really doesn't make sense to mix %(ms) with those.) secs, frac = divmod(round(t,3), 1) ms = int(round(1e3*frac)) fmt = fmt.replace("%(ms)", "%03d" % ms) else: # Assume fmt contains %(ms_) and %(us). secs, frac = divmod(round(t,6), 1) ms = int(round(1e3*frac)) ms_, us = divmod(int(round(1e6*frac)),1000) fmt = fmt.replace("%(ms_)", "%03d" % ms_) fmt = fmt.replace("%(us)", "%03d" % us) if not timetuple: timetuple = localtime(secs) return strftime(fmt, timetuple)
def format(self, ticks, numlabels=None, char_width=None, fill_ratio = 0.3, ticker=None): """ Formats a set of time values. Parameters ---------- ticks : array of numbers The tick values to be formatted numlabels Not used. char_width : number The total character width available for labelling the interval. fill_ratio : 0.0 < float <= 1.0 Ratio of the available width that will be occupied by label text. ticker : AbstractScale object Object that can calculate the number of labels needed. Returns ------- List of formatted labels. """ # In order to pick the right set of labels, we need to determine # the resolution of the ticks. We can do this using a ticker if # it's provided, or by computing the resolution from the actual # ticks we've been given. if len(ticks) == 0: return [] span = abs(ticks[-1] - ticks[0]) if ticker: r = ticker.resolution else: r = span / (len(ticks) - 1) resol = self._get_resolution(r, span) widths, formats = self.formats[resol] format = formats[0] if char_width: # If a width is provided, then we pick the most appropriate scale, # otherwise just use the widest format good_formats = array(formats)[widths * len(ticks) < fill_ratio * char_width] if len(good_formats) > 0: format = good_formats[-1] # Apply the format to the tick values labels = [] resol_ndx = self.format_order.index(resol) # This dictionary maps the name of a time resolution (in self.format_order) # to its index in a time.localtime() timetuple. The default is to map # everything to index 0, which is year. This is not ideal; it might cause # a problem with the tick at midnight, january 1st, 0 a.d. being incorrectly # promoted at certain tick resolutions. time_tuple_ndx_for_resol = dict.fromkeys(self.format_order, 0) time_tuple_ndx_for_resol.update( { "seconds" : 5, "minsec" : 4, "minutes" : 4, "hourmin" : 3, "hours" : 3, }) # As we format each tick, check to see if we are at a boundary of the # next higher unit of time. If so, replace the current format with one # from that resolution. This is not the best heuristic in the world, # but it works! There is some trickiness here due to having to deal # with hybrid formats in a reasonable manner. for t in ticks: try: tm = localtime(t) s = strftimeEx(format, t, tm) except ValueError, e: warnings.warn("Unable to convert tick for timestamp " + str(t)) labels.append("ERR") continue hybrid_handled = False next_ndx = resol_ndx # The way to check that we are at the boundary of the next unit of # time is by checking that we have 0 units of the resolution, i.e. # we are at zero minutes, so display hours, or we are at zero seconds, # so display minutes (and if that is zero as well, then display hours). while tm[ time_tuple_ndx_for_resol[self.format_order[next_ndx]] ] == 0: next_ndx += 1 if next_ndx == len(self.format_order): break if resol in ("minsec", "hourmin") and not hybrid_handled: if (resol == "minsec" and tm.tm_min == 0 and tm.tm_sec != 0) or \ (resol == "hourmin" and tm.tm_hour == 0 and tm.tm_min != 0): next_format = self.formats[self.format_order[resol_ndx-1]][1][0] s = strftimeEx(next_format, t, tm) break else: hybrid_handled = True next_format = self.formats[self.format_order[next_ndx]][1][0] s = strftimeEx(next_format, t, tm) if self.strip_leading_zeros: ss = s.lstrip('0') if ss != s and (ss == '' or not ss[0].isdigit()): # A label such as '000ms' should leave one zero. ss = '0' + ss labels.append(ss) else: labels.append(s)