109 lines
3.1 KiB
Python
109 lines
3.1 KiB
Python
from datetime import datetime, timedelta
|
|
from typing import Union, Optional
|
|
|
|
def timeago(dt: Union[datetime, str], now: Optional[datetime] = None) -> str:
|
|
"""
|
|
Convert a datetime to a human-readable relative time string.
|
|
|
|
Args:
|
|
dt: The datetime to convert (can be datetime object or ISO format string)
|
|
now: Optional reference time (defaults to current time)
|
|
|
|
Returns:
|
|
str: Human-readable relative time string (e.g., "2 hours ago", "3 days ago")
|
|
"""
|
|
if isinstance(dt, str):
|
|
dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
|
|
|
|
if now is None:
|
|
now = datetime.utcnow()
|
|
|
|
diff = now - dt
|
|
|
|
# Less than a minute
|
|
if diff < timedelta(minutes=1):
|
|
return "just now"
|
|
|
|
# Less than an hour
|
|
if diff < timedelta(hours=1):
|
|
minutes = int(diff.total_seconds() / 60)
|
|
return f"{minutes} minute{'s' if minutes != 1 else ''} ago"
|
|
|
|
# Less than a day
|
|
if diff < timedelta(days=1):
|
|
hours = int(diff.total_seconds() / 3600)
|
|
return f"{hours} hour{'s' if hours != 1 else ''} ago"
|
|
|
|
# Less than a week
|
|
if diff < timedelta(days=7):
|
|
days = diff.days
|
|
return f"{days} day{'s' if days != 1 else ''} ago"
|
|
|
|
# Less than a month
|
|
if diff < timedelta(days=30):
|
|
weeks = int(diff.days / 7)
|
|
return f"{weeks} week{'s' if weeks != 1 else ''} ago"
|
|
|
|
# Less than a year
|
|
if diff < timedelta(days=365):
|
|
months = int(diff.days / 30)
|
|
return f"{months} month{'s' if months != 1 else ''} ago"
|
|
|
|
# More than a year
|
|
years = int(diff.days / 365)
|
|
return f"{years} year{'s' if years != 1 else ''} ago"
|
|
|
|
def format_datetime(dt: Union[datetime, str], format: str = "%Y-%m-%d %H:%M:%S") -> str:
|
|
"""
|
|
Format a datetime object or ISO string to a specified format.
|
|
|
|
Args:
|
|
dt: The datetime to format (can be datetime object or ISO format string)
|
|
format: The format string to use (defaults to "YYYY-MM-DD HH:MM:SS")
|
|
|
|
Returns:
|
|
str: Formatted datetime string
|
|
"""
|
|
if isinstance(dt, str):
|
|
dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
|
|
return dt.strftime(format)
|
|
|
|
def parse_datetime(dt_str: str) -> datetime:
|
|
"""
|
|
Parse a datetime string in various formats to a datetime object.
|
|
|
|
Args:
|
|
dt_str: The datetime string to parse
|
|
|
|
Returns:
|
|
datetime: Parsed datetime object
|
|
|
|
Raises:
|
|
ValueError: If the string cannot be parsed
|
|
"""
|
|
# Try ISO format first
|
|
try:
|
|
return datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
|
|
except ValueError:
|
|
pass
|
|
|
|
# Try common formats
|
|
formats = [
|
|
"%Y-%m-%d %H:%M:%S",
|
|
"%Y-%m-%d %H:%M",
|
|
"%Y-%m-%d",
|
|
"%d/%m/%Y %H:%M:%S",
|
|
"%d/%m/%Y %H:%M",
|
|
"%d/%m/%Y",
|
|
"%m/%d/%Y %H:%M:%S",
|
|
"%m/%d/%Y %H:%M",
|
|
"%m/%d/%Y"
|
|
]
|
|
|
|
for fmt in formats:
|
|
try:
|
|
return datetime.strptime(dt_str, fmt)
|
|
except ValueError:
|
|
continue
|
|
|
|
raise ValueError(f"Could not parse datetime string: {dt_str}") |