Skip to content

Commit e61d064

Browse files
committed
Fix #2149. Account for end_date when calculating relative date_time_between
1 parent bbcab85 commit e61d064

File tree

2 files changed

+83
-36
lines changed

2 files changed

+83
-36
lines changed

faker/providers/date_time/__init__.py

+53-25
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,28 @@ def timestamp_to_datetime(timestamp: Union[int, float], tzinfo: Optional[TzInfo]
3333
return convert_timestamp_to_datetime(timestamp, tzinfo)
3434

3535

36+
def get_now_date_time(
37+
start_date: Optional[DateParseType], end_date: Optional[DateParseType], tzinfo: Optional[TzInfo]
38+
) -> datetime:
39+
if isinstance(start_date, datetime) and not isinstance(end_date, datetime):
40+
now = start_date
41+
elif isinstance(end_date, datetime) and not isinstance(start_date, datetime):
42+
now = end_date
43+
else:
44+
now = datetime.now(tzinfo)
45+
return now
46+
47+
48+
def get_now_date(start_date: Optional[DateParseType], end_date: Optional[DateParseType]) -> dtdate:
49+
if isinstance(start_date, dtdate) and not isinstance(end_date, dtdate):
50+
now = start_date
51+
elif isinstance(end_date, dtdate) and not isinstance(start_date, dtdate):
52+
now = end_date
53+
else:
54+
now = dtdate.today()
55+
return now
56+
57+
3658
def change_year(current_date: dtdate, year_diff: int) -> dtdate:
3759
"""
3860
Unless the current_date is February 29th, it is fine to just subtract years.
@@ -1830,17 +1852,18 @@ def unix_time(
18301852
18311853
:example: 1061306726.6
18321854
"""
1833-
start_datetime = self._parse_start_datetime(start_datetime)
1834-
end_datetime = self._parse_end_datetime(end_datetime)
1855+
now = get_now_date_time(start_datetime, end_datetime, tzinfo=None)
1856+
start_datetime = self._parse_start_datetime(now, start_datetime)
1857+
end_datetime = self._parse_end_datetime(now, end_datetime)
18351858
return float(self._rand_seconds(start_datetime, end_datetime))
18361859

18371860
def time_delta(self, end_datetime: Optional[DateParseType] = None) -> timedelta:
18381861
"""
18391862
Get a timedelta object
18401863
"""
1841-
start_datetime = self._parse_start_datetime("now")
1842-
end_datetime = self._parse_end_datetime(end_datetime)
1843-
seconds = end_datetime - start_datetime
1864+
now = datetime.now()
1865+
end = self._parse_end_datetime(now, end_datetime)
1866+
seconds = end - datetime_to_timestamp(now)
18441867

18451868
ts = self._rand_seconds(*sorted([0, seconds]))
18461869
return timedelta(seconds=ts)
@@ -1882,8 +1905,9 @@ def date_time_ad(
18821905
# simply change that class method to use this magic number as a
18831906
# default value when None is provided.
18841907

1885-
start_time = -62135596800 if start_datetime is None else self._parse_start_datetime(start_datetime)
1886-
end_datetime = self._parse_end_datetime(end_datetime)
1908+
now = get_now_date_time(start_datetime, end_datetime, tzinfo)
1909+
start_time = -62135596800 if start_datetime is None else self._parse_start_datetime(now, start_datetime)
1910+
end_datetime = self._parse_end_datetime(now, end_datetime)
18871911

18881912
ts = self._rand_seconds(start_time, end_datetime)
18891913
# NOTE: using datetime.fromtimestamp(ts) directly will raise
@@ -1948,18 +1972,18 @@ def time_object(self, end_datetime: Optional[DateParseType] = None) -> dttime:
19481972
return self.date_time(end_datetime=end_datetime).time()
19491973

19501974
@classmethod
1951-
def _parse_start_datetime(cls, value: Optional[DateParseType]) -> int:
1975+
def _parse_start_datetime(cls, now: datetime, value: Optional[DateParseType]) -> int:
19521976
if value is None:
19531977
return 0
19541978

1955-
return cls._parse_date_time(value)
1979+
return cls._parse_date_time(value, now)
19561980

19571981
@classmethod
1958-
def _parse_end_datetime(cls, value: Optional[DateParseType]) -> int:
1982+
def _parse_end_datetime(cls, now: datetime, value: Optional[DateParseType]) -> int:
19591983
if value is None:
1960-
return datetime_to_timestamp(datetime.now())
1984+
return datetime_to_timestamp(now)
19611985

1962-
return cls._parse_date_time(value)
1986+
return cls._parse_date_time(value, now)
19631987

19641988
@classmethod
19651989
def _parse_date_string(cls, value: str) -> Dict[str, float]:
@@ -1997,10 +2021,9 @@ def _parse_timedelta(cls, value: Union[timedelta, str, float]) -> Union[float, i
19972021
raise ParseError(f"Invalid format for timedelta {value!r}")
19982022

19992023
@classmethod
2000-
def _parse_date_time(cls, value: DateParseType, tzinfo: Optional[TzInfo] = None) -> int:
2024+
def _parse_date_time(cls, value: DateParseType, now: datetime, tzinfo: Optional[TzInfo] = None) -> int:
20012025
if isinstance(value, (datetime, dtdate)):
20022026
return datetime_to_timestamp(value)
2003-
now = datetime.now(tzinfo)
20042027
if isinstance(value, timedelta):
20052028
return datetime_to_timestamp(now + value)
20062029
if isinstance(value, str):
@@ -2013,12 +2036,11 @@ def _parse_date_time(cls, value: DateParseType, tzinfo: Optional[TzInfo] = None)
20132036
raise ParseError(f"Invalid format for date {value!r}")
20142037

20152038
@classmethod
2016-
def _parse_date(cls, value: DateParseType) -> dtdate:
2039+
def _parse_date(cls, value: DateParseType, today: dtdate) -> dtdate:
20172040
if isinstance(value, datetime):
20182041
return value.date()
20192042
elif isinstance(value, dtdate):
20202043
return value
2021-
today = dtdate.today()
20222044
if isinstance(value, timedelta):
20232045
return today + value
20242046
if isinstance(value, str):
@@ -2046,8 +2068,9 @@ def date_time_between(
20462068
:example: datetime('1999-02-02 11:42:52')
20472069
:return: datetime
20482070
"""
2049-
start_date = self._parse_date_time(start_date, tzinfo=tzinfo)
2050-
end_date = self._parse_date_time(end_date, tzinfo=tzinfo)
2071+
now = get_now_date_time(start_date, end_date, tzinfo)
2072+
start_date = self._parse_date_time(start_date, now, tzinfo=tzinfo)
2073+
end_date = self._parse_date_time(end_date, now, tzinfo=tzinfo)
20512074
if end_date - start_date <= 1:
20522075
ts = start_date + self.generator.random.random()
20532076
else:
@@ -2067,9 +2090,9 @@ def date_between(self, start_date: DateParseType = "-30y", end_date: DateParseTy
20672090
:example: Date('1999-02-02')
20682091
:return: Date
20692092
"""
2070-
2071-
start_date = self._parse_date(start_date)
2072-
end_date = self._parse_date(end_date)
2093+
today = get_now_date(start_date, end_date)
2094+
start_date = self._parse_date(start_date, today)
2095+
end_date = self._parse_date(end_date, today)
20732096
return self.date_between_dates(date_start=start_date, date_end=end_date)
20742097

20752098
def future_datetime(self, end_date: DateParseType = "+30d", tzinfo: Optional[TzInfo] = None) -> datetime:
@@ -2141,13 +2164,17 @@ def date_time_between_dates(
21412164
:example: datetime('1999-02-02 11:42:52')
21422165
:return: datetime
21432166
"""
2167+
today = get_now_date(datetime_start, datetime_end)
2168+
now = datetime.combine(today, datetime.min.time(), tzinfo)
21442169
datetime_start_ = (
21452170
datetime_to_timestamp(datetime.now(tzinfo))
21462171
if datetime_start is None
2147-
else self._parse_date_time(datetime_start)
2172+
else self._parse_date_time(datetime_start, now)
21482173
)
21492174
datetime_end_ = (
2150-
datetime_to_timestamp(datetime.now(tzinfo)) if datetime_end is None else self._parse_date_time(datetime_end)
2175+
datetime_to_timestamp(datetime.now(tzinfo))
2176+
if datetime_end is None
2177+
else self._parse_date_time(datetime_end, now)
21512178
)
21522179

21532180
timestamp = self._rand_seconds(datetime_start_, datetime_end_)
@@ -2398,8 +2425,9 @@ def time_series(
23982425
``distrib`` is a callable that accepts ``<datetime>`` and returns ``<value>``
23992426
24002427
"""
2401-
start_date_ = self._parse_date_time(start_date, tzinfo=tzinfo)
2402-
end_date_ = self._parse_date_time(end_date, tzinfo=tzinfo)
2428+
now = get_now_date_time(start_date, end_date, tzinfo)
2429+
start_date_ = self._parse_date_time(start_date, now, tzinfo=tzinfo)
2430+
end_date_ = self._parse_date_time(end_date, now, tzinfo=tzinfo)
24032431

24042432
if end_date_ < start_date_:
24052433
raise ValueError("`end_date` must be greater than `start_date`.")

tests/providers/test_date_time.py

+30-11
Original file line numberDiff line numberDiff line change
@@ -127,31 +127,33 @@ def test_future_date(self):
127127
assert future_date > date.today()
128128

129129
def test_parse_date_time(self):
130-
timestamp = DatetimeProvider._parse_date_time("+30d")
131-
now = DatetimeProvider._parse_date_time("now")
130+
reference = datetime.now()
131+
timestamp = DatetimeProvider._parse_date_time("+30d", reference)
132+
now = DatetimeProvider._parse_date_time("now", reference)
132133
assert timestamp > now
133134
delta = timedelta(days=30)
134-
from_delta = DatetimeProvider._parse_date_time(delta)
135-
from_int = DatetimeProvider._parse_date_time(timestamp)
135+
from_delta = DatetimeProvider._parse_date_time(delta, reference)
136+
from_int = DatetimeProvider._parse_date_time(timestamp, reference)
136137

137138
assert datetime.fromtimestamp(from_delta).date() == (datetime.fromtimestamp(timestamp).date())
138139

139140
assert datetime.fromtimestamp(from_int).date() == (datetime.fromtimestamp(timestamp).date())
140141

141142
def test_parse_date(self):
142-
parsed = DatetimeProvider._parse_date("+30d")
143-
now = DatetimeProvider._parse_date("now")
144-
today = DatetimeProvider._parse_date("today")
143+
reference = date.today()
144+
parsed = DatetimeProvider._parse_date("+30d", reference)
145+
now = DatetimeProvider._parse_date("now", reference)
146+
today = DatetimeProvider._parse_date("today", reference)
145147
assert isinstance(parsed, date)
146148
assert isinstance(now, date)
147149
assert isinstance(today, date)
148150
assert today == date.today()
149151
assert now == today
150152
assert parsed == today + timedelta(days=30)
151-
assert DatetimeProvider._parse_date(datetime.now()) == today
152-
assert DatetimeProvider._parse_date(parsed) == parsed
153-
assert DatetimeProvider._parse_date(30) == parsed
154-
assert DatetimeProvider._parse_date(timedelta(days=30)) == parsed
153+
assert DatetimeProvider._parse_date(datetime.now(), reference) == today
154+
assert DatetimeProvider._parse_date(parsed, reference) == parsed
155+
assert DatetimeProvider._parse_date(30, reference) == parsed
156+
assert DatetimeProvider._parse_date(timedelta(days=30), reference) == parsed
155157

156158
def test_timezone_conversion(self):
157159
from faker.providers.date_time import datetime_to_timestamp
@@ -590,6 +592,23 @@ def test_change_year(self):
590592
with self.assertRaises(ValueError):
591593
change_year(today, -today.year)
592594

595+
def test_relative_end_date(self):
596+
a = []
597+
b = []
598+
fake = Faker()
599+
600+
fake.seed_instance(2595)
601+
for _ in range(10):
602+
a.append(fake.date_time_between(start_date="-3y", end_date=datetime(2025, 1, 14, 23, 59, 59, 0)))
603+
604+
time.sleep(2)
605+
606+
fake.seed_instance(2595)
607+
for _ in range(10):
608+
b.append(fake.date_time_between(start_date="-3y", end_date=datetime(2025, 1, 14, 23, 59, 59, 0)))
609+
610+
assert a == b
611+
593612

594613
class TestDeDe(unittest.TestCase):
595614
def setUp(self):

0 commit comments

Comments
 (0)