Beispiel #1
0
def parse_time(timestring):
    '''
    Parses ISO 8601 times into datetime.time objects.

    Following ISO 8601 formats are supported:
      (as decimal separator a ',' or a '.' is allowed)
      hhmmss.ssTZD    basic complete time
      hh:mm:ss.ssTZD  extended compelte time
      hhmm.mmTZD      basic reduced accuracy time
      hh:mm.mmTZD     extended reduced accuracy time
      hh.hhTZD        basic reduced accuracy time
    TZD is the time zone designator which can be in the following format:
              no designator indicates local time zone
      Z       UTC
      +-hhmm  basic hours and minutes
      +-hh:mm extended hours and minutes
      +-hh    hours
    '''
    isotimes = build_time_regexps()
    for pattern in isotimes:
        match = pattern.match(timestring)
        if match:
            groups = match.groupdict()
            for key, value in groups.items():
                if value is not None:
                    groups[key] = value.replace(',', '.')
            tzinfo = build_tzinfo(groups['tzname'], groups['tzsign'],
                                  int(groups['tzhour'] or 0),
                                  int(groups['tzmin'] or 0))
            if 'second' in groups:
                # round to microseconds if fractional seconds are more precise
                second = Decimal(groups['second']).quantize(Decimal('.000001'))
                second = min(second, Decimal('59.999999'))

                microsecond = (second - int(second)) * int(1e6)
                # int(...) ... no rounding
                # to_integral() ... rounding
                return time(int(groups['hour']), int(groups['minute']),
                            int(second), int(microsecond.to_integral()),
                            tzinfo)
            if 'minute' in groups:
                minute = Decimal(groups['minute'])
                second = (minute - int(minute)) * 60
                microsecond = (second - int(second)) * int(1e6)
                return time(int(groups['hour']), int(minute), int(second),
                            int(microsecond.to_integral()), tzinfo)
            else:
                microsecond, second, minute = 0, 0, 0
            hour = Decimal(groups['hour'])
            minute = (hour - int(hour)) * 60
            second = (minute - int(minute)) * 60
            microsecond = (second - int(second)) * int(1e6)
            return time(int(hour), int(minute), int(second),
                        int(microsecond.to_integral()), tzinfo)
    raise ISO8601Error('Unrecognised ISO 8601 time format: %r' % timestring)
Beispiel #2
0
def parse_time(timestring):
    '''
    Parses ISO 8601 times into datetime.time objects.
    
    Following ISO 8601 formats are supported:
      (as decimal separator a ',' or a '.' is allowed)
      hhmmss.ssTZD    basic complete time
      hh:mm:ss.ssTZD  extended compelte time
      hhmm.mmTZD      basic reduced accuracy time
      hh:mm.mmTZD     extended reduced accuracy time
      hh.hhTZD        basic reduced accuracy time
    TZD is the time zone designator which can be in the following format:
              no designator indicates local time zone
      Z       UTC
      +-hhmm  basic hours and minutes
      +-hh:mm extended hours and minutes
      +-hh    hours
    '''
    isotimes = build_time_regexps()
    for pattern in isotimes:
        match = pattern.match(timestring)
        if match:
            groups = match.groupdict()
            for key, value in groups.items():
                if value is not None:
                    groups[key] = value.replace(',', '.')
            tzinfo = build_tzinfo(groups['tzname'], groups['tzsign'],
                                  int(groups['tzhour'] or 0),
                                  int(groups['tzmin'] or 0))
            if 'second' in groups:
                frac, second = math.modf(float(groups['second']))
                microsecond = frac * 1e6
                return time(int(groups['hour']), int(groups['minute']),
                            int(second), int(microsecond), tzinfo)
            if 'minute' in groups:
                frac, minute = math.modf(float(groups['minute']))
                frac, second = math.modf(frac * 60.0)
                microsecond = frac * 1e6
                return time(int(groups['hour']), int(minute), int(second),
                            int(microsecond), tzinfo)
            else:
                microsecond, second, minute = 0, 0, 0
            frac, hour = math.modf(float(groups['hour']))
            frac, minute = math.modf(frac * 60.0)
            frac, second = math.modf(frac * 60.0)
            microsecond = frac * 1e6
            return time(int(hour), int(minute), int(second), int(microsecond),
                        tzinfo)
    raise ISO8601Error('Unrecognised ISO 8601 time format: %r' % timestring)
def parse_datetime(datetimestring):
    """
    Parses ISO 8601 date-times into datetime.datetime objects.

    This function uses parse_date and parse_time to do the job, so it allows
    more combinations of date and time representations, than the actual
    ISO 8601:2004 standard allows.
    """
    try:
        datestring, timestring = datetimestring.split("T")
    except ValueError:
        raise ISO8601Error("ISO 8601 time designator 'T' missing. Unable to"
                           " parse datetime string %r" % datetimestring)
    tmpdate = parse_date(datestring)
    tmptime = parse_time(timestring)
    return datetime.combine(tmpdate, tmptime)
Beispiel #4
0
 def __str__(self):
     ret = ""
     if self.repeat:
         ret += "R%d/" % self.repeat
     if self.start and self.end:
         ret += "%s/%s" % (datetime_isoformat(
             self.start), datetime_isoformat(self.end))
     elif self.start and self.duration:
         ret += "%s/%s" % (datetime_isoformat(
             self.start), duration_isoformat(self.duration))
     elif self.duration and self.end:
         ret += "%s/%s" % (duration_isoformat(
             self.duration), datetime_isoformat(self.end))
     else:
         raise ISO8601Error(
             "Could not produce a valid ISO8601 interval string")
     return ret
Beispiel #5
0
def parse_tzinfo(tzstring):
    '''
    Parses ISO 8601 time zone designators to tzinfo objecs.

    A time zone designator can be in the following format:
              no designator indicates local time zone
      Z       UTC
      +-hhmm  basic hours and minutes
      +-hh:mm extended hours and minutes
      +-hh    hours
    '''
    match = TZ_RE.match(tzstring)
    if match:
        groups = match.groupdict()
        return build_tzinfo(groups['tzname'], groups['tzsign'],
                            int(groups['tzhour'] or 0),
                            int(groups['tzmin'] or 0))
    raise ISO8601Error('%s not a valid time zone info' % tzstring)
Beispiel #6
0
def parse_tzinfo(tzstring):
    """
    Parses ISO 8601 time zone designators to tzinfo objects.

    A time zone designator can be in the following format:
              no designator indicates local time zone
      Z       UTC
      +-hhmm  basic hours and minutes
      +-hh:mm extended hours and minutes
      +-hh    hours
    """
    match = TZ_RE.match(tzstring)
    if match:
        groups = match.groupdict()
        return build_tzinfo(
            groups["tzname"],
            groups["tzsign"],
            int(groups["tzhour"] or 0),
            int(groups["tzmin"] or 0),
        )
    raise ISO8601Error("%s not a valid time zone info" % tzstring)
Beispiel #7
0
def interval_isoformat(arg1, arg2, repeat=None, fmt=None):
    output_r = output_1 = output_2 = None

    # check format
    fmt_segments = [None, None, None]
    if fmt:
        fmt_segments = fmt.split("/")
        if len(fmt_segments) == 3:
            r_fmt = fmt_segments[0]
            # only allow R or Rn
            if r_fmt == "R":
                output_r = "R"
            if r_fmt == "Rn":
                output_r = "R"
                if repeat:
                    if not isinstance(repeat, (int, long)) or repeat < 0:
                        raise TypeError(
                            "repeat must be an whole, non-negative number")
                    output_r += repeat

            # remove repeat format and proceed with other 2 arg formatting
            fmt_segments = fmt_segments[1:]
    else:
        if repeat:
            output_r = "R"
    if isinstance(arg1, datetime):
        str_1 = datetime_isoformat(arg1, fmt_segments[0])
        #assert (isinstance(arg2, datetime, time, date, timedelta))
        if isinstance(arg2, datetime):
            str_2 = datetime_isoformat(arg2, format=fmt_segments[1])
        elif isinstance(arg2, date):
            str_2 = date_isoformat(arg2, format=fmt_segments[1])
    elif isinstance(arg1, datetime.timedelta):
        str1 = duration_isoformat(arg1)
        #assert (isinstance(arg2, datetime))
    else:
        raise ISO8601Error(
            "arg1 must be a datetime, time, date, or timedelta object")
