aboutsummaryrefslogtreecommitdiff
path: root/src/utils/datetime
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils/datetime')
-rw-r--r--src/utils/datetime/__init__.py2
-rw-r--r--src/utils/datetime/common.py42
-rw-r--r--src/utils/datetime/format.py77
-rw-r--r--src/utils/datetime/parse.py25
4 files changed, 146 insertions, 0 deletions
diff --git a/src/utils/datetime/__init__.py b/src/utils/datetime/__init__.py
new file mode 100644
index 00000000..dbe546db
--- /dev/null
+++ b/src/utils/datetime/__init__.py
@@ -0,0 +1,2 @@
+from .common import *
+from . import format, parse
diff --git a/src/utils/datetime/common.py b/src/utils/datetime/common.py
new file mode 100644
index 00000000..7235fbab
--- /dev/null
+++ b/src/utils/datetime/common.py
@@ -0,0 +1,42 @@
+import datetime as _datetime
+import enum
+
+ISO8601_FORMAT_DT = "%Y-%m-%dT%H:%M:%S"
+ISO8601_FORMAT_TZ = "%z"
+
+TIME_HUMAN = "%H:%M:%S"
+DATE_HUMAN = "%Y-%m-%d"
+
+class TimeSpec(enum.Enum):
+ NORMAL = 1
+ MILLISECOND = 2
+
+TIME_SECOND = 1
+TIME_MINUTE = TIME_SECOND*60
+TIME_HOUR = TIME_MINUTE*60
+TIME_DAY = TIME_HOUR*24
+TIME_WEEK = TIME_DAY*7
+
+SECONDS_MINUTES = 60
+SECONDS_HOURS = SECONDS_MINUTES*60
+SECONDS_DAYS = SECONDS_HOURS*24
+SECONDS_WEEKS = SECONDS_DAYS*7
+
+UNIT_MINIMUM = 6
+UNIT_SECOND = 5
+UNIT_MINUTE = 4
+UNIT_HOUR = 3
+UNIT_DAY = 2
+UNIT_WEEK = 1
+UNIT_MONTH = 1
+UNIT_YEAR = 1
+
+def utcnow() -> _datetime.datetime:
+ return _datetime.datetime.utcnow().replace(tzinfo=_datetime.timezone.utc)
+
+def timestamp(seconds: float) -> _datetime.datetime:
+ return _datetime.datetime.fromtimestamp(seconds).replace(
+ tzinfo=_datetime.timezone.utc)
+
+def seconds_since(dt: _datetime.datetime) -> float:
+ return (utcnow()-dt).total_seconds()
diff --git a/src/utils/datetime/format.py b/src/utils/datetime/format.py
new file mode 100644
index 00000000..feefde82
--- /dev/null
+++ b/src/utils/datetime/format.py
@@ -0,0 +1,77 @@
+import typing
+import datetime as _datetime
+import dateutil.relativedelta
+from .common import *
+
+def iso8601(dt: _datetime.datetime, timespec: TimeSpec=TimeSpec.NORMAL
+ ) -> str:
+ dt_format = dt.strftime(ISO8601_FORMAT_DT)
+ tz_format = dt.strftime(ISO8601_FORMAT_TZ)
+
+ ms_format = ""
+ if timespec == TimeSpec.MILLISECOND:
+ ms_format = ".%s" % str(int(dt.microsecond/1000)).zfill(3)
+
+ return "%s%s%s" % (dt_format, ms_format, tz_format)
+def iso8601_format_now(timespec: TimeSpec=TimeSpec.NORMAL) -> str:
+ return iso8601(utcnow(), timespec)
+
+def datetime_human(dt: _datetime.datetime, timespec: TimeSpec=TimeSpec.NORMAL):
+ date = _datetime.datetime.strftime(dt, DATE_HUMAN)
+ time = _datetime.datetime.strftime(dt, TIME_HUMAN)
+ if timespec == TimeSpec.MILLISECOND:
+ time += ".%s" % str(int(dt.microsecond/1000)).zfill(3)
+ return "%s %s" % (date, time)
+def date_human(dt: _datetime.datetime, timespec: TimeSpec=TimeSpec.NORMAL):
+ return _datetime.datetime.strftime(dt, DATE_HUMAN)
+
+def time_unit(seconds: int) -> typing.Tuple[int, str]:
+ since = None
+ unit = None
+ if seconds >= TIME_WEEK:
+ since = seconds/TIME_WEEK
+ unit = "week"
+ elif seconds >= TIME_DAY:
+ since = seconds/TIME_DAY
+ unit = "day"
+ elif seconds >= TIME_HOUR:
+ since = seconds/TIME_HOUR
+ unit = "hour"
+ elif seconds >= TIME_MINUTE:
+ since = seconds/TIME_MINUTE
+ unit = "minute"
+ else:
+ since = seconds
+ unit = "second"
+ since = int(since)
+ if since > 1:
+ unit = "%ss" % unit # pluralise the unit
+ return (since, unit)
+
+def to_pretty_time(total_seconds: int, minimum_unit: int=UNIT_SECOND,
+ max_units: int=UNIT_MINIMUM) -> str:
+ if total_seconds == 0:
+ return "0s"
+
+ now = utcnow()
+ later = now+_datetime.timedelta(seconds=total_seconds)
+ relative = dateutil.relativedelta.relativedelta(later, now)
+
+ out: typing.List[str] = []
+ if relative.years and minimum_unit >= UNIT_YEAR and len(out) < max_units:
+ out.append("%dy" % relative.years)
+ if relative.months and minimum_unit >= UNIT_MONTH and len(out) < max_units:
+ out.append("%dmo" % relative.months)
+ if relative.weeks and minimum_unit >= UNIT_WEEK and len(out) < max_units:
+ out.append("%dw" % relative.weeks)
+ if relative.days and minimum_unit >= UNIT_DAY and len(out) < max_units:
+ out.append("%dd" % relative.days)
+ if relative.hours and minimum_unit >= UNIT_HOUR and len(out) < max_units:
+ out.append("%dh" % relative.hours)
+ if relative.minutes and minimum_unit >= UNIT_MINUTE and len(out) < max_units:
+ out.append("%dmi" % relative.minutes)
+ if relative.seconds and minimum_unit >= UNIT_SECOND and len(out) < max_units:
+ out.append("%ds" % relative.seconds)
+
+ return " ".join(out)
+
diff --git a/src/utils/datetime/parse.py b/src/utils/datetime/parse.py
new file mode 100644
index 00000000..8e372f8c
--- /dev/null
+++ b/src/utils/datetime/parse.py
@@ -0,0 +1,25 @@
+import re, typing
+import datetime as _datetime
+import dateutil.parser
+from .common import *
+
+def iso8601(s: str) -> _datetime.datetime:
+ return dateutil.parser.parse(s)
+
+REGEX_PRETTYTIME = re.compile(
+ r"(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?", re.I)
+
+def from_pretty_time(pretty_time: str) -> typing.Optional[int]:
+ seconds = 0
+
+ match = re.match(REGEX_PRETTYTIME, pretty_time)
+ if match:
+ seconds += int(match.group(1) or 0)*SECONDS_WEEKS
+ seconds += int(match.group(2) or 0)*SECONDS_DAYS
+ seconds += int(match.group(3) or 0)*SECONDS_HOURS
+ seconds += int(match.group(4) or 0)*SECONDS_MINUTES
+ seconds += int(match.group(5) or 0)
+
+ if seconds > 0:
+ return seconds
+ return None