예제 #1
0
def parse_duration(isodurationstr, builder=PythonTimeBuilder):
    #Given a string representing an ISO 8601 duration, return a
    #a duration built by the given builder. Valid formats are:
    #
    #PnYnMnDTnHnMnS (or any reduced precision equivalent)
    #PnW
    #P<date>T<time>

    if compat.is_string(isodurationstr) is False:
        raise ValueError('Duration must be string.')

    if len(isodurationstr) == 0:
        raise ISOFormatError(
            '"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr))

    if isodurationstr[0] != 'P':
        raise ISOFormatError('ISO 8601 duration must start with a P.')

    #If Y, M, D, H, S, or W are in the string,
    #assume it is a specified duration
    if _has_any_component(isodurationstr,
                          ['Y', 'M', 'D', 'H', 'S', 'W']) is True:
        parseresult = _parse_duration_prescribed(isodurationstr)
        return builder.build_duration(**parseresult)

    if isodurationstr.find('T') != -1:
        parseresult = _parse_duration_combined(isodurationstr)
        return builder.build_duration(**parseresult)

    raise ISOFormatError(
        '"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr))
예제 #2
0
    def test_is_string(self):
        self.assertTrue(is_string('asdf'))
        self.assertTrue(is_string(''))

        if PY2 is True:
            self.assertTrue(is_string(unicode('asdf')))

        self.assertFalse(is_string(None))
        self.assertFalse(is_string(123))
        self.assertFalse(is_string(4.56))
        self.assertFalse(is_string([]))
        self.assertFalse(is_string({}))
예제 #3
0
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
    if is_string(isotimestr) is False:
        raise ValueError('Time must be string.')

    timestr = _split_tz(isotimestr)[0].replace(':', '')

    if (len(timestr) == 0 or timestr[0].isdigit() is False
            or timestr[-1].isdigit() is False):
        raise ISOFormatError(
            '"{0}" is not a valid ISO 8601 time.'.format(timestr))

    #Format must be hhmmss, hhmm, or hh
    timestrlen = find_separator(timestr)
    if timestrlen == -1:
        timestrlen = len(timestr)

    if timestrlen == 6:
        #hhmmss
        return TimeResolution.Seconds
    elif timestrlen == 4:
        #hhmm
        return TimeResolution.Minutes
    elif timestrlen == 2:
        #hh
        return TimeResolution.Hours

    raise ISOFormatError(
        '"{0}" is not a valid ISO 8601 time.'.format(isotimestr))
예제 #4
0
def parse_timezone(tzstr, builder=PythonTimeBuilder):
    # tzstr can be Z, ±hh:mm, ±hhmm, ±hh
    if is_string(tzstr) is False:
        raise ValueError("Time zone must be string.")

    if len(tzstr) == 1 and tzstr[0] == "Z":
        return builder.build_timezone(negative=False, Z=True, name=tzstr)
    elif len(tzstr) == 6:
        # ±hh:mm
        hourstr = tzstr[1:3]
        minutestr = tzstr[4:6]

        if tzstr[0] == "-" and hourstr == "00" and minutestr == "00":
            raise ISOFormatError("Negative ISO 8601 time offset must not "
                                 "be 0.")
    elif len(tzstr) == 5:
        # ±hhmm
        hourstr = tzstr[1:3]
        minutestr = tzstr[3:5]

        if tzstr[0] == "-" and hourstr == "00" and minutestr == "00":
            raise ISOFormatError("Negative ISO 8601 time offset must not "
                                 "be 0.")
    elif len(tzstr) == 3:
        # ±hh
        hourstr = tzstr[1:3]
        minutestr = None

        if tzstr[0] == "-" and hourstr == "00":
            raise ISOFormatError("Negative ISO 8601 time offset must not "
                                 "be 0.")
    else:
        raise ISOFormatError(
            '"{0}" is not a valid ISO 8601 time offset.'.format(tzstr))

    for componentstr in [hourstr, minutestr]:
        if componentstr is not None:
            if componentstr.isdigit() is False:
                raise ISOFormatError(
                    '"{0}" is not a valid ISO 8601 time offset.'.format(tzstr))

    if tzstr[0] == "+":
        return builder.build_timezone(negative=False,
                                      hh=hourstr,
                                      mm=minutestr,
                                      name=tzstr)

    if tzstr[0] == "-":
        return builder.build_timezone(negative=True,
                                      hh=hourstr,
                                      mm=minutestr,
                                      name=tzstr)

    raise ISOFormatError(
        '"{0}" is not a valid ISO 8601 time offset.'.format(tzstr))
예제 #5
0
    def test_is_string(self):
        self.assertTrue(is_string("asdf"))
        self.assertTrue(is_string(""))

        # pylint: disable=undefined-variable
        if PY2 is True:
            self.assertTrue(is_string(unicode("asdf")))

        self.assertFalse(is_string(None))
        self.assertFalse(is_string(123))
        self.assertFalse(is_string(4.56))
        self.assertFalse(is_string([]))
        self.assertFalse(is_string({}))
예제 #6
0
def parse_timezone(tzstr, builder=PythonTimeBuilder):
    #tzstr can be Z, ±hh:mm, ±hhmm, ±hh
    if is_string(tzstr) is False:
        raise ValueError('Time zone must be string.')

    if 'Z' in tzstr:
        if len(tzstr) != 1:
            raise ISOFormatError(
                '"{0}" is not a valid ISO 8601 time offset.'.format(tzstr))

        return builder.build_timezone(negative=False, Z=True, name=tzstr)
    elif len(tzstr) == 6:
        #±hh:mm
        hourstr = tzstr[1:3]
        minutestr = tzstr[4:6]

        if tzstr[0] == '-' and hourstr == '00' and minutestr == '00':
            raise ISOFormatError('Negative ISO 8601 time offset must not '
                                 'be 0.')
    elif len(tzstr) == 5:
        #±hhmm
        hourstr = tzstr[1:3]
        minutestr = tzstr[3:5]

        if tzstr[0] == '-' and hourstr == '00' and minutestr == '00':
            raise ISOFormatError('Negative ISO 8601 time offset must not '
                                 'be 0.')
    elif len(tzstr) == 3:
        #±hh
        hourstr = tzstr[1:3]
        minutestr = None

        if tzstr[0] == '-' and hourstr == '00':
            raise ISOFormatError('Negative ISO 8601 time offset must not '
                                 'be 0.')
    else:
        raise ISOFormatError(
            '"{0}" is not a valid ISO 8601 time offset.'.format(tzstr))

    if tzstr[0] == '+':
        return builder.build_timezone(negative=False,
                                      hh=hourstr,
                                      mm=minutestr,
                                      name=tzstr)
    elif tzstr[0] == '-':
        return builder.build_timezone(negative=True,
                                      hh=hourstr,
                                      mm=minutestr,
                                      name=tzstr)

    raise ISOFormatError(
        '"{0}" is not a valid ISO 8601 time offset.'.format(tzstr))
예제 #7
0
def parse_interval(
    isointervalstr,
    intervaldelimiter="/",
    datetimedelimiter="T",
    builder=PythonTimeBuilder,
):
    # Given a string representing an ISO 8601 interval, return an
    # interval built by the given builder. Valid formats are:
    #
    # <start>/<end>
    # <start>/<duration>
    # <duration>/<end>
    #
    # The <start> and <end> values can represent dates, or datetimes,
    # not times.
    #
    # The format:
    #
    # <duration>
    #
    # Is expressly not supported as there is no way to provide the additional
    # required context.

    if is_string(isointervalstr) is False:
        raise ValueError("Interval must be string.")

    if len(isointervalstr) == 0:
        raise ISOFormatError("Interval string is empty.")

    if isointervalstr[0] == "R":
        raise ISOFormatError("ISO 8601 repeating intervals must be parsed "
                             "with parse_repeating_interval.")

    intervaldelimitercount = isointervalstr.count(intervaldelimiter)

    if intervaldelimitercount == 0:
        raise ISOFormatError('Interval delimiter "{0}" is not in interval '
                             'string "{1}".'.format(intervaldelimiter,
                                                    isointervalstr))

    if intervaldelimitercount > 1:
        raise ISOFormatError(
            "{0} is not a valid ISO 8601 interval".format(isointervalstr))

    return _parse_interval(isointervalstr, builder, intervaldelimiter,
                           datetimedelimiter)
예제 #8
0
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)
예제 #9
0
def parse_repeating_interval(isointervalstr, intervaldelimiter='/',
                             datetimedelimiter='T', builder=PythonTimeBuilder):
    #Given a string representing an ISO 8601 interval repeating, return an
    #interval built by the given builder. Valid formats are:
    #
    #Rnn/<interval>
    #R/<interval>

    if is_string(isointervalstr) is False:
        raise ValueError('Interval must be string.')

    if len(isointervalstr) == 0:
        raise ISOFormatError('Repeating interval string is empty.')

    if intervaldelimiter not in isointervalstr:
        raise ISOFormatError('Interval delimiter "{0}" is not in interval '
                              'string "{1}".'
                             .format(intervaldelimiter, isointervalstr))

    if isointervalstr[0] != 'R':
        raise ISOFormatError('ISO 8601 repeating interval must start '
                             'with an R.')

    #Parse the number of iterations
    iterationpart, intervalpart = isointervalstr.split(intervaldelimiter, 1)

    if len(iterationpart) > 1:
        R = False
        Rnn = iterationpart[1:]
    else:
        R = True
        Rnn = None

    interval = _parse_interval(intervalpart, TupleBuilder,
                               intervaldelimiter, datetimedelimiter)

    return builder.build_repeating_interval(R=R, Rnn=Rnn, interval=interval)
예제 #10
0
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)
예제 #11
0
def parse_date(isodatestr, builder=PythonTimeBuilder):
    #Given a string in any ISO 8601 date format, return a datetime.date
    #object that corresponds to the given date. Valid string formats are:
    #
    #Y[YYY]
    #YYYY-MM-DD
    #YYYYMMDD
    #YYYY-MM
    #YYYY-Www
    #YYYYWww
    #YYYY-Www-D
    #YYYYWwwD
    #YYYY-DDD
    #YYYYDDD
    if is_string(isodatestr) is False:
        raise ValueError('Date must be string.')

    if isodatestr.startswith('+') or isodatestr.startswith('-'):
        raise NotImplementedError('ISO 8601 extended year representation '
                                  'not supported.')

    if len(isodatestr) == 0 or isodatestr.count('-') > 2:
        raise ISOFormatError(
            '"{0}" is not a valid ISO 8601 date.'.format(isodatestr))
    yearstr = None
    monthstr = None
    daystr = None
    weekstr = None
    weekdaystr = None
    ordinaldaystr = None

    if len(isodatestr) <= 4:
        #Y[YYY]
        yearstr = isodatestr
    elif 'W' in isodatestr:
        if len(isodatestr) == 10:
            #YYYY-Www-D
            yearstr = isodatestr[0:4]
            weekstr = isodatestr[6:8]
            weekdaystr = isodatestr[9]
        elif len(isodatestr) == 8:
            if '-' in isodatestr:
                #YYYY-Www
                yearstr = isodatestr[0:4]
                weekstr = isodatestr[6:]
            else:
                #YYYYWwwD
                yearstr = isodatestr[0:4]
                weekstr = isodatestr[5:7]
                weekdaystr = isodatestr[7]
        elif len(isodatestr) == 7:
            #YYYYWww
            yearstr = isodatestr[0:4]
            weekstr = isodatestr[5:]
    elif len(isodatestr) == 7:
        if '-' in isodatestr:
            #YYYY-MM
            yearstr = isodatestr[0:4]
            monthstr = isodatestr[5:]
        else:
            #YYYYDDD
            yearstr = isodatestr[0:4]
            ordinaldaystr = isodatestr[4:]
    elif len(isodatestr) == 8:
        if '-' in isodatestr:
            #YYYY-DDD
            yearstr = isodatestr[0:4]
            ordinaldaystr = isodatestr[5:]
        else:
            #YYYYMMDD
            yearstr = isodatestr[0:4]
            monthstr = isodatestr[4:6]
            daystr = isodatestr[6:]
    elif len(isodatestr) == 10:
        #YYYY-MM-DD
        yearstr = isodatestr[0:4]
        monthstr = isodatestr[5:7]
        daystr = isodatestr[8:]
    else:
        raise ISOFormatError(
            '"{0}" is not a valid ISO 8601 date.'.format(isodatestr))

    hascomponent = False

    for componentstr in [
            yearstr, monthstr, daystr, weekstr, weekdaystr, ordinaldaystr
    ]:
        if componentstr is not None:
            hascomponent = True

            if componentstr.isdigit() is False:
                raise ISOFormatError(
                    '"{0}" is not a valid ISO 8601 date.'.format(isodatestr))

    if hascomponent is False:
        raise ISOFormatError(
            '"{0}" is not a valid ISO 8601 date.'.format(isodatestr))

    return builder.build_date(YYYY=yearstr,
                              MM=monthstr,
                              DD=daystr,
                              Www=weekstr,
                              D=weekdaystr,
                              DDD=ordinaldaystr)
