150 lines
4.0 KiB
Python
150 lines
4.0 KiB
Python
# -*- coding: UTF-8 -*-
|
|
|
|
import re
|
|
import datetime
|
|
|
|
_nanosecond_size = 1
|
|
_microsecond_size = 1000 * _nanosecond_size
|
|
_millisecond_size = 1000 * _microsecond_size
|
|
_second_size = 1000 * _millisecond_size
|
|
_minute_size = 60 * _second_size
|
|
_hour_size = 60 * _minute_size
|
|
_day_size = 24 * _hour_size
|
|
_week_size = 7 * _day_size
|
|
_month_size = 30 * _day_size
|
|
_year_size = 365 * _day_size
|
|
|
|
units = {
|
|
"ns": _nanosecond_size,
|
|
"us": _microsecond_size,
|
|
"µs": _microsecond_size,
|
|
"μs": _microsecond_size,
|
|
"ms": _millisecond_size,
|
|
"s": _second_size,
|
|
"m": _minute_size,
|
|
"h": _hour_size,
|
|
"d": _day_size,
|
|
"w": _week_size,
|
|
"mm": _month_size,
|
|
"y": _year_size,
|
|
}
|
|
|
|
_duration_re = re.compile(r'([\d\.]+)([a-zµμ]+)')
|
|
|
|
|
|
class DurationError(ValueError):
|
|
"""duration error"""
|
|
|
|
|
|
def from_str(duration):
|
|
"""Parse a duration string to a datetime.timedelta"""
|
|
|
|
original = duration
|
|
|
|
if duration in ("0", "+0", "-0"):
|
|
return datetime.timedelta()
|
|
|
|
sign = 1
|
|
if duration and duration[0] in '+-':
|
|
if duration[0] == '-':
|
|
sign = -1
|
|
duration = duration[1:]
|
|
|
|
matches = list(_duration_re.finditer(duration))
|
|
if not matches:
|
|
raise DurationError("Invalid duration {}".format(original))
|
|
if matches[0].start() != 0 or matches[-1].end() != len(duration):
|
|
raise DurationError(
|
|
'Extra chars at start or end of duration {}'.format(original))
|
|
|
|
total = 0
|
|
for match in matches:
|
|
value, unit = match.groups()
|
|
if unit not in units:
|
|
raise DurationError(
|
|
"Unknown unit {} in duration {}".format(unit, original))
|
|
try:
|
|
total += float(value) * units[unit]
|
|
except Exception:
|
|
raise DurationError(
|
|
"Invalid value {} in duration {}".format(value, original))
|
|
|
|
microseconds = total / _microsecond_size
|
|
return datetime.timedelta(microseconds=sign * microseconds)
|
|
|
|
def to_str(delta, extended=False):
|
|
"""Format a datetime.timedelta to a duration string"""
|
|
|
|
total_seconds = delta.total_seconds()
|
|
sign = "-" if total_seconds < 0 else ""
|
|
nanoseconds = round(abs(total_seconds * _second_size), 0)
|
|
|
|
if abs(total_seconds) < 1:
|
|
result_str = _to_str_small(nanoseconds, extended)
|
|
else:
|
|
result_str = _to_str_large(nanoseconds, extended)
|
|
|
|
return "{}{}".format(sign, result_str)
|
|
|
|
|
|
def _to_str_small(nanoseconds, extended):
|
|
|
|
result_str = ""
|
|
|
|
if not nanoseconds:
|
|
return "0"
|
|
|
|
milliseconds = int(nanoseconds / _millisecond_size)
|
|
if milliseconds:
|
|
nanoseconds -= _millisecond_size * milliseconds
|
|
result_str += "{:g}ms".format(milliseconds)
|
|
|
|
microseconds = int(nanoseconds / _microsecond_size)
|
|
if microseconds:
|
|
nanoseconds -= _microsecond_size * microseconds
|
|
result_str += "{:g}us".format(microseconds)
|
|
|
|
if nanoseconds:
|
|
result_str += "{:g}ns".format(nanoseconds)
|
|
|
|
return result_str
|
|
|
|
|
|
def _to_str_large(nanoseconds, extended):
|
|
|
|
result_str = ""
|
|
|
|
if extended:
|
|
|
|
years = int(nanoseconds / _year_size)
|
|
if years:
|
|
nanoseconds -= _year_size * years
|
|
result_str += "{:g}y".format(years)
|
|
|
|
months = int(nanoseconds / _month_size)
|
|
if months:
|
|
nanoseconds -= _month_size * months
|
|
result_str += "{:g}mm".format(months)
|
|
|
|
days = int(nanoseconds / _day_size)
|
|
if days:
|
|
nanoseconds -= _day_size * days
|
|
result_str += "{:g}d".format(days)
|
|
|
|
hours = int(nanoseconds / _hour_size)
|
|
if hours:
|
|
nanoseconds -= _hour_size * hours
|
|
result_str += "{:g}h".format(hours)
|
|
|
|
minutes = int(nanoseconds / _minute_size)
|
|
if minutes:
|
|
nanoseconds -= _minute_size * minutes
|
|
result_str += "{:g}m".format(minutes)
|
|
|
|
seconds = float(nanoseconds) / float(_second_size)
|
|
if seconds:
|
|
nanoseconds -= _second_size * seconds
|
|
result_str += "{:g}s".format(seconds)
|
|
|
|
return result_str
|