/
/
/
1"""Helpers for date and time."""
2
3from __future__ import annotations
4
5import datetime
6import os
7from pathlib import Path
8from zoneinfo import available_timezones
9
10LOCAL_TIMEZONE = datetime.datetime.now(datetime.UTC).astimezone().tzinfo
11
12
13def utc() -> datetime.datetime:
14 """Get current UTC datetime."""
15 return datetime.datetime.now(datetime.UTC)
16
17
18def utc_timestamp() -> float:
19 """Return UTC timestamp in seconds as float."""
20 return utc().timestamp()
21
22
23def now() -> datetime.datetime:
24 """Get current datetime in local timezone."""
25 return datetime.datetime.now(LOCAL_TIMEZONE)
26
27
28def now_timestamp() -> float:
29 """Return current datetime as timestamp in local timezone."""
30 return now().timestamp()
31
32
33def future_timestamp(**kwargs: float) -> float:
34 """Return current timestamp + timedelta."""
35 return (now() + datetime.timedelta(**kwargs)).timestamp()
36
37
38def from_utc_timestamp(timestamp: float) -> datetime.datetime:
39 """Return datetime from UTC timestamp."""
40 return datetime.datetime.fromtimestamp(timestamp, datetime.UTC)
41
42
43def iso_from_utc_timestamp(timestamp: float) -> str:
44 """Return ISO 8601 datetime string from UTC timestamp."""
45 return from_utc_timestamp(timestamp).isoformat()
46
47
48def from_iso_string(iso_datetime: str) -> datetime.datetime:
49 """Return datetime from ISO datetime string."""
50 return datetime.datetime.fromisoformat(iso_datetime)
51
52
53def host_timezone_name() -> str:
54 """
55 Return the host's IANA timezone name (e.g. "Europe/Amsterdam"), falling back to "UTC".
56
57 Unlike ``LOCAL_TIMEZONE`` (a fixed-offset tzinfo, e.g. "CEST"), this resolves an actual
58 IANA zone name, checked in order: the ``TZ`` environment variable, then the
59 ``/etc/localtime`` symlink target. Never raises.
60 """
61 available = available_timezones()
62 tz_env = os.environ.get("TZ", "").strip()
63 if tz_env in available:
64 return tz_env
65 try:
66 link_target = str(Path("/etc/localtime").readlink())
67 except OSError:
68 link_target = ""
69 _, _, candidate = link_target.partition("zoneinfo/")
70 if candidate in available:
71 return candidate
72 return "UTC"
73
74
75def local_clock_time_to_utc(hour: int, minute: int = 0) -> tuple[int, int]:
76 """
77 Convert a server-local wall clock time to UTC hour/minute.
78
79 This uses the server's current local timezone offset.
80 """
81 local_timezone = LOCAL_TIMEZONE or datetime.UTC
82 local_datetime = datetime.datetime.now(local_timezone).replace(
83 hour=hour,
84 minute=minute,
85 second=0,
86 microsecond=0,
87 )
88 utc_datetime = local_datetime.astimezone(datetime.UTC)
89 return utc_datetime.hour, utc_datetime.minute
90