diff options
Diffstat (limited to 'libs/aniso8601/time.py')
-rw-r--r-- | libs/aniso8601/time.py | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/libs/aniso8601/time.py b/libs/aniso8601/time.py new file mode 100644 index 000000000..31fab048e --- /dev/null +++ b/libs/aniso8601/time.py @@ -0,0 +1,203 @@ +# -*- 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) |