def parse_date(datestring, yeardigits=4, expanded=False, defaultmonth=1, defaultday=1):
    """
    Parse an ISO 8601 date string into a datetime.date object.

    As the datetime.date implementation is limited to dates starting from
    0001-01-01, negative dates (BC) and year 0 can not be parsed by this
    method.

    For incomplete dates, this method chooses the first day for it. For
    instance if only a century is given, this method returns the 1st of
    January in year 1 of this century.

    supported formats: (expanded formats are shown with 6 digits for year)
      YYYYMMDD    +-YYYYYYMMDD      basic complete date
      YYYY-MM-DD  +-YYYYYY-MM-DD    extended complete date
      YYYYWwwD    +-YYYYYYWwwD      basic complete week date
      YYYY-Www-D  +-YYYYYY-Www-D    extended complete week date
      YYYYDDD     +-YYYYYYDDD       basic ordinal date
      YYYY-DDD    +-YYYYYY-DDD      extended ordinal date
      YYYYWww     +-YYYYYYWww       basic incomplete week date
      YYYY-Www    +-YYYYYY-Www      extended incomplete week date
      YYYMM       +-YYYYYYMM        basic incomplete month date
      YYY-MM      +-YYYYYY-MM       incomplete month date
      YYYY        +-YYYYYY          incomplete year date
      YY          +-YYYY            incomplete century date

    @param datestring: the ISO date string to parse
    @param yeardigits: how many digits are used to represent a year
    @param expanded: if True then +/- signs are allowed. This parameter
                     is forced to True, if yeardigits != 4

    @return: a datetime.date instance represented by datestring
    @raise ISO8601Error: if this function can not parse the datestring
    @raise ValueError: if datestring can not be represented by datetime.date
    """
    if yeardigits != 4:
        expanded = True
    isodates = build_date_regexps(yeardigits, expanded)
    for pattern in isodates:
        match = pattern.match(datestring)
        if match:
            groups = match.groupdict()
            # sign, century, year, month, week, day,
            # FIXME: negative dates not possible with python standard types
            sign = (groups["sign"] == "-" and -1) or 1
            if "century" in groups:
                return date(
                    sign * (int(groups["century"]) * 100 + 1), defaultmonth, defaultday
                )
            if "month" not in groups:  # weekdate or ordinal date
                ret = date(sign * int(groups["year"]), 1, 1)
                if "week" in groups:
                    isotuple = ret.isocalendar()
                    if "day" in groups:
                        days = int(groups["day"] or 1)
                    else:
                        days = 1
                    # if first week in year, do weeks-1
                    return ret + timedelta(
                        weeks=int(groups["week"]) - (((isotuple[1] == 1) and 1) or 0),
                        days=-isotuple[2] + days,
                    )
                elif "day" in groups:  # ordinal date
                    return ret + timedelta(days=int(groups["day"]) - 1)
                else:  # year date
                    return ret.replace(month=defaultmonth, day=defaultday)
            # year-, month-, or complete date
            if "day" not in groups or groups["day"] is None:
                day = defaultday
            else:
                day = int(groups["day"])
            return date(
                sign * int(groups["year"]), int(groups["month"]) or defaultmonth, day
            )
    raise ISO8601Error("Unrecognised ISO 8601 date format: %r" % datestring)
