bitranox/lib_cast

View on GitHub
lib_cast/lib_cast.py

Summary

Maintainability
F
3 days
Test Coverage
"""
Usage:  lib_cast (-h | -v | -i)

    -h, --help          show help
    -v, --version       show version
    -i, --info          show Info

cast float, int, time, etc. to human readable text with SI Prefixes and more
this module exposes no other useful functions to the commandline
"""
# docopt syntax see : http://docopt.org/


# STDLIB
import datetime
from decimal import Decimal
from docopt import docopt   # type: ignore
from math import log
import time
from typing import Any, Dict, List, SupportsFloat, SupportsInt, Union


# OWN
import lib_csv
import lib_list
import lib_regexp

# PROJ
try:
    from .__doc__ import __doc__
    from . import __init__conf__
except (ImportError, ModuleNotFoundError):      # pragma: no cover
    # imports for doctest
    import __init__conf__                       # type: ignore  # pragma: no cover
    from __doc__ import __doc__                 # type: ignore  # pragma: no cover


def cast_float_2_string(value: Union[Decimal, float],
                        digits: int = 12,
                        decimals: int = 2,
                        comma_seperator: str = ',',
                        remove_trailing_zeros: bool = False,
                        strip: bool = False) -> str:
    """
    decimal --> string

    >>> assert cast_float_2_string(100000.52)                                               == '   100000,52'
    >>> assert cast_float_2_string(-100000.52)                                              == '  -100000,52'
    >>> assert cast_float_2_string(-100000.52, strip=True)                                  == '-100000,52'
    >>> assert cast_float_2_string(-100000.52, comma_seperator='.', strip=True)             == '-100000.52'
    >>> # test remove trailing zeros, but keep position for tables
    >>> assert cast_float_2_string(-100000.00)                                              == '  -100000,00'
    >>> assert cast_float_2_string(-100000.00, remove_trailing_zeros=True)                  == '  -100000   '
    >>> assert cast_float_2_string(-100000.00, remove_trailing_zeros=True, strip = True)    == '-100000'

    """
    s_value = ('{value:' + str(digits) + '.' + str(decimals) + 'f}').format(value=value)
    s_value = s_value.replace('.', comma_seperator)

    if remove_trailing_zeros and decimals > 0:
        parts = s_value.rsplit(comma_seperator, 1)
        s_value = parts[0] + ' ' * (len(parts[1]) + 1)

    if strip:
        s_value = s_value.strip()

    return s_value


