Source code for arkimet.scan.timedef

from typing import Tuple, Dict, Any

UNIT_MINUTE = 0
UNIT_HOUR = 1
UNIT_DAY = 2
UNIT_MONTH = 3
UNIT_YEAR = 4
UNIT_DECADE = 5
UNIT_NORMAL = 6
UNIT_CENTURY = 7
UNIT_3HOURS = 10
UNIT_6HOURS = 11
UNIT_12HOURS = 12
UNIT_SECOND = 13
UNIT_MISSING = 255

UNIT_SUFFIXES = {
    UNIT_MINUTE: "m",
    UNIT_HOUR: "h",
    UNIT_DAY: "d",
    UNIT_MONTH: "mo",
    UNIT_YEAR: "y",
    UNIT_DECADE: "de",
    UNIT_NORMAL: "no",
    UNIT_CENTURY: "ce",
    UNIT_3HOURS: "h3",
    UNIT_6HOURS: "h6",
    UNIT_12HOURS: "h12",
    UNIT_SECOND: "s",
}


def timeunit_suffix(unit: int) -> str:
    suffix = UNIT_SUFFIXES.get(unit)
    if suffix is not None:
        return suffix
    if unit == UNIT_MISSING:
        raise ValueError("cannot find suffix for MISSING (255) time unit")
    else:
        raise ValueError("cannot find find time unit suffix: time unit is unknown ({})".format(unit))


def unit_can_be_seconds(unit: int) -> bool:
    """
    Return true if the unit can be represented in seconds, false if the unit can
    be represented in months.
    """
    if unit in (UNIT_SECOND, UNIT_MINUTE, UNIT_HOUR, UNIT_3HOURS, UNIT_6HOURS, UNIT_12HOURS, UNIT_DAY):
        return True
    if unit in (UNIT_MONTH, UNIT_YEAR, UNIT_DECADE, UNIT_NORMAL, UNIT_CENTURY, UNIT_MISSING):
        return False
    raise ValueError("invalid timedef unit {}".format(unit))


def compare_units(unit1: int, unit2: int) -> int:
    """
    Return -1 if unit1 represent a smaller time lapse than unit2, 0 if they are
    the same, 1 if unit1 represents a bigger time lapse than unit1.

    Raises an exception if the two units cannot be compared (like seconds and
    months)
    """
    if unit1 == unit2:
        return 0

    if unit_can_be_seconds(unit1):
        if not unit_can_be_seconds(unit2):
            raise ValueError("cannot compare two different kinds of time units ({} and {})".format(
                timeunit_suffix(unit1), timeunit_suffix(unit2)))

        if unit1 == UNIT_SECOND:
            return -1
        elif unit1 == UNIT_MINUTE:
            if unit2 == UNIT_SECOND:
                return 1
            else:
                return -1
        elif unit1 == UNIT_HOUR:
            if unit2 in (UNIT_MINUTE, UNIT_SECOND):
                return 1
            else:
                return -1
        elif unit1 == UNIT_3HOURS:
            if unit2 in (UNIT_HOUR, UNIT_MINUTE, UNIT_SECOND):
                return 1
            else:
                return -1
        elif unit1 == UNIT_6HOURS:
            if unit2 in (UNIT_3HOURS, UNIT_HOUR, UNIT_MINUTE, UNIT_SECOND):
                return 1
            else:
                return -1
        elif unit1 == UNIT_12HOURS:
            if unit2 in (UNIT_6HOURS, UNIT_3HOURS, UNIT_HOUR, UNIT_MINUTE, UNIT_SECOND):
                return 1
            else:
                return -1
        elif unit1 == UNIT_DAY:
            return 1
        else:
            raise ValueError("unable to compare unknown unit type {}".format(timeunit_suffix(unit1)))
    else:
        if unit_can_be_seconds(unit2):
            raise ValueError("cannot compare two different kinds of time units ({} and {})".format(
                timeunit_suffix(unit1), timeunit_suffix(unit2)))
        if unit1 == UNIT_MONTH:
            return -1
        elif unit1 == UNIT_YEAR:
            if unit2 == UNIT_MONTH:
                return 1
            else:
                return -1
        elif unit1 == UNIT_DECADE:
            if unit2 in (UNIT_MONTH, UNIT_YEAR):
                return 1
            else:
                return -1
        elif unit1 == UNIT_NORMAL:
            if unit2 in (UNIT_MONTH, UNIT_YEAR, UNIT_DECADE):
                return 1
            else:
                return -1
        elif unit1 == UNIT_CENTURY:
            if unit2 in (UNIT_MONTH, UNIT_YEAR, UNIT_DECADE, UNIT_NORMAL):
                return 1
            else:
                return -1
        else:
            raise ValueError("unable to compare unknown unit type {}".format(timeunit_suffix(unit1)))


