Spaces:
Sleeping
Sleeping
| """ | |
| Timezone utilities for CSH2 Web Dashboard. | |
| The PLC logs in US/Eastern. The database has two time columns: | |
| - dateandtime: Eastern local time (no timezone info) | |
| - utc_full_timestamp: real UTC (timestamptz) | |
| The cycle_periods table stores Eastern times incorrectly labeled as UTC. | |
| This module provides consistent conversion and display helpers. | |
| """ | |
| from datetime import datetime, timedelta, timezone | |
| from zoneinfo import ZoneInfo | |
| import pandas as pd | |
| from core.config import DISPLAY_TZ, DISPLAY_TZ_LABEL, CYCLE_PERIODS_UTC_OFFSET | |
| def to_eastern(dt) -> datetime: | |
| """Convert a UTC datetime to Eastern time for display. | |
| Accepts timezone-aware (UTC) or naive (assumed UTC) datetimes. | |
| Returns a timezone-aware datetime in US/Eastern. | |
| """ | |
| if dt is None: | |
| return None | |
| if isinstance(dt, pd.Timestamp): | |
| dt = dt.to_pydatetime() | |
| if dt.tzinfo is None: | |
| dt = dt.replace(tzinfo=timezone.utc) | |
| return dt.astimezone(DISPLAY_TZ) | |
| def to_utc(dt) -> datetime: | |
| """Convert an Eastern datetime to UTC for database queries. | |
| Accepts timezone-aware (Eastern) or naive (assumed Eastern) datetimes. | |
| Returns a timezone-aware datetime in UTC. | |
| """ | |
| if dt is None: | |
| return None | |
| if isinstance(dt, pd.Timestamp): | |
| dt = dt.to_pydatetime() | |
| if dt.tzinfo is None: | |
| dt = dt.replace(tzinfo=DISPLAY_TZ) | |
| return dt.astimezone(timezone.utc) | |
| def cycle_periods_to_utc(dt) -> datetime: | |
| """Convert a cycle_periods timestamp (Eastern mislabeled as UTC) to real UTC. | |
| The cycle_periods table stores Eastern local times with a +00:00 suffix. | |
| Adding 4 hours converts to real UTC for sensor data queries. | |
| """ | |
| if dt is None: | |
| return None | |
| if isinstance(dt, pd.Timestamp): | |
| dt = dt.to_pydatetime() | |
| return dt + CYCLE_PERIODS_UTC_OFFSET | |
| def cycle_periods_to_eastern(dt) -> datetime: | |
| """Convert a cycle_periods timestamp to a properly labeled Eastern datetime. | |
| The stored values ARE Eastern — just strip the wrong UTC label and apply | |
| the correct US/Eastern timezone. | |
| """ | |
| if dt is None: | |
| return None | |
| if isinstance(dt, pd.Timestamp): | |
| dt = dt.to_pydatetime() | |
| # Strip the incorrect UTC tzinfo, replace with Eastern | |
| naive = dt.replace(tzinfo=None) | |
| return naive.replace(tzinfo=DISPLAY_TZ) | |
| def format_et(dt, fmt: str = '%b %d %H:%M') -> str: | |
| """Format a datetime for display in Eastern time. | |
| If the datetime is UTC, converts to Eastern first. | |
| Appends the timezone label. | |
| """ | |
| if dt is None: | |
| return "—" | |
| eastern = to_eastern(dt) if (dt.tzinfo and dt.tzinfo != DISPLAY_TZ) else dt | |
| return f"{eastern.strftime(fmt)} {DISPLAY_TZ_LABEL}" | |
| def convert_df_timestamps_to_eastern(df: pd.DataFrame, col: str = 'timestamp') -> pd.DataFrame: | |
| """Convert a DataFrame's timestamp column from UTC to Eastern for display. | |
| Returns a new DataFrame (does not modify in place). | |
| """ | |
| if col not in df.columns: | |
| return df | |
| df = df.copy() | |
| if df[col].dt.tz is not None: | |
| df[col] = df[col].dt.tz_convert(DISPLAY_TZ) | |
| else: | |
| # Naive timestamps assumed UTC | |
| df[col] = df[col].dt.tz_localize('UTC').dt.tz_convert(DISPLAY_TZ) | |
| return df | |