def cast_float_to_human_readable_size(value: Union[Decimal, float, int], unit: str = 'Byte', decimals: int = 2,
                                      base1024: bool = False, short_form: bool = False,
                                      remove_trailing_zeros: bool = False, show_exponent: bool = True) -> str:
    """
    format numbers into human readable format

    :param value:                 the value in float or int
    :param unit:                  the unit, like Byte, seconds, Volts
    :param decimals:              number of dezimals
    :param base1024:              True or False for base1024 or base10
    :param short_form:            True : short Prefix like "k", False : long Prefix like "Kilo"
    :param remove_trailing_zeros: dont show the decimals when they are zero : 1024 kByte instead 1024.00 kByte
    :param show_exponent:         '10.00 MilliVolt (x10^-3)'
    :return:  String

    IEC Prefixe (2**n)                  ISO Prefixe (10**-n)                ISO Prefixe (10**n)
    =======================             =======================             =======================
    'Ki', 'Kibi', (x1024^1)             'm', 'Milli', (x10^-3)              'k', 'Kilo' , (x10^3)
    'Mi', 'Mebi', (x1024^2)             'µ', 'Mikro', (x10^-6)              'M', 'Mega' , (x10^6)
    'Gi', 'Gibi', (x1024^3)             'n', 'Nano' , (x10^-9)              'G', 'Giga' , (x10^9)
    'Ti', 'Tebi', (x1024^4)             'p', 'Piko' , (x10^-12)             'T', 'Tera' , (x10^12)
    'Pi', 'Pebi', (x1024^5)             'f', 'Femto', (x10^-15)             'P', 'Peta' , (x10^15)
    'Ei', 'Exbi', (x1024^6)             'a', 'Atto' , (x10^-18)             'E', 'Exa'  , (x10^18)
    'Zi', 'Zebi', (x1024^7)             'z', 'Zepto', (x10^-21)             'Z', 'Zetta', (x10^21)
    'Yi', 'Yobi', (x1024^8)             'y', 'Yokto', (x10^-24)             'Y', 'Yotta', (x10^24)


    >>> cast_float_to_human_readable_size(0.1,'Volt')
    '100.00 MilliVolt (x10^-3)'
    >>> cast_float_to_human_readable_size(0.1,'V', short_form=True)
    '100.00 mV (x10^-3)'
    >>> cast_float_to_human_readable_size(0.1,'V', short_form=True, show_exponent=False)
    '100.00 mV'
    >>> cast_float_to_human_readable_size(0.1,'V', short_form=True, show_exponent=False, remove_trailing_zeros=True)
    '100 mV'
    >>> cast_float_to_human_readable_size(0.01,'Volt')
    '10.00 MilliVolt (x10^-3)'
    >>> cast_float_to_human_readable_size(0.001,'Volt')
    '1.00 MilliVolt (x10^-3)'
    >>> cast_float_to_human_readable_size(0.000001,'Volt')
    '1.00 MikroVolt (x10^-6)'
    >>> cast_float_to_human_readable_size(0.000000001,'Volt')
    '1.00 NanoVolt (x10^-9)'
    >>> cast_float_to_human_readable_size(0.000000000001,'Volt')
    '1.00 PikoVolt (x10^-12)'
    >>> cast_float_to_human_readable_size(0.000000000000001,'Volt')
    '1.00 FemtoVolt (x10^-15)'
    >>> cast_float_to_human_readable_size(0.000000000000000001,'Volt')
    '1.00 AttoVolt (x10^-18)'
    >>> cast_float_to_human_readable_size(0.000000000000000000001,'Volt')
    '1.00 ZeptoVolt (x10^-21)'
    >>> cast_float_to_human_readable_size(0.000000000000000000000001,'Volt')
    '1.00 YoktoVolt (x10^-24)'
    >>> cast_float_to_human_readable_size(0.0000000000000000000000001,'Volt') # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    '0.10 YoktoVolt (x10^-24)'
    >>> cast_float_to_human_readable_size(0,'Volt')
    '0.00 Volt'
    >>> cast_float_to_human_readable_size(0,'Volt',remove_trailing_zeros=True)
    '0 Volt'
    >>> cast_float_to_human_readable_size(1,'Volt')
    '1.00 Volt'
    >>> cast_float_to_human_readable_size(1.00,'Volt')
    '1.00 Volt'
    >>> cast_float_to_human_readable_size(10,'Volt')
    '10.00 Volt'
    >>> cast_float_to_human_readable_size(100,'Volt')
    '100.00 Volt'
    >>> cast_float_to_human_readable_size(1000,'Volt')
    '1.00 KiloVolt (x10^3)'
    >>> cast_float_to_human_readable_size(1000000,'Volt')
    '1.00 MegaVolt (x10^6)'
    >>> cast_float_to_human_readable_size(1000000000,'Volt')
    '1.00 GigaVolt (x10^9)'
    >>> cast_float_to_human_readable_size(1000000000000,'Volt')
    '1.00 TeraVolt (x10^12)'
    >>> cast_float_to_human_readable_size(1000000000000000,'Volt')
    '1.00 PetaVolt (x10^15)'
    >>> cast_float_to_human_readable_size(1000000000000000000,'Volt')
    '1.00 ExaVolt (x10^18)'
    >>> cast_float_to_human_readable_size(1000000000000000000000,'Volt')
    '1.00 ZettaVolt (x10^21)'
    >>> cast_float_to_human_readable_size(1000000000000000000000000,'Volt')
    '1.00 YottaVolt (x10^24)'
    >>> cast_float_to_human_readable_size(1000000000000000000000000000,'Volt')
    '1000.00 YottaVolt (x10^24)'


    >>> cast_float_to_human_readable_size(1.00,'Volt',remove_trailing_zeros=True)
    '1 Volt'
    >>> cast_float_to_human_readable_size(1.5)
    '1.50 Byte'
    >>> cast_float_to_human_readable_size(12.3789,decimals=0)
    '12 Byte'
    >>> cast_float_to_human_readable_size(12.3789)
    '12.38 Byte'
    >>> cast_float_to_human_readable_size(1024,base1024=True)
    '1 KibiByte (x1024^1)'
    >>> cast_float_to_human_readable_size(65535,base1024=True)
    '64 KibiByte (x1024^1)'
    >>> cast_float_to_human_readable_size(-3456,base1024=True)
    '-3 KibiByte (x1024^1)'
    >>> cast_float_to_human_readable_size(0.1,base1024=True)
    '0 Byte'
    >>> cast_float_to_human_readable_size(-0.1,base1024=True)
    '0 Byte'

    """

    value = float(value)
    # handling für negative Zahlen - Absolutwert verwenden und in der Ausgabe '-' hinzufügen
    b_negative = False
    if value < 0:
        b_negative = True
        value = value * -1

    n_factor = 1000         # Basis for Log
    n_list_index_add = 8      # Position der Liste für value 1...999 , also kein prefix

    # bei IEC Prefix, keine Kommazahlen, keine Dezimalstellen anzeichen
    if base1024:
        value = round(value, 0)           # keine Kommazahlen für Base 1024
        remove_trailing_zeros = True    # keine trailing Zeros
        decimals = 0                    # keine Nachkommastellen, Runden auf 1 Stelle
        n_factor = 1024                 # basis 1024
        n_list_index_add = 0            # Die Liste für Basis 1024 startet mit 0

    # handling für Null, denn log von 0 geht nicht
    if value == 0:
        if remove_trailing_zeros:
            s_result = '0 ' + unit
        else:
            s_format = '{:.%sf} ' % decimals  # format String
            s_result = s_format.format(0) + unit
        return s_result

    if base1024:          # IEC Prefixe (2**n)
        lst_prefix = [('', '', ''),
                      ('Ki', 'Kibi', ' (x1024^1)'),
                      ('Mi', 'Mebi', ' (x1024^2)'),
                      ('Gi', 'Gibi', ' (x1024^3)'),
                      ('Ti', 'Tebi', ' (x1024^4)'),
                      ('Pi', 'Pebi', ' (x1024^5)'),
                      ('Ei', 'Exbi', ' (x1024^6)'),
                      ('Zi', 'Zebi', ' (x1024^7)'),
                      ('Yi', 'Yobi', ' (x1024^8)')]

    else:       # ISO Prefixe (10**n)
        lst_prefix = [('y', 'Yokto', ' (x10^-24)'),
                      ('z', 'Zepto', ' (x10^-21)'),
                      ('a', 'Atto', ' (x10^-18)'),
                      ('f', 'Femto', ' (x10^-15)'),
                      ('p', 'Piko', ' (x10^-12)'),
                      ('n', 'Nano', ' (x10^-9)'),
                      ('µ', 'Mikro', ' (x10^-6)'),
                      ('m', 'Milli', ' (x10^-3)'),
                      ('', '', ''),
                      ('k', 'Kilo', ' (x10^3)'),
                      ('M', 'Mega', ' (x10^6)'),
                      ('G', 'Giga', ' (x10^9)'),
                      ('T', 'Tera', ' (x10^12)'),
                      ('P', 'Peta', ' (x10^15)'),
                      ('E', 'Exa', ' (x10^18)'),
                      ('Z', 'Zetta', ' (x10^21)'),
                      ('Y', 'Yotta', ' (x10^24)')]

    exponent = log(value, n_factor)
    if value < 1:
        exponent = exponent - 1
        # sonst bekommen wir bei value=0.001  1000 Mikro statt 1 Milli is_int funktioniert hier nicht
        if int(exponent) == exponent:
            exponent = exponent + 1      # da log immer float gibt - daher if int(exponent) == exponent
    exponent = int(exponent)

    if exponent < -8:  # wenn kleiner als yokto 10^-24, dann trotzdem in yokto angeben
        exponent = -8

    if exponent > 8:  # wenn grösser als Yotta 10^24, dann trotzdem in Yotta angeben
        exponent = 8

    quotient = value / n_factor ** exponent

    n_index = exponent + n_list_index_add

    s_short_prefix, s_prefix, s_mul = lst_prefix[n_index]
    if short_form:
        s_prefix = s_short_prefix

    f_ret_val = round(quotient, decimals)     # auf gewünschte Stellen runden
    s_format = '{:.%sf}' % decimals           # format String
    s_ret_val = s_format.format(f_ret_val)    # Zahlenwert nun als String

    if b_negative:
        s_ret_val = '-' + s_ret_val

    if remove_trailing_zeros and decimals > 0:
        s_ret_val = s_ret_val.rstrip('0').rstrip('.')

    s_ret_val = s_ret_val + ' ' + s_prefix + unit
    if show_exponent:
        s_ret_val = s_ret_val + s_mul

    return s_ret_val


