You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
5.6 KiB
204 lines
5.6 KiB
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2021, Brandon Nielsen
|
|
# All rights reserved.
|
|
#
|
|
# This software may be modified and distributed under the terms
|
|
# of the BSD license. See the LICENSE file for details.
|
|
|
|
from aniso8601.builders import TupleBuilder
|
|
from aniso8601.builders.python import PythonTimeBuilder
|
|
from aniso8601.compat import is_string
|
|
from aniso8601.date import parse_date
|
|
from aniso8601.decimalfraction import normalize
|
|
from aniso8601.exceptions import ISOFormatError
|
|
from aniso8601.resolution import TimeResolution
|
|
from aniso8601.timezone import parse_timezone
|
|
|
|
TIMEZONE_DELIMITERS = ["Z", "+", "-"]
|
|
|
|
|
|
def get_time_resolution(isotimestr):
|
|
# Valid time formats are:
|
|
#
|
|
# hh:mm:ss
|
|
# hhmmss
|
|
# hh:mm
|
|
# hhmm
|
|
# hh
|
|
# hh:mm:ssZ
|
|
# hhmmssZ
|
|
# hh:mmZ
|
|
# hhmmZ
|
|
# hhZ
|
|
# hh:mm:ss±hh:mm
|
|
# hhmmss±hh:mm
|
|
# hh:mm±hh:mm
|
|
# hhmm±hh:mm
|
|
# hh±hh:mm
|
|
# hh:mm:ss±hhmm
|
|
# hhmmss±hhmm
|
|
# hh:mm±hhmm
|
|
# hhmm±hhmm
|
|
# hh±hhmm
|
|
# hh:mm:ss±hh
|
|
# hhmmss±hh
|
|
# hh:mm±hh
|
|
# hhmm±hh
|
|
# hh±hh
|
|
isotimetuple = parse_time(isotimestr, builder=TupleBuilder)
|
|
|
|
return _get_time_resolution(isotimetuple)
|
|
|
|
|
|
def get_datetime_resolution(isodatetimestr, delimiter="T"):
|
|
# <date>T<time>
|
|
#
|
|
# Time part cannot be omittted so return time resolution
|
|
isotimetuple = parse_datetime(
|
|
isodatetimestr, delimiter=delimiter, builder=TupleBuilder
|
|
).time
|
|
|
|
return _get_time_resolution(isotimetuple)
|
|
|
|
|
|
def _get_time_resolution(isotimetuple):
|
|
if isotimetuple.ss is not None:
|
|
return TimeResolution.Seconds
|
|
|
|
if isotimetuple.mm is not None:
|
|
return TimeResolution.Minutes
|
|
|
|
return TimeResolution.Hours
|
|
|
|
|
|
def parse_time(isotimestr, builder=PythonTimeBuilder):
|
|
# Given a string in any ISO 8601 time format, return a datetime.time object
|
|
# that corresponds to the given time. Fixed offset tzdata will be included
|
|
# if UTC offset is given in the input string. Valid time formats are:
|
|
#
|
|
# hh:mm:ss
|
|
# hhmmss
|
|
# hh:mm
|
|
# hhmm
|
|
# hh
|
|
# hh:mm:ssZ
|
|
# hhmmssZ
|
|
# hh:mmZ
|
|
# hhmmZ
|
|
# hhZ
|
|
# hh:mm:ss±hh:mm
|
|
# hhmmss±hh:mm
|
|
# hh:mm±hh:mm
|
|
# hhmm±hh:mm
|
|
# hh±hh:mm
|
|
# hh:mm:ss±hhmm
|
|
# hhmmss±hhmm
|
|
# hh:mm±hhmm
|
|
# hhmm±hhmm
|
|
# hh±hhmm
|
|
# hh:mm:ss±hh
|
|
# hhmmss±hh
|
|
# hh:mm±hh
|
|
# hhmm±hh
|
|
# hh±hh
|
|
if is_string(isotimestr) is False:
|
|
raise ValueError("Time must be string.")
|
|
|
|
if len(isotimestr) == 0:
|
|
raise ISOFormatError('"{0}" is not a valid ISO 8601 time.'.format(isotimestr))
|
|
|
|
timestr = normalize(isotimestr)
|
|
|
|
hourstr = None
|
|
minutestr = None
|
|
secondstr = None
|
|
tzstr = None
|
|
|
|
fractionalstr = None
|
|
|
|
# Split out the timezone
|
|
for delimiter in TIMEZONE_DELIMITERS:
|
|
delimiteridx = timestr.find(delimiter)
|
|
|
|
if delimiteridx != -1:
|
|
tzstr = timestr[delimiteridx:]
|
|
timestr = timestr[0:delimiteridx]
|
|
|
|
# Split out the fractional component
|
|
if timestr.find(".") != -1:
|
|
timestr, fractionalstr = timestr.split(".", 1)
|
|
|
|
if fractionalstr.isdigit() is False:
|
|
raise ISOFormatError(
|
|
'"{0}" is not a valid ISO 8601 time.'.format(isotimestr)
|
|
)
|
|
|
|
if len(timestr) == 2:
|
|
# hh
|
|
hourstr = timestr
|
|
elif len(timestr) == 4 or len(timestr) == 5:
|
|
# hh:mm
|
|
# hhmm
|
|
if timestr.count(":") == 1:
|
|
hourstr, minutestr = timestr.split(":")
|
|
else:
|
|
hourstr = timestr[0:2]
|
|
minutestr = timestr[2:]
|
|
elif len(timestr) == 6 or len(timestr) == 8:
|
|
# hh:mm:ss
|
|
# hhmmss
|
|
if timestr.count(":") == 2:
|
|
hourstr, minutestr, secondstr = timestr.split(":")
|
|
else:
|
|
hourstr = timestr[0:2]
|
|
minutestr = timestr[2:4]
|
|
secondstr = timestr[4:]
|
|
else:
|
|
raise ISOFormatError('"{0}" is not a valid ISO 8601 time.'.format(isotimestr))
|
|
|
|
for componentstr in [hourstr, minutestr, secondstr]:
|
|
if componentstr is not None and componentstr.isdigit() is False:
|
|
raise ISOFormatError(
|
|
'"{0}" is not a valid ISO 8601 time.'.format(isotimestr)
|
|
)
|
|
|
|
if fractionalstr is not None:
|
|
if secondstr is not None:
|
|
secondstr = secondstr + "." + fractionalstr
|
|
elif minutestr is not None:
|
|
minutestr = minutestr + "." + fractionalstr
|
|
else:
|
|
hourstr = hourstr + "." + fractionalstr
|
|
|
|
if tzstr is None:
|
|
tz = None
|
|
else:
|
|
tz = parse_timezone(tzstr, builder=TupleBuilder)
|
|
|
|
return builder.build_time(hh=hourstr, mm=minutestr, ss=secondstr, tz=tz)
|
|
|
|
|
|
def parse_datetime(isodatetimestr, delimiter="T", builder=PythonTimeBuilder):
|
|
# Given a string in ISO 8601 date time format, return a datetime.datetime
|
|
# object that corresponds to the given date time.
|
|
# By default, the ISO 8601 specified T delimiter is used to split the
|
|
# date and time (<date>T<time>). Fixed offset tzdata will be included
|
|
# if UTC offset is given in the input string.
|
|
if is_string(isodatetimestr) is False:
|
|
raise ValueError("Date time must be string.")
|
|
|
|
if delimiter not in isodatetimestr:
|
|
raise ISOFormatError(
|
|
'Delimiter "{0}" is not in combined date time '
|
|
'string "{1}".'.format(delimiter, isodatetimestr)
|
|
)
|
|
|
|
isodatestr, isotimestr = isodatetimestr.split(delimiter, 1)
|
|
|
|
datepart = parse_date(isodatestr, builder=TupleBuilder)
|
|
|
|
timepart = parse_time(isotimestr, builder=TupleBuilder)
|
|
|
|
return builder.build_datetime(datepart, timepart)
|