import asyncio from datetime import datetime, timedelta from injector import inject, singleton from dateutil.relativedelta import relativedelta from dateutil.parser import parse from .logger import getLogger import pytz import os from pytz import timezone, utc from tzlocal import get_localzone_name, get_localzone from dateutil.tz import tzlocal logger = getLogger(__name__) def get_local_tz(): methods = [ _infer_timezone_from_env, _infer_timezone_from_system, _infer_timezone_from_name, _infer_timezone_from_offset ] for method in methods: try: tz = method() if tz is not None: return tz except Exception: pass return utc def _infer_timezone_from_offset(): now = datetime.now() desired_offset = tzlocal().utcoffset(now) for tz_name in pytz.all_timezones: tz = pytz.timezone(tz_name) if desired_offset == tz.utcoffset(now): return tz return None def _infer_timezone_from_name(): name = get_localzone_name() if name is not None: return timezone(name) return None def _infer_timezone_from_system(): tz = get_localzone() if tz is not None: return tz return None def _infer_timezone_from_env(): if "TZ" in os.environ: tz = timezone(os.environ["TZ"]) if tz is not None: return tz return None @singleton class Time(object): @inject def __init__(self, local_tz=get_local_tz()): self.local_tz = local_tz self._offset = timedelta(seconds=0) def now(self) -> datetime: return datetime.now(pytz.utc) + self._offset def nowLocal(self) -> datetime: return datetime.now(self.local_tz) + self._offset @property def offset(self): return self._offset @offset.setter def offset(self, delta: timedelta): self._offset = delta @classmethod def parse(cls, text: str) -> datetime: ret = parse(text) if ret.tzinfo is None: ret = ret.replace(tzinfo=utc) return ret def toLocal(self, dt: datetime) -> datetime: return dt.astimezone(self.local_tz) def localize(self, dt: datetime) -> datetime: return self.local_tz.localize(dt) def toUtc(self, dt: datetime) -> datetime: return dt.astimezone(utc) async def sleepAsync(self, seconds: float, early_exit: asyncio.Event = None) -> None: if early_exit is None: await asyncio.sleep(seconds) else: try: await asyncio.wait_for(early_exit.wait(), seconds) except asyncio.TimeoutError: pass def local(self, year, month, day, hour=0, minute=0, second=0, ms=0): return self.local_tz.localize(datetime(year, month, day, hour, minute, second, ms)) def formatDelta(self, time: datetime, now=None) -> str: if not now: now = self.now() delta: relativedelta = None flavor = "" if time < now: delta = relativedelta(now, time) flavor = " ago" else: delta = relativedelta(time, now) flavor = "" if delta.years > 0: plural = "s" if delta.years > 1 else "" return "{0} year{2}{1}".format(delta.years, flavor, plural) if (delta.months != 0): plural = "s" if delta.months > 1 else "" if delta.days > 15: return "{0} month{2}{1}".format(delta.months + 1, flavor, plural) return "{0} month{2}{1}".format(delta.months, flavor, plural) if (delta.days != 0): plural = "s" if delta.days > 1 else "" if delta.hours >= 12: return "{0} day{2}{1}".format(delta.days + 1, flavor, plural) return "{0} day{2}{1}".format(delta.days, flavor, plural) if (delta.hours != 0): plural = "s" if delta.hours > 1 else "" if delta.minutes >= 30: return "{0} hour{2}{1}".format(delta.hours + 1, flavor, plural) return "{0} hour{2}{1}".format(delta.hours, flavor, plural) if (delta.minutes != 0): plural = "s" if delta.minutes > 1 else "" if delta.minutes >= 30: return "{0} minute{2}{1}".format(delta.minutes + 1, flavor, plural) return "{0} minute{2}{1}".format(delta.minutes, flavor, plural) if (delta.seconds != 0): plural = "s" if delta.seconds > 1 else "" return "{0} second{2}{1}".format(delta.seconds, flavor, plural) return "right now" def asRfc3339String(self, time: datetime) -> str: if time is None: time = self.now() return time.strftime("%Y-%m-%dT%H:%M:%SZ") class AcceleratedTime(Time): def __init__(self, dialation=1.0): super().__init__() self.start = datetime.now(utc) self.dialation = dialation def now(self): return self.start + timedelta(seconds=(datetime.now(utc) - self.start).total_seconds() * self.dialation) def nowLocal(self) -> datetime: return self.localize(self.now()) async def sleepAsync(self, seconds: float) -> None: await asyncio.sleep(seconds / self.dialation)