from datetime import datetime from typing import Callable, Literal, cast from my.core import datetime_aware ''' Depending on the specific data provider and your level of paranoia you might expect different behaviour.. E.g.: - if your objects already have tz info, you might not need to call localize() at all - it's safer when either all of your objects are tz aware or all are tz unware, not a mixture - you might trust your original timezone, or it might just be UTC, and you want to use something more reasonable ''' TzPolicy = Literal[ 'keep' , # if datetime is tz aware, just preserve it 'convert', # if datetime is tz aware, convert to provider's tz 'throw' , # if datetime is tz aware, throw exception # todo 'warn'? not sure if very useful ] # backwards compatibility Policy = TzPolicy def default_policy() -> TzPolicy: try: from my.config import time as user_config return cast(TzPolicy, user_config.tz.policy) except Exception as e: # todo meh.. need to think how to do this more carefully # rationale: do not mess with user's data unless they want return 'keep' def localize_with_policy( lfun: Callable[[datetime], datetime_aware], dt: datetime, policy: TzPolicy=default_policy() ) -> datetime_aware: tz = dt.tzinfo if tz is None: return lfun(dt) if policy == 'keep': return dt elif policy == 'convert': ldt = lfun(dt.replace(tzinfo=None)) return dt.astimezone(ldt.tzinfo) else: # policy == 'error': raise RuntimeError(f"{dt} already has timezone information (use 'policy' argument to adjust this behaviour)")