add my.location.home, use it as location/timezone fallback
This commit is contained in:
parent
1f2e595be9
commit
dfea664f57
4 changed files with 113 additions and 9 deletions
|
@ -32,3 +32,7 @@ class bluemaestro:
|
|||
|
||||
class google:
|
||||
takeout_path: Paths = ''
|
||||
|
||||
class location:
|
||||
class home:
|
||||
current = (1.0, -1.0)
|
||||
|
|
61
my/location/home.py
Normal file
61
my/location/home.py
Normal 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
|
|
@ -25,15 +25,18 @@ logger = LazyLogger(__name__, level='debug')
|
|||
|
||||
|
||||
# todo should move to config? not sure
|
||||
_FASTER: bool = False
|
||||
@lru_cache(1)
|
||||
def _timezone_finder():
|
||||
from timezonefinder import TimezoneFinder as Finder # type: ignore
|
||||
if _FASTER:
|
||||
from timezonefinder import TimezoneFinderL as Finder # type: ignore
|
||||
_FASTER: bool = True
|
||||
@lru_cache(2)
|
||||
def _timezone_finder(fast: bool):
|
||||
if fast:
|
||||
# less precise, but faster
|
||||
from timezonefinder import TimezoneFinderL as Finder # type: ignore
|
||||
else:
|
||||
from timezonefinder import TimezoneFinder as Finder # type: ignore
|
||||
return Finder(in_memory=True)
|
||||
|
||||
|
||||
# todo move to common?
|
||||
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}")
|
||||
continue
|
||||
tz = pytz.timezone(zone)
|
||||
# TODO this is probably a bit expensive... test & benchmark
|
||||
ldt = l.dt.astimezone(tz)
|
||||
ndate = ldt.date()
|
||||
if pdt is not None and ndate < pdt.date():
|
||||
|
@ -100,9 +104,27 @@ def _get_day_tz(d: date) -> Optional[pytz.BaseTzInfo]:
|
|||
break
|
||||
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]:
|
||||
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:
|
||||
|
|
21
tests/tz.py
21
tests/tz.py
|
@ -13,6 +13,15 @@ def test_iter_tzs() -> None:
|
|||
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:
|
||||
fut = datetime.now() + timedelta(days=100)
|
||||
# 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..
|
||||
from my.cfg import config
|
||||
class user_config:
|
||||
class google:
|
||||
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
|
||||
|
|
Loading…
Add table
Reference in a new issue