def cast_float_2_human_readable_timediff(float_seconds: Union[Decimal, float], language: str = 'de') -> str:
    """
    dient dazu Laufzeiten von Programmen gerundet in einem lesbaren Format anzuzeigen

    >>> cast_float_2_human_readable_timediff(89452.456898418)
    '  1 Tage,  0 Stunden, 50 Minuten, 52 Sekunden'
    >>> cast_float_2_human_readable_timediff(86572.456898418)
    '  1 Tage,  0 Stunden,  2 Minuten, 52 Sekunden'
    >>> cast_float_2_human_readable_timediff(7600.456898418)
    ' 2 Stunden,  6 Minuten, 40 Sekunden'
    >>> cast_float_2_human_readable_timediff(2455.456898418)
    '40 Minuten, 55 Sekunden'
    >>> cast_float_2_human_readable_timediff(955.456898418)
    '15 Minuten, 55 Sekunden'
    >>> cast_float_2_human_readable_timediff(155.456898418)
    ' 2 Minuten, 35 Sekunden'
    >>> cast_float_2_human_readable_timediff(52.456898418)
    '52.5 Sekunden'
    >>> cast_float_2_human_readable_timediff(8.456898418)
    '8.46 Sekunden'
    >>> cast_float_2_human_readable_timediff(2.456898418)
    '2.46 Sekunden'
    >>> cast_float_2_human_readable_timediff(0.456898418)
    '457 ms'
    >>> cast_float_2_human_readable_timediff(0.056898418)
    '56.9 ms'
    >>> cast_float_2_human_readable_timediff(0.006898418)
    '6.90 ms'
    >>> cast_float_2_human_readable_timediff(0.000898418)
    '898 µs'
    >>> cast_float_2_human_readable_timediff(0.000098418)
    '98.4 µs'
    >>> cast_float_2_human_readable_timediff(0.000008418)
    '8.42 µs'
    >>> cast_float_2_human_readable_timediff(0.000000418)
    '418 ns'
    >>> cast_float_2_human_readable_timediff(0.0000000418)
    '42 ns'
    >>> cast_float_2_human_readable_timediff(0.00000000418)
    '4 ns'
    >>> cast_float_2_human_readable_timediff(0.00000000498)
    '5 ns'
    >>> cast_float_2_human_readable_timediff(0.000000000418)
    '0 ns'
    """

    language_lower = language.lower()

    hash_tag_days_by_language = dict()
    hash_tag_hours_by_language = dict()
    hash_tag_minutes_by_language = dict()
    hash_tag_seconds_by_language = dict()

    hash_tag_days_by_language['de'] = 'Tage'
    hash_tag_days_by_language['en'] = 'days'
    hash_tag_hours_by_language['de'] = 'Stunden'
    hash_tag_hours_by_language['en'] = 'hours'
    hash_tag_minutes_by_language['de'] = 'Minuten'
    hash_tag_minutes_by_language['en'] = 'minutes'
    hash_tag_seconds_by_language['de'] = 'Sekunden'
    hash_tag_seconds_by_language['en'] = 'seconds'

    tag_days = hash_tag_days_by_language[language_lower]
    tag_hours = hash_tag_hours_by_language[language_lower]
    tag_minutes = hash_tag_minutes_by_language[language_lower]
    tag_seconds = hash_tag_seconds_by_language[language_lower]

    n_days, f_remainder = divmod(float_seconds, 86400)
    n_hours, f_remainder = divmod(f_remainder, 3600)
    n_minutes, f_remainder = divmod(f_remainder, 60)
    f_seconds = f_remainder
    f_milliseconds = f_seconds * 1000
    f_microseconds = f_seconds * 1000000
    f_nanoseconds = f_seconds * 1000000000

    n_days = int(n_days)
    n_hours = int(n_hours)
    n_minutes = int(n_minutes)

    if n_days > 0:
        s_timediff_de = '{:3.0f} {tag_days}, {:2.0f} {tag_hours}, {:2.0f} {tag_minutes}, {:2.0f} {tag_seconds}'\
            .format(n_days, n_hours, n_minutes, f_seconds,
                    tag_days=tag_days, tag_hours=tag_hours, tag_minutes=tag_minutes, tag_seconds=tag_seconds)

    elif n_hours > 0:
        s_timediff_de = '{:2.0f} {tag_hours}, {:2.0f} {tag_minutes}, {:2.0f} {tag_seconds}'\
            .format(n_hours, n_minutes, f_seconds,
                    tag_hours=tag_hours, tag_minutes=tag_minutes, tag_seconds=tag_seconds)
    elif n_minutes > 0:
        s_timediff_de = '{:2.0f} {tag_minutes}, {:2.0f} {tag_seconds}'\
            .format(n_minutes, f_seconds, tag_minutes=tag_minutes, tag_seconds=tag_seconds)

    # grösser gleich 10 Sekunden : eine Kommastelle anzeigen , z.Bsp 12.4 Sekunden
    elif f_seconds >= 10:
        s_timediff_de = '{:2.1f} {tag_seconds}'.format(f_seconds, tag_seconds=tag_seconds)
    # grösser 1 Sekunde : zwei Kommastellen anzeigen , z.Bsp 8.43 Sekunden
    elif f_seconds >= 1:
        s_timediff_de = '{:1.2f} {tag_seconds}'.format(f_seconds, tag_seconds=tag_seconds)
    # grösser gleich 100ms : keine Kommastellen anzeigen , z.Bsp 214 ms
    elif f_milliseconds >= 100:
        s_timediff_de = '{:3.0f} ms'.format(f_milliseconds)
    # grösser gleich 10ms : eine Kommastellen anzeigen , z.Bsp 64,5 ms
    elif f_milliseconds >= 10:
        s_timediff_de = '{:2.1f} ms'.format(f_milliseconds)
    # grösser gleich 1ms : zwei Kommastellen anzeigen , z.Bsp 4,53 ms
    elif f_milliseconds >= 1:
        s_timediff_de = '{:1.2f} ms'.format(f_milliseconds)
    # grösser gleich 100µs : keine Kommastellen anzeigen , z.Bsp 243 µs
    elif f_microseconds >= 100:
        s_timediff_de = '{:3.0f} µs'.format(f_microseconds)
    # grösser gleich 10µs : eine Kommastellen anzeigen , z.Bsp 24.3 µs
    elif f_microseconds >= 10:
        s_timediff_de = '{:2.1f} µs'.format(f_microseconds)
    # grösser gleich 1µs : zwei Kommastellen anzeigen , z.Bsp 4.32 µs
    elif f_microseconds >= 1:
        s_timediff_de = '{:1.2f} µs'.format(f_microseconds)
    # grösser gleich 100 ns keine Kommastellen angeben, z.Bsp. 243 ns
    elif f_nanoseconds >= 100:
        s_timediff_de = '{:3.0f} ns'.format(f_nanoseconds)
    # grösser gleich 10 ns keine Kommastellen angeben, z.Bsp. 24 ns
    elif f_nanoseconds >= 10:
        s_timediff_de = '{:2.0f} ns'.format(f_nanoseconds)
    # grösser gleich 1 ns keine Kommastellen angeben, z.Bsp. 9 ns
    elif f_nanoseconds >= 1:
        s_timediff_de = '{:1.0f} ns'.format(f_nanoseconds)
    # kleiner 1 ns keine Kommastellen angeben, z.Bsp. 0 ns
    else:
        s_timediff_de = '{:1.0f} ns'.format(f_nanoseconds)
    return s_timediff_de


