Massive update - API and other modules added
This commit is contained in:
225
usda_vision_system/core/timezone_utils.py
Normal file
225
usda_vision_system/core/timezone_utils.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
Timezone utilities for the USDA Vision Camera System.
|
||||
|
||||
This module provides timezone-aware datetime handling for Atlanta, Georgia.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import pytz
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class TimezoneManager:
|
||||
"""Manages timezone-aware datetime operations"""
|
||||
|
||||
def __init__(self, timezone_name: str = "America/New_York"):
|
||||
self.timezone_name = timezone_name
|
||||
self.timezone = pytz.timezone(timezone_name)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
# Log timezone information
|
||||
self.logger.info(f"Timezone manager initialized for {timezone_name}")
|
||||
self._log_timezone_info()
|
||||
|
||||
def _log_timezone_info(self) -> None:
|
||||
"""Log current timezone information"""
|
||||
now = self.now()
|
||||
self.logger.info(f"Current local time: {now}")
|
||||
self.logger.info(f"Current UTC time: {self.to_utc(now)}")
|
||||
self.logger.info(f"Timezone: {now.tzname()} (UTC{now.strftime('%z')})")
|
||||
|
||||
def now(self) -> datetime.datetime:
|
||||
"""Get current time in the configured timezone"""
|
||||
return datetime.datetime.now(self.timezone)
|
||||
|
||||
def utc_now(self) -> datetime.datetime:
|
||||
"""Get current UTC time"""
|
||||
return datetime.datetime.now(pytz.UTC)
|
||||
|
||||
def to_local(self, dt: datetime.datetime) -> datetime.datetime:
|
||||
"""Convert datetime to local timezone"""
|
||||
if dt.tzinfo is None:
|
||||
# Assume UTC if no timezone info
|
||||
dt = pytz.UTC.localize(dt)
|
||||
return dt.astimezone(self.timezone)
|
||||
|
||||
def to_utc(self, dt: datetime.datetime) -> datetime.datetime:
|
||||
"""Convert datetime to UTC"""
|
||||
if dt.tzinfo is None:
|
||||
# Assume local timezone if no timezone info
|
||||
dt = self.timezone.localize(dt)
|
||||
return dt.astimezone(pytz.UTC)
|
||||
|
||||
def localize(self, dt: datetime.datetime) -> datetime.datetime:
|
||||
"""Add timezone info to naive datetime (assumes local timezone)"""
|
||||
if dt.tzinfo is not None:
|
||||
return dt
|
||||
return self.timezone.localize(dt)
|
||||
|
||||
def format_timestamp(self, dt: Optional[datetime.datetime] = None,
|
||||
include_timezone: bool = True) -> str:
|
||||
"""Format datetime as timestamp string"""
|
||||
if dt is None:
|
||||
dt = self.now()
|
||||
|
||||
if dt.tzinfo is None:
|
||||
dt = self.localize(dt)
|
||||
|
||||
if include_timezone:
|
||||
return dt.strftime("%Y-%m-%d %H:%M:%S %Z")
|
||||
else:
|
||||
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def format_filename_timestamp(self, dt: Optional[datetime.datetime] = None) -> str:
|
||||
"""Format datetime for use in filenames (no special characters)"""
|
||||
if dt is None:
|
||||
dt = self.now()
|
||||
|
||||
if dt.tzinfo is None:
|
||||
dt = self.localize(dt)
|
||||
|
||||
return dt.strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
def parse_timestamp(self, timestamp_str: str) -> datetime.datetime:
|
||||
"""Parse timestamp string to datetime"""
|
||||
try:
|
||||
# Try parsing with timezone info
|
||||
return datetime.datetime.fromisoformat(timestamp_str)
|
||||
except ValueError:
|
||||
try:
|
||||
# Try parsing without timezone (assume local)
|
||||
dt = datetime.datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
|
||||
return self.localize(dt)
|
||||
except ValueError:
|
||||
try:
|
||||
# Try parsing filename format
|
||||
dt = datetime.datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
|
||||
return self.localize(dt)
|
||||
except ValueError:
|
||||
raise ValueError(f"Unable to parse timestamp: {timestamp_str}")
|
||||
|
||||
def is_dst(self, dt: Optional[datetime.datetime] = None) -> bool:
|
||||
"""Check if datetime is during daylight saving time"""
|
||||
if dt is None:
|
||||
dt = self.now()
|
||||
|
||||
if dt.tzinfo is None:
|
||||
dt = self.localize(dt)
|
||||
|
||||
return bool(dt.dst())
|
||||
|
||||
def get_timezone_offset(self, dt: Optional[datetime.datetime] = None) -> str:
|
||||
"""Get timezone offset string (e.g., '-0500' or '-0400')"""
|
||||
if dt is None:
|
||||
dt = self.now()
|
||||
|
||||
if dt.tzinfo is None:
|
||||
dt = self.localize(dt)
|
||||
|
||||
return dt.strftime('%z')
|
||||
|
||||
def get_timezone_name(self, dt: Optional[datetime.datetime] = None) -> str:
|
||||
"""Get timezone name (e.g., 'EST' or 'EDT')"""
|
||||
if dt is None:
|
||||
dt = self.now()
|
||||
|
||||
if dt.tzinfo is None:
|
||||
dt = self.localize(dt)
|
||||
|
||||
return dt.tzname()
|
||||
|
||||
|
||||
# Global timezone manager instance for Atlanta, Georgia
|
||||
atlanta_tz = TimezoneManager("America/New_York")
|
||||
|
||||
|
||||
# Convenience functions
|
||||
def now_atlanta() -> datetime.datetime:
|
||||
"""Get current Atlanta time"""
|
||||
return atlanta_tz.now()
|
||||
|
||||
|
||||
def format_atlanta_timestamp(dt: Optional[datetime.datetime] = None) -> str:
|
||||
"""Format timestamp in Atlanta timezone"""
|
||||
return atlanta_tz.format_timestamp(dt)
|
||||
|
||||
|
||||
def format_filename_timestamp(dt: Optional[datetime.datetime] = None) -> str:
|
||||
"""Format timestamp for filenames"""
|
||||
return atlanta_tz.format_filename_timestamp(dt)
|
||||
|
||||
|
||||
def to_atlanta_time(dt: datetime.datetime) -> datetime.datetime:
|
||||
"""Convert any datetime to Atlanta time"""
|
||||
return atlanta_tz.to_local(dt)
|
||||
|
||||
|
||||
def check_time_sync() -> dict:
|
||||
"""Check if system time appears to be synchronized"""
|
||||
import requests
|
||||
|
||||
result = {
|
||||
"system_time": now_atlanta(),
|
||||
"timezone": atlanta_tz.get_timezone_name(),
|
||||
"offset": atlanta_tz.get_timezone_offset(),
|
||||
"dst": atlanta_tz.is_dst(),
|
||||
"sync_status": "unknown",
|
||||
"time_diff_seconds": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
# Check against world time API
|
||||
response = requests.get(
|
||||
"http://worldtimeapi.org/api/timezone/America/New_York",
|
||||
timeout=5
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
api_time = datetime.datetime.fromisoformat(data['datetime'])
|
||||
|
||||
# Convert to same timezone for comparison
|
||||
system_time = atlanta_tz.now()
|
||||
time_diff = abs((system_time.replace(tzinfo=None) -
|
||||
api_time.replace(tzinfo=None)).total_seconds())
|
||||
|
||||
result["api_time"] = api_time
|
||||
result["time_diff_seconds"] = time_diff
|
||||
|
||||
if time_diff < 5:
|
||||
result["sync_status"] = "synchronized"
|
||||
elif time_diff < 30:
|
||||
result["sync_status"] = "minor_drift"
|
||||
else:
|
||||
result["sync_status"] = "out_of_sync"
|
||||
else:
|
||||
result["error"] = f"API returned status {response.status_code}"
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def log_time_info(logger: Optional[logging.Logger] = None) -> None:
|
||||
"""Log comprehensive time information"""
|
||||
if logger is None:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
sync_info = check_time_sync()
|
||||
|
||||
logger.info("=== TIME SYNCHRONIZATION STATUS ===")
|
||||
logger.info(f"System time: {sync_info['system_time']}")
|
||||
logger.info(f"Timezone: {sync_info['timezone']} ({sync_info['offset']})")
|
||||
logger.info(f"Daylight Saving: {'Yes' if sync_info['dst'] else 'No'}")
|
||||
logger.info(f"Sync status: {sync_info['sync_status']}")
|
||||
|
||||
if sync_info.get('time_diff_seconds') is not None:
|
||||
logger.info(f"Time difference: {sync_info['time_diff_seconds']:.2f} seconds")
|
||||
|
||||
if sync_info.get('error'):
|
||||
logger.warning(f"Time sync check error: {sync_info['error']}")
|
||||
|
||||
logger.info("=====================================")
|
||||
Reference in New Issue
Block a user