#!/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