- Moved Jinja2 template setup to a shared configuration file (templates_config.py) for consistent usage across routers. - Introduced timezone utilities in a new module (timezone.py) to handle UTC to local time conversions and formatting. - Updated all relevant routers to use the new shared template configuration and timezone filters. - Enhanced templates to utilize local time formatting for various datetime fields, improving user experience with timezone awareness.
174 lines
4.1 KiB
Python
174 lines
4.1 KiB
Python
"""
|
|
Timezone utilities for Terra-View.
|
|
|
|
Provides consistent timezone handling throughout the application.
|
|
All database times are stored in UTC; this module converts for display.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from zoneinfo import ZoneInfo
|
|
from typing import Optional
|
|
|
|
from backend.database import SessionLocal
|
|
from backend.models import UserPreferences
|
|
|
|
|
|
# Default timezone if none set
|
|
DEFAULT_TIMEZONE = "America/New_York"
|
|
|
|
|
|
def get_user_timezone() -> str:
|
|
"""
|
|
Get the user's configured timezone from preferences.
|
|
|
|
Returns:
|
|
Timezone string (e.g., "America/New_York")
|
|
"""
|
|
db = SessionLocal()
|
|
try:
|
|
prefs = db.query(UserPreferences).filter_by(id=1).first()
|
|
if prefs and prefs.timezone:
|
|
return prefs.timezone
|
|
return DEFAULT_TIMEZONE
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def get_timezone_info(tz_name: str = None) -> ZoneInfo:
|
|
"""
|
|
Get ZoneInfo object for the specified or user's timezone.
|
|
|
|
Args:
|
|
tz_name: Timezone name, or None to use user preference
|
|
|
|
Returns:
|
|
ZoneInfo object
|
|
"""
|
|
if tz_name is None:
|
|
tz_name = get_user_timezone()
|
|
try:
|
|
return ZoneInfo(tz_name)
|
|
except Exception:
|
|
return ZoneInfo(DEFAULT_TIMEZONE)
|
|
|
|
|
|
def utc_to_local(dt: datetime, tz_name: str = None) -> datetime:
|
|
"""
|
|
Convert a UTC datetime to local timezone.
|
|
|
|
Args:
|
|
dt: Datetime in UTC (naive or aware)
|
|
tz_name: Target timezone, or None to use user preference
|
|
|
|
Returns:
|
|
Datetime in local timezone
|
|
"""
|
|
if dt is None:
|
|
return None
|
|
|
|
tz = get_timezone_info(tz_name)
|
|
|
|
# Assume naive datetime is UTC
|
|
if dt.tzinfo is None:
|
|
dt = dt.replace(tzinfo=ZoneInfo("UTC"))
|
|
|
|
return dt.astimezone(tz)
|
|
|
|
|
|
def local_to_utc(dt: datetime, tz_name: str = None) -> datetime:
|
|
"""
|
|
Convert a local datetime to UTC.
|
|
|
|
Args:
|
|
dt: Datetime in local timezone (naive or aware)
|
|
tz_name: Source timezone, or None to use user preference
|
|
|
|
Returns:
|
|
Datetime in UTC (naive, for database storage)
|
|
"""
|
|
if dt is None:
|
|
return None
|
|
|
|
tz = get_timezone_info(tz_name)
|
|
|
|
# Assume naive datetime is in local timezone
|
|
if dt.tzinfo is None:
|
|
dt = dt.replace(tzinfo=tz)
|
|
|
|
# Convert to UTC and strip tzinfo for database storage
|
|
return dt.astimezone(ZoneInfo("UTC")).replace(tzinfo=None)
|
|
|
|
|
|
def format_local_datetime(dt: datetime, fmt: str = "%Y-%m-%d %H:%M", tz_name: str = None) -> str:
|
|
"""
|
|
Format a UTC datetime as local time string.
|
|
|
|
Args:
|
|
dt: Datetime in UTC
|
|
fmt: strftime format string
|
|
tz_name: Target timezone, or None to use user preference
|
|
|
|
Returns:
|
|
Formatted datetime string in local time
|
|
"""
|
|
if dt is None:
|
|
return "N/A"
|
|
|
|
local_dt = utc_to_local(dt, tz_name)
|
|
return local_dt.strftime(fmt)
|
|
|
|
|
|
def format_local_time(dt: datetime, tz_name: str = None) -> str:
|
|
"""
|
|
Format a UTC datetime as local time (HH:MM format).
|
|
|
|
Args:
|
|
dt: Datetime in UTC
|
|
tz_name: Target timezone
|
|
|
|
Returns:
|
|
Time string in HH:MM format
|
|
"""
|
|
return format_local_datetime(dt, "%H:%M", tz_name)
|
|
|
|
|
|
def format_local_date(dt: datetime, tz_name: str = None) -> str:
|
|
"""
|
|
Format a UTC datetime as local date (YYYY-MM-DD format).
|
|
|
|
Args:
|
|
dt: Datetime in UTC
|
|
tz_name: Target timezone
|
|
|
|
Returns:
|
|
Date string
|
|
"""
|
|
return format_local_datetime(dt, "%Y-%m-%d", tz_name)
|
|
|
|
|
|
def get_timezone_abbreviation(tz_name: str = None) -> str:
|
|
"""
|
|
Get the abbreviation for a timezone (e.g., EST, EDT, PST).
|
|
|
|
Args:
|
|
tz_name: Timezone name, or None to use user preference
|
|
|
|
Returns:
|
|
Timezone abbreviation
|
|
"""
|
|
tz = get_timezone_info(tz_name)
|
|
now = datetime.now(tz)
|
|
return now.strftime("%Z")
|
|
|
|
|
|
# Common US timezone choices for settings dropdown
|
|
TIMEZONE_CHOICES = [
|
|
("America/New_York", "Eastern Time (ET)"),
|
|
("America/Chicago", "Central Time (CT)"),
|
|
("America/Denver", "Mountain Time (MT)"),
|
|
("America/Los_Angeles", "Pacific Time (PT)"),
|
|
("America/Anchorage", "Alaska Time (AKT)"),
|
|
("Pacific/Honolulu", "Hawaii Time (HT)"),
|
|
("UTC", "UTC"),
|
|
]
|