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_pair
is 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 theTmLiteral
object.value_pair
is 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_pair
by using theTmParser
object. 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 False
Print 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
TmAutoScale
object, appropriate units can be selected automatically. The optiontmparser
in the initializer can be used to pass a customTmParser
object. 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 genericTmParser
object.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_pair
properly the functionTmFormat
can 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 ns
Now 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: