Repository: joke2k/faker

Issue:
Cannot seed datetime generator
The faker lib allows you to seed Python's `random` instance, and the expected result is that all faker calls will have deterministic results every time the code is run. But the datetime generator relies on the `unix_time()` method, which drifts based on the current time:
```
@classmethod
def unix_time(cls):
    """
    Get a timestamp between January 1, 1970 and now
    :example 1061306726
    """
    return random.randint(0, int(time()))
```
The usage of time() as the end date means that datetime results change over time, even if random has been seeded.

I suggest making these methods accept an optional `end_datetime` (maybe `start_datetime` as well) parameter that would let the caller set the range of acceptable datetimes. That would allow users seeding random to get deterministic results. 


Discussion:
Thanks @ndbradley730! That sounds like a good idea! Could you submit a Pull Request?
@fcurella Was this fixed? I went to go test this right now, when seeding I seem to get the same expected value. 

```
import unittest

class SeedTimeTestCase(unittest.TestCase):
    def test_seed_time(self):
        from time import time
        import random
        
        for _ in range(100):
            seedint = random.randint(0,123456789)
            random.seed(seedint)
            prev = random.randint(0, int(time()))
            for j in range(100):
                random.seed(seedint)
                curr = random.randint(0, int(time()))
                self.assertEqual(curr, prev)
                prev = curr
                
                #No seed, should be different now
                curr = random.randint(0, int(time()))
                self.assertNotEqual(curr, prev)

if __name__ == '__main__':
    unittest.main()
```

Even doing something like this from faker seems to work ok:

```
import unittest

class SeedTimeTestCase(unittest.TestCase):
    def test_seed_time(self):
        from faker import Faker
        gen = Faker()
        
        for _ in range(100):
            seedint = gen.random_int(max=123456789)
            gen.seed(seedint)
            prev = gen.unix_time()
            for j in range(100):
                gen.seed(seedint)
                curr = gen.unix_time()
                self.assertEqual(curr, prev)
                prev = curr
                
                #No seed, should be different now
                curr = gen.unix_time()
                self.assertNotEqual(curr, prev)

if __name__ == '__main__':
    unittest.main()
```

@kungfu71186 i dont think it's fixed. I'm still waiting on a PR
@kungfu71186 I think the issue is that the range of `randint` increments by 1 every second. Try putting a `time.sleep(1)` somewhere in the loop
I see now. It's has to be way more than a second. for instance

```
random.seed(1)
random.randint(0,11999999999)
random.seed(1)
random.randint(0,10000000000)
```

will be the same output. 

Ok, i can do a PR for this

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
@@ -246,21 +246,23 @@ class Provider(BaseProvider):
 
     regex = re.compile(timedelta_pattern)
 
-    def unix_time(self):
+    def unix_time(self, end_datetime=None):
         """
         Get a timestamp between January 1, 1970 and now
         :example 1061306726
         """
-        return self.generator.random.randint(0, int(time()))
+        end_datetime = self._parse_end_datetime(end_datetime)
+        return self.generator.random.randint(0, end_datetime)
 
-    def time_delta(self):
+    def time_delta(self, end_datetime=None):
         """
         Get a timedelta object
         """
-        ts = self.generator.random.randint(0, int(time()))
+        end_datetime = self._parse_end_datetime(end_datetime)
+        ts = self.generator.random.randint(0, end_datetime)
         return timedelta(seconds=ts)
 
-    def date_time(self, tzinfo=None):
+    def date_time(self, tzinfo=None, end_datetime=None):
         """
         Get a datetime object for a date between January 1, 1970 and now
         :param tzinfo: timezone, instance of datetime.tzinfo subclass
@@ -270,16 +272,17 @@ def date_time(self, tzinfo=None):
         # NOTE: On windows, the lowest value you can get from windows is 86400
         #       on the first day. Known python issue: 
         #       https://bugs.python.org/issue30684
-        return datetime(1970, 1, 1,tzinfo=tzinfo) + timedelta(seconds=self.unix_time())
+        return datetime(1970, 1, 1,tzinfo=tzinfo) + timedelta(seconds=self.unix_time(end_datetime=end_datetime))
 
-    def date_time_ad(self, tzinfo=None):
+    def date_time_ad(self, tzinfo=None, end_datetime=None):
         """
         Get a datetime object for a date between January 1, 001 and now
         :param tzinfo: timezone, instance of datetime.tzinfo subclass
         :example DateTime('1265-03-22 21:15:52')
         :return datetime
         """
-        ts = self.generator.random.randint(-62135600400, int(time()))
+        end_datetime = self._parse_end_datetime(end_datetime)
+        ts = self.generator.random.randint(-62135600400, end_datetime)
         # NOTE: using datetime.fromtimestamp(ts) directly will raise
         #       a "ValueError: timestamp out of range for platform time_t"
         #       on some platforms due to system C functions;
@@ -289,42 +292,49 @@ def date_time_ad(self, tzinfo=None):
         #       https://bugs.python.org/issue30684
         return datetime(1970, 1, 1,tzinfo=tzinfo) + timedelta(seconds=ts)
 
-    def iso8601(self, tzinfo=None):
+    def iso8601(self, tzinfo=None, end_datetime=None):
         """
         :param tzinfo: timezone, instance of datetime.tzinfo subclass
         :example '2003-10-21T16:05:52+0000'
         """
-        return self.date_time(tzinfo).isoformat()
+        return self.date_time(tzinfo,end_datetime=end_datetime).isoformat()
 
-    def date(self, pattern='%Y-%m-%d'):
+    def date(self, pattern='%Y-%m-%d', end_datetime=None):
         """
         Get a date string between January 1, 1970 and now
         :param pattern format
         :example '2008-11-27'
         """
-        return self.date_time().strftime(pattern)
+        return self.date_time(end_datetime=end_datetime).strftime(pattern)
 
-    def date_object(self):
+    def date_object(self, end_datetime=None):
         """
         Get a date object between January 1, 1970 and now
         :example datetime.date(2016, 9, 20)
         """
-        return self.date_time().date()
+        return self.date_time(end_datetime=end_datetime).date()
 
-    def time(self, pattern='%H:%M:%S'):
+    def time(self, pattern='%H:%M:%S', end_datetime = None):
         """
         Get a time string (24h format by default)
         :param pattern format
         :example '15:02:34'
         """
-        return self.date_time().time().strftime(pattern)
+        return self.date_time(end_datetime=end_datetime).time().strftime(pattern)
 
-    def time_object(self):
+    def time_object(self, end_datetime = None):
         """
         Get a time object
         :example datetime.time(15, 56, 56, 772876)
         """
-        return self.date_time().time()
+        return self.date_time(end_datetime=end_datetime).time()
+    
+    @classmethod
+    def _parse_end_datetime(cls, value):
+        if value is None:
+            return int(time())
+
+        return cls._parse_date_time(value)
 
     @classmethod
     def _parse_date_string(cls, value):


Test patch: