Spaces:
Sleeping
Sleeping
| """Time humanizing functions.""" | |
| import datetime as dt | |
| import math | |
| from enum import Enum | |
| from functools import total_ordering | |
| class Unit(Enum): | |
| MICROSECONDS = 0 | |
| MILLISECONDS = 1 | |
| SECONDS = 2 | |
| MINUTES = 3 | |
| HOURS = 4 | |
| DAYS = 5 | |
| MONTHS = 6 | |
| YEARS = 7 | |
| def __lt__(self, other): | |
| if self.__class__ is other.__class__: | |
| return self.value < other.value | |
| return NotImplemented | |
| def _now(): | |
| return dt.datetime.now() | |
| def _abs_timedelta(delta): | |
| if delta.days < 0: | |
| now = _now() | |
| return now - (now + delta) | |
| return delta | |
| def _date_and_delta(value, *, now=None): | |
| if not now: | |
| now = _now() | |
| if isinstance(value, dt.datetime): | |
| date = value | |
| delta = now - value | |
| elif isinstance(value, dt.timedelta): | |
| date = now - value | |
| delta = value | |
| else: | |
| try: | |
| value = int(value) | |
| delta = dt.timedelta(seconds=value) | |
| date = now - delta | |
| except (ValueError, TypeError): | |
| return None, value | |
| return date, _abs_timedelta(delta) | |
| def naturaldelta(value, months=True, minimum_unit="seconds") -> str: | |
| """Return a natural representation of a timedelta or number of seconds. | |
| Does not include tense (use naturaltime for past/future). | |
| Examples: | |
| >>> import datetime as dt | |
| >>> naturaldelta(dt.timedelta(seconds=90)) | |
| 'a minute' | |
| >>> naturaldelta(dt.timedelta(hours=2)) | |
| '2 hours' | |
| >>> naturaldelta(dt.timedelta(days=400)) | |
| 'a year' | |
| """ | |
| tmp = Unit[minimum_unit.upper()] | |
| if tmp not in (Unit.SECONDS, Unit.MILLISECONDS, Unit.MICROSECONDS): | |
| raise ValueError(f"Minimum unit '{minimum_unit}' not supported") | |
| minimum_unit = tmp | |
| if isinstance(value, dt.timedelta): | |
| delta = value | |
| else: | |
| try: | |
| value = int(value) | |
| delta = dt.timedelta(seconds=value) | |
| except (ValueError, TypeError): | |
| return value | |
| seconds = abs(delta.seconds) | |
| days = abs(delta.days) | |
| years = days // 365 | |
| days = days % 365 | |
| months_count = int(days // 30.5) | |
| if not years and days < 1: | |
| if seconds == 0: | |
| return "a moment" | |
| elif seconds == 1: | |
| return "a second" | |
| elif seconds < 60: | |
| return f"{seconds} seconds" if seconds > 1 else "a second" | |
| elif 60 <= seconds < 120: | |
| return "a minute" | |
| elif 120 <= seconds < 3600: | |
| minutes = seconds // 60 | |
| return f"{minutes} minutes" | |
| elif 3600 <= seconds < 7200: | |
| return "an hour" | |
| else: | |
| hours = seconds // 3600 | |
| return f"{hours} hours" | |
| elif years == 0: | |
| if days == 1: | |
| return "a day" | |
| if not months or not months_count: | |
| return f"{days} days" | |
| elif months_count == 1: | |
| return "a month" | |
| return f"{months_count} months" | |
| elif years == 1: | |
| if not months_count and not days: | |
| return "a year" | |
| elif not months_count: | |
| return f"1 year, {days} days" if days > 1 else "1 year, a day" | |
| elif months_count == 1: | |
| return "1 year, 1 month" | |
| return f"1 year, {months_count} months" | |
| return f"{years} years" | |
| def naturaltime(value, future=False, months=True, minimum_unit="seconds", when=None) -> str: | |
| """Return a natural representation of a time relative to now. | |
| Examples: | |
| >>> import datetime as dt | |
| >>> naturaltime(dt.timedelta(seconds=30)) | |
| '30 seconds ago' | |
| >>> naturaltime(dt.timedelta(hours=1), future=True) | |
| 'an hour from now' | |
| """ | |
| now = when or _now() | |
| date, delta = _date_and_delta(value, now=now) | |
| if date is None: | |
| return value | |
| if isinstance(value, (dt.datetime, dt.timedelta)): | |
| future = date > now | |
| ago = "%s from now" if future else "%s ago" | |
| delta_str = naturaldelta(delta, months, minimum_unit) | |
| if delta_str == "a moment": | |
| return "now" | |
| return ago % delta_str | |
| def naturalday(value, format="%b %d") -> str: | |
| """Return 'today', 'tomorrow', 'yesterday', or a formatted date string. | |
| Examples: | |
| >>> import datetime as dt | |
| >>> naturalday(dt.date.today()) | |
| 'today' | |
| """ | |
| try: | |
| value = dt.date(value.year, value.month, value.day) | |
| except (AttributeError, OverflowError, ValueError): | |
| return value | |
| delta = value - dt.date.today() | |
| if delta.days == 0: | |
| return "today" | |
| elif delta.days == 1: | |
| return "tomorrow" | |
| elif delta.days == -1: | |
| return "yesterday" | |
| return value.strftime(format) | |
| def naturaldate(value) -> str: | |
| """Like naturalday, but appends year for dates more than ~5 months away.""" | |
| try: | |
| value = dt.date(value.year, value.month, value.day) | |
| except (AttributeError, OverflowError, ValueError): | |
| return value | |
| delta = _abs_timedelta(value - dt.date.today()) | |
| if delta.days >= 5 * 365 / 12: | |
| return naturalday(value, "%b %d %Y") | |
| return naturalday(value) | |
| def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") -> str: | |
| """Return a precise, human-readable representation of a timedelta. | |
| Examples: | |
| >>> import datetime as dt | |
| >>> precisedelta(dt.timedelta(seconds=3633, days=2)) | |
| '2 days and 1 hour and 33 seconds' | |
| """ | |
| date, delta = _date_and_delta(value) | |
| if date is None: | |
| return value | |
| suppress_units = {Unit[s.upper()] for s in suppress} | |
| min_unit = Unit[minimum_unit.upper()] | |
| days = delta.days | |
| secs = delta.seconds | |
| years, days = divmod(days, 365) | |
| months_count = int(days // 30.5) | |
| days = days % 30 | |
| hours, secs = divmod(secs, 3600) | |
| minutes, secs = divmod(secs, 60) | |
| parts = [] | |
| for count, singular, plural in [ | |
| (years, "year", "years"), | |
| (months_count, "month", "months"), | |
| (days, "day", "days"), | |
| (hours, "hour", "hours"), | |
| (minutes, "minute", "minutes"), | |
| (secs, "second", "seconds"), | |
| ]: | |
| if count > 0: | |
| label = singular if count == 1 else plural | |
| parts.append(f"{count} {label}") | |
| if not parts: | |
| return "0 seconds" | |
| if len(parts) == 1: | |
| return parts[0] | |
| return " and ".join(parts) | |