# -*- coding: utf-8 -*- # Copyright (c) 2018, 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 datetime import datetime from aniso8601.duration import parse_duration from aniso8601.exceptions import ISOFormatError from aniso8601.time import parse_datetime from aniso8601.date import parse_date def parse_interval(isointervalstr, intervaldelimiter='/', datetimedelimiter='T', relative=False): #Given a string representing an ISO 8601 interval, return a #tuple of datetime.date or date.datetime objects representing the beginning #and end of the specified interval. Valid formats are: # #/ #/ #/ # #The and values can represent dates, or datetimes, #not times. # #The format: # # # #Is expressly not supported as there is no way to provide the addtional #required context. if isointervalstr[0] == 'R': raise ISOFormatError('ISO 8601 repeating intervals must be parsed with parse_repeating_interval.') interval_parts = _parse_interval_parts(isointervalstr, intervaldelimiter, datetimedelimiter, relative) return (interval_parts[0], interval_parts[1]) def parse_repeating_interval(isointervalstr, intervaldelimiter='/', datetimedelimiter='T', relative=False): #Given a string representing an ISO 8601 interval repating, return a #generator of datetime.date or date.datetime objects representing the #dates specified by the repeating interval. Valid formats are: # #Rnn/ #R/ 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: iterations = int(iterationpart[1:]) else: iterations = None interval_parts = _parse_interval_parts(intervalpart, intervaldelimiter, datetimedelimiter, relative=relative) #Now, build and return the generator if iterations is not None: return _date_generator(interval_parts[0], interval_parts[2], iterations) return _date_generator_unbounded(interval_parts[0], interval_parts[2]) def _parse_interval_parts(isointervalstr, intervaldelimiter='/', datetimedelimiter='T', relative=False): #Returns a tuple containing the start of the interval, the end of the interval, and the interval timedelta firstpart, secondpart = isointervalstr.split(intervaldelimiter) if firstpart[0] == 'P': #/ #Notice that these are not returned 'in order' (earlier to later), this #is to maintain consistency with parsing / durations, as #well as making repeating interval code cleaner. Users who desire #durations to be in order can use the 'sorted' operator. #We need to figure out if is a date, or a datetime if secondpart.find(datetimedelimiter) != -1: # is a datetime duration = parse_duration(firstpart, relative=relative) enddatetime = parse_datetime(secondpart, delimiter=datetimedelimiter) return (enddatetime, enddatetime - duration, -duration) # must just be a date duration = parse_duration(firstpart, relative=relative) enddate = parse_date(secondpart) #See if we need to upconvert to datetime to preserve resolution if firstpart.find(datetimedelimiter) != -1: return (enddate, datetime.combine(enddate, datetime.min.time()) - duration, -duration) return (enddate, enddate - duration, -duration) elif secondpart[0] == 'P': #/ #We need to figure out if is a date, or a datetime if firstpart.find(datetimedelimiter) != -1: # is a datetime duration = parse_duration(secondpart, relative=relative) startdatetime = parse_datetime(firstpart, delimiter=datetimedelimiter) return (startdatetime, startdatetime + duration, duration) # must just be a date duration = parse_duration(secondpart, relative=relative) startdate = parse_date(firstpart) #See if we need to upconvert to datetime to preserve resolution if secondpart.find(datetimedelimiter) != -1: return (startdate, datetime.combine(startdate, datetime.min.time()) + duration, duration) return (startdate, startdate + duration, duration) #/ if firstpart.find(datetimedelimiter) != -1 and secondpart.find(datetimedelimiter) != -1: #Both parts are datetimes start_datetime = parse_datetime(firstpart, delimiter=datetimedelimiter) end_datetime = parse_datetime(secondpart, delimiter=datetimedelimiter) return (start_datetime, end_datetime, end_datetime - start_datetime) elif firstpart.find(datetimedelimiter) != -1 and secondpart.find(datetimedelimiter) == -1: #First part is a datetime, second part is a date start_datetime = parse_datetime(firstpart, delimiter=datetimedelimiter) end_date = parse_date(secondpart) return (start_datetime, end_date, datetime.combine(end_date, datetime.min.time()) - start_datetime) elif firstpart.find(datetimedelimiter) == -1 and secondpart.find(datetimedelimiter) != -1: #First part is a date, second part is a datetime start_date = parse_date(firstpart) end_datetime = parse_datetime(secondpart, delimiter=datetimedelimiter) return (start_date, end_datetime, end_datetime - datetime.combine(start_date, datetime.min.time())) #Both parts are dates start_date = parse_date(firstpart) end_date = parse_date(secondpart) return (start_date, end_date, end_date - start_date) def _date_generator(startdate, timedelta, iterations): currentdate = startdate currentiteration = 0 while currentiteration < iterations: yield currentdate #Update the values currentdate += timedelta currentiteration += 1 def _date_generator_unbounded(startdate, timedelta): currentdate = startdate while True: yield currentdate #Update the value currentdate += timedelta