def cast_float_2_human_readable_dimension(dimension_in_meters: Union[Decimal, float], language: str = 'de', unit_short: bool = True) -> str:
    """
    >>> cast_float_2_human_readable_dimension(dimension_in_meters=Decimal('9876.987654'))
    '9.88 km'
    >>> cast_float_2_human_readable_dimension(dimension_in_meters=Decimal('876.987654'))
    '877 m'
    >>> cast_float_2_human_readable_dimension(dimension_in_meters=Decimal('76.987654'))
    '77.0 m'
    >>> cast_float_2_human_readable_dimension(dimension_in_meters=Decimal('6.987654'))
    '6.99 m'
    >>> cast_float_2_human_readable_dimension(dimension_in_meters=Decimal('0.987654'))
    '988 mm'
    >>> cast_float_2_human_readable_dimension(dimension_in_meters=Decimal('0.087654'))
    '88 mm'
    >>> cast_float_2_human_readable_dimension(dimension_in_meters=Decimal('0.007654'))
    '7.65 mm'
    >>> cast_float_2_human_readable_dimension(dimension_in_meters=Decimal('0.000654'))
    '0.654 mm'
    >>> cast_float_2_human_readable_dimension(dimension_in_meters=Decimal('0.000054'))
    '54.0 µm'
    """
    language = language.lower()
    dict_m = {True: {'de': 'm', 'en': 'm'}, False: {'de': 'Meter', 'en': 'meters'}}
    dict_mm = {True: {'de': 'mm', 'en': 'mm'}, False: {'de': 'Millimeter', 'en': 'millimeter'}}
    dict_um = {True: {'de': 'µm', 'en': 'µm'}, False: {'de': 'Mikrometer', 'en': 'micrometer'}}

    if dimension_in_meters >= 1000:
        dict_km = {True: {'de': 'km', 'en': 'km'}, False: {'de': 'Kilometer', 'en': 'kilometer'}}
        dimension_in_kilometers = dimension_in_meters / 1000
        s_dimension = "{0:.2f} ".format(dimension_in_kilometers) + dict_km[unit_short][language]
        return s_dimension
    elif dimension_in_meters >= 100:
        s_dimension = "{0:.0f} ".format(dimension_in_meters) + dict_m[unit_short][language]
        return s_dimension
    elif dimension_in_meters >= 10:
        s_dimension = "{0:.1f} ".format(dimension_in_meters) + dict_m[unit_short][language]
        return s_dimension
    elif dimension_in_meters >= 1:
        s_dimension = "{0:.2f} ".format(dimension_in_meters) + dict_m[unit_short][language]
        return s_dimension
    elif dimension_in_meters >= 0.010:
        dimension_in_mm = dimension_in_meters * 1000
        s_dimension = "{0:.0f} ".format(dimension_in_mm) + dict_mm[unit_short][language]
        return s_dimension
    elif dimension_in_meters >= 0.001:
        dimension_in_mm = dimension_in_meters * 1000
        s_dimension = "{0:.2f} ".format(dimension_in_mm) + dict_mm[unit_short][language]
        return s_dimension
    elif dimension_in_meters >= 0.0001:
        dimension_in_mm = dimension_in_meters * 1000
        s_dimension = "{0:.3f} ".format(dimension_in_mm) + dict_mm[unit_short][language]
        return s_dimension
    else:
        dimension_in_um = dimension_in_meters * 1000000
        s_dimension = "{0:.1f} ".format(dimension_in_um) + dict_um[unit_short][language]
        return s_dimension


def cast_float_2_human_readable_iterations(float_seconds: Union[Decimal, float]) -> str:
    """
    >>> cast_float_2_human_readable_iterations(42328.123456789)
    '2.04 Iterationen pro Tag'
    >>> cast_float_2_human_readable_iterations(3456.123456789)
    '1.04 Iterationen pro Stunde'
    >>> cast_float_2_human_readable_iterations(100.123456789)
    '35.96 Iterationen pro Stunde'
    >>> cast_float_2_human_readable_iterations(32.123456789)
    '1.87 Iterationen pro Minute'
    >>> cast_float_2_human_readable_iterations(0.123456789)
    '8.10 Iterationen pro Sekunde'
    >>> cast_float_2_human_readable_iterations(0.0123456789)
    '81 Iterationen pro Sekunde'
    >>> cast_float_2_human_readable_iterations(0.00023158)
    '4318 Iterationen pro Sekunde'
    >>> cast_float_2_human_readable_iterations(0.00000158)
    '632911 Iterationen pro Sekunde'
    >>> cast_float_2_human_readable_iterations(0)
    '∞ pro Sekunde (nicht messbar)'

    """
    if float_seconds > 3600:
        s_iterations = '{iterations:2.2f} Iterationen pro Tag'.format(iterations=86400 / float_seconds)
    elif float_seconds > 60:
        s_iterations = '{iterations:2.2f} Iterationen pro Stunde'.format(iterations=3600 / float_seconds)
    elif float_seconds > 1:
        s_iterations = '{iterations:2.2f} Iterationen pro Minute'.format(iterations=60 / float_seconds)
    elif float_seconds > 0.02:
        s_iterations = '{iterations:1.2f} Iterationen pro Sekunde'.format(iterations=1 / float_seconds)
    elif float_seconds > 0:
        s_iterations = '{iterations:1.0f} Iterationen pro Sekunde'.format(iterations=1 / float_seconds)
    else:
        s_iterations = '∞ pro Sekunde (nicht messbar)'
    return s_iterations


