summaryrefslogtreecommitdiff
path: root/datetime_utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'datetime_utils.py')
-rw-r--r--datetime_utils.py175
1 files changed, 175 insertions, 0 deletions
diff --git a/datetime_utils.py b/datetime_utils.py
new file mode 100644
index 0000000..d70bf4a
--- /dev/null
+++ b/datetime_utils.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3
+
+"""Utilities related to dates and times and datetimes."""
+
+import datetime
+import logging
+import re
+from typing import NewType
+
+import pytz
+
+import constants
+
+logger = logging.getLogger(__name__)
+
+
+def now_pst() -> datetime.datetime:
+ return datetime.datetime.now(tz=pytz.timezone("US/Pacific"))
+
+
+def now() -> datetime.datetime:
+ return datetime.datetime.now()
+
+
+def datetime_to_string(
+ dt: datetime.datetime,
+ *,
+ date_time_separator=" ",
+ include_timezone=True,
+ include_dayname=False,
+ include_seconds=True,
+ include_fractional=False,
+ twelve_hour=True,
+) -> str:
+ """A nice way to convert a datetime into a string."""
+ fstring = ""
+ if include_dayname:
+ fstring += "%a/"
+ fstring = f"%Y/%b/%d{date_time_separator}"
+ if twelve_hour:
+ fstring += "%I:%M"
+ if include_seconds:
+ fstring += ":%S"
+ fstring += "%p"
+ else:
+ fstring += "%H:%M"
+ if include_seconds:
+ fstring += ":%S"
+ if include_fractional:
+ fstring += ".%f"
+ if include_timezone:
+ fstring += "%z"
+ return dt.strftime(fstring).strip()
+
+
+def timestamp() -> str:
+ """Return a timestamp for now in Pacific timezone."""
+ ts = datetime.datetime.now(tz=pytz.timezone("US/Pacific"))
+ return datetime_to_string(ts, include_timezone=True)
+
+
+def time_to_string(
+ dt: datetime.datetime,
+ *,
+ include_seconds=True,
+ include_fractional=False,
+ include_timezone=False,
+ twelve_hour=True,
+) -> str:
+ """A nice way to convert a datetime into a time (only) string."""
+ fstring = ""
+ if twelve_hour:
+ fstring += "%l:%M"
+ if include_seconds:
+ fstring += ":%S"
+ fstring += "%p"
+ else:
+ fstring += "%H:%M"
+ if include_seconds:
+ fstring += ":%S"
+ if include_fractional:
+ fstring += ".%f"
+ if include_timezone:
+ fstring += "%z"
+ return dt.strftime(fstring).strip()
+
+
+def seconds_to_timedelta(seconds: int) -> datetime.timedelta:
+ """Convert a delta in seconds into a timedelta."""
+ return datetime.timedelta(seconds=seconds)
+
+
+MinuteOfDay = NewType("MinuteOfDay", int)
+
+
+def minute_number(hour: int, minute: int) -> MinuteOfDay:
+ """Convert hour:minute into minute number from start of day."""
+ return MinuteOfDay(hour * 60 + minute)
+
+
+def datetime_to_minute_number(dt: datetime.datetime) -> MinuteOfDay:
+ """Convert a datetime into a minute number (of the day)"""
+ return minute_number(dt.hour, dt.minute)
+
+
+def minute_number_to_time_string(minute_num: MinuteOfDay) -> str:
+ """Convert minute number from start of day into hour:minute am/pm string."""
+ hour = minute_num // 60
+ minute = minute_num % 60
+ ampm = "a"
+ if hour > 12:
+ hour -= 12
+ ampm = "p"
+ if hour == 12:
+ ampm = "p"
+ if hour == 0:
+ hour = 12
+ return f"{hour:2}:{minute:02}{ampm}"
+
+
+def parse_duration(duration: str) -> int:
+ """Parse a duration in string form."""
+ seconds = 0
+ m = re.search(r'(\d+) *d[ays]*', duration)
+ if m is not None:
+ seconds += int(m.group(1)) * 60 * 60 * 24
+ m = re.search(r'(\d+) *h[ours]*', duration)
+ if m is not None:
+ seconds += int(m.group(1)) * 60 * 60
+ m = re.search(r'(\d+) *m[inutes]*', duration)
+ if m is not None:
+ seconds += int(m.group(1)) * 60
+ m = re.search(r'(\d+) *s[econds]*', duration)
+ if m is not None:
+ seconds += int(m.group(1))
+ return seconds
+
+
+def describe_duration(age: int) -> str:
+ """Describe a duration."""
+ days = divmod(age, constants.SECONDS_PER_DAY)
+ hours = divmod(days[1], constants.SECONDS_PER_HOUR)
+ minutes = divmod(hours[1], constants.SECONDS_PER_MINUTE)
+
+ descr = ""
+ if days[0] > 1:
+ descr = f"{int(days[0])} days, "
+ elif days[0] == 1:
+ descr = "1 day, "
+ if hours[0] > 1:
+ descr = descr + f"{int(hours[0])} hours, "
+ elif hours[0] == 1:
+ descr = descr + "1 hour, "
+ if len(descr) > 0:
+ descr = descr + "and "
+ if minutes[0] == 1:
+ descr = descr + "1 minute"
+ else:
+ descr = descr + f"{int(minutes[0])} minutes"
+ return descr
+
+
+def describe_duration_briefly(age: int) -> str:
+ """Describe a duration briefly."""
+ days = divmod(age, constants.SECONDS_PER_DAY)
+ hours = divmod(days[1], constants.SECONDS_PER_HOUR)
+ minutes = divmod(hours[1], constants.SECONDS_PER_MINUTE)
+
+ descr = ""
+ if days[0] > 0:
+ descr = f"{int(days[0])}d "
+ if hours[0] > 0:
+ descr = descr + f"{int(hours[0])}h "
+ descr = descr + f"{int(minutes[0])}m"
+ return descr