Commits

Brandon Nielsen  committed 485e4bf

Added repeating intervals.

  • Participants
  • Parent commits 417551e

Comments (0)

Files changed (2)

File aniso8601/__init__.py

 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
-    #an end of the specified interval. Valid formats are:
+    #and end of the specified interval. Valid formats are:
     #
     #<start>/<end>
     #<start>/<duration>
 
     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 asmaking 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 - duration, enddatetime)
+            return (enddatetime, enddatetime - duration)
         else:
             #<end> must just be a date
             duration = parse_duration(firstpart)
             enddate = parse_date(secondpart)
 
-            return (enddate - duration, enddate)
+            return (enddate, enddate - duration)
     elif secondpart[0] == 'P':
         #<start>/<duration>
         #We need to figure out if <start> is a date, or a datetime
             #Both parts are dates
             return (parse_date(firstpart), parse_date(secondpart))
 
+def parse_repeating_interval(isointervalstr, intervaldelimiter='/', datetimedelimiter='T'):
+    #Given a string representing an ISO8601 interval repating, return a
+    #generator of datetime.date or date.datetime objects representing the
+    #dates specified by the repeating interval. Valid formats are:
+    #
+    #Rnn/<interval>
+    #R/<interval>
+
+    if isointervalstr[0] != 'R':
+        raise ValueError('String is not a valid ISO8601 repeating interval.')
+
+    #Parse the number of iterations
+    iterationpart, intervalpart = isointervalstr.split(intervaldelimiter, 1)
+
+    if len(iterationpart) > 1:
+        iterations = int(iterationpart[1:])
+    else:
+        iterations = None
+
+    interval = parse_interval(intervalpart, intervaldelimiter, datetimedelimiter)
+
+    intervaltimedelta = interval[1] - interval[0]
+
+    #Now, build and return the generator
+    if iterations != None:
+        return date_generator(interval[0], intervaltimedelta, iterations)
+    else:
+        return date_generator_unbounded(interval[0], intervaltimedelta)
+
 def parse_year(yearstr):
     #yearstr is of the format Y[YYY]
     #
     #Return the start of the year
     return fourth_jan - delta
 
+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
+
 class UTCOffset(datetime.tzinfo):
     def __init__(self, name, utcdelta):
         self._name = name

File aniso8601/test_aniso8601.py

 
     def test_parse_interval(self):
         resultinterval = aniso8601.parse_interval('P1M/1981-04-05T01:01:00')
-        self.assertEqual(resultinterval[0], datetime.datetime(year=1981, month=3, day=6, hour=1, minute=1))
-        self.assertEqual(resultinterval[1], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1))
+        self.assertEqual(resultinterval[0], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1))
+        self.assertEqual(resultinterval[1], datetime.datetime(year=1981, month=3, day=6, hour=1, minute=1))
 
         resultinterval = aniso8601.parse_interval('P1M/1981-04-05')
-        self.assertEqual(resultinterval[0], datetime.date(year=1981, month=3, day=6))
-        self.assertEqual(resultinterval[1], datetime.date(year=1981, month=4, day=5))
+        self.assertEqual(resultinterval[0], datetime.date(year=1981, month=4, day=5))
+        self.assertEqual(resultinterval[1], datetime.date(year=1981, month=3, day=6))
 
         resultinterval = aniso8601.parse_interval('1981-04-05T01:01:00/P1M1DT1M')
         self.assertEqual(resultinterval[0], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1))
         self.assertEqual(resultinterval[0], datetime.date(year=1980, month=3, day=5))
         self.assertEqual(resultinterval[1], datetime.date(year=1981, month=4, day=5))
 
+        resultinterval = aniso8601.parse_interval('1981-04-05/1980-03-05')
+        self.assertEqual(resultinterval[0], datetime.date(year=1981, month=4, day=5))
+        self.assertEqual(resultinterval[1], datetime.date(year=1980, month=3, day=5))
+
         resultinterval = aniso8601.parse_interval('1980-03-05T01:01:00--1981-04-05T01:01:00', intervaldelimiter='--')
         self.assertEqual(resultinterval[0], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1))
         self.assertEqual(resultinterval[1], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1))
         self.assertEqual(resultinterval[0], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1))
         self.assertEqual(resultinterval[1], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1))
 
+    def test_parse_repeating_interval(self):
+        results = list(aniso8601.parse_repeating_interval('R3/1981-04-05/P1D'))
+        self.assertEqual(results[0], datetime.date(year=1981, month=4, day=5))
+        self.assertEqual(results[1], datetime.date(year=1981, month=4, day=6))
+        self.assertEqual(results[2], datetime.date(year=1981, month=4, day=7))
+
+        results = list(aniso8601.parse_repeating_interval('R11/PT1H2M/1980-03-05T01:01:00'))
+
+        for dateindex in xrange(0, 11):
+             self.assertEqual(results[dateindex], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) - dateindex * datetime.timedelta(hours=1, minutes=2))
+
+        results = list(aniso8601.parse_repeating_interval('R2--1980-03-05T01:01:00--1981-04-05T01:01:00', intervaldelimiter='--'))
+        self.assertEqual(results[0], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1))
+        self.assertEqual(results[1], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1))
+
+        results = list(aniso8601.parse_repeating_interval('R2/1980-03-05 01:01:00/1981-04-05 01:01:00', datetimedelimiter=' '))
+        self.assertEqual(results[0], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1))
+        self.assertEqual(results[1], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1))
+
+        resultgenerator = aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00')
+
+        for dateindex in xrange(0, 11):
+             self.assertEqual(resultgenerator.next(), datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) - dateindex * datetime.timedelta(hours=1, minutes=2))
+
     def test_parse_year(self):
         date = aniso8601.parse_year('2013')
         self.assertEqual(date.year, 2013)