import datetime
import re
from . import base
from . import primitives
from . import domains
from .. import exceptions
# Patterns
REGEX_FROM_ISO8601 = r"""(?P<year>\d{4})-(?P<month>\d\d)-(?P<day>\d\d)(?:T|\ )
(?P<hour>\d\d):(?P<minute>\d\d)
(?::(?P<second>\d\d)(?:(?:\.|,)(?P<sec_frac>\d{1,6}))?)?
(?:(?P<tzd_offset>(?P<tzd_sign>[+−-])(?P<tzd_hour>\d\d):?(?P<tzd_minute>\d\d)?)
|(?P<tzd_utc>Z))?$"""
REGEX_TO_ISO8601 = '%Y-%m-%dT%H:%M:%S.%f%z'
REGEX_FROM_TIME = r"""(?P<hour>\d\d):(?P<minute>\d\d)
(?::(?P<second>\d\d)(?:(?:\.|,)(?P<sec_frac>\d{1,6}))?)?$"""
# Types
[docs]class DateTimeType(primitives.Primitive):
NATIVE = datetime.datetime
def __init__(self, regex=REGEX_FROM_ISO8601, **kw):
super().__init__(**kw)
domains.RegexDomain(self, regex, re.X)
@base.skip_falsy
def validate(self, value):
super().validate(value)
@base.skip_falsy
def to_native(self, value):
if self.is_type_match(value):
return value
elif not isinstance(value, str):
e_msg = "Value is not a string: {}"
raise exceptions.TypeException(e_msg.format(value))
# Verify regex
match = self._regex.match(value)
if not match:
e_msg = "Value did not match date pattern: {}"
raise exceptions.TypeException(e_msg.format(value))
# Map of regex keys that had values
parts = {k: v for k, v in match.groupdict().items() if v is not None}
# Accessor for regex parts as numbers
p = lambda name: int(parts.get(name, 0))
# Microseconds
microsecond = 0
if 'sec_frac' in parts:
sec_frac = p('sec_frac')
sf_len = sec_frac and len(parts['sec_frac'])
microsecond = sec_frac and sec_frac * 10 ** (6 - sf_len)
# Timezones
tz = None
if 'tzd_utc' in parts:
tz = datetime.timezone.utc
elif 'tzd_offset' in parts:
tz_sign = 1 if parts['tzd_sign'] == '+' else -1
tz_offset = (p('tzd_hour') * 60 + p('tzd_minute')) * tz_sign
if tz_offset == 0:
tz = datetime.timezone.utc
else:
tz = datetime.timezone(datetime.timedelta(minutes=tz_offset))
return self.NATIVE(
p('year'), p('month'), p('day'), p('hour'), p('minute'),
p('second'), microsecond, tz
)
@base.skip_falsy
def to_primitive(self, value, context=None):
if isinstance(value, str) and self._regex.match(value):
return value
elif isinstance(value, self.NATIVE):
return value.strftime(REGEX_TO_ISO8601)
e_msg = "Value must be iso8601 string or datetime.datetime: {}"
raise exceptions.TypeException(e_msg.format(value))
[docs]class TimeType(primitives.Primitive):
NATIVE = datetime.time
def __init__(self, regex=REGEX_FROM_TIME, **kw):
super().__init__(**kw)
domains.RegexDomain(self, regex, re.X)
@base.skip_falsy
def validate(self, value):
super().validate(value)
@base.skip_falsy
def to_native(self, value):
if isinstance(value, self.NATIVE):
return value
parts = {}
match = self._regex.match(value)
for k, v in match.groupdict().items():
if v is not None:
parts[k] = int(v)
return datetime.time(**parts)
@base.skip_falsy
def to_primitive(self, value):
if isinstance(value, self.NATIVE):
return value.isoformat()
if isinstance(value, str):
return value
e_msg = "Value must be time string or datetime.time: {}"
raise exceptions.TypeException(e_msg.format(value))