87 lines
3.6 KiB
Python
87 lines
3.6 KiB
Python
import datetime
|
|
import logging
|
|
import re
|
|
import pytz
|
|
from six import string_types
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Iso8601Serializer:
|
|
marker = 'ISO8601'
|
|
separator = ':'
|
|
|
|
_re = re.compile(r'^(?P<date>'
|
|
r'(?P<year>\d{4})'
|
|
r'-(?P<mon>(0[1-9])|(1[012]))'
|
|
r'-(?P<day>(0[1-9])|([12][0-9])|(3[01]))'
|
|
r')?(?P<sep>.)?'
|
|
r'(?P<time>'
|
|
r'(?P<hour>([01][0-9])|(2[0123]))'
|
|
r':(?P<min>[0-5][0-9])'
|
|
r':(?P<sec>[0-5][0-9])'
|
|
r'([,.]\d{1,10})?'
|
|
r')?'
|
|
r'(?P<offset>(?P<offdir>[\-+])(?P<offhours>([01][0-9])|(2[0123]))(:(?P<offmins>[0-5][0-9]))?)?$')
|
|
|
|
def __init__(self, instance=None, text=None):
|
|
if instance is not None:
|
|
self.instance = instance
|
|
elif text is not None:
|
|
self.instance = Iso8601Serializer.deserialize(text)
|
|
|
|
@property
|
|
def str(self):
|
|
return Iso8601Serializer.serialize(self.instance)
|
|
|
|
@classmethod
|
|
def serialize(cls, value, ignore_unsupported_input=False):
|
|
strval = None
|
|
if isinstance(value, datetime.datetime):
|
|
strval = value.isoformat()
|
|
elif isinstance(value, datetime.date):
|
|
strval = value.isoformat()
|
|
elif isinstance(value, datetime.time):
|
|
strval = value.isoformat()
|
|
elif not ignore_unsupported_input:
|
|
raise ValueError('Expected datetime.datetime, datetime.date or datetime.time,'
|
|
' not {}'.format(value.__class__.__name__))
|
|
|
|
if strval is not None:
|
|
value = '{marker}{sep}{str}'.format(marker=cls.marker, sep=cls.separator, str=strval)
|
|
|
|
return value
|
|
|
|
@classmethod
|
|
def deserialize(cls, value, ignore_unsupported_input=False):
|
|
prefix = '{marker}{sep}'.format(marker=cls.marker, sep=cls.separator)
|
|
if isinstance(value, string_types) and value.startswith(prefix):
|
|
haystack = value[len(prefix):]
|
|
m = cls._re.match(haystack)
|
|
if m is not None:
|
|
gd = m.groupdict()
|
|
if gd['hour'] is None:
|
|
value = datetime.date(int(gd['year']), int(gd['mon']), int(gd['day']))
|
|
elif gd['year'] is None:
|
|
value = datetime.time(hour=int(gd['hour']), minute=int(gd['min']), second=int(gd['sec']))
|
|
else:
|
|
value = datetime.datetime(int(gd['year']), int(gd['mon']), int(gd['day']),
|
|
hour=int(gd['hour']), minute=int(gd['min']), second=int(gd['sec']))
|
|
if gd['offset'] is not None:
|
|
offset = datetime.timedelta(hours=int(gd['offhours']))
|
|
if gd['offmins'] is not None:
|
|
offset += datetime.timedelta(minutes=int(gd['offmins']))
|
|
if gd['offdir'] == '+':
|
|
value -= offset
|
|
elif gd['offdir'] == '-':
|
|
value += offset
|
|
else:
|
|
raise ValueError('Offset format not recognized \'{str}\''.format(str=gd['offset']))
|
|
value = value.replace(tzinfo=pytz.UTC)
|
|
elif not ignore_unsupported_input:
|
|
raise ValueError('Format not recognized \'{str}\''.format(str=haystack))
|
|
elif not ignore_unsupported_input:
|
|
raise ValueError('Expected string type,'
|
|
' not {}'.format(value.__class__.__name__))
|
|
return value
|