37 lines
1.1 KiB
Python
37 lines
1.1 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
|
|
TZ_RE = re.compile(r"^(?P<sign>[+-])(?P<h>\d{2})(?::?(?P<m>\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}"
|