def cast_float_2_human_readable_weight(weight_in_kg: Union[float, Decimal], language: str = 'de') -> str:
    """
    >>> cast_float_2_human_readable_weight(Decimal('1899.567'), language='de')
    '1.90 Tonnen'
    >>> cast_float_2_human_readable_weight(Decimal('1899.567'), language='en')
    '1.90 tons'
    >>> cast_float_2_human_readable_weight(Decimal('899.567'))
    '900 Kilogramm'
    >>> cast_float_2_human_readable_weight(Decimal('99.567'))
    '99.6 Kilogramm'
    >>> cast_float_2_human_readable_weight(Decimal('9.567'))
    '9.57 Kilogramm'
    >>> cast_float_2_human_readable_weight(Decimal('0.567'))
    '567 Gramm'
    >>> cast_float_2_human_readable_weight(Decimal('0.067'))
    '67 Gramm'
    >>> cast_float_2_human_readable_weight(Decimal('0.007'))
    '7 Gramm'
    >>> cast_float_2_human_readable_weight(1899.567, language='de')
    '1.90 Tonnen'

    """
    dict_kg = {'de': 'Kilogramm', 'en': 'kilogram'}
    language = language.lower()

    if weight_in_kg >= 1000:
        dict_tons = {'de': 'Tonnen', 'en': 'tons'}
        weight_in_tons = weight_in_kg / 1000
        s_weight = "{0:.2f} ".format(weight_in_tons) + dict_tons[language]
        return s_weight
    elif weight_in_kg >= 100:
        s_weight = "{0:.0f} ".format(weight_in_kg) + dict_kg[language]
        return s_weight
    elif weight_in_kg >= 10:
        s_weight = "{0:.1f} ".format(weight_in_kg) + dict_kg[language]
        return s_weight
    elif weight_in_kg >= 1:
        s_weight = "{0:.2f} ".format(weight_in_kg) + dict_kg[language]
        return s_weight
    else:
        dict_grams = {'de': 'Gramm', 'en': 'grams'}
        weight_in_gramm = weight_in_kg * 1000
        s_weight = "{0:.0f} ".format(weight_in_gramm) + dict_grams[language]
        return s_weight


def cast_human_readable_size_to_float(s_human_readable_size: Union[str, int, bool, float]) -> float:
    """
    IEC (2^n)
    Ki(Kibi 2^10), Mi(Mebi 2^20), Gi(Gibi 2^30) Ti(Tebi 2^40),
    Pi(Pebi 2^50), Ei(Exbi 2^60), Zi(Zebi 2^70), Yi(Yobi 2^80)

    SI (10^n)
    Y(Yotta 10^24), Z(Zetta 10^21), E(Exa 10^18), P(Peta 10^15), T(Tera 10^12),
    G(Giga 10^9), M(Mega 10^6), k(kilo 10^3), h(hekto 10^2),da(Deka 10^1),
    d(Dezi 10^-1), c(Zenti 10^-2), m(Milli 10^-3), u|µ(Mikro 10^-6), n(Nano 10^-9),
    p(Piko 10^-12), f(Femto 10^-15), a(Atto 10^-18), z(Zepto 10^-21), y(Yokto 10^-24)

    :param s_human_readable_size:  True: Hexadezimaler Multiplikator (1024),
                                   False : dezimaler Multiplikator (1000), default=True
    :return:                n_result, Wert als Integer oder Decimal

    >>> cast_human_readable_size_to_float('2GB')
    2000000000
    >>> cast_human_readable_size_to_float(5)
    5.0
    >>> cast_human_readable_size_to_float('-1')
    -1.0
    >>> cast_human_readable_size_to_float('0')
    0.0
    >>> cast_human_readable_size_to_float('1024 Byte')      # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    Traceback (most recent call last):
    ...
    ValueError: can not identify SI or IEC prefix 'Byte'

    >>> cast_human_readable_size_to_float('65000 byte')     # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    Traceback (most recent call last):
    ...
    ValueError: can not identify SI or IEC prefix 'byte'

    >>> cast_human_readable_size_to_float('10 Mb')
    10000000
    >>> cast_human_readable_size_to_float('5 Gb')
    5000000000
    >>> cast_human_readable_size_to_float('5.5 Gb')
    5500000000
    >>> cast_human_readable_size_to_float('5.5 GiByte')
    5905580032
    >>> cast_human_readable_size_to_float('5.5 useconds')
    5.5e-06
    >>> cast_human_readable_size_to_float('5.5 milliseconds')
    0.0055
    >>> cast_human_readable_size_to_float(True)
    1.0
    >>> cast_human_readable_size_to_float(False)
    0.0

    """

    # wenn die Größe keine Instanz von String, so unverändert retour geben.
    if not isinstance(s_human_readable_size, str):
        return float(s_human_readable_size)

    # suche den ersten buchstaben a-ze
    n_position, s_first_letter_found = lib_regexp.regexp_check_chars_azAZ.search(s_human_readable_size)

    result = float(s_human_readable_size[:n_position])

    if n_position is None:
        return result

    s_prefix = s_human_readable_size[n_position:]

    lst_prefixe = [('Ki', 'Kibi', 2**10),
                   ('Mi', 'Mebi', 2**20),
                   ('Gi', 'Gibi', 2**30),
                   ('Ti', 'Tebi', 2**40),
                   ('Pi', 'Pebi', 2**50),
                   ('Ei', 'Exbi', 2**60),
                   ('Zi', 'Zebi', 2**70),
                   ('Yi', 'Yobi', 2**80),
                   ('Y', 'Yotta', 1E24),
                   ('Z', 'Zetta', 1E21),
                   ('E', 'Exa', 1E18),
                   ('P', 'Peta', 1E15),
                   ('T', 'Tera', 1E12),
                   ('G', 'Giga', 1E9),
                   ('M', 'Mega', 1E6),
                   ('k', 'kilo', 1E3),
                   ('h', 'hekto', 1E2),
                   ('da', 'Deka', 10),
                   ('d', 'Dezi', 1E-1),
                   ('c', 'Zenti', 1E-2),
                   ('m', 'Milli', 1E-3),
                   ('u', 'Mikro', 1E-6),
                   ('µ', 'Mikro', 1E-6),
                   ('n', 'Nano', 1E-9),
                   ('p', 'Piko', 1E-12),
                   ('f', 'Femto', 1E-15),
                   ('a', 'Atto', 1E-18),
                   ('z', 'Zepto', 1E-21),
                   ('y', 'Yokto', 1E-24)
                   ]

    for (s_pref, s_pref_name, dec_faktor) in lst_prefixe:
        if s_prefix.startswith(s_pref) or s_prefix.lower().startswith(s_pref_name.lower()):        # suche nach den ganzen Einheiten lowercase
            result = result * dec_faktor
            if int(result) == result:    # wenn möglich in int retournieren
                return int(result)
            else:
                return result

    raise ValueError("can not identify SI or IEC prefix '{}'".format(s_prefix))


