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:
parent
adea3770de
commit
093b9d83bc
1 changed files with 66 additions and 20 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue