diff --git a/faker/providers/date_time/__init__.py b/faker/providers/date_time/__init__.py index 17b9debdf0..b3a2197f5e 100644 --- a/faker/providers/date_time/__init__.py +++ b/faker/providers/date_time/__init__.py @@ -1,3 +1,4 @@ +import platform import re from calendar import timegm @@ -73,6 +74,21 @@ class ParseError(ValueError): class Provider(BaseProvider): + # NOTE: Windows only guarantee second precision, in order to emulate that + # we need to inspect the platform to determine which function is most + # appropriate to generate random seconds with. + if platform.system() == "Windows": + + @property + def _rand_seconds(self): + return self.generator.random.randint + + else: + + @property + def _rand_seconds(self): + return self.generator.random.uniform + centuries: ElementsType[str] = [ "I", "II", @@ -1814,7 +1830,7 @@ def unix_time( """ start_datetime = self._parse_start_datetime(start_datetime) end_datetime = self._parse_end_datetime(end_datetime) - return self.generator.random.randint(start_datetime, end_datetime) + return self._rand_seconds(start_datetime, end_datetime) def time_delta(self, end_datetime: Optional[DateParseType] = None) -> timedelta: """ @@ -1824,7 +1840,7 @@ def time_delta(self, end_datetime: Optional[DateParseType] = None) -> timedelta: end_datetime = self._parse_end_datetime(end_datetime) seconds = end_datetime - start_datetime - ts = self.generator.random.randint(*sorted([0, seconds])) + ts = self._rand_seconds(*sorted([0, seconds])) return timedelta(seconds=ts) def date_time( @@ -1867,7 +1883,7 @@ def date_time_ad( start_time = -62135596800 if start_datetime is None else self._parse_start_datetime(start_datetime) end_datetime = self._parse_end_datetime(end_datetime) - ts = self.generator.random.randint(start_time, end_datetime) + ts = self._rand_seconds(start_time, 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; @@ -2033,7 +2049,7 @@ def date_time_between( if end_date - start_date <= 1: ts = start_date + self.generator.random.random() else: - ts = self.generator.random.randint(start_date, end_date) + ts = self._rand_seconds(start_date, end_date) if tzinfo is None: return datetime(1970, 1, 1, tzinfo=tzinfo) + timedelta(seconds=ts) else: @@ -2132,7 +2148,7 @@ def date_time_between_dates( datetime_to_timestamp(datetime.now(tzinfo)) if datetime_end is None else self._parse_date_time(datetime_end) ) - timestamp = self.generator.random.randint(datetime_start_, datetime_end_) + timestamp = self._rand_seconds(datetime_start_, datetime_end_) try: if tzinfo is None: pick = convert_timestamp_to_datetime(timestamp, tzlocal()) diff --git a/tests/providers/test_date_time.py b/tests/providers/test_date_time.py index 492b3ec342..bb8cb69a0a 100644 --- a/tests/providers/test_date_time.py +++ b/tests/providers/test_date_time.py @@ -183,7 +183,6 @@ def test_datetimes_with_and_without_tzinfo(self): assert not self.fake.iso8601().endswith("+00:00") assert self.fake.iso8601(utc).endswith("+00:00") assert self.fake.iso8601()[10] == "T" - assert len(self.fake.iso8601()) == 19 assert len(self.fake.iso8601(timespec="hours")) == 13 assert len(self.fake.iso8601(timespec="minutes")) == 16 assert len(self.fake.iso8601(timespec="seconds")) == 19 @@ -194,6 +193,14 @@ def test_datetimes_with_and_without_tzinfo(self): assert self.fake.iso8601(tzinfo=utc, sep=" ")[10] == " " assert self.fake.iso8601(tzinfo=utc, sep="_")[10] == "_" + @pytest.mark.skipif(not sys.platform.startswith("win"), reason="windows does not support sub second precision") + def test_iso8601_fractional_seconds_win(self): + assert len(self.fake.iso8601()) == 19 + + @pytest.mark.skipif(sys.platform.startswith("win"), reason="non windows does support sub second precision") + def test_iso8601_fractional_seconds_non_win(self): + assert len(self.fake.iso8601()) == 26 + def test_date_object(self): assert isinstance(self.fake.date_object(), date) @@ -493,7 +500,7 @@ def test_unix_time(self): constrained_unix_time = self.fake.unix_time(end_datetime=end_datetime, start_datetime=start_datetime) - self.assertIsInstance(constrained_unix_time, int) + self.assertIsInstance(constrained_unix_time, (int, float)) self.assertBetween( constrained_unix_time, datetime_to_timestamp(start_datetime), @@ -505,7 +512,7 @@ def test_unix_time(self): recent_unix_time = self.fake.unix_time(start_datetime=one_day_ago) - self.assertIsInstance(recent_unix_time, int) + self.assertIsInstance(recent_unix_time, (int, float)) self.assertBetween( recent_unix_time, datetime_to_timestamp(one_day_ago), @@ -517,7 +524,7 @@ def test_unix_time(self): distant_unix_time = self.fake.unix_time(end_datetime=one_day_after_epoch_start) - self.assertIsInstance(distant_unix_time, int) + self.assertIsInstance(distant_unix_time, (int, float)) self.assertBetween( distant_unix_time, datetime_to_timestamp(epoch_start), @@ -527,7 +534,7 @@ def test_unix_time(self): # Ensure wide-open unix_times are generated correctly self.fake.unix_time() - self.assertIsInstance(constrained_unix_time, int) + self.assertIsInstance(constrained_unix_time, (int, float)) self.assertBetween(constrained_unix_time, 0, datetime_to_timestamp(now)) # Ensure it does not throw error with startdate='now' for machines with negative offset @@ -538,6 +545,16 @@ def test_unix_time(self): if platform.system() != "Windows": del os.environ["TZ"] + @pytest.mark.skipif(not sys.platform.startswith("win"), reason="windows does not support sub second precision") + def test_unix_time_win(self): + unix_time = self.fake.unix_time() + assert isinstance(unix_time, int) + + @pytest.mark.skipif(sys.platform.startswith("win"), reason="non windows does support sub second precision") + def test_unix_time_non_win(self): + unix_time = self.fake.unix_time() + assert isinstance(unix_time, float) + def test_change_year(self): _2020_06_01 = datetime.strptime("2020-06-01", "%Y-%m-%d") _20_years_ago = change_year(_2020_06_01, -20)