def cast_to_bool(value: Union[str, int, bool, float, None]) -> bool:
    """
    >>> cast_to_bool('')
    False
    >>> cast_to_bool('j')
    True
    >>> cast_to_bool('n')
    False
    >>> cast_to_bool('1')
    True
    >>> cast_to_bool('0')
    False
    >>> cast_to_bool('0.0')
    False
    >>> cast_to_bool('2.0')
    True
    >>> cast_to_bool(False)
    False
    >>> cast_to_bool(True)
    True
    >>> cast_to_bool(1)
    True
    >>> cast_to_bool(1.0)
    True
    >>> cast_to_bool(0.0)
    False
    >>> cast_to_bool(None)
    False

    >>> cast_to_bool('xx')  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    Traceback (most recent call last):
        ...
    ValueError: Value "xx" cant be casted to bool


    """
    if isinstance(value, str):
        value = value.strip().lower()
    if not value:
        return False
    if value in ('j', 'ja', 'y', 'yes', 'true', 'wahr', '1'):
        return True
    elif value in ('n', 'no', 'nein', 'false', 'falsch', 'unwahr', 'none', '0'):
        return False
    elif is_castable_to_float(value):
        float_value = float(value)
        if float_value:
            return True
        else:
            return False
    else:
        raise ValueError('Value "{}" cant be casted to bool'.format(str(value)))


def cast_str_2_dec(s_value: str, s_comma_seperator: str = ',') -> Decimal:
    """
    string --> Decimal
    >>> assert cast_str_2_dec('100000,52') == Decimal('100000.52')
    """
    s_value = s_value.replace(s_comma_seperator, '.')
    dec_value = Decimal(s_value)
    return dec_value


def cast_str_2_list(s_input: str, keep_empty_list_items: bool = True) -> List[Any]:
    """
    >>> cast_str_2_list('a, "x, y" , b')
    ['a', 'x, y', 'b']
    >>> cast_str_2_list('a')
    ['a']
    >>> cast_str_2_list('a, b')
    ['a', 'b']
    >>> cast_str_2_list('a, b')
    ['a', 'b']
    >>> cast_str_2_list('a, , b')
    ['a', '', 'b']
    >>> cast_str_2_list('a, , b', keep_empty_list_items=False)
    ['a', 'b']
    >>> cast_str_2_list('"a, , b"')
    ['a, , b']
    >>> cast_str_2_list('a, x y , b')
    ['a', 'x y', 'b']
    >>> cast_str_2_list('a, "x, y" , b')
    ['a', 'x, y', 'b']
    >>> cast_str_2_list('a, "x,y" , b')
    ['a', 'x,y', 'b']

    """
    items = lib_csv.cast_csv_2_list(csv_str=s_input)
    items = lib_list.ls_strip_elements(items)
    if not keep_empty_list_items:
        items = lib_list.ls_del_empty_elements(items)
    return items


def cast_list_of_strings_to_lower(list_of_strings: List[str]) -> List[str]:
    """
    >>> cast_list_of_strings_to_lower(['Abra','WhaT'])
    ['abra', 'what']
    """
    return [my_string.lower() for my_string in list_of_strings]


def get_type_as_string(instance: object) -> str:
    """
    >>> x='a'
    >>> get_type_as_string(x)
    'str'

    >>> x=1
    >>> get_type_as_string(x)
    'int'

    >>> import decimal
    >>> x=decimal.Decimal(1.00)
    >>> get_type_as_string(x)
    'Decimal'
    >>> x=[]
    >>> get_type_as_string(x)
    'list'

    """
    return type(instance).__name__


def is_castable_to_bool(value: Union[str, int, bool, float, None]) -> bool:
    """
    >>> is_castable_to_bool(True)
    True
    >>> is_castable_to_bool('xx')
    False
    >>> is_castable_to_bool('True')
    True
    >>> is_castable_to_bool('')
    True
    >>> is_castable_to_bool(None)
    True

    """

    try:
        cast_to_bool(value)
        return True
    except ValueError:
        return False


def is_castable_to_float(value: Union[SupportsFloat, str, bytes, bytearray]) -> bool:
    """
    prüft ob das objekt in float umgewandelt werden kann

    Argumente  : o_object    : der Wert der zu prüfen ist
    Returns    : True|False
    Exceptions : keine

    >>> is_castable_to_float(1)
    True
    >>> is_castable_to_float('1')
    True
    >>> is_castable_to_float('1.0')
    True
    >>> is_castable_to_float('1,0')
    False
    >>> is_castable_to_float('True')
    False
    >>> is_castable_to_float(True)
    True
    >>> is_castable_to_float('')
    False
    >>> is_castable_to_float(None)  # noqa
    False

    """
    try:
        float(value)
        return True
    except (ValueError, TypeError):
        return False