예제 #12
0
def get_date_resolution(isodatestr):
    #Valid string formats are:
    #
    #Y[YYY]
    #YYYY-MM-DD
    #YYYYMMDD
    #YYYY-MM
    #YYYY-Www
    #YYYYWww
    #YYYY-Www-D
    #YYYYWwwD
    #YYYY-DDD
    #YYYYDDD
    if is_string(isodatestr) is False:
        raise ValueError('Date must be string.')

    if isodatestr.startswith('+') or isodatestr.startswith('-'):
        raise NotImplementedError('ISO 8601 extended year representation '
                                  'not supported.')

    if (len(isodatestr) == 0 or isodatestr[0].isdigit() is False
            or isodatestr[-1].isdigit() is False):
        raise ISOFormatError(
            '"{0}" is not a valid ISO 8601 date.'.format(isodatestr))

    if isodatestr.find('W') != -1:
        #Handle ISO 8601 week date format
        hyphens_present = 1 if isodatestr.find('-') != -1 else 0
        week_date_len = 7 + hyphens_present
        weekday_date_len = 8 + 2 * hyphens_present

        if len(isodatestr) == week_date_len:
            #YYYY-Www
            #YYYYWww
            return DateResolution.Week
        elif len(isodatestr) == weekday_date_len:
            #YYYY-Www-D
            #YYYYWwwD
            return DateResolution.Weekday
        else:
            raise ISOFormatError(
                '"{0}" is not a valid ISO 8601 week date.'.format(isodatestr))

    #If the size of the string of 4 or less,
    #assume its a truncated year representation
    if len(isodatestr) <= 4:
        return DateResolution.Year

    #An ISO string may be a calendar represntation if:
    # 1) When split on a hyphen, the sizes of the parts are 4, 2, 2 or 4, 2
    # 2) There are no hyphens, and the length is 8
    datestrsplit = isodatestr.split('-')

    #Check case 1
    if len(datestrsplit) == 2:
        if len(datestrsplit[0]) == 4 and len(datestrsplit[1]) == 2:
            return DateResolution.Month

    if len(datestrsplit) == 3:
        if (len(datestrsplit[0]) == 4 and len(datestrsplit[1]) == 2
                and len(datestrsplit[2]) == 2):
            return DateResolution.Day

    #Check case 2
    if len(isodatestr) == 8 and isodatestr.find('-') == -1:
        return DateResolution.Day

    #An ISO string may be a ordinal date representation if:
    # 1) When split on a hyphen, the sizes of the parts are 4, 3
    # 2) There are no hyphens, and the length is 7

    #Check case 1
    if len(datestrsplit) == 2:
        if len(datestrsplit[0]) == 4 and len(datestrsplit[1]) == 3:
            return DateResolution.Ordinal

    #Check case 2
    if len(isodatestr) == 7 and isodatestr.find('-') == -1:
        return DateResolution.Ordinal

    #None of the date representations match
    raise ISOFormatError('"{0}" is not an ISO 8601 date, perhaps it '
                         'represents a time or datetime.'.format(isodatestr))