# gc3libs.quantity¶

Manipulation of quantities with units attached with automated conversion among compatible units.

For details and the discussion leading up to this, see: <https://github.com/uzh/gc3pie/issues/47>

class gc3libs.quantity.Duration

Represent the duration of a time lapse.

Construction of a duration can be done by parsing a string specification; several formats are accepted:

• A duration is an aggregate of days, hours, minutes and seconds:

>>> l3 = Duration('1day 4hours 9minutes 16seconds')
>>> l3.amount(Duration.s) # convert to seconds
101356

• Any of the terms can be omitted (in which case it defaults to zero):

>>> l4 = Duration('1day 4hours 16seconds')
>>> l4 == l3 - Duration('9 minutes')
True

• The unit names can be singular or plural, and any amount of space can be added between the time unit name and the associated amount:

>>> l5 = Duration('3 hour 42 minute')
>>> l6 = Duration('3 hours 42 minutes')
>>> l7 = Duration('3hours 42minutes')
>>> l5 == l6 == l7
True

• Unit names can also be abbreviated using just the leading letter:

>>> l8 = Duration('3h 42m')
>>> l9 = Duration('3h42m')
>>> l8 == l9
True

• The abbreviated formats HH:MM:SS and DD:HH:MM:SS are also accepted:

>>> # 1 hour + 1 minute + 1 second
>>> l1 = Duration('01:01:01')
>>> l1 == Duration('3661 s')
True

>>> # 1 day, 2 hours, 3 minutes, 4 seconds
>>> l2 = Duration('01:02:03:04')
>>> l2.amount(Duration.s)
93784


However, the formats HH:MM and MM:SS are rejected as ambiguous:

>>> # is this hours:minutes or minutes:seconds ?
>>> l0 = Duration('01:02')
Traceback (most recent call last):
...
ValueError: Duration '01:02' is ambiguous: use '1m 2s' ...

• Finally, you can specify a duration like any other quantity, as an integral amount of a given time unit:

>>> l1 = Duration('1 day')
>>> l2 = Duration('86400 s')
>>> l1 == l2
True


A new quantity can also be defined as a multiple of an existing one:

>>> an_hour = Duration('1 hour')
>>> a_day = 24 * an_hour
>>> a_day.amount(Duration.h)
24


The quantities Duration.hours, Duration.minutes and Duration.seconds (and their single-letter abbreviations h, m, s) are pre-defined with their obvious meaning.

Also module-level aliases hours, minutes and seconds (and the one-letter forms) are available:

>>> a_day1 = 24*hours
>>> a_day2 = 1440*minutes
>>> a_day3 = 86400*seconds


This allows for yet another way of constructing duration objects, i.e., by passing the amount and the unit separately to the constructor:

>>> a_day4 = Duration(24, hours)


Two durations are equal if they indicate the exact same amount in seconds:

>>> a_day1 == a_day2
True
>>> a_day1.amount(s)
86400
>>> a_day2.amount(s)
86400

>>> a_day == an_hour
False
>>> a_day.amount(minutes)
1440
>>> an_hour.amount(minutes)
60


Basic arithmetic is possible with durations:

>>> two_hours = an_hour + an_hour
>>> two_hours == 2*an_hour
True
>>> an_hour == two_hours / 2
True

>>> one_hour = two_hours - an_hour
>>> one_hour.amount(seconds)
3600


It is also possible to add duration quantities defined with different units; the result is naturally expressed in the smaller unit of the two:

>>> one_hour_and_half = an_hour + 30*minutes
>>> one_hour_and_half
Duration(90, unit=m)


Note that the two unit class and numeric amount are accessible through the unit and amount() attributes:

>>> one_hour_and_half.unit
Duration(1, unit=m)
>>> one_hour_and_half.amount()
90


The amount() method accepts an optional specification of an alternate unit to express the amount into:

>>> one_hour_and_half.amount(Duration.hours)
1


An optional conv argument is available to specify a numerical domain for conversion, in case the default integer arithmetic is not precise enough:

>>> one_hour_and_half.amount(Duration.hours, conv=float)
1.5


The to_str() method allows representing a duration as a string, and provides choice of the output format and unit. The format string should contain exactly two %-specifiers: the first one is used to format the numerical amount, and the second one to format the measurement unit name.

By default, the unit used originally for defining the quantity is used:

>>> '1 [hour]' == an_hour.to_str('%d [%s]')
True


This can be overridden by specifying an optional second argument unit:

>>> '60 [m]' == an_hour.to_str('%d [%s]', unit=Duration.m)
True


A third optional argument conv can set the numerical type to be used for conversion computations:

>>> '60.0 [m]' == an_hour.to_str('%.1f [%s]', unit=Duration.m, conv=float)
True


The default numerical type is int, which in particular implies that you get a null amount if the requested unit is larger than the quantity:

