Repository: dateutil/dateutil (Python)

Issue:
Infinite loop in rrule.rrule if count is set to 0
`rrule.rrule` yields values indefinitely if the `count` argument is set to 0. Example:

```
In [2]: for x in rrule.rrule(freq=rrule.DAILY, count=1): print(x)
2016-12-01 17:53:11

In [3]: for x in rrule.rrule(freq=rrule.DAILY, count=0): print(x)
2016-12-01 17:53:15
2016-12-02 17:53:15
2016-12-03 17:53:15
...
```

Even if 0 is not an acceptable value for that parameter, this looks like a bug.


Hints:
@vaultah Yes, this is definitely a problem. It's not so much an infinite loop, though, since `rrule` is by default an infinite generator anyway, it's just that `count` is being ignored.

I haven't looked at where it's happening in the code, but I suspect that there's a check like `if count` somewhere in there, expecting that `count` will either be a positive integer or `None` (unspecified). I think maybe we can do something like:

```python
if count is not None and count < 1:
    raise ValueError('count parameter must be positive')
```

Then we just need to add tests for `count=0` and `count=1`.

Feel free to make a PR, otherwise I'll try and handle this before the next release.

Patch:
diff --git a/dateutil/rrule.py b/dateutil/rrule.py
--- a/dateutil/rrule.py
+++ b/dateutil/rrule.py
@@ -256,13 +256,13 @@ def xafter(self, dt, count=None, inc=False):
         n = 0
         for d in gen:
             if comp(d, dt):
-                yield d
-
                 if count is not None:
                     n += 1
-                    if n >= count:
+                    if n > count:
                         break
 
+                yield d
+
     def between(self, after, before, inc=False, count=1):
         """ Returns all the occurrences of the rrule between after and before.
         The inc keyword defines what happens if after and/or before are
@@ -444,7 +444,7 @@ def __init__(self, freq, dtstart=None,
             until = datetime.datetime.fromordinal(until.toordinal())
         self._until = until
 
-        if count and until:
+        if count is not None and until:
             warn("Using both 'count' and 'until' is inconsistent with RFC 2445"
                  " and has been deprecated in dateutil. Future versions will "
                  "raise an error.", DeprecationWarning)
@@ -689,7 +689,7 @@ def __str__(self):
         if self._wkst:
             parts.append('WKST=' + repr(weekday(self._wkst))[0:2])
 
-        if self._count:
+        if self._count is not None:
             parts.append('COUNT=' + str(self._count))
 
         if self._until:
@@ -844,13 +844,13 @@ def _iter(self):
                         self._len = total
                         return
                     elif res >= self._dtstart:
-                        total += 1
-                        yield res
-                        if count:
+                        if count is not None:
                             count -= 1
-                            if not count:
+                            if count < 0:
                                 self._len = total
                                 return
+                        total += 1
+                        yield res
             else:
                 for i in dayset[start:end]:
                     if i is not None:
@@ -861,14 +861,15 @@ def _iter(self):
                                 self._len = total
                                 return
                             elif res >= self._dtstart:
-                                total += 1
-                                yield res
-                                if count:
+                                if count is not None:
                                     count -= 1
-                                    if not count:
+                                    if count < 0:
                                         self._len = total
                                         return
 
+                                total += 1
+                                yield res
+
             # Handle frequency and interval
             fixday = False
             if freq == YEARLY:


Test patch:
diff --git a/dateutil/test/test_rrule.py b/dateutil/test/test_rrule.py
--- a/dateutil/test/test_rrule.py
+++ b/dateutil/test/test_rrule.py
@@ -2472,6 +2472,12 @@ def testCount(self):
                                dtstart=datetime(1997, 9, 2, 9, 0)).count(),
                          3)
 
+    def testCountZero(self):
+        self.assertEqual(rrule(YEARLY,
+                               count=0,
+                               dtstart=datetime(1997, 9, 2, 9, 0)).count(),
+                         0)
+
     def testContains(self):
         rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))
         self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True)