702d080a1e
Google BK
176 lines
5.2 KiB
Python
176 lines
5.2 KiB
Python
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)
|