>>> '0 [days]' == an_hour.to_str('%d [%s]', unit=Duration.days)
True


Conversion to string uses the unit originally used for defining the quantity and the %g%s format:

>>> str(an_hour)
'1hour'

to_timedelta()

Convert a duration into a Python datetime.timedelta object.

This is useful to operate on Python’s datetime.time and datetime.date objects, which can be added or subtracted to datetime.timedelta.

class gc3libs.quantity.Memory

Represent an amount of RAM.

Construction of a memory quantity can be done by parsing a string specification (amount followed by unit):

>>> byte = Memory('1 B')
>>> kilobyte = Memory('1 kB')


A new quantity can also be defined as a multiple of an existing one:

>>> a_thousand_kB = 1000*kilobyte


The base-10 units (up to TB, Terabytes) and base-2 (up to TiB, TiBiBytes) are available as attributes of the Memory class. This allows for a third way of constructing quantity objects, i.e., by passing the amount and the unit separately to the constructor:

>>> a_megabyte = Memory(1, Memory.MB)
>>> a_mibibyte = Memory(1, Memory.MiB)

>>> a_gigabyte = 1*Memory.GB
>>> a_gibibyte = 1*Memory.GiB

>>> two_terabytes = 2*Memory.TB
>>> two_tibibytes = 2*Memory.TiB


Two memory quantities are equal if they indicate the exact same amount in bytes:

>>> kilobyte == 1000*byte
True
>>> a_megabyte == a_mibibyte
False
>>> a_megabyte < a_mibibyte
True
>>> a_megabyte > a_gigabyte
False


Basic arithmetic is possible with memory quantities:

>>> two_bytes = byte + byte
>>> two_bytes == 2*byte
True
>>> half_gigabyte = a_gigabyte / 2
>>> a_gigabyte == half_gigabyte * 2
True
>>> a_megabyte == a_gigabyte / 1000
True


The ratio of two memory quantities is correctly computed as a pure (floating-point) number:

>>> a_gigabyte / a_megabyte
1000.0


It is also possible to add memory quantities defined with different units; the result is naturally expressed in the smaller unit of the two:

>>> one_gigabyte_and_half = 1*Memory.GB + 500*Memory.MB
>>> one_gigabyte_and_half
Memory(1500, unit=MB)


Note that the two unit class and numeric amount are accessible through the unit and amount() attributes:

>>> one_gigabyte_and_half.unit
Memory(1, unit=MB)
>>> one_gigabyte_and_half.amount()
1500


The amount() method accepts an optional specification of an alternate unit to express the amount into:

>>> one_gigabyte_and_half.amount(Memory.GB)
1


An optional conv argument is available to specify a numerical domain for conversion, in case the default integer arithmetic is not precise enough:

>>> one_gigabyte_and_half.amount(Memory.GB, conv=float)
1.5


The to_str() method allows representing a quantity as a string, and provides choice of the output format and unit. The format string should contain exactly two %-specifiers: the first one is used to format the numerical amount, and the second one to format the measurement unit name.

By default, the unit used originally for defining the quantity is used:

>>> '1 [MB]' == a_megabyte.to_str('%d [%s]')
True


This can be overridden by specifying an optional second argument unit:

>>> '1000 [kB]' == a_megabyte.to_str('%d [%s]', unit=Memory.kB)
True


A third optional argument conv can set the numerical type to be used for conversion computations:

>>> '0.001GB' == a_megabyte.to_str('%g%s', unit=Memory.GB, conv=float)
True


The default numerical type is int, which in particular implies that you get a null amount if the requested unit is larger than the quantity:

>>> '0GB' == a_megabyte.to_str('%g%s', unit=Memory.GB, conv=int)
True


Conversion to string uses the unit originally used for defining the quantity and the %g%s format:

>>> str(a_megabyte)
'1MB'

class gc3libs.quantity.Quantity(base_unit_name, **other_units)

Metaclass for creating quantity classes.

This factory creates subclasses of _Quantity and bootstraps the base unit.

The name of the base unit is given as argument to the metaclass instance:

>>> @add_metaclass(Quantity('B'))
... class Memory1(object):
...   pass
...
>>> B = Memory1('1 B')
>>> print (2*B)
2B


Optional keyword arguments create additional units; the argument key gives the unit name, and its value gives the ratio of the new unit to the base unit. For example:

>>> @add_metaclass(Quantity('B', kB=1000, MB=1000*1000))
... class Memory2(object):
...   pass
...
>>> a_thousand_kB = Memory2('1000kB')
>>> MB = Memory2('1   MB')
>>> a_thousand_kB == MB
True


Note that the units (base and additional) are also available as class attributes for easier referencing in Python code:

>>> a_thousand_kB == Memory2.MB
True