Beispiel #9
0
def parse_duration(datestring):
    """
    Parses an ISO 8601 durations into datetime.timedelta or Duration objects.

    If the ISO date string does not contain years or months, a timedelta
    instance is returned, else a Duration instance is returned.

    The following duration formats are supported:
      -PnnW                  duration in weeks
      -PnnYnnMnnDTnnHnnMnnS  complete duration specification
      -PYYYYMMDDThhmmss      basic alternative complete date format
      -PYYYY-MM-DDThh:mm:ss  extended alternative complete date format
      -PYYYYDDDThhmmss       basic alternative ordinal date format
      -PYYYY-DDDThh:mm:ss    extended alternative ordinal date format

    The '-' is optional.

    Limitations:  ISO standard defines some restrictions about where to use
      fractional numbers and which component and format combinations are
      allowed. This parser implementation ignores all those restrictions and
      returns something when it is able to find all necessary components.
      In detail:
        it does not check, whether only the last component has fractions.
        it allows weeks specified with all other combinations

      The alternative format does not support durations with years, months or
      days set to 0.
    """
    if not isinstance(datestring, basestring):
        raise TypeError("Expecting a string %r" % datestring)
    match = ISO8601_PERIOD_REGEX.match(datestring)
    if not match:
        # try alternative format:
        if datestring.startswith("P"):
            durdt = parse_datetime(datestring[1:])
            if durdt.year != 0 or durdt.month != 0:
                # create Duration
                ret = Duration(days=durdt.day,
                               seconds=durdt.second,
                               microseconds=durdt.microsecond,
                               minutes=durdt.minute,
                               hours=durdt.hour,
                               months=durdt.month,
                               years=durdt.year)
            else:  # FIXME: currently not possible in alternative format
                # create timedelta
                ret = timedelta(days=durdt.day,
                                seconds=durdt.second,
                                microseconds=durdt.microsecond,
                                minutes=durdt.minute,
                                hours=durdt.hour)
            return ret
        raise ISO8601Error("Unable to parse duration string %r" % datestring)
    groups = match.groupdict()
    for key, val in groups.items():
        if key not in ('separator', 'sign'):
            if val is None:
                groups[key] = "0n"
            #print groups[key]
            if key in ('years', 'months'):
                groups[key] = Decimal(groups[key][:-1].replace(',', '.'))
            else:
                # these values are passed into a timedelta object, which works with floats.
                groups[key] = float(groups[key][:-1].replace(',', '.'))
    if groups["years"] == 0 and groups["months"] == 0:
        ret = timedelta(days=groups["days"],
                        hours=groups["hours"],
                        minutes=groups["minutes"],
                        seconds=groups["seconds"],
                        weeks=groups["weeks"])
        if groups["sign"] == '-':
            ret = timedelta(0) - ret
    else:
        ret = Duration(years=groups["years"],
                       months=groups["months"],
                       days=groups["days"],
                       hours=groups["hours"],
                       minutes=groups["minutes"],
                       seconds=groups["seconds"],
                       weeks=groups["weeks"])
        if groups["sign"] == '-':
            ret = Duration(0) - ret
    return ret