def is_castable_to_int(value: Union[str, bytes, SupportsInt]) -> bool:
    """
    >>> is_castable_to_int(1)
    True
    >>> is_castable_to_int('1')
    True
    >>> is_castable_to_int('1.0')
    False
    >>> is_castable_to_int('1,0')
    False
    >>> is_castable_to_int('True')
    False
    >>> is_castable_to_int(True)
    True
    >>> is_castable_to_int('')
    False
    >>> is_castable_to_int(None)
    False

    """
    try:
        int(value)
        return True
    except (ValueError, TypeError):
        return False


def cast_datetime_2_str(d_datetime: datetime.datetime, b_format_for_filename: bool = False) -> str:
    """
    Konvertiere datetime auf String im Format yyyy-mm-dd hh:mm:ss
    oder zur Verwendung in File- oder Verzeichnisnamen als String yyyy-mm-dd_hh-mm-ss

    >>> # gibt z.Bsp.: '2017-11-08_13-10-57'
    >>> dt = datetime.datetime.now()
    >>> cast_datetime_2_str(dt,True)
    '...-...-..._...-...-...'

    >>> # gibt z.Bsp.: '2017-11-08 13:10:57'
    >>> cast_datetime_2_str(dt)
    '...-...-...:...:...'

    """

    s_datetime = d_datetime.strftime("%Y-%m-%d %H:%M:%S")
    if b_format_for_filename:
        s_datetime = s_datetime.replace(':', '-')
        s_datetime = s_datetime.replace(' ', '_')                    # 2015-06-06_17-21-02
    return s_datetime


def cast_timestamp_seconds_since_epoch_to_text(seconds: Union[int, float, str],
                                               timezone: str = "UTC",
                                               time_in_nanoseconds: bool = False,
                                               date_time_format: str = '%Y-%m-%d %H:%M:%S') -> str:
    """
    >>> assert cast_timestamp_seconds_since_epoch_to_text(0,"UTC") == '1970-01-01 00:00:00'
    >>> assert cast_timestamp_seconds_since_epoch_to_text("0","LOCAL")
    >>> assert cast_timestamp_seconds_since_epoch_to_text(1590674483 ,"UTC") == '2020-05-28 14:01:23'
    >>> assert cast_timestamp_seconds_since_epoch_to_text(1590674574765797619 ,"UTC", time_in_nanoseconds=True) == '2020-05-28 14:02:55'
    """
    seconds = float(seconds)

    if time_in_nanoseconds:
        seconds = seconds / 1E9

    seconds = int(round(seconds))

    if timezone == "UTC":
        epochseconds = time.gmtime(seconds)
    else:
        epochseconds = time.localtime(seconds)

    return time.strftime(date_time_format, epochseconds)


def cast_str_2_datetime(s_datetime: str, b_format_for_filename: bool = False) -> datetime.datetime:
    """
    konvertiere String im Format yyyy-mm-dd hh:mm:ss in datetime.datetime Format
    oder bei Verwendung von  b_format_for_filename String yyyy-mm-dd_hh-mm-ss in datetime.datetime Format

    >>> cast_str_2_datetime('2018-07-01 12:00:00')
    datetime.datetime(2018, 7, 1, 12, 0)
    >>> cast_str_2_datetime('2018-07-01_12-00-00', b_format_for_filename=True)
    datetime.datetime(2018, 7, 1, 12, 0)

    """

    if b_format_for_filename:
        s_datetime_date, s_datetime_time = s_datetime.split('_')
        s_datetime_time = s_datetime_time.replace('-', ':')
        s_datetime = s_datetime_date + ' ' + s_datetime_time

    s_date, s_time = s_datetime.split(' ')
    s_year, s_month, s_day = s_date.split('-')
    n_year = int(s_year)
    n_month = int(s_month)
    n_day = int(s_day)
    s_hour, s_minute, s_second = s_time.split(':')
    n_hour = int(s_hour)
    n_minute = int(s_minute)
    n_second = int(s_second)
    d_datetime = datetime.datetime(n_year, n_month, n_day, n_hour, n_minute, n_second)
    return d_datetime


# we might import this module and call main from another program and pass docopt args manually
def main(docopt_args: Dict[str, Union[bool, str]]) -> None:
    """
    >>> docopt_args = dict()
    >>> docopt_args['--version'] = True
    >>> docopt_args['--info'] = False
    >>> main(docopt_args)   # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    version: ...


    >>> docopt_args['--version'] = False
    >>> docopt_args['--info'] = True
    >>> main(docopt_args)   # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    information for ...

    >>> docopt_args['--version'] = False
    >>> docopt_args['--info'] = False
    >>> main(docopt_args)


    """
    if docopt_args['--version']:
        __init__conf__.print_version()
    elif docopt_args['--info']:
        __init__conf__.print_info()


# entry point via commandline
def main_commandline() -> None:
    """
    >>> main_commandline()  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    Traceback (most recent call last):
        ...
    docopt.DocoptExit: ...

    """
    docopt_args = docopt(__doc__)
    main(docopt_args)       # pragma: no cover


# entry point if main
if __name__ == '__main__':
    main_commandline()