tools (common/date_utils) handle timestamp and in + delta2human:

regex used to parse datetimes has been improved to handle a unix time (which can be used
with `+ <delta>` or `- <delta>`), and the `in <delta>` (e.g. `in 6 days`).

`DEFAULT_DATETIME` has been removed as dateutil use current date by default, which is the
expected behaviour.

Add `delta2human` method which convert the difference of 2 unix times to a human friendly
approximate text.

rel 372
This commit is contained in:
Goffi 2022-09-22 00:01:48 +02:00
commit 093b9d83bc

View file

@ -19,22 +19,30 @@
"""tools to help manipulating time and dates"""
from sat.core.constants import Const as C
from sat.core.i18n import _
from typing import Union
import calendar
import datetime
from dateutil import tz, parser
import re
import time
from babel import dates
from dateutil import parser, tz
from dateutil.parser import ParserError
from dateutil.relativedelta import relativedelta
from dateutil.utils import default_tzinfo
from dateutil.parser import ParserError
from babel import dates
import calendar
import time
import re
RELATIVE_RE = re.compile(r"(?P<date>.*?)(?P<direction>[-+]?) *(?P<quantity>\d+) *"
r"(?P<unit>(second|sec|s|minute|min|month|mo|m|hour|hr|h|day|d"
r"|week|w|year|yr|y))s?"
r"(?P<ago> +ago)?", re.I)
from sat.core import exceptions
from sat.core.constants import Const as C
from sat.core.i18n import _
RELATIVE_RE = re.compile(
r"\s*(?P<in>\bin\b)?"
r"(?P<date>[^+-].+[^\s+-])?\s*(?P<direction>[-+])?\s*"
r"\s*(?P<quantity>\d+)\s*"
r"(?P<unit>(second|sec|s|minute|min|month|mo|m|hour|hr|h|day|d|week|w|year|yr|y))s?"
r"(?P<ago>\s+ago)?\s*",
re.I
)
TIME_SYMBOL_MAP = {
"s": "second",
"sec": "second",
@ -51,8 +59,6 @@ TIME_SYMBOL_MAP = {
YEAR_FIRST_RE = re.compile(r"\d{4}[^\d]+")
TZ_UTC = tz.tzutc()
TZ_LOCAL = tz.gettz()
# used to replace values when something is missing
DEFAULT_DATETIME = datetime.datetime(2000, 0o1, 0o1)
def date_parse(value, default_tz=TZ_UTC):
@ -67,13 +73,17 @@ def date_parse(value, default_tz=TZ_UTC):
try:
dt = default_tzinfo(
parser.parse(value, default=DEFAULT_DATETIME, dayfirst=dayfirst),
parser.parse(value, dayfirst=dayfirst),
default_tz)
except ParserError as e:
if value == "now":
dt = datetime.datetime.now(tz.tzutc())
else:
raise e
try:
# the date may already be a timestamp
return int(value)
except ValueError:
raise e
return calendar.timegm(dt.utctimetuple())
def date_parse_ext(value, default_tz=TZ_UTC):
@ -92,20 +102,30 @@ def date_parse_ext(value, default_tz=TZ_UTC):
if m is None:
return date_parse(value, default_tz=default_tz)
if m.group("direction") and m.group("ago"):
if sum(1 for g in ("direction", "in", "ago") if m.group(g)) > 1:
raise ValueError(
_("You can't use a direction (+ or -) and \"ago\" at the same time"))
_('You can use only one of direction (+ or -), "in" and "ago"'))
if m.group("direction") == '-' or m.group("ago"):
direction = -1
else:
direction = 1
date = m.group("date").strip().lower()
date = m.group("date")
if date is not None:
date = date.strip()
if not date or date == "now":
dt = datetime.datetime.now(tz.tzutc())
else:
dt = default_tzinfo(parser.parse(date, dayfirst=True), default_tz)
try:
dt = default_tzinfo(parser.parse(date, dayfirst=True), default_tz)
except ParserError as e:
try:
timestamp = int(date)
except ValueError:
raise e
else:
dt = datetime.datetime.fromtimestamp(timestamp, tz.tzutc())
quantity = int(m.group("quantity"))
unit = m.group("unit").lower()
@ -191,3 +211,29 @@ def date_fmt(timestamp, fmt="short", date_only=False, auto_limit=7, auto_old_fmt
else:
return dates.format_datetime(timestamp, format=fmt, locale=locale_str,
tzinfo=tz_info)
def delta2human(start_ts: Union[float, int], end_ts: Union[float, int]) -> str:
"""Convert delta of 2 unix times to human readable text
@param start_ts: timestamp of starting time
@param end_ts: timestamp of ending time
"""
if end_ts < start_ts:
raise exceptions.InternalError(
"end timestamp must be bigger or equal to start timestamp !"
)
rd = relativedelta(
datetime.datetime.fromtimestamp(end_ts),
datetime.datetime.fromtimestamp(start_ts)
)
text_elems = []
for unit in ("years", "months", "days", "hours", "minutes"):
value = getattr(rd, unit)
if value == 1:
# we remove final "s" when there is only 1
text_elems.append(f"1 {unit[:-1]}")
elif value > 1:
text_elems.append(f"{value} {unit}")
return ", ".join(text_elems)