def test_parse_date_badstr(self): testtuples = ('W53', '2004-W', '2014-01-230', '2014-012-23', '201-01-23', '201401230', '201401', 'bad', '') for testtuple in testtuples: with self.assertRaises(ISOFormatError): parse_date(testtuple, builder=None)
def _parse_interval(isointervalstr, builder, intervaldelimiter='/', datetimedelimiter='T'): #Returns a tuple containing the start of the interval, the end of the #interval, and or the interval duration firstpart, secondpart = isointervalstr.split(intervaldelimiter) if len(firstpart) == 0 or len(secondpart) == 0: raise ISOFormatError( '{0} is not a valid ISO 8601 interval'.format(isointervalstr)) if firstpart[0] == 'P': #<duration>/<end> #Notice that these are not returned 'in order' (earlier to later), this #is to maintain consistency with parsing <start>/<end> durations, as #well as making repeating interval code cleaner. Users who desire #durations to be in order can use the 'sorted' operator. duration = parse_duration(firstpart, builder=TupleBuilder) #We need to figure out if <end> is a date, or a datetime if secondpart.find(datetimedelimiter) != -1: #<end> is a datetime endtuple = parse_datetime(secondpart, delimiter=datetimedelimiter, builder=TupleBuilder) else: endtuple = parse_date(secondpart, builder=TupleBuilder) return builder.build_interval(end=endtuple, duration=duration) elif secondpart[0] == 'P': #<start>/<duration> #We need to figure out if <start> is a date, or a datetime duration = parse_duration(secondpart, builder=TupleBuilder) if firstpart.find(datetimedelimiter) != -1: #<start> is a datetime starttuple = parse_datetime(firstpart, delimiter=datetimedelimiter, builder=TupleBuilder) else: #<start> must just be a date starttuple = parse_date(firstpart, builder=TupleBuilder) return builder.build_interval(start=starttuple, duration=duration) #<start>/<end> if firstpart.find(datetimedelimiter) != -1: #Both parts are datetimes starttuple = parse_datetime(firstpart, delimiter=datetimedelimiter, builder=TupleBuilder) else: starttuple = parse_date(firstpart, builder=TupleBuilder) endtuple = _parse_interval_end(secondpart, starttuple, datetimedelimiter) return builder.build_interval(start=starttuple, end=endtuple)
def test_parse_date(self): testtuples = ( ("2013", {"YYYY": "2013"}), ("0001", {"YYYY": "0001"}), ("19", {"YYYY": "19"}), ("1981-04-05", {"YYYY": "1981", "MM": "04", "DD": "05"}), ("19810405", {"YYYY": "1981", "MM": "04", "DD": "05"}), ("1981-04", {"YYYY": "1981", "MM": "04"}), ("2004-W53", {"YYYY": "2004", "Www": "53"}), ("2009-W01", {"YYYY": "2009", "Www": "01"}), ("2004-W53-6", {"YYYY": "2004", "Www": "53", "D": "6"}), ("2004W53", {"YYYY": "2004", "Www": "53"}), ("2004W536", {"YYYY": "2004", "Www": "53", "D": "6"}), ("1981-095", {"YYYY": "1981", "DDD": "095"}), ("1981095", {"YYYY": "1981", "DDD": "095"}), ) for testtuple in testtuples: with mock.patch.object( aniso8601.date.PythonTimeBuilder, "build_date" ) as mockBuildDate: mockBuildDate.return_value = testtuple[1] result = parse_date(testtuple[0]) self.assertEqual(result, testtuple[1]) mockBuildDate.assert_called_once_with(**testtuple[1])
def _parse_duration_combined(durationstr, relative): #Period of the form P<date>T<time> #Split the string in to its component parts datepart, timepart = durationstr[1:].split('T') #We skip the 'P' datevalue = parse_date(datepart) timevalue = parse_time(timepart) if relative is True: try: import dateutil.relativedelta return dateutil.relativedelta.relativedelta( years=datevalue.year, months=datevalue.month, days=datevalue.day, hours=timevalue.hour, minutes=timevalue.minute, seconds=timevalue.second, microseconds=timevalue.microsecond) except ImportError: raise RuntimeError( 'dateutil must be installed for relative duration support.') else: totaldays = datevalue.year * 365 + datevalue.month * 30 + datevalue.day return datetime.timedelta(days=totaldays, hours=timevalue.hour, minutes=timevalue.minute, seconds=timevalue.second, microseconds=timevalue.microsecond)
def test_parse_date_badstr(self): testtuples = ( "W53", "2004-W", "2014-01-230", "2014-012-23", "201-01-23", "201401230", "201401", "9999 W53", "20.50230", "198104", "bad", "", ) for testtuple in testtuples: with self.assertRaises(ISOFormatError): parse_date(testtuple, builder=None)
def test_parse_date_mockbuilder(self): mockBuilder = mock.Mock() expectedargs = {"YYYY": "1981", "MM": "04", "DD": "05"} mockBuilder.build_date.return_value = expectedargs result = parse_date("1981-04-05", builder=mockBuilder) self.assertEqual(result, expectedargs) mockBuilder.build_date.assert_called_once_with(**expectedargs)
def test_parse_date_mockbuilder(self): mockBuilder = mock.Mock() expectedargs = {'YYYY': '1981', 'MM': '04', 'DD': '05'} mockBuilder.build_date.return_value = expectedargs result = parse_date('1981-04-05', builder=mockBuilder) self.assertEqual(result, expectedargs) mockBuilder.build_date.assert_called_once_with(**expectedargs)
def _parse_duration_combined(durationstr, builder): #Period of the form P<date>T<time> #Split the string in to its component parts datepart, timepart = durationstr[1:].split('T') #We skip the 'P' datevalue = parse_date(datepart, builder=TupleBuilder) timevalue = parse_time(timepart, builder=TupleBuilder) return builder.build_duration(PnY=datevalue[0], PnM=datevalue[1], PnD=datevalue[2], TnH=timevalue[0], TnM=timevalue[1], TnS=timevalue[2])
def _parse_duration_combined(durationstr): #Period of the form P<date>T<time> #Split the string in to its component parts datepart, timepart = durationstr[1:].split('T') #We skip the 'P' datevalue = parse_date(datepart) timevalue = parse_time(timepart) totaldays = datevalue.year * 365 + datevalue.month * 30 + datevalue.day return datetime.timedelta(days=totaldays, hours=timevalue.hour, minutes=timevalue.minute, seconds=timevalue.second, microseconds=timevalue.microsecond)
def test_parse_date(self): testtuples = (('2013', { 'YYYY': '2013' }), ('0001', { 'YYYY': '0001' }), ('19', { 'YYYY': '19' }), ('1981-04-05', { 'YYYY': '1981', 'MM': '04', 'DD': '05' }), ('19810405', { 'YYYY': '1981', 'MM': '04', 'DD': '05' }), ('1981-04', { 'YYYY': '1981', 'MM': '04' }), ('2004-W53', { 'YYYY': '2004', 'Www': '53' }), ('2009-W01', { 'YYYY': '2009', 'Www': '01' }), ('2004-W53-6', { 'YYYY': '2004', 'Www': '53', 'D': '6' }), ('2004W53', { 'YYYY': '2004', 'Www': '53' }), ('2004W536', { 'YYYY': '2004', 'Www': '53', 'D': '6' }), ('1981-095', { 'YYYY': '1981', 'DDD': '095' }), ('1981095', { 'YYYY': '1981', 'DDD': '095' })) for testtuple in testtuples: with mock.patch.object(aniso8601.builder.PythonTimeBuilder, 'build_date') as mockBuildDate: mockBuildDate.return_value = testtuple[1] result = parse_date(testtuple[0]) self.assertEqual(result, testtuple[1]) mockBuildDate.assert_called_once_with(**testtuple[1])
def parse_datetime(isodatetimestr, delimiter='T'): #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. isodatestr, isotimestr = isodatetimestr.split(delimiter) datepart = parse_date(isodatestr) timepart = parse_time(isotimestr) return datetime.datetime.combine(datepart, timepart)
def _parse_duration_combined(durationstr): #Period of the form P<date>T<time> #Split the string in to its component parts datepart, timepart = durationstr[1:].split('T', 1) #We skip the 'P' datevalue = parse_date(datepart, builder=TupleBuilder) timevalue = parse_time(timepart, builder=TupleBuilder) return { 'PnY': datevalue.YYYY, 'PnM': datevalue.MM, 'PnD': datevalue.DD, 'TnH': timevalue.hh, 'TnM': timevalue.mm, 'TnS': timevalue.ss }
def _parse_duration_combined(durationstr): # Period of the form P<date>T<time> # Split the string in to its component parts datepart, timepart = durationstr[1:].split("T", 1) # We skip the 'P' datevalue = parse_date(datepart, builder=TupleBuilder) timevalue = parse_time(timepart, builder=TupleBuilder) return { "PnY": datevalue.YYYY, "PnM": datevalue.MM, "PnD": datevalue.DD, "TnH": timevalue.hh, "TnM": timevalue.mm, "TnS": timevalue.ss, }
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)
def _parse_duration_combined(durationstr, relative): #Period of the form P<date>T<time> #Split the string in to its component parts datepart, timepart = durationstr[1:].split('T') #We skip the 'P' datevalue = parse_date(datepart) timevalue = parse_time(timepart) if relative is True: try: import dateutil.relativedelta return dateutil.relativedelta.relativedelta(years=datevalue.year, months=datevalue.month, days=datevalue.day, hours=timevalue.hour, minutes=timevalue.minute, seconds=timevalue.second, microseconds=timevalue.microsecond) except ImportError: raise RuntimeError('dateutil must be installed for relative duration support.') else: totaldays = datevalue.year * 365 + datevalue.month * 30 + datevalue.day return datetime.timedelta(days=totaldays, hours=timevalue.hour, minutes=timevalue.minute, seconds=timevalue.second, microseconds=timevalue.microsecond)
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: # #<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 addtional #required context. firstpart, secondpart = isointervalstr.split(intervaldelimiter) if firstpart[0] == 'P': #<duration>/<end> #Notice that these are not returned 'in order' (earlier to later), this #is to maintain consistency with parsing <start>/<end> 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 <end> is a date, or a datetime if secondpart.find(datetimedelimiter) != -1: #<end> is a datetime duration = parse_duration(firstpart, relative=relative) enddatetime = parse_datetime(secondpart, delimiter=datetimedelimiter) return (enddatetime, enddatetime - duration) else: #<end> 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) else: return (enddate, enddate - duration) elif secondpart[0] == 'P': #<start>/<duration> #We need to figure out if <start> is a date, or a datetime if firstpart.find(datetimedelimiter) != -1: #<end> is a datetime duration = parse_duration(secondpart, relative=relative) startdatetime = parse_datetime(firstpart, delimiter=datetimedelimiter) return (startdatetime, startdatetime + duration) else: #<start> 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) else: return (startdate, startdate + duration) else: #<start>/<end> if firstpart.find(datetimedelimiter) != -1 and secondpart.find( datetimedelimiter) != -1: #Both parts are datetimes return (parse_datetime(firstpart, delimiter=datetimedelimiter), parse_datetime(secondpart, delimiter=datetimedelimiter)) elif firstpart.find(datetimedelimiter) != -1 and secondpart.find( datetimedelimiter) == -1: #First part is a datetime, second part is a date return (parse_datetime(firstpart, delimiter=datetimedelimiter), parse_date(secondpart)) elif firstpart.find(datetimedelimiter) == -1 and secondpart.find( datetimedelimiter) != -1: #First part is a date, second part is a datetime return (parse_date(firstpart), parse_datetime(secondpart, delimiter=datetimedelimiter)) else: #Both parts are dates return (parse_date(firstpart), parse_date(secondpart))
def test_parse_date(self): date = parse_date('2013') self.assertEqual(date.year, 2013) self.assertEqual(date.month, 1) self.assertEqual(date.day, 1) date = parse_date('0001') self.assertEqual(date.year, 1) self.assertEqual(date.month, 1) self.assertEqual(date.day, 1) date = parse_date('19') self.assertEqual(date.year, 1900) self.assertEqual(date.month, 1) self.assertEqual(date.day, 1) date = parse_date('1981-04-05') self.assertEqual(date.year, 1981) self.assertEqual(date.month, 4) self.assertEqual(date.day, 5) date = parse_date('19810405') self.assertEqual(date.year, 1981) self.assertEqual(date.month, 4) self.assertEqual(date.day, 5) date = parse_date('1981-04') self.assertEqual(date.year, 1981) self.assertEqual(date.month, 4) self.assertEqual(date.day, 1) date = parse_date('2004-W53') self.assertEqual(date.year, 2004) self.assertEqual(date.month, 12) self.assertEqual(date.weekday(), 0) date = parse_date('2009-W01') self.assertEqual(date.year, 2008) self.assertEqual(date.month, 12) self.assertEqual(date.weekday(), 0) date = parse_date('2004-W53-6') self.assertEqual(date.year, 2005) self.assertEqual(date.month, 1) self.assertEqual(date.day, 1) date = parse_date('2004W53') self.assertEqual(date.year, 2004) self.assertEqual(date.month, 12) self.assertEqual(date.weekday(), 0) date = parse_date('2004W536') self.assertEqual(date.year, 2005) self.assertEqual(date.month, 1) self.assertEqual(date.day, 1) date = parse_date('1981-095') self.assertEqual(date.year, 1981) self.assertEqual(date.month, 4) self.assertEqual(date.day, 5) date = parse_date('1981095') self.assertEqual(date.year, 1981) self.assertEqual(date.month, 4) self.assertEqual(date.day, 5)
def test_parse_date(self): date = parse_date('2013') self.assertEqual(date, datetime.date(2013, 1, 1)) date = parse_date('0001') self.assertEqual(date, datetime.date(1, 1, 1)) date = parse_date('19') self.assertEqual(date, datetime.date(1900, 1, 1)) date = parse_date('1981-04-05') self.assertEqual(date, datetime.date(1981, 4, 5)) date = parse_date('19810405') self.assertEqual(date, datetime.date(1981, 4, 5)) date = parse_date('1981-04') self.assertEqual(date, datetime.date(1981, 4, 1)) date = parse_date('2004-W53') self.assertEqual(date, datetime.date(2004, 12, 27)) self.assertEqual(date.weekday(), 0) date = parse_date('2009-W01') self.assertEqual(date, datetime.date(2008, 12, 29)) self.assertEqual(date.weekday(), 0) date = parse_date('2004-W53-6') self.assertEqual(date, datetime.date(2005, 1, 1)) self.assertEqual(date.weekday(), 5) date = parse_date('2004W53') self.assertEqual(date, datetime.date(2004, 12, 27)) self.assertEqual(date.weekday(), 0) date = parse_date('2004W536') self.assertEqual(date, datetime.date(2005, 1, 1)) self.assertEqual(date.weekday(), 5) date = parse_date('1981-095') self.assertEqual(date, datetime.date(1981, 4, 5)) date = parse_date('1981095') self.assertEqual(date, datetime.date(1981, 4, 5))
def test_parse_date_badtype(self): testtuples = (None, 1, False, 1.234) for testtuple in testtuples: with self.assertRaises(ValueError): parse_date(testtuple, builder=None)
def test_parse_date_bounds(self): #0 isn't a valid week number with self.assertRaises(WeekOutOfBoundsError): parse_date('2003-W00') with self.assertRaises(WeekOutOfBoundsError): parse_date('2003W00') #Week must not be larger than 53 with self.assertRaises(WeekOutOfBoundsError): parse_date('2004-W54') with self.assertRaises(WeekOutOfBoundsError): parse_date('2004W54') #0 isn't a valid day number with self.assertRaises(DayOutOfBoundsError): parse_date('2001-W02-0') with self.assertRaises(DayOutOfBoundsError): parse_date('2001W020') #Day must not be larger than 7 with self.assertRaises(DayOutOfBoundsError): parse_date('2001-W02-8') with self.assertRaises(DayOutOfBoundsError): parse_date('2001W028')
def _parse_interval( isointervalstr, builder, intervaldelimiter="/", datetimedelimiter="T" ): # Returns a tuple containing the start of the interval, the end of the # interval, and or the interval duration firstpart, secondpart = isointervalstr.split(intervaldelimiter) if firstpart[0] == "P": # <duration>/<end> # Notice that these are not returned 'in order' (earlier to later), this # is to maintain consistency with parsing <start>/<end> 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 <end> is a date, or a datetime if secondpart.find(datetimedelimiter) != -1: # <end> is a datetime duration = parse_duration(firstpart, builder=TupleBuilder) enddatetime = parse_datetime( secondpart, delimiter=datetimedelimiter, builder=TupleBuilder ) return builder.build_interval(end=enddatetime, duration=duration) # <end> must just be a date duration = parse_duration(firstpart, builder=TupleBuilder) enddate = parse_date(secondpart, builder=TupleBuilder) return builder.build_interval(end=enddate, duration=duration) elif secondpart[0] == "P": # <start>/<duration> # We need to figure out if <start> is a date, or a datetime if firstpart.find(datetimedelimiter) != -1: # <start> is a datetime duration = parse_duration(secondpart, builder=TupleBuilder) startdatetime = parse_datetime( firstpart, delimiter=datetimedelimiter, builder=TupleBuilder ) return builder.build_interval(start=startdatetime, duration=duration) # <start> must just be a date duration = parse_duration(secondpart, builder=TupleBuilder) startdate = parse_date(firstpart, builder=TupleBuilder) return builder.build_interval(start=startdate, duration=duration) # <start>/<end> if ( firstpart.find(datetimedelimiter) != -1 and secondpart.find(datetimedelimiter) != -1 ): # Both parts are datetimes start_datetime = parse_datetime( firstpart, delimiter=datetimedelimiter, builder=TupleBuilder ) end_datetime = parse_datetime( secondpart, delimiter=datetimedelimiter, builder=TupleBuilder ) return builder.build_interval(start=start_datetime, end=end_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, builder=TupleBuilder ) end_date = parse_date(secondpart, builder=TupleBuilder) return builder.build_interval(start=start_datetime, end=end_date) 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, builder=TupleBuilder) end_datetime = parse_datetime( secondpart, delimiter=datetimedelimiter, builder=TupleBuilder ) return builder.build_interval(start=start_date, end=end_datetime) # Both parts are dates start_date = parse_date(firstpart, builder=TupleBuilder) end_date = parse_date(secondpart, builder=TupleBuilder) return builder.build_interval(start=start_date, end=end_date)
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': #<duration>/<end> #Notice that these are not returned 'in order' (earlier to later), this #is to maintain consistency with parsing <start>/<end> durations, as #well as making repeating interval code cleaner. users_dispatcher who desire #durations to be in order can use the 'sorted' operator. #We need to figure out if <end> is a date, or a datetime if secondpart.find(datetimedelimiter) != -1: #<end> is a datetime duration = parse_duration(firstpart, relative=relative) enddatetime = parse_datetime(secondpart, delimiter=datetimedelimiter) return (enddatetime, enddatetime - duration, -duration) else: #<end> 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) else: return (enddate, enddate - duration, -duration) elif secondpart[0] == 'P': #<start>/<duration> #We need to figure out if <start> is a date, or a datetime if firstpart.find(datetimedelimiter) != -1: #<start> is a datetime duration = parse_duration(secondpart, relative=relative) startdatetime = parse_datetime(firstpart, delimiter=datetimedelimiter) return (startdatetime, startdatetime + duration, duration) else: #<start> 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) else: return (startdate, startdate + duration, duration) else: #<start>/<end> 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())) else: #Both parts are dates start_date = parse_date(firstpart) end_date = parse_date(secondpart) return (start_date, end_date, end_date - start_date)
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': #<duration>/<end> #Notice that these are not returned 'in order' (earlier to later), this #is to maintain consistency with parsing <start>/<end> 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 <end> is a date, or a datetime if secondpart.find(datetimedelimiter) != -1: #<end> is a datetime duration = parse_duration(firstpart, relative=relative) enddatetime = parse_datetime(secondpart, delimiter=datetimedelimiter) return (enddatetime, enddatetime - duration, -duration) else: #<end> 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) else: return (enddate, enddate - duration, -duration) elif secondpart[0] == 'P': #<start>/<duration> #We need to figure out if <start> is a date, or a datetime if firstpart.find(datetimedelimiter) != -1: #<start> is a datetime duration = parse_duration(secondpart, relative=relative) startdatetime = parse_datetime(firstpart, delimiter=datetimedelimiter) return (startdatetime, startdatetime + duration, duration) else: #<start> 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) else: return (startdate, startdate + duration, duration) else: #<start>/<end> 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())) else: #Both parts are dates start_date = parse_date(firstpart) end_date = parse_date(secondpart) return (start_date, end_date, end_date - start_date)
def parse_interval(isointervalstr, intervaldelimiter='/', datetimedelimiter='T'): #Given a string representing an ISO8601 interval, return a #tuple of datetime.date or date.datetime objects representing the beginning #and end of the specified interval. 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 addtional #required context. firstpart, secondpart = isointervalstr.split(intervaldelimiter) if firstpart[0] == 'P': #<duration>/<end> #Notice that these are not returned 'in order' (earlier to later), this #is to maintain consistency with parsing <start>/<end> 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 <end> is a date, or a datetime if secondpart.find(datetimedelimiter) != -1: #<end> is a datetime duration = parse_duration(firstpart) enddatetime = parse_datetime(secondpart, delimiter=datetimedelimiter) return (enddatetime, enddatetime - duration) else: #<end> must just be a date duration = parse_duration(firstpart) 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) else: return (enddate, enddate - duration) elif secondpart[0] == 'P': #<start>/<duration> #We need to figure out if <start> is a date, or a datetime if firstpart.find(datetimedelimiter) != -1: #<end> is a datetime duration = parse_duration(secondpart) startdatetime = parse_datetime(firstpart, delimiter=datetimedelimiter) return (startdatetime, startdatetime + duration) else: #<start> must just be a date duration = parse_duration(secondpart) 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) else: return (startdate, startdate + duration) else: #<start>/<end> if firstpart.find(datetimedelimiter) != -1 and secondpart.find(datetimedelimiter) != -1: #Both parts are datetimes return (parse_datetime(firstpart, delimiter=datetimedelimiter), parse_datetime(secondpart, delimiter=datetimedelimiter)) elif firstpart.find(datetimedelimiter) != -1 and secondpart.find(datetimedelimiter) == -1: #First part is a datetime, second part is a date return (parse_datetime(firstpart, delimiter=datetimedelimiter), parse_date(secondpart)) elif firstpart.find(datetimedelimiter) == -1 and secondpart.find(datetimedelimiter) != -1: #First part is a date, second part is a datetime return (parse_date(firstpart), parse_datetime(secondpart, delimiter=datetimedelimiter)) else: #Both parts are dates return (parse_date(firstpart), parse_date(secondpart))
def _parse_interval_end(endstr, starttuple, datetimedelimiter): datestr = None timestr = None monthstr = None daystr = None concise = False if type(starttuple) is DateTuple: startdatetuple = starttuple else: #Start is a datetime startdatetuple = starttuple.date if datetimedelimiter in endstr: datestr, timestr = endstr.split(datetimedelimiter, 1) elif ':' in endstr: timestr = endstr else: datestr = endstr if timestr is not None: endtimetuple = parse_time(timestr, builder=TupleBuilder) #End is just a time if datestr is None: return endtimetuple #Handle backwards concise representation if datestr.count('-') == 1: monthstr, daystr = datestr.split('-') concise = True elif len(datestr) <= 2: daystr = datestr concise = True elif len(datestr) <= 4: monthstr = datestr[0:2] daystr = datestr[2:] concise = True if concise is True: concisedatestr = startdatetuple.YYYY #Separators required because concise elements may be missing digits if monthstr is not None: concisedatestr += '-' + monthstr elif startdatetuple.MM is not None: concisedatestr += '-' + startdatetuple.MM concisedatestr += '-' + daystr enddatetuple = parse_date(concisedatestr, builder=TupleBuilder) #Clear unsupplied components if monthstr is None: enddatetuple = TupleBuilder.build_date(DD=enddatetuple.DD) else: #Year not provided enddatetuple = TupleBuilder.build_date(MM=enddatetuple.MM, DD=enddatetuple.DD) else: enddatetuple = parse_date(datestr, builder=TupleBuilder) if timestr is None: return enddatetuple return TupleBuilder.build_datetime(enddatetuple, endtimetuple)