core/compat: move fromisoformat to .core.compat module
This commit is contained in:
parent
09e0f66892
commit
a843407e40
8 changed files with 69 additions and 34 deletions
|
@ -23,7 +23,7 @@ def inputs() -> Sequence[Path]:
|
||||||
|
|
||||||
|
|
||||||
from .core import dataclass, Json, PathIsh, datetime_aware
|
from .core import dataclass, Json, PathIsh, datetime_aware
|
||||||
from .core.common import isoparse
|
from .core.compat import fromisoformat
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -39,6 +39,7 @@ class Entry:
|
||||||
@property
|
@property
|
||||||
def dt(self) -> datetime_aware:
|
def dt(self) -> datetime_aware:
|
||||||
# contains utc already
|
# contains utc already
|
||||||
|
# TODO after python>=3.11, could just use fromisoformat
|
||||||
ds = self.json['date']
|
ds = self.json['date']
|
||||||
elen = 27
|
elen = 27
|
||||||
lds = len(ds)
|
lds = len(ds)
|
||||||
|
@ -46,10 +47,10 @@ class Entry:
|
||||||
# ugh. sometimes contains less that 6 decimal points
|
# ugh. sometimes contains less that 6 decimal points
|
||||||
ds = ds[:-1] + '0' * (elen - lds) + 'Z'
|
ds = ds[:-1] + '0' * (elen - lds) + 'Z'
|
||||||
elif lds > elen:
|
elif lds > elen:
|
||||||
# ahd sometimes more...
|
# and sometimes more...
|
||||||
ds = ds[:elen - 1] + 'Z'
|
ds = ds[:elen - 1] + 'Z'
|
||||||
|
|
||||||
return isoparse(ds)
|
return fromisoformat(ds)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self) -> Optional[str]:
|
def active(self) -> Optional[str]:
|
||||||
|
|
|
@ -313,20 +313,18 @@ class classproperty(Generic[_R]):
|
||||||
# def __get__(self) -> _R:
|
# def __get__(self) -> _R:
|
||||||
# return self.f()
|
# return self.f()
|
||||||
|
|
||||||
# TODO deprecate in favor of datetime_aware
|
# for now just serves documentation purposes... but one day might make it statically verifiable where possible?
|
||||||
tzdatetime = datetime
|
# TODO e.g. maybe use opaque mypy alias?
|
||||||
|
datetime_naive = datetime
|
||||||
|
datetime_aware = datetime
|
||||||
|
|
||||||
|
|
||||||
# TODO doctests?
|
# TODO deprecate
|
||||||
def isoparse(s: str) -> tzdatetime:
|
tzdatetime = datetime_aware
|
||||||
"""
|
|
||||||
Parses timestamps formatted like 2020-05-01T10:32:02.925961Z
|
|
||||||
"""
|
# TODO deprecate (although could be used in modules)
|
||||||
# TODO could use dateutil? but it's quite slow as far as I remember..
|
from .compat import fromisoformat as isoparse
|
||||||
# TODO support non-utc.. somehow?
|
|
||||||
assert s.endswith('Z'), s
|
|
||||||
s = s[:-1] + '+00:00'
|
|
||||||
return datetime.fromisoformat(s)
|
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
@ -590,12 +588,6 @@ def asdict(thing: Any) -> Json:
|
||||||
raise TypeError(f'Could not convert object {thing} to dict')
|
raise TypeError(f'Could not convert object {thing} to dict')
|
||||||
|
|
||||||
|
|
||||||
# for now just serves documentation purposes... but one day might make it statically verifiable where possible?
|
|
||||||
# TODO e.g. maybe use opaque mypy alias?
|
|
||||||
datetime_naive = datetime
|
|
||||||
datetime_aware = datetime
|
|
||||||
|
|
||||||
|
|
||||||
def assert_subpackage(name: str) -> None:
|
def assert_subpackage(name: str) -> None:
|
||||||
# can lead to some unexpected issues if you 'import cachew' which being in my/core directory.. so let's protect against it
|
# can lead to some unexpected issues if you 'import cachew' which being in my/core directory.. so let's protect against it
|
||||||
# NOTE: if we use overlay, name can be smth like my.origg.my.core.cachew ...
|
# NOTE: if we use overlay, name can be smth like my.origg.my.core.cachew ...
|
||||||
|
|
|
@ -76,3 +76,42 @@ if sys.version_info[:2] <= (3, 9):
|
||||||
return lo
|
return lo
|
||||||
else:
|
else:
|
||||||
from bisect import bisect_left
|
from bisect import bisect_left
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
if sys.version_info[:2] >= (3, 11):
|
||||||
|
fromisoformat = datetime.fromisoformat
|
||||||
|
else:
|
||||||
|
def fromisoformat(date_string: str) -> datetime:
|
||||||
|
# didn't support Z as "utc" before 3.11
|
||||||
|
if date_string.endswith('Z'):
|
||||||
|
# NOTE: can be removed from 3.11?
|
||||||
|
# https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat
|
||||||
|
date_string = date_string[:-1] + '+00:00'
|
||||||
|
return datetime.fromisoformat(date_string)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fromisoformat() -> None:
|
||||||
|
from datetime import timezone
|
||||||
|
|
||||||
|
# feedbin has this format
|
||||||
|
assert fromisoformat('2020-05-01T10:32:02.925961Z') == datetime(
|
||||||
|
2020, 5, 1, 10, 32, 2, 925961, timezone.utc,
|
||||||
|
)
|
||||||
|
|
||||||
|
# polar has this format
|
||||||
|
assert fromisoformat('2018-11-28T22:04:01.304Z') == datetime(
|
||||||
|
2018, 11, 28, 22, 4, 1, 304000, timezone.utc,
|
||||||
|
)
|
||||||
|
|
||||||
|
# stackexchange, runnerup has this format
|
||||||
|
assert fromisoformat('2020-11-30T00:53:12Z') == datetime(
|
||||||
|
2020, 11, 30, 0, 53, 12, 0, timezone.utc,
|
||||||
|
)
|
||||||
|
|
||||||
|
# arbtt has this format (sometimes less/more than 6 digits in milliseconds)
|
||||||
|
# TODO doesn't work atm, not sure if really should be supported...
|
||||||
|
# maybe should have flags for weird formats?
|
||||||
|
# assert isoparse('2017-07-18T18:59:38.21731Z') == datetime(
|
||||||
|
# 2017, 7, 18, 18, 59, 38, 217310, timezone.utc,
|
||||||
|
# )
|
||||||
|
|
|
@ -24,7 +24,7 @@ from .query import (
|
||||||
ET,
|
ET,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .common import isoparse
|
from .compat import fromisoformat
|
||||||
|
|
||||||
|
|
||||||
timedelta_regex = re.compile(r"^((?P<weeks>[\.\d]+?)w)?((?P<days>[\.\d]+?)d)?((?P<hours>[\.\d]+?)h)?((?P<minutes>[\.\d]+?)m)?((?P<seconds>[\.\d]+?)s)?$")
|
timedelta_regex = re.compile(r"^((?P<weeks>[\.\d]+?)w)?((?P<days>[\.\d]+?)d)?((?P<hours>[\.\d]+?)h)?((?P<minutes>[\.\d]+?)m)?((?P<seconds>[\.\d]+?)s)?$")
|
||||||
|
@ -78,7 +78,7 @@ def parse_datetime_float(date_str: str) -> float:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
return isoparse(ds).timestamp()
|
return fromisoformat(ds).timestamp()
|
||||||
except (AssertionError, ValueError):
|
except (AssertionError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ from typing import List, Dict, Iterable, NamedTuple, Sequence, Optional
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from .core import LazyLogger, Json, Res
|
from .core import LazyLogger, Json, Res
|
||||||
from .core.common import isoparse
|
from .core.compat import fromisoformat
|
||||||
from .core.error import echain, sort_res_by
|
from .core.error import echain, sort_res_by
|
||||||
from .core.konsume import wrap, Zoomable, Wdict
|
from .core.konsume import wrap, Zoomable, Wdict
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ class Loader:
|
||||||
cmap[hlid] = ccs
|
cmap[hlid] = ccs
|
||||||
ccs.append(Comment(
|
ccs.append(Comment(
|
||||||
cid=cid.value,
|
cid=cid.value,
|
||||||
created=isoparse(crt.value),
|
created=fromisoformat(crt.value),
|
||||||
text=html.value, # TODO perhaps coonvert from html to text or org?
|
text=html.value, # TODO perhaps coonvert from html to text or org?
|
||||||
))
|
))
|
||||||
v.consume()
|
v.consume()
|
||||||
|
@ -183,7 +183,7 @@ class Loader:
|
||||||
|
|
||||||
yield Highlight(
|
yield Highlight(
|
||||||
hid=hid,
|
hid=hid,
|
||||||
created=isoparse(crt),
|
created=fromisoformat(crt),
|
||||||
selection=text,
|
selection=text,
|
||||||
comments=tuple(comments),
|
comments=tuple(comments),
|
||||||
tags=tuple(htags),
|
tags=tuple(htags),
|
||||||
|
@ -221,7 +221,7 @@ class Loader:
|
||||||
path = Path(config.polar_dir) / 'stash' / filename
|
path = Path(config.polar_dir) / 'stash' / filename
|
||||||
|
|
||||||
yield Book(
|
yield Book(
|
||||||
created=isoparse(added),
|
created=fromisoformat(added),
|
||||||
uid=self.uid,
|
uid=self.uid,
|
||||||
path=path,
|
path=path,
|
||||||
title=title,
|
title=title,
|
||||||
|
|
|
@ -7,7 +7,8 @@ from my.config import feedbin as config
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
||||||
from ..core.common import listify, get_files, isoparse
|
from ..core.common import listify, get_files
|
||||||
|
from ..core.compat import fromisoformat
|
||||||
from .common import Subscription
|
from .common import Subscription
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ def parse_file(f: Path):
|
||||||
raw = json.loads(f.read_text())
|
raw = json.loads(f.read_text())
|
||||||
for r in raw:
|
for r in raw:
|
||||||
yield Subscription(
|
yield Subscription(
|
||||||
created_at=isoparse(r['created_at']),
|
created_at=fromisoformat(r['created_at']),
|
||||||
title=r['title'],
|
title=r['title'],
|
||||||
url=r['site_url'],
|
url=r['site_url'],
|
||||||
id=r['id'],
|
id=r['id'],
|
||||||
|
|
|
@ -11,7 +11,8 @@ from pathlib import Path
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from .core import Res, get_files
|
from .core import Res, get_files
|
||||||
from .core.common import isoparse, Json
|
from .core.common import Json
|
||||||
|
from .core.compat import fromisoformat
|
||||||
|
|
||||||
import tcxparser # type: ignore[import-untyped]
|
import tcxparser # type: ignore[import-untyped]
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ def _parse(f: Path) -> Workout:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id' : f.name, # not sure?
|
'id' : f.name, # not sure?
|
||||||
'start_time' : isoparse(tcx.started_at),
|
'start_time' : fromisoformat(tcx.started_at),
|
||||||
'duration' : timedelta(seconds=tcx.duration),
|
'duration' : timedelta(seconds=tcx.duration),
|
||||||
'sport' : sport,
|
'sport' : sport,
|
||||||
'heart_rate_avg': tcx.hr_avg,
|
'heart_rate_avg': tcx.hr_avg,
|
||||||
|
@ -58,7 +59,7 @@ def _parse(f: Path) -> Workout:
|
||||||
# tcx.hr_values(),
|
# tcx.hr_values(),
|
||||||
# # todo cadence?
|
# # todo cadence?
|
||||||
# ):
|
# ):
|
||||||
# t = isoparse(ts)
|
# t = fromisoformat(ts)
|
||||||
|
|
||||||
|
|
||||||
def workouts() -> Iterable[Res[Workout]]:
|
def workouts() -> Iterable[Res[Workout]]:
|
||||||
|
|
|
@ -16,7 +16,8 @@ config = make_config(stackexchange)
|
||||||
|
|
||||||
# TODO just merge all of them and then filter?.. not sure
|
# TODO just merge all of them and then filter?.. not sure
|
||||||
|
|
||||||
from ..core.common import Json, isoparse
|
from ..core.common import Json
|
||||||
|
from ..core.compat import fromisoformat
|
||||||
from typing import NamedTuple, Iterable
|
from typing import NamedTuple, Iterable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
class Vote(NamedTuple):
|
class Vote(NamedTuple):
|
||||||
|
@ -25,7 +26,7 @@ class Vote(NamedTuple):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def when(self) -> datetime:
|
def when(self) -> datetime:
|
||||||
return isoparse(self.j['eventTime'])
|
return fromisoformat(self.j['eventTime'])
|
||||||
|
|
||||||
# todo Url return type?
|
# todo Url return type?
|
||||||
@property
|
@property
|
||||||
|
|
Loading…
Add table
Reference in a new issue