Beispiel #10
0
def parse_interval(interval_string):
    if not isinstance(interval_string, string_types):
        raise TypeError("Expecing a string")

    segment_count = interval_string.count(SEGMENT_DELIM)
    if segment_count < 1 or segment_count > 2:
        raise ISO8601Error(
            "Improper number of interval string segments. Must have 1 or 2")

    segments = interval_string.split(SEGMENT_DELIM)
    for idx, seg in enumerate(segments):
        if len(seg) == 0:
            return ISO8601Error("Interval segment index %s was empty" % idx)

    count = None
    if len(segments) == 3:
        # Rn/start/end
        # Rn/start/duration
        # Rn/duration/end
        s0 = segments[0]
        match = ISO8601_REPEAT_REGEX.match(s0)
        if not match:
            raise ISO8601Error("Repeat notation did not match expected")
        groups = match.groupdict()
        count = groups.get("count", None)
        if len(count) > 0:
            count = int(count)
        segments = segments[1:]

    s0 = segments[0]
    s1 = segments[1]
    # remaining segments are either
    # 1) start/end.
    #     start must be a fully specified datetime format
    #     end can either be a time, date, or datetime
    # 2) start/duration
    #     start must be a fully specified datetime format
    #     duration must be a valid duration format
    # 3) duration/end
    #     duration must be a valid duration format
    #     end must be a fully specified datetime format
    start = None
    end = None
    duration = None
    try:  # (1)
        start = parse_datetime(s0)
        print("second to last term is a datetime")
    except:
        try:
            duration = parse_duration(s0)
            print("second to last term is a datetime")
        except:
            raise ISO8601Error(
                "First term after repeat must be either " +
                "a fully specified datetime or a valid duration")
    # look at last term
    # this isn't the prettiest way to do it, but it is effective
    # could also build the regexes from other modules, but delegation avoids code duplication
    if start:
        # last term must be a duration, date, time or datetime
        try:
            end = parse_datetime(s1)
            print("last term is a datetime")
        except:
            try:
                end = parse_date(s1)
                print("last term is a date")
            except:
                try:
                    end = parse_time(s1)
                    print("last term is a time")
                except:
                    try:
                        duration = parse_duration(s1)
                        print("last term is a duration")
                    except:
                        raise ISO8601Error(
                            "When first term after repeat is a datetime, " +
                            "last term must be either a duration, datetime, date, or time"
                        )
    elif duration:
        # last term must be the end datetime
        try:
            end = parse_datetime(s1)
        except:
            raise ISO8601Error("If first term after repeat is a duration, " +
                               "last term must be a datetime")

    interval = Interval(start=start, end=end, duration=duration, repeat=count)
    print(interval)
Beispiel #11
0
def parse_time(timestring):
    """
    Parses ISO 8601 times into datetime.time objects.

    Following ISO 8601 formats are supported:
      (as decimal separator a ',' or a '.' is allowed)
      hhmmss.ssTZD    basic complete time
      hh:mm:ss.ssTZD  extended complete time
      hhmm.mmTZD      basic reduced accuracy time
      hh:mm.mmTZD     extended reduced accuracy time
      hh.hhTZD        basic reduced accuracy time
    TZD is the time zone designator which can be in the following format:
              no designator indicates local time zone
      Z       UTC
      +-hhmm  basic hours and minutes
      +-hh:mm extended hours and minutes
      +-hh    hours
    """
    isotimes = build_time_regexps()
    for pattern in isotimes:
        match = pattern.match(timestring)
        if match:
            groups = match.groupdict()
            for key, value in groups.items():
                if value is not None:
                    groups[key] = value.replace(",", ".")
            tzinfo = build_tzinfo(
                groups["tzname"],
                groups["tzsign"],
                int(groups["tzhour"] or 0),
                int(groups["tzmin"] or 0),
            )
            if "second" in groups:
                second = Decimal(groups["second"]).quantize(
                    Decimal(".000001"), rounding=ROUND_FLOOR
                )
                microsecond = (second - int(second)) * int(1e6)
                # int(...) ... no rounding
                # to_integral() ... rounding
                return time(
                    int(groups["hour"]),
                    int(groups["minute"]),
                    int(second),
                    int(microsecond.to_integral()),
                    tzinfo,
                )
            if "minute" in groups:
                minute = Decimal(groups["minute"])
                second = Decimal((minute - int(minute)) * 60).quantize(
                    Decimal(".000001"), rounding=ROUND_FLOOR
                )
                microsecond = (second - int(second)) * int(1e6)
                return time(
                    int(groups["hour"]),
                    int(minute),
                    int(second),
                    int(microsecond.to_integral()),
                    tzinfo,
                )
            else:
                microsecond, second, minute = 0, 0, 0
            hour = Decimal(groups["hour"])
            minute = (hour - int(hour)) * 60
            second = (minute - int(minute)) * 60
            microsecond = (second - int(second)) * int(1e6)
            return time(
                int(hour),
                int(minute),
                int(second),
                int(microsecond.to_integral()),
                tzinfo,
            )
    raise ISO8601Error("Unrecognised ISO 8601 time format: %r" % timestring)