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:
|
class google:
|
||||||
takeout_path: Paths = ''
|
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
|
# 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:
|
||||||
|
|
21
tests/tz.py
21
tests/tz.py
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue