34 lines
1.2 KiB
Python
34 lines
1.2 KiB
Python
"""Publication dates from RSS/Atom/APIs, normalized to aware UTC datetimes."""
|
|
from datetime import datetime, timezone
|
|
from email.utils import parsedate_to_datetime
|
|
import re
|
|
|
|
|
|
def parse_published(value):
|
|
"""Accept Unix seconds/milliseconds, ISO 8601 or RFC 2822; invalid -> None.
|
|
|
|
Dates without a timezone are interpreted as UTC, not the host's local zone.
|
|
"""
|
|
if value is None or not str(value).strip():
|
|
return None
|
|
text = str(value).strip()
|
|
try:
|
|
if re.fullmatch(r"\d{4}", text):
|
|
return None # A bare year is not a Unix timestamp.
|
|
if re.fullmatch(r"\d{8}", text):
|
|
return datetime.strptime(text, "%Y%m%d").replace(tzinfo=timezone.utc)
|
|
if re.fullmatch(r"-?\d+(?:\.\d+)?", text):
|
|
stamp = float(text)
|
|
if abs(stamp) >= 10**12:
|
|
stamp /= 1000
|
|
return datetime.fromtimestamp(stamp, timezone.utc)
|
|
try:
|
|
dt = datetime.fromisoformat(text.replace("Z", "+00:00"))
|
|
except ValueError:
|
|
dt = parsedate_to_datetime(text)
|
|
if dt.tzinfo is None:
|
|
dt = dt.replace(tzinfo=timezone.utc)
|
|
return dt.astimezone(timezone.utc)
|
|
except (ValueError, TypeError, OverflowError, OSError):
|
|
return None
|