def _parse_minute_time(timestr, tz, builder): # Format must be hhmm, hhmm., hh:mm or hh:mm. if timestr.count(':') == 1: # hh:mm or hh:mm. hourstr, minutestr = timestr.split(':') else: # hhmm or hhmm. hourstr = timestr[0:2] minutestr = timestr[2:] return builder.build_time(hh=normalize(hourstr), mm=normalize(minutestr), tz=tz)
def _parse_second_time(timestr, tz, builder): # Format must be hhmmss, hhmmss., hh:mm:ss or hh:mm:ss. if timestr.count(':') == 2: # hh:mm:ss or hh:mm:ss. hourstr, minutestr, secondstr = timestr.split(':') else: # hhmmss or hhmmss. hourstr = timestr[0:2] minutestr = timestr[2:4] secondstr = timestr[4:] return builder.build_time(hh=normalize(hourstr), mm=normalize(minutestr), ss=normalize(secondstr), tz=tz)
def _parse_duration_prescribed_time(isodurationstr): #durationstr can be of the form PnYnMnDTnHnMnS timeidx = isodurationstr.find('T') datestr = isodurationstr[:timeidx] timestr = normalize(isodurationstr[timeidx + 1:]) hourstr = None minutestr = None secondstr = None houridx = timestr.find('H') minuteidx = timestr.find('M') secondidx = timestr.find('S') if houridx != -1 and minuteidx != -1 and secondidx != -1: hourstr = timestr[0:houridx] minutestr = timestr[houridx + 1:minuteidx] secondstr = timestr[minuteidx + 1:-1] elif houridx != -1 and minuteidx != -1: hourstr = timestr[0:houridx] minutestr = timestr[houridx + 1:minuteidx] elif minuteidx != -1 and secondidx != -1: minutestr = timestr[0:minuteidx] secondstr = timestr[minuteidx + 1:-1] elif houridx != -1: hourstr = timestr[0:-1] elif minuteidx != -1: minutestr = timestr[0:-1] elif secondidx != -1: secondstr = timestr[0:-1] else: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr)) for componentstr in [hourstr, minutestr, secondstr]: if componentstr is not None: if '.' in componentstr: intstr, fractionalstr = componentstr.split('.', 1) if intstr.isdigit() is False: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format( isodurationstr)) else: if componentstr.isdigit() is False: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format( isodurationstr)) #Parse any date components durationdict = {'PnY': None, 'PnM': None, 'PnW': None, 'PnD': None} if len(datestr) > 1: durationdict = _parse_duration_prescribed_notime(datestr) durationdict.update({'TnH': hourstr, 'TnM': minutestr, 'TnS': secondstr}) return durationdict
def _parse_hour(timestr, tz, builder): # Format must be hh or hh. hourstr = timestr if hourstr == "24": return builder.build_time(tz=tz) return builder.build_time(hh=normalize(hourstr), tz=tz)
def _parse_duration_prescribed_notime(isodurationstr): # durationstr can be of the form PnYnMnD or PnW durationstr = normalize(isodurationstr) yearstr = None monthstr = None daystr = None weekstr = None weekidx = durationstr.find("W") yearidx = durationstr.find("Y") monthidx = durationstr.find("M") dayidx = durationstr.find("D") if weekidx != -1: weekstr = durationstr[1:-1] elif yearidx != -1 and monthidx != -1 and dayidx != -1: yearstr = durationstr[1:yearidx] monthstr = durationstr[yearidx + 1:monthidx] daystr = durationstr[monthidx + 1:-1] elif yearidx != -1 and monthidx != -1: yearstr = durationstr[1:yearidx] monthstr = durationstr[yearidx + 1:monthidx] elif yearidx != -1 and dayidx != -1: yearstr = durationstr[1:yearidx] daystr = durationstr[yearidx + 1:dayidx] elif monthidx != -1 and dayidx != -1: monthstr = durationstr[1:monthidx] daystr = durationstr[monthidx + 1:-1] elif yearidx != -1: yearstr = durationstr[1:-1] elif monthidx != -1: monthstr = durationstr[1:-1] elif dayidx != -1: daystr = durationstr[1:-1] else: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr)) for componentstr in [yearstr, monthstr, daystr, weekstr]: if componentstr is not None: if "." in componentstr: intstr, fractionalstr = componentstr.split(".", 1) if intstr.isdigit() is False: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format( isodurationstr)) else: if componentstr.isdigit() is False: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format( isodurationstr)) return {"PnY": yearstr, "PnM": monthstr, "PnW": weekstr, "PnD": daystr}
def _parse_duration_prescribed_notime(isodurationstr): #durationstr can be of the form PnYnMnD or PnW durationstr = normalize(isodurationstr) yearstr = None monthstr = None daystr = None weekstr = None weekidx = durationstr.find('W') yearidx = durationstr.find('Y') monthidx = durationstr.find('M') dayidx = durationstr.find('D') if weekidx != -1: weekstr = durationstr[1:-1] elif yearidx != -1 and monthidx != -1 and dayidx != -1: yearstr = durationstr[1:yearidx] monthstr = durationstr[yearidx + 1:monthidx] daystr = durationstr[monthidx + 1:-1] elif yearidx != -1 and monthidx != -1: yearstr = durationstr[1:yearidx] monthstr = durationstr[yearidx + 1:monthidx] elif monthidx != -1 and dayidx != -1: monthstr = durationstr[1:monthidx] daystr = durationstr[monthidx + 1:-1] elif yearidx != -1: yearstr = durationstr[1:-1] elif monthidx != -1: monthstr = durationstr[1:-1] elif dayidx != -1: daystr = durationstr[1:-1] else: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr)) for componentstr in [yearstr, monthstr, daystr, weekstr]: if componentstr is not None: if '.' in componentstr: intstr, fractionalstr = componentstr.split('.', 1) if intstr.isdigit() is False: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format( isodurationstr)) else: if componentstr.isdigit() is False: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format( isodurationstr)) return {'PnY': yearstr, 'PnM': monthstr, 'PnW': weekstr, 'PnD': daystr}
def _parse_duration_element(durationstr, elementstr): # Extracts the specified portion of a duration, for instance, given: # durationstr = 'T4H5M6.1234S' # elementstr = 'H' # # returns 4 # # Note that the string must start with a character, so its assumed the # full duration string would be split at the 'T' durationstartindex = 0 durationendindex = durationstr.find(elementstr) for characterindex in compat.range(durationendindex - 1, 0, -1): if durationstr[characterindex].isalpha() is True: durationstartindex = characterindex break durationstartindex += 1 return normalize(durationstr[durationstartindex:durationendindex])
def _parse_duration_prescribed(isodurationstr): #durationstr can be of the form PnYnMnDTnHnMnS or PnW #Make sure the end character is valid #https://bitbucket.org/nielsenb/aniso8601/issues/9/durations-with-trailing-garbage-are-parsed if isodurationstr[-1] not in ['Y', 'M', 'D', 'H', 'S', 'W']: raise ISOFormatError('ISO 8601 duration must end with a valid ' 'character.') #Make sure only the lowest order element has decimal precision durationstr = normalize(isodurationstr) if durationstr.count('.') > 1: raise ISOFormatError('ISO 8601 allows only lowest order element to ' 'have a decimal fraction.') seperatoridx = durationstr.find('.') if seperatoridx != -1: remaining = durationstr[seperatoridx + 1:-1] #There should only ever be 1 letter after a decimal if there is more #then one, the string is invalid if remaining.isdigit() is False: raise ISOFormatError('ISO 8601 duration must end with ' 'a single valid character.') #Do not allow W in combination with other designators #https://bitbucket.org/nielsenb/aniso8601/issues/2/week-designators-should-not-be-combinable if (durationstr.find('W') != -1 and _has_any_component( durationstr, ['Y', 'M', 'D', 'H', 'S']) is True): raise ISOFormatError('ISO 8601 week designators may not be combined ' 'with other time designators.') #Parse the elements of the duration if durationstr.find('T') == -1: return _parse_duration_prescribed_notime(durationstr) return _parse_duration_prescribed_time(durationstr)
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 test_normalize(self): self.assertEqual(normalize(''), '') self.assertEqual(normalize('12.34'), '12.34') self.assertEqual(normalize('123,45'), '123.45') self.assertEqual(normalize('123,45,67'), '123.45.67')
def _parse_duration_prescribed_time(isodurationstr): # durationstr can be of the form PnYnMnDTnHnMnS timeidx = isodurationstr.find("T") datestr = isodurationstr[:timeidx] timestr = normalize(isodurationstr[timeidx + 1:]) hourstr = None minutestr = None secondstr = None houridx = timestr.find("H") minuteidx = timestr.find("M") secondidx = timestr.find("S") if houridx != -1 and minuteidx != -1 and secondidx != -1: hourstr = timestr[0:houridx] minutestr = timestr[houridx + 1:minuteidx] secondstr = timestr[minuteidx + 1:-1] elif houridx != -1 and minuteidx != -1: hourstr = timestr[0:houridx] minutestr = timestr[houridx + 1:minuteidx] elif houridx != -1 and secondidx != -1: hourstr = timestr[0:houridx] secondstr = timestr[houridx + 1:-1] elif minuteidx != -1 and secondidx != -1: minutestr = timestr[0:minuteidx] secondstr = timestr[minuteidx + 1:-1] elif houridx != -1: hourstr = timestr[0:-1] elif minuteidx != -1: minutestr = timestr[0:-1] elif secondidx != -1: secondstr = timestr[0:-1] else: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr)) for componentstr in [hourstr, minutestr, secondstr]: if componentstr is not None: if "." in componentstr: intstr, fractionalstr = componentstr.split(".", 1) if intstr.isdigit() is False: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format( isodurationstr)) else: if componentstr.isdigit() is False: raise ISOFormatError( '"{0}" is not a valid ISO 8601 duration.'.format( isodurationstr)) # Parse any date components durationdict = {"PnY": None, "PnM": None, "PnW": None, "PnD": None} if len(datestr) > 1: durationdict = _parse_duration_prescribed_notime(datestr) durationdict.update({"TnH": hourstr, "TnM": minutestr, "TnS": secondstr}) return durationdict
def test_normalize(self): self.assertEqual(normalize(""), "") self.assertEqual(normalize("12.34"), "12.34") self.assertEqual(normalize("123,45"), "123.45") self.assertEqual(normalize("123,45,67"), "123.45.67")