diff --git a/doc/example_config/my/config/__init__.py b/doc/example_config/my/config/__init__.py index ec70750..dda7149 100644 --- a/doc/example_config/my/config/__init__.py +++ b/doc/example_config/my/config/__init__.py @@ -33,6 +33,12 @@ class bluemaestro: class google: takeout_path: Paths = '' + +from typing import Sequence, Union, Tuple +from datetime import datetime, date +DateIsh = Union[datetime, date, str] +LatLon = Tuple[float, float] class location: - class home: - current = (1.0, -1.0) + # todo ugh, need to think about it... mypy wants the type here to be general, otherwise it can't deduce + # and we can't import the types from the module itself, otherwise would be circular. common module? + home: Union[LatLon, Sequence[Tuple[DateIsh, LatLon]]] = (1.0, -1.0) diff --git a/my/location/home.py b/my/location/home.py index 4c392f1..91b7ef6 100644 --- a/my/location/home.py +++ b/my/location/home.py @@ -2,52 +2,63 @@ Simple location provider, serving as a fallback when more detailed data isn't available ''' from dataclasses import dataclass -from datetime import datetime, date +from datetime import datetime, date, time, timezone from functools import lru_cache -from typing import Optional, Sequence, Tuple, Union +from typing import Optional, Sequence, Tuple, Union, cast from ..core.common import fromisoformat -from my.config import location as L -user_config = L.home +from my.config import location as user_config -DateIsh = Union[datetime, str] +DateIsh = Union[datetime, date, str] # todo hopefully reasonable? might be nice to add name or something too LatLon = Tuple[float, float] @dataclass -class home(user_config): +class Config(user_config): + home: Union[ + LatLon, # either single, 'current' location + Sequence[Tuple[ # or, a sequence of location history + DateIsh, # date when you moved to + LatLon, # the location + ]] + ] # 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]]: + def _history(self) -> Sequence[Tuple[datetime, LatLon]]: + home1 = self.home + # tood ugh, can't test for isnstance LatLon, it's a tuple itself + home2: Sequence[Tuple[DateIsh, LatLon]] + if isinstance(home1[0], tuple): + # already a sequence + home2 = cast(Sequence[Tuple[DateIsh, LatLon]], home1) + else: + # must be a pair of coordinates. also doesn't really matter which date to pick? + loc = cast(LatLon, home1) + home2 = [(datetime.min, loc)] + # todo cache? res = [] - for loc, x in self.past: + for x, loc in home2: dt: datetime if isinstance(x, str): dt = fromisoformat(x) - else: + elif isinstance(x, datetime): dt = x - res.append((loc, dt)) + else: + dt = datetime.combine(x, time.min) + # todo not sure about doing it here, but makes it easier to compare.. + if dt.tzinfo is None: + dt = dt.replace(tzinfo=timezone.utc) + res.append((dt, loc)) + res = list(sorted(res, key=lambda p: p[0])) return res - from ..core.cfg import make_config -config = make_config(home) +config = make_config(Config) @lru_cache(maxsize=None) @@ -55,7 +66,12 @@ def get_location(dt: datetime) -> LatLon: ''' Interpolates the location at dt ''' - for loc, pdt in config._past: - if dt <= pdt: + if dt.tzinfo is None: + dt = dt.replace(tzinfo=timezone.utc) + hist = list(reversed(config._history)) + for pdt, loc in hist: + if dt >= pdt: return loc - return config.current + else: + # I guess the most reasonable is to fallback on the first location + return hist[-1][1] diff --git a/tests/tz.py b/tests/tz.py index 5d3083c..fe9423c 100644 --- a/tests/tz.py +++ b/tests/tz.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date, timezone from pathlib import Path import sys @@ -44,6 +44,14 @@ def test_tz() -> None: assert tz is not None assert tz.zone == 'Europe/Rome' + tz = LTZ._get_tz(D('20201001 14:15:16')) + assert tz is not None + assert tz.zone == 'Europe/Moscow' + + tz = LTZ._get_tz(datetime.min) + assert tz is not None + assert tz.zone == 'America/New_York' + def D(dstr: str) -> datetime: return datetime.strptime(dstr, '%Y%m%d %H:%M:%S') @@ -72,11 +80,15 @@ def prepare(tmp_path: Path): config.google = google # type: ignore class location: - class home: - current = (42.697842, 23.325973) # Bulgaria, Sofia - past = [ - ((40.7128, -74.0060), '2005-12-04'), # NY - ] + home = ( + # supports ISO strings + ('2005-12-04' , (42.697842, 23.325973)), # Bulgaria, Sofia + # supports date/datetime objects + (date(year=1980, month=2, day=15) , (40.7128 , -74.0060 )), # NY + # check tz handling.. + (datetime.fromtimestamp(1600000000, tz=timezone.utc), (55.7558 , 37.6173 )), # Moscow, Russia + ) + # note: order doesn't matter, will be sorted in the data provider config.location = location # type: ignore yield