diff options
Diffstat (limited to 'datetime_utils.py')
| -rw-r--r-- | datetime_utils.py | 175 |
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 |
