from __future__ import annotations import re from datetime import datetime, timedelta, timezone TZ_RE = re.compile(r"^(?P[+-])(?P\d{2})(?::?(?P\d{2}))?$") def local_timezone() -> timezone: offset = datetime.now().astimezone().utcoffset() if offset is None: return timezone.utc return timezone(offset) def parse_timezone_offset(value: str) -> timezone: match = TZ_RE.match(value.strip()) if not match: raise ValueError("timezone must look like +09:00 or -05:30") sign = 1 if match.group("sign") == "+" else -1 hours = int(match.group("h")) minutes = int(match.group("m") or "0") if minutes not in {0, 15, 30, 45}: raise ValueError("timezone minutes must be 00, 15, 30, or 45") return timezone(sign * timedelta(hours=hours, minutes=minutes)) def timezone_to_string(tz: timezone) -> str: offset = tz.utcoffset(None) if offset is None: return "+00:00" total_minutes = int(offset.total_seconds() // 60) sign = "+" if total_minutes >= 0 else "-" total_minutes = abs(total_minutes) return f"{sign}{total_minutes // 60:02d}:{total_minutes % 60:02d}"