Repository: joke2k/faker

Issue:
date_time_between does not generate same values on multiple runs with time passed between runs
* Faker version:  33.3.8
* OS: Sonoma 14.6.1

faker date_time_between does not reproduce the same date and time for a given seed.

### Steps to reproduce

Run the code:
```
fake = Faker()
    seed_start = int(2595)
    Faker.seed(seed_start)
    for i in range(10):
        print(fake.date_time_between('-3y'))
        if i == 4:  # reset the seed  to clear previous values for the second 5 passes
            Faker.seed(seed_start)
```
Output:
```
2024-02-08 17:08:30.053243
2024-01-04 09:40:53.843103
2024-12-22 22:12:02.216101
2025-01-08 12:54:04.497594
2025-01-05 16:27:42.947237
2024-02-08 17:08:30.053243    # the same values are repeated for the second 5 times
2024-01-04 09:40:53.843103
2024-12-22 22:12:02.216101
2025-01-08 12:54:04.497594
2025-01-05 16:27:42.947237
```
Run the code again... after the delay to post the first results:
```
2024-02-08 17:12:24.053243
2024-01-04 09:44:47.843103
2024-12-22 22:15:56.216101
2025-01-08 12:57:58.497594
2025-01-05 16:31:36.947237
2024-02-08 17:12:24.053243
2024-01-04 09:44:47.843103
2024-12-22 22:15:56.216101
2025-01-08 12:57:58.497594
2025-01-05 16:31:36.947237
```

### Expected behavior

Expected that the values should be the same as the first time:
```
2024-02-08 17:08:30.053243
2024-01-04 09:40:53.843103
2024-12-22 22:12:02.216101
2025-01-08 12:54:04.497594
2025-01-05 16:27:42.947237
2024-02-08 17:08:30.053243
2024-01-04 09:40:53.843103
2024-12-22 22:12:02.216101
2025-01-08 12:54:04.497594
2025-01-05 16:27:42.947237
```


Discussion:

Patch:
diff --git a/faker/providers/date_time/__init__.py b/faker/providers/date_time/__init__.py
index 0c67fb13..c9ef1dca 100644
--- a/faker/providers/date_time/__init__.py
+++ b/faker/providers/date_time/__init__.py
@@ -33,6 +33,28 @@ def timestamp_to_datetime(timestamp: Union[int, float], tzinfo: Optional[TzInfo]
     return convert_timestamp_to_datetime(timestamp, tzinfo)
 
 
+def get_now_date_time(
+    start_date: Optional[DateParseType], end_date: Optional[DateParseType], tzinfo: Optional[TzInfo]
+) -> datetime:
+    if isinstance(start_date, datetime) and not isinstance(end_date, datetime):
+        now = start_date
+    elif isinstance(end_date, datetime) and not isinstance(start_date, datetime):
+        now = end_date
+    else:
+        now = datetime.now(tzinfo)
+    return now
+
+
+def get_now_date(start_date: Optional[DateParseType], end_date: Optional[DateParseType]) -> dtdate:
+    if isinstance(start_date, dtdate) and not isinstance(end_date, dtdate):
+        now = start_date
+    elif isinstance(end_date, dtdate) and not isinstance(start_date, dtdate):
+        now = end_date
+    else:
+        now = dtdate.today()
+    return now
+
+
 def change_year(current_date: dtdate, year_diff: int) -> dtdate:
     """
     Unless the current_date is February 29th, it is fine to just subtract years.
@@ -1830,17 +1852,18 @@ class Provider(BaseProvider):
 
         :example: 1061306726.6
         """
-        start_datetime = self._parse_start_datetime(start_datetime)
-        end_datetime = self._parse_end_datetime(end_datetime)
+        now = get_now_date_time(start_datetime, end_datetime, tzinfo=None)
+        start_datetime = self._parse_start_datetime(now, start_datetime)
+        end_datetime = self._parse_end_datetime(now, end_datetime)
         return float(self._rand_seconds(start_datetime, end_datetime))
 
     def time_delta(self, end_datetime: Optional[DateParseType] = None) -> timedelta:
         """
         Get a timedelta object
         """
-        start_datetime = self._parse_start_datetime("now")
-        end_datetime = self._parse_end_datetime(end_datetime)
-        seconds = end_datetime - start_datetime
+        now = datetime.now()
+        end = self._parse_end_datetime(now, end_datetime)
+        seconds = end - datetime_to_timestamp(now)
 
         ts = self._rand_seconds(*sorted([0, seconds]))
         return timedelta(seconds=ts)
@@ -1882,8 +1905,9 @@ class Provider(BaseProvider):
         # simply change that class method to use this magic number as a
         # default value when None is provided.
 
-        start_time = -62135596800 if start_datetime is None else self._parse_start_datetime(start_datetime)
-        end_datetime = self._parse_end_datetime(end_datetime)
+        now = get_now_date_time(start_datetime, end_datetime, tzinfo)
+        start_time = -62135596800 if start_datetime is None else self._parse_start_datetime(now, start_datetime)
+        end_datetime = self._parse_end_datetime(now, end_datetime)
 
         ts = self._rand_seconds(start_time, end_datetime)
         # NOTE: using datetime.fromtimestamp(ts) directly will raise
@@ -1948,18 +1972,18 @@ class Provider(BaseProvider):
         return self.date_time(end_datetime=end_datetime).time()
 
     @classmethod
-    def _parse_start_datetime(cls, value: Optional[DateParseType]) -> int:
+    def _parse_start_datetime(cls, now: datetime, value: Optional[DateParseType]) -> int:
         if value is None:
             return 0
 
-        return cls._parse_date_time(value)
+        return cls._parse_date_time(value, now)
 
     @classmethod
-    def _parse_end_datetime(cls, value: Optional[DateParseType]) -> int:
+    def _parse_end_datetime(cls, now: datetime, value: Optional[DateParseType]) -> int:
         if value is None:
-            return datetime_to_timestamp(datetime.now())
+            return datetime_to_timestamp(now)
 
-        return cls._parse_date_time(value)
+        return cls._parse_date_time(value, now)
 
     @classmethod
     def _parse_date_string(cls, value: str) -> Dict[str, float]:
@@ -1997,10 +2021,9 @@ class Provider(BaseProvider):
         raise ParseError(f"Invalid format for timedelta {value!r}")
 
     @classmethod
-    def _parse_date_time(cls, value: DateParseType, tzinfo: Optional[TzInfo] = None) -> int:
+    def _parse_date_time(cls, value: DateParseType, now: datetime, tzinfo: Optional[TzInfo] = None) -> int:
         if isinstance(value, (datetime, dtdate)):
             return datetime_to_timestamp(value)
-        now = datetime.now(tzinfo)
         if isinstance(value, timedelta):
             return datetime_to_timestamp(now + value)
         if isinstance(value, str):
@@ -2013,12 +2036,11 @@ class Provider(BaseProvider):
         raise ParseError(f"Invalid format for date {value!r}")
 
     @classmethod
-    def _parse_date(cls, value: DateParseType) -> dtdate:
+    def _parse_date(cls, value: DateParseType, today: dtdate) -> dtdate:
         if isinstance(value, datetime):
             return value.date()
         elif isinstance(value, dtdate):
             return value
-        today = dtdate.today()
         if isinstance(value, timedelta):
             return today + value
         if isinstance(value, str):
@@ -2046,8 +2068,9 @@ class Provider(BaseProvider):
         :example: datetime('1999-02-02 11:42:52')
         :return: datetime
         """
-        start_date = self._parse_date_time(start_date, tzinfo=tzinfo)
-        end_date = self._parse_date_time(end_date, tzinfo=tzinfo)
+        now = get_now_date_time(start_date, end_date, tzinfo)
+        start_date = self._parse_date_time(start_date, now, tzinfo=tzinfo)
+        end_date = self._parse_date_time(end_date, now, tzinfo=tzinfo)
         if end_date - start_date <= 1:
             ts = start_date + self.generator.random.random()
         else:
@@ -2067,9 +2090,9 @@ class Provider(BaseProvider):
         :example: Date('1999-02-02')
         :return: Date
         """
-
-        start_date = self._parse_date(start_date)
-        end_date = self._parse_date(end_date)
+        today = get_now_date(start_date, end_date)
+        start_date = self._parse_date(start_date, today)
+        end_date = self._parse_date(end_date, today)
         return self.date_between_dates(date_start=start_date, date_end=end_date)
 
     def future_datetime(self, end_date: DateParseType = "+30d", tzinfo: Optional[TzInfo] = None) -> datetime:
@@ -2141,13 +2164,17 @@ class Provider(BaseProvider):
         :example: datetime('1999-02-02 11:42:52')
         :return: datetime
         """
+        today = get_now_date(datetime_start, datetime_end)
+        now = datetime.combine(today, datetime.min.time(), tzinfo)
         datetime_start_ = (
             datetime_to_timestamp(datetime.now(tzinfo))
             if datetime_start is None
-            else self._parse_date_time(datetime_start)
+            else self._parse_date_time(datetime_start, now)
         )
         datetime_end_ = (
-            datetime_to_timestamp(datetime.now(tzinfo)) if datetime_end is None else self._parse_date_time(datetime_end)
+            datetime_to_timestamp(datetime.now(tzinfo))
+            if datetime_end is None
+            else self._parse_date_time(datetime_end, now)
         )
 
         timestamp = self._rand_seconds(datetime_start_, datetime_end_)
@@ -2398,8 +2425,9 @@ class Provider(BaseProvider):
         ``distrib`` is a callable that accepts ``<datetime>`` and returns ``<value>``
 
         """
-        start_date_ = self._parse_date_time(start_date, tzinfo=tzinfo)
-        end_date_ = self._parse_date_time(end_date, tzinfo=tzinfo)
+        now = get_now_date_time(start_date, end_date, tzinfo)
+        start_date_ = self._parse_date_time(start_date, now, tzinfo=tzinfo)
+        end_date_ = self._parse_date_time(end_date, now, tzinfo=tzinfo)
 
         if end_date_ < start_date_:
             raise ValueError("`end_date` must be greater than `start_date`.")


Test patch:
diff --git a/tests/providers/test_date_time.py b/tests/providers/test_date_time.py
index a03546a8..7c500cbb 100644
--- a/tests/providers/test_date_time.py
+++ b/tests/providers/test_date_time.py
@@ -127,31 +127,33 @@ class TestDateTime(unittest.TestCase):
         assert future_date > date.today()
 
     def test_parse_date_time(self):
-        timestamp = DatetimeProvider._parse_date_time("+30d")
-        now = DatetimeProvider._parse_date_time("now")
+        reference = datetime.now()
+        timestamp = DatetimeProvider._parse_date_time("+30d", reference)
+        now = DatetimeProvider._parse_date_time("now", reference)
         assert timestamp > now
         delta = timedelta(days=30)
-        from_delta = DatetimeProvider._parse_date_time(delta)
-        from_int = DatetimeProvider._parse_date_time(timestamp)
+        from_delta = DatetimeProvider._parse_date_time(delta, reference)
+        from_int = DatetimeProvider._parse_date_time(timestamp, reference)
 
         assert datetime.fromtimestamp(from_delta).date() == (datetime.fromtimestamp(timestamp).date())
 
         assert datetime.fromtimestamp(from_int).date() == (datetime.fromtimestamp(timestamp).date())
 
     def test_parse_date(self):
-        parsed = DatetimeProvider._parse_date("+30d")
-        now = DatetimeProvider._parse_date("now")
-        today = DatetimeProvider._parse_date("today")
+        reference = date.today()
+        parsed = DatetimeProvider._parse_date("+30d", reference)
+        now = DatetimeProvider._parse_date("now", reference)
+        today = DatetimeProvider._parse_date("today", reference)
         assert isinstance(parsed, date)
         assert isinstance(now, date)
         assert isinstance(today, date)
         assert today == date.today()
         assert now == today
         assert parsed == today + timedelta(days=30)
-        assert DatetimeProvider._parse_date(datetime.now()) == today
-        assert DatetimeProvider._parse_date(parsed) == parsed
-        assert DatetimeProvider._parse_date(30) == parsed
-        assert DatetimeProvider._parse_date(timedelta(days=30)) == parsed
+        assert DatetimeProvider._parse_date(datetime.now(), reference) == today
+        assert DatetimeProvider._parse_date(parsed, reference) == parsed
+        assert DatetimeProvider._parse_date(30, reference) == parsed
+        assert DatetimeProvider._parse_date(timedelta(days=30), reference) == parsed
 
     def test_timezone_conversion(self):
         from faker.providers.date_time import datetime_to_timestamp
@@ -590,6 +592,23 @@ class TestDateTime(unittest.TestCase):
         with self.assertRaises(ValueError):
             change_year(today, -today.year)
 
+    def test_relative_end_date(self):
+        a = []
+        b = []
+        fake = Faker()
+
+        fake.seed_instance(2595)
+        for _ in range(10):
+            a.append(fake.date_time_between(start_date="-3y", end_date=datetime(2025, 1, 14, 23, 59, 59, 0)))
+
+        time.sleep(2)
+
+        fake.seed_instance(2595)
+        for _ in range(10):
+            b.append(fake.date_time_between(start_date="-3y", end_date=datetime(2025, 1, 14, 23, 59, 59, 0)))
+
+        assert a == b
+
 
 class TestDeDe(unittest.TestCase):
     def setUp(self):