Skip to content

Instantly share code, notes, and snippets.

@numberoverzero
Created February 23, 2025 04:21
Show Gist options
  • Save numberoverzero/ac23edfa6c220b97db2bde07703c6563 to your computer and use it in GitHub Desktop.
Save numberoverzero/ac23edfa6c220b97db2bde07703c6563 to your computer and use it in GitHub Desktop.
low-effort humanizing datetimes relative to now
from datetime import datetime, timedelta, timezone
__all__ = ["now", "humanize"]
_year = timedelta(days=365)
_month = _year / 12
_day = timedelta(days=1)
_hour = timedelta(hours=1)
_minute = timedelta(minutes=1)
_second = timedelta(seconds=1)
_zero = timedelta()
_segments = (
("year", _year),
("month", _month),
("day", _day),
("hour", _hour),
("minute", _minute),
)
_precise_segments = (("second", _second),)
def now() -> datetime:
return datetime.now(tz=timezone.utc)
def humanize(d: datetime, truncate_pct: float | None = None, show_seconds: bool = False) -> str:
"""
negative intervals render with "ago"::
past = now() - timedelta(hours=2)
future = now() + timedelta(days=1)
humanize(future) -> 1 day
humanize(past) -> 2 hours ago
"""
remaining = d - now() # you could pass in a timedelta object instead of hardcoding against now()
if truncate_pct is None:
truncate_pct = 0.15
is_negative = remaining < _zero
remaining = abs(remaining)
if show_seconds:
segments = _segments + _precise_segments
else:
segments = _segments
parts = []
for name, unit in segments:
n, remaining = divmod(remaining, unit)
if n:
plural = "s" if n > 1 else ""
parts.append(f"{n} {name}{plural}")
# consider stopping, now that we've found _something_
if remaining / unit <= truncate_pct:
break
if not parts:
return "just now"
suffix = " ago" if is_negative else ""
return ", ".join(parts) + suffix
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment