# -*- 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