add my.location.home, use it as location/timezone fallback

This commit is contained in:
Dima Gerasimov 2020-10-06 00:18:43 +01:00 committed by karlicoss
parent 1f2e595be9
commit dfea664f57
4 changed files with 113 additions and 9 deletions

View file

@ -32,3 +32,7 @@ class bluemaestro:
class google: class google:
takeout_path: Paths = '' takeout_path: Paths = ''
class location:
class home:
current = (1.0, -1.0)

61
my/location/home.py Normal file
View file

@ -0,0 +1,61 @@
'''
Simple location provider, serving as a fallback when more detailed data isn't available
'''
from dataclasses import dataclass
from datetime import datetime, date
from functools import lru_cache
from typing import Optional, Sequence, Tuple, Union
from ..core.common import fromisoformat
from my.config import location as L
user_config = L.home
DateIsh = Union[datetime, str]
# todo hopefully reasonable? might be nice to add name or something too
LatLon = Tuple[float, float]
@dataclass
class home(user_config):
# TODO could make current Optional and somehow determine from system settings?
# todo possibly also could be core config.. but not sure
current: LatLon
'''
First element is location, the second is the date when you left it (datetime/ISO string)
'''
past: Sequence[Tuple[LatLon, DateIsh]] = ()
# todo test for proper localized/not localized handling as well
# todo make sure they are increasing
@property
def _past(self) -> Sequence[Tuple[LatLon, datetime]]:
# todo cache?
res = []
for loc, x in self.past:
dt: datetime
if isinstance(x, str):
dt = fromisoformat(x)
else:
dt = x
res.append((loc, dt))
return res
from ..core.cfg import make_config
config = make_config(home)
@lru_cache(maxsize=None)
def get_location(dt: datetime) -> LatLon:
'''
Interpolates the location at dt
'''
for loc, pdt in config._past:
if dt <= pdt:
return loc
return config.current

View file

@ -25,15 +25,18 @@ logger = LazyLogger(__name__, level='debug')
# todo should move to config? not sure # todo should move to config? not sure
_FASTER: bool = False _FASTER: bool = True
@lru_cache(1) @lru_cache(2)
def _timezone_finder(): def _timezone_finder(fast: bool):
from timezonefinder import TimezoneFinder as Finder # type: ignore if fast:
if _FASTER: # less precise, but faster
from timezonefinder import TimezoneFinderL as Finder # type: ignore from timezonefinder import TimezoneFinderL as Finder # type: ignore
else:
from timezonefinder import TimezoneFinder as Finder # type: ignore
return Finder(in_memory=True) return Finder(in_memory=True)
# todo move to common?
Zone = str Zone = str
@ -55,6 +58,7 @@ def _iter_local_dates(start=0, stop=None) -> Iterator[DayWithZone]:
warnings.append(f"Couldn't figure out tz for {l}") warnings.append(f"Couldn't figure out tz for {l}")
continue continue
tz = pytz.timezone(zone) tz = pytz.timezone(zone)
# TODO this is probably a bit expensive... test & benchmark
ldt = l.dt.astimezone(tz) ldt = l.dt.astimezone(tz)
ndate = ldt.date() ndate = ldt.date()
if pdt is not None and ndate < pdt.date(): if pdt is not None and ndate < pdt.date():
@ -100,9 +104,27 @@ def _get_day_tz(d: date) -> Optional[pytz.BaseTzInfo]:
break break
return None if zone is None else pytz.timezone(zone) return None if zone is None else pytz.timezone(zone)
# ok to cache, there are only a few home locations?
@lru_cache(maxsize=None)
def _get_home_tz(loc) -> Optional[pytz.BaseTzInfo]:
(lat, lng) = loc
finder = _timezone_finder(fast=False) # ok to use slow here for better precision
zone = finder.timezone_at(lat=lat, lng=lng)
if zone is None:
# TODO shouldn't really happen, warn?
return None
else:
return pytz.timezone(zone)
def _get_tz(dt: datetime) -> Optional[pytz.BaseTzInfo]: def _get_tz(dt: datetime) -> Optional[pytz.BaseTzInfo]:
return _get_day_tz(d=dt.date()) res = _get_day_tz(d=dt.date())
if res is not None:
return res
# fallback to home tz
from ...location import home
loc = home.get_location(dt)
return _get_home_tz(loc=loc)
def localize(dt: datetime) -> datetime: def localize(dt: datetime) -> datetime:

View file

@ -13,6 +13,15 @@ def test_iter_tzs() -> None:
assert len(ll) > 3 assert len(ll) > 3
def test_past() -> None:
# should fallback to the home location provider
dt = D('20000101 12:34:45')
dt = TZ.localize(dt)
tz = dt.tzinfo
assert tz is not None
assert getattr(tz, 'zone') == 'America/New_York'
def test_future() -> None: def test_future() -> None:
fut = datetime.now() + timedelta(days=100) fut = datetime.now() + timedelta(days=100)
# shouldn't crash at least # shouldn't crash at least
@ -55,8 +64,16 @@ def prepare(tmp_path: Path):
# FIXME ugh. early import/inheritance of user_confg in my.google.takeout.paths messes things up.. # FIXME ugh. early import/inheritance of user_confg in my.google.takeout.paths messes things up..
from my.cfg import config from my.cfg import config
class user_config: class google:
takeout_path = tmp_path takeout_path = tmp_path
config.google = user_config # type: ignore config.google = google # type: ignore
class location:
class home:
current = (1.0, 1.0)
past = [
((40.7128, -74.0060), '2005-12-04'), # NY
]
config.location = location # type: ignore
yield yield