core: remove vendorized py37 isoformat code
This commit is contained in:
parent
03dd1271f4
commit
7493770d4d
8 changed files with 7 additions and 146 deletions
|
@ -358,12 +358,11 @@ def isoparse(s: str) -> tzdatetime:
|
||||||
"""
|
"""
|
||||||
Parses timestamps formatted like 2020-05-01T10:32:02.925961Z
|
Parses timestamps formatted like 2020-05-01T10:32:02.925961Z
|
||||||
"""
|
"""
|
||||||
from .compat import fromisoformat
|
|
||||||
# TODO could use dateutil? but it's quite slow as far as I remember..
|
# TODO could use dateutil? but it's quite slow as far as I remember..
|
||||||
# TODO support non-utc.. somehow?
|
# TODO support non-utc.. somehow?
|
||||||
assert s.endswith('Z'), s
|
assert s.endswith('Z'), s
|
||||||
s = s[:-1] + '+00:00'
|
s = s[:-1] + '+00:00'
|
||||||
return fromisoformat(s)
|
return datetime.fromisoformat(s)
|
||||||
|
|
||||||
from .compat import Literal
|
from .compat import Literal
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ Some backwards compatibility stuff/deprecation helpers
|
||||||
'''
|
'''
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from . import warnings
|
from . import warnings
|
||||||
from .common import LazyLogger
|
from .common import LazyLogger
|
||||||
|
@ -12,16 +11,6 @@ from .common import LazyLogger
|
||||||
logger = LazyLogger('my.core.compat')
|
logger = LazyLogger('my.core.compat')
|
||||||
|
|
||||||
|
|
||||||
fromisoformat: Callable[[str], datetime]
|
|
||||||
import sys
|
|
||||||
if sys.version_info[:2] >= (3, 7):
|
|
||||||
# prevent mypy on py3.6 from complaining...
|
|
||||||
fromisoformat_real = datetime.fromisoformat
|
|
||||||
fromisoformat = fromisoformat_real
|
|
||||||
else:
|
|
||||||
from .py37 import fromisoformat
|
|
||||||
|
|
||||||
|
|
||||||
def pre_pip_dal_handler(
|
def pre_pip_dal_handler(
|
||||||
name: str,
|
name: str,
|
||||||
e: ModuleNotFoundError,
|
e: ModuleNotFoundError,
|
||||||
|
|
|
@ -127,8 +127,8 @@ def attach_dt(e: Exception, *, dt: Optional[datetime]) -> Exception:
|
||||||
|
|
||||||
# todo it might be problematic because might mess with timezones (when it's converted to string, it's converted to a shift)
|
# todo it might be problematic because might mess with timezones (when it's converted to string, it's converted to a shift)
|
||||||
def extract_error_datetime(e: Exception) -> Optional[datetime]:
|
def extract_error_datetime(e: Exception) -> Optional[datetime]:
|
||||||
from .compat import fromisoformat
|
|
||||||
import re
|
import re
|
||||||
|
from datetime import datetime
|
||||||
for x in reversed(e.args):
|
for x in reversed(e.args):
|
||||||
if isinstance(x, datetime):
|
if isinstance(x, datetime):
|
||||||
return x
|
return x
|
||||||
|
@ -139,7 +139,7 @@ def extract_error_datetime(e: Exception) -> Optional[datetime]:
|
||||||
continue
|
continue
|
||||||
ss = m.group(0)
|
ss = m.group(0)
|
||||||
# todo not sure if should be defensive??
|
# todo not sure if should be defensive??
|
||||||
return fromisoformat(ss)
|
return datetime.fromisoformat(ss)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
122
my/core/py37.py
122
my/core/py37.py
|
@ -1,122 +0,0 @@
|
||||||
# borrowed from /usr/lib/python3.7/datetime.py
|
|
||||||
from datetime import datetime, timezone, timedelta
|
|
||||||
|
|
||||||
def _parse_isoformat_date(dtstr):
|
|
||||||
# It is assumed that this function will only be called with a
|
|
||||||
# string of length exactly 10, and (though this is not used) ASCII-only
|
|
||||||
year = int(dtstr[0:4])
|
|
||||||
if dtstr[4] != '-':
|
|
||||||
raise ValueError('Invalid date separator: %s' % dtstr[4])
|
|
||||||
|
|
||||||
month = int(dtstr[5:7])
|
|
||||||
|
|
||||||
if dtstr[7] != '-':
|
|
||||||
raise ValueError('Invalid date separator')
|
|
||||||
|
|
||||||
day = int(dtstr[8:10])
|
|
||||||
|
|
||||||
return [year, month, day]
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_hh_mm_ss_ff(tstr):
|
|
||||||
# Parses things of the form HH[:MM[:SS[.fff[fff]]]]
|
|
||||||
len_str = len(tstr)
|
|
||||||
|
|
||||||
time_comps = [0, 0, 0, 0]
|
|
||||||
pos = 0
|
|
||||||
for comp in range(0, 3):
|
|
||||||
if (len_str - pos) < 2:
|
|
||||||
raise ValueError('Incomplete time component')
|
|
||||||
|
|
||||||
time_comps[comp] = int(tstr[pos:pos+2])
|
|
||||||
|
|
||||||
pos += 2
|
|
||||||
next_char = tstr[pos:pos+1]
|
|
||||||
|
|
||||||
if not next_char or comp >= 2:
|
|
||||||
break
|
|
||||||
|
|
||||||
if next_char != ':':
|
|
||||||
raise ValueError('Invalid time separator: %c' % next_char)
|
|
||||||
|
|
||||||
pos += 1
|
|
||||||
|
|
||||||
if pos < len_str:
|
|
||||||
if tstr[pos] != '.':
|
|
||||||
raise ValueError('Invalid microsecond component')
|
|
||||||
else:
|
|
||||||
pos += 1
|
|
||||||
|
|
||||||
len_remainder = len_str - pos
|
|
||||||
if len_remainder not in (3, 6):
|
|
||||||
raise ValueError('Invalid microsecond component')
|
|
||||||
|
|
||||||
time_comps[3] = int(tstr[pos:])
|
|
||||||
if len_remainder == 3:
|
|
||||||
time_comps[3] *= 1000
|
|
||||||
|
|
||||||
return time_comps
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_isoformat_time(tstr):
|
|
||||||
# Format supported is HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]
|
|
||||||
len_str = len(tstr)
|
|
||||||
if len_str < 2:
|
|
||||||
raise ValueError('Isoformat time too short')
|
|
||||||
|
|
||||||
# This is equivalent to re.search('[+-]', tstr), but faster
|
|
||||||
tz_pos = (tstr.find('-') + 1 or tstr.find('+') + 1)
|
|
||||||
timestr = tstr[:tz_pos-1] if tz_pos > 0 else tstr
|
|
||||||
|
|
||||||
time_comps = _parse_hh_mm_ss_ff(timestr)
|
|
||||||
|
|
||||||
tzi = None
|
|
||||||
if tz_pos > 0:
|
|
||||||
tzstr = tstr[tz_pos:]
|
|
||||||
|
|
||||||
# Valid time zone strings are:
|
|
||||||
# HH:MM len: 5
|
|
||||||
# HH:MM:SS len: 8
|
|
||||||
# HH:MM:SS.ffffff len: 15
|
|
||||||
|
|
||||||
if len(tzstr) not in (5, 8, 15):
|
|
||||||
raise ValueError('Malformed time zone string')
|
|
||||||
|
|
||||||
tz_comps = _parse_hh_mm_ss_ff(tzstr)
|
|
||||||
if all(x == 0 for x in tz_comps):
|
|
||||||
tzi = timezone.utc
|
|
||||||
else:
|
|
||||||
tzsign = -1 if tstr[tz_pos - 1] == '-' else 1
|
|
||||||
|
|
||||||
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
|
|
||||||
seconds=tz_comps[2], microseconds=tz_comps[3])
|
|
||||||
|
|
||||||
tzi = timezone(tzsign * td)
|
|
||||||
|
|
||||||
time_comps.append(tzi)
|
|
||||||
|
|
||||||
return time_comps
|
|
||||||
|
|
||||||
def fromisoformat(date_string, cls=datetime):
|
|
||||||
"""Construct a datetime from the output of datetime.isoformat()."""
|
|
||||||
if not isinstance(date_string, str):
|
|
||||||
raise TypeError('fromisoformat: argument must be str')
|
|
||||||
|
|
||||||
# Split this at the separator
|
|
||||||
dstr = date_string[0:10]
|
|
||||||
tstr = date_string[11:]
|
|
||||||
|
|
||||||
try:
|
|
||||||
date_components = _parse_isoformat_date(dstr)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError('Invalid isoformat string: %s' % date_string)
|
|
||||||
|
|
||||||
if tstr:
|
|
||||||
try:
|
|
||||||
time_components = _parse_isoformat_time(tstr)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError('Invalid isoformat string: %s' % date_string)
|
|
||||||
else:
|
|
||||||
time_components = [0, 0, 0, 0, None]
|
|
||||||
|
|
||||||
return cls(*(date_components + time_components))
|
|
|
@ -24,7 +24,6 @@ from .query import (
|
||||||
ET,
|
ET,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .compat import fromisoformat
|
|
||||||
from .common import isoparse
|
from .common import isoparse
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,7 +73,7 @@ def parse_datetime_float(date_str: str) -> float:
|
||||||
return ds_float
|
return ds_float
|
||||||
try:
|
try:
|
||||||
# isoformat - default format when you call str() on datetime
|
# isoformat - default format when you call str() on datetime
|
||||||
return fromisoformat(ds).timestamp()
|
return datetime.fromisoformat(ds).timestamp()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -6,8 +6,6 @@ from datetime import datetime, date, time, timezone
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Sequence, Tuple, Union, cast
|
from typing import Sequence, Tuple, Union, cast
|
||||||
|
|
||||||
from ..core.compat import fromisoformat
|
|
||||||
|
|
||||||
from my.config import location as user_config
|
from my.config import location as user_config
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,7 +42,7 @@ class Config(user_config):
|
||||||
for x, loc in home2:
|
for x, loc in home2:
|
||||||
dt: datetime
|
dt: datetime
|
||||||
if isinstance(x, str):
|
if isinstance(x, str):
|
||||||
dt = fromisoformat(x)
|
dt = datetime.fromisoformat(x)
|
||||||
elif isinstance(x, datetime):
|
elif isinstance(x, datetime):
|
||||||
dt = x
|
dt = x
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -6,7 +6,6 @@ from datetime import datetime
|
||||||
from typing import NamedTuple, Dict, Optional, Iterable
|
from typing import NamedTuple, Dict, Optional, Iterable
|
||||||
|
|
||||||
from .core import get_files
|
from .core import get_files
|
||||||
from .core.compat import fromisoformat
|
|
||||||
|
|
||||||
from my.config import taplog as user_config
|
from my.config import taplog as user_config
|
||||||
|
|
||||||
|
@ -41,7 +40,7 @@ class Entry(NamedTuple):
|
||||||
ts = self.row['timestamp']
|
ts = self.row['timestamp']
|
||||||
# already with timezone apparently
|
# already with timezone apparently
|
||||||
# TODO not sure if should stil localize though? it only kept tz offset, not real tz
|
# TODO not sure if should stil localize though? it only kept tz offset, not real tz
|
||||||
return fromisoformat(ts)
|
return datetime.fromisoformat(ts)
|
||||||
# TODO also has gps info!
|
# TODO also has gps info!
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -141,7 +141,6 @@ def localize(dt: datetime) -> tzdatetime:
|
||||||
|
|
||||||
from ...core import stat, Stats
|
from ...core import stat, Stats
|
||||||
def stats() -> Stats:
|
def stats() -> Stats:
|
||||||
from ...core.compat import fromisoformat
|
|
||||||
# TODO not sure what would be a good stat() for this module...
|
# TODO not sure what would be a good stat() for this module...
|
||||||
# might be nice to print some actual timezones?
|
# might be nice to print some actual timezones?
|
||||||
# there aren't really any great iterables to expose
|
# there aren't really any great iterables to expose
|
||||||
|
@ -150,6 +149,6 @@ def stats() -> Stats:
|
||||||
# note: deliberately take + 2 years, so the iterator exhausts. otherwise stuff might never get cached
|
# note: deliberately take + 2 years, so the iterator exhausts. otherwise stuff might never get cached
|
||||||
# need to think about it...
|
# need to think about it...
|
||||||
for Y in range(1990, last):
|
for Y in range(1990, last):
|
||||||
dt = fromisoformat(f'{Y}-01-01 01:01:01')
|
dt = datetime.fromisoformat(f'{Y}-01-01 01:01:01')
|
||||||
yield localize(dt)
|
yield localize(dt)
|
||||||
return stat(localized_years)
|
return stat(localized_years)
|
||||||
|
|
Loading…
Add table
Reference in a new issue