def restrict_unit(val: int, unit: int) -> Tuple[int, int]:
    """
    Convert the value/unit pair to an equivalent pair with a unit with a smaller
    granularity (e.g. years to months, hours to minutes), with no loss of
    precision.

    Return (val, unit) if the value was unchanged because no further change is
    possible, otherwise the new (val, unit) pair
    """
    if unit == UNIT_MINUTE:
        return val * 60, UNIT_SECOND
    elif unit == UNIT_HOUR:
        return val * 60, UNIT_MINUTE
    elif unit == UNIT_DAY:
        return val * 24, UNIT_HOUR
    elif unit == UNIT_MONTH:
        return val, unit
    elif unit == UNIT_YEAR:
        return val * 12, UNIT_MONTH
    elif unit == UNIT_DECADE:
        return val * 10, UNIT_YEAR
    elif unit == UNIT_NORMAL:
        return val * 3, UNIT_DECADE
    elif unit == UNIT_CENTURY:
        return val * 10, UNIT_DECADE
    elif unit == UNIT_3HOURS:
        return val * 3, UNIT_HOUR
    elif unit == UNIT_6HOURS:
        return val * 2, UNIT_3HOURS
    elif unit == UNIT_12HOURS:
        return val * 2, UNIT_6HOURS
    elif unit == UNIT_SECOND:
        return val, unit
    elif unit == UNIT_MISSING:
        return val, unit
    else:
        return val, unit


def enlarge_unit(val: int, unit: int) -> bool:
    """
    Convert the value/unit pair to an equivalent pair with a unit with a larger
    granularity (e.g. months to years, minutes to hours), with no loss of
    precision.

    Return (val, unit) if the value was unchanged because no further change is
    possible, otherwise the new (val, unit) tuple
    """
    if unit == UNIT_MINUTE:
        if (val % 60) == 0:
            return val // 60, UNIT_HOUR
    elif unit == UNIT_HOUR:
        if (val % 24) == 0:
            return val // 24, UNIT_DAY
    elif unit == UNIT_DAY:
        return val, unit
    elif unit == UNIT_MONTH:
        if (val % 12) == 0:
            return val // 12, UNIT_YEAR
    elif unit == UNIT_YEAR:
        if (val % 10) == 0:
            return val // 10, UNIT_DECADE
    elif unit == UNIT_DECADE:
        if (val % 100) == 0:
            return val // 100, UNIT_CENTURY
    elif unit == UNIT_NORMAL:
        return val, unit
    elif unit == UNIT_CENTURY:
        return val, unit
    elif unit == UNIT_3HOURS:
        if (val % 2) == 0:
            return val // 2, UNIT_6HOURS
    elif unit == UNIT_6HOURS:
        if (val % 2) == 0:
            return val // 2, UNIT_12HOURS
    elif unit == UNIT_12HOURS:
        if (val % 2) == 0:
            return val // 2, UNIT_DAY
    elif unit == UNIT_SECOND:
        if (val % 60) == 0:
            return val // 60, UNIT_MINUTE
    elif unit == UNIT_MISSING:
        return val, unit
    else:
        return val, unit

    return val, unit


[docs]def make_same_units(tr: Dict[str, Any]): """ Make sure val1:unit1 and val2:unit2 are expressed using the same units. """ unit1, unit2 = tr["step_unit"], tr["stat_unit"] val1, val2 = tr["step_len"], tr["stat_len"] if unit1 == unit2: return # Try enlarging the smallest while True: cmp = compare_units(unit1, unit2) if cmp < 0: tval1, tunit1 = enlarge_unit(val1, unit1) if tunit1 == unit1: break else: val1, unit1 = tval1, tunit1 elif cmp == 0: tr["step_unit"], tr["stat_unit"] = unit1, unit2 tr["step_len"], tr["stat_len"] = val1, val2 return else: tval2, tunit2 = enlarge_unit(val2, unit2) if tunit2 == unit2: break else: val2, unit2 = tval2, tunit2 # Still not matching, try restricting the largest while True: cmp = compare_units(unit1, unit2) if cmp < 0: tval2, tunit2 = restrict_unit(val2, unit2) if tunit2 == unit2: break else: val2, unit2 = tval2, tunit2 elif cmp == 0: tr["step_unit"], tr["stat_unit"] = unit1, unit2 tr["step_len"], tr["stat_len"] = val1, val2 return else: tval1, tunit1 = restrict_unit(val1, unit1) if tunit1 == unit1: break else: val1, unit1 = tval1, tunit1 raise ValueError("Cannot convert {} and {} to the same unit".format( timeunit_suffix(unit1), timeunit_suffix(unit2)))
__all__ = [ "UNIT_MINUTE", "UNIT_HOUR", "UNIT_DAY", "UNIT_MONTH", "UNIT_YEAR", "UNIT_DECADE", "UNIT_NORMAL", "UNIT_CENTURY", "UNIT_3HOURS", "UNIT_6HOURS", "UNIT_12HOURS", "UNIT_SECOND", "UNIT_MISSING", "make_same_units", ]