Repository: joke2k/faker

Issue:
past_datetime() crashes when start_date is within one second of now
I ran into a problem while generating a bunch of fake dates where I was calling `fake.past_datetime()` with the start date using the result of a previous `fake.past_datetime()` call (simulating a bunch of files being created as part of a collection). The first call could randomly return a value which is within one second of the current time, causing the second call to trigger an exception from `random.randint()` because the `a` and `b` values were the same.

This was pretty easy to solve simply by using `fake.date_time_between(start_date=previous_created_date)` but it might be useful if `past_datetime()` was safe to call like this.
past_datetime() crashes when start_date is within one second of now
I ran into a problem while generating a bunch of fake dates where I was calling `fake.past_datetime()` with the start date using the result of a previous `fake.past_datetime()` call (simulating a bunch of files being created as part of a collection). The first call could randomly return a value which is within one second of the current time, causing the second call to trigger an exception from `random.randint()` because the `a` and `b` values were the same.

This was pretty easy to solve simply by using `fake.date_time_between(start_date=previous_created_date)` but it might be useful if `past_datetime()` was safe to call like this.


Discussion:
@acdha Could you provide a unit test reproducing the exception? It would be really helpful in understanding exactly where to fix this bug
  
It'd be slow since it's a race condition but the logic was basically this:

```python
import random
import faker

fake = faker.Faker()

for i in range(0, random.randint(1, 1000)):
    collection_start = fake.past_datetime()

    for j in range(0, random.randint(1, 100000)):
        # This was used as a fake file creation time:
        fake.past_datetime(start_date=collection_start)
```

Looking at this a bit more, my comment above wasn't quite right – `random.randint` handles the case where it's called with the same value – but the error is the same as what I get calling `fake.past_datetime(start_date='now')` so it must have been a timing race where the first call returned within a second of the current time and then when `past_datetime` did the `-1s` call it was effectively calling `randint(1, 0)` and getting that error. That'd fit with it happening very infrequently out of millions of calls. I'm not sure whether it'd be worth handling it inclusively and just returning the start_date when the end date is less than or equal to the value or simply documenting that you need to use `date_time_between` if you can't guarantee that `start_date` is at least a couple seconds in the past.
@acdha Could you provide a unit test reproducing the exception? It would be really helpful in understanding exactly where to fix this bug
  
It'd be slow since it's a race condition but the logic was basically this:

```python
import random
import faker

fake = faker.Faker()

for i in range(0, random.randint(1, 1000)):
    collection_start = fake.past_datetime()

    for j in range(0, random.randint(1, 100000)):
        # This was used as a fake file creation time:
        fake.past_datetime(start_date=collection_start)
```

Looking at this a bit more, my comment above wasn't quite right – `random.randint` handles the case where it's called with the same value – but the error is the same as what I get calling `fake.past_datetime(start_date='now')` so it must have been a timing race where the first call returned within a second of the current time and then when `past_datetime` did the `-1s` call it was effectively calling `randint(1, 0)` and getting that error. That'd fit with it happening very infrequently out of millions of calls. I'm not sure whether it'd be worth handling it inclusively and just returning the start_date when the end date is less than or equal to the value or simply documenting that you need to use `date_time_between` if you can't guarantee that `start_date` is at least a couple seconds in the past.

Resolving patch:
diff --git a/faker/providers/date_time/__init__.py b/faker/providers/date_time/__init__.py
--- a/faker/providers/date_time/__init__.py
+++ b/faker/providers/date_time/__init__.py
@@ -1342,7 +1342,10 @@ def date_time_between(self, start_date='-30y', end_date='now', tzinfo=None):
         """
         start_date = self._parse_date_time(start_date, tzinfo=tzinfo)
         end_date = self._parse_date_time(end_date, tzinfo=tzinfo)
-        ts = self.generator.random.randint(start_date, end_date)
+        if end_date - start_date <= 1:
+            ts = start_date + self.generator.random.random()
+        else:
+            ts = self.generator.random.randint(start_date, end_date)
         return datetime(1970, 1, 1, tzinfo=tzinfo) + timedelta(seconds=ts)
 
     def date_between(self, start_date='-30y', end_date='today'):


Test patch:
diff --git a/tests/providers/test_date_time.py b/tests/providers/test_date_time.py
--- a/tests/providers/test_date_time.py
+++ b/tests/providers/test_date_time.py
@@ -191,6 +191,10 @@ def test_date_time_between_dates_with_tzinfo(self):
         self.assertTrue(datetime_start <= random_date)
         self.assertTrue(datetime_end >= random_date)
 
+    def test_past_datetime_within_second(self):
+        # Should not raise a ``ValueError``
+        self.factory.past_datetime(start_date='+1s')
+
     def test_date_between_dates(self):
         date_end = date.today()
         date_start = date_end - timedelta(days=10)