Safe and accurate parsing of time literals (values + units) is both common and crucial requirement. While C++ has a mechanism through STL chrono, such a functionality is absent in python (as per my information).
Library: GitHub Gist.
What is a time-literal?
Just to be clear, time literal here means anything of this form : 5ns, 4min, 15.5ms. It is a numeric value (float) followed by alphabetic constants that indicate special treatment of the alpha-numeric string constant. It is similar to the use of b while declaring bytes or using x while defining a hexadecimal integer:
binary = 0b10000
hexadecimal = 0x12f
Since python does not natively support time-literals, it can be implemented by using python strings. Here is my attempt to do the same, and while we are at it, implement some additional features as well.
Here is a rundown of the library:
A
value_pairis an entity of the following form: [float,TmLiteral]. The container should be a list. The first is the absolute numeric value and the second quantity is the units, which is represented by theTmLiteralobject.value_pairis fundamental to this library, since strings are parsed into value pairs. All inter-conversions also apply to value pairs. For performing such operations, the literal must first be converted into avalue_pairby using theTmParserobject. To verify if the given entity is a valid value-pair:from tmliteral import * IsValuePair([4.2, FindTmL("seconds")]) #-> returns True IsValuePair([4.2, "seconds"]) #-> returns FalsePrint all available pre-defined literals:
import tmliteral.time_table for tml in tmliteral.time_table: print(tml.abbr, ' : ', tml)Output:
xxx : invalid time value s : second Ys : yottasecond Zs : zettasecond Es : exasecond Ps : petasecond Ts : terasecond Gs : gigasecond Ms : megasecond ks : kilosecond hs : hectosecond das : decasecond ds : decisecond cs : centisecond ms : millisecond us : microsecond ns : nanosecond ps : picosecond fs : femtosecond as : attosecond zs : zeptosecond ys : yoctosecond min : minute h : hour d : day week : week year : calender year leapyear : calender leap year gregyear : gregorian calendar year julianyear : julian astronomical year
Create a user defined literal or find a pre-defined literal:
# Simulation unit which is equivalent to microseconds. sim_time = TmL("sim", -6, simulation unit) # TmL(abbr, exp, name, mul_factor=1.0) # Get a pre-defined time unit ms = FindTmL("ms") also_ms = FindTmL("millisecond")
Parsing time literals from strings can be done by creating a parser object:
parser = TmParser() parser.add_literal(sim_time) tm1 = parser.parse("4.2fs") # Out→ [4.2, < TmLiteral : femtosecond >] tm2 = parser["4.2fs"] # Same tm_sim = parser["5 sim"] # Out→ [5.0, < TmLiteral : simulation time >]
Conversions between units (casting) can be handled by creating a cast object:
ns_cast = TmCast.Find("ns") # Make a nano-second cast time = parser["5 ms"] ns_time = ns_cast.cast(time) # Returns 5000 ns_time2 = ns_cast[tm2] # Equivalent to `cast` member function.
Using the
TmAutoScaleobject, appropriate units can be selected automatically. The optiontmparserin the initializer can be used to pass a customTmParserobject. The auto-scale object inherits the literal order of the parser object in the “default”mode. If this option is not specified, the auto-scaler constructs a new genericTmParserobject.This module is extremely useful with timing of processes and converts arbitrary time values in seconds (or nanoseconds) to more “human readable” units. The object can be constructed in a variety of
mode:- “default” : Does not modify the preference of literals which is inherited from a parser object.
- prefer_clock_units" : Prefers clock units over SI. clock units are [“second”, “minute”, “hour”, “day”, “week”, “calender year”]
- “prefer_SI_units” : Prefers SI units over clock units.
- “clock” : Only uses clock units
- “SI” : Only uses SI units
- “extended_process_timing” : Uses [“nanoseconds”, “microseconds”, “milliseconds”] + clock units. This is useful for timing of processes which requires “human-comprehensible units” on a power scale.
tauto = TmAutoScale(mode="prefer_clock_units") tauto.scale(parser["4e5 picoseconds"], set_scale=100) #->[399.99999999999994, < TmLiteral : nanosecond >] tauto2 = TmAutoScale(mode="prefer_clock_units") tauto2["5e7 seconds"] #-> [1.5854895991882294, < TmLiteral : calender year >] tauto2["5e4 seconds"] #->[0.5787037037037037, < TmLiteral : day >]To print a
value_pairproperly the functionTmFormatcan be used:time1 = parser["5 ms"] time2 = parser["1 ns"] print(TmFormat(time1, format="full")) #-> 5.0 milliseconds print(TmFormat(time1, format="abbr")) #-> 5.0 ms print(TmFormat(time2, format="full")) #-> 1.0 nanosecond print(TmFormat(time2, format="abbr")) #-> 1.0 nsNow my favorite feature: the Expression Parser can perform arithmetic operations on time values that have any arbitrary units. Operations that are currently supported : {+, - , *, /, %}.
exp = TmExpParser() print(exp.parse("10ms + 5ns + 10ms")) #-> [0.020000005, < TmLiteral : second >]
I did not have a use-case in mind for this feature. But I am sure that somebody else can. Just did this for fun.
I hope you find this library useful! Below is a view of the library: