Skip to content

Commit c93867f

Browse files
author
rodrigo.nogueira
committed
feat: add future_date, future_datetime, past_date, past_datetime methods
Add methods to generate dates and datetimes relative to the current time: - future_date(days=30): Random date between tomorrow and N days ahead - future_datetime(days=30, timezone=None): Random datetime from now+1s to N days ahead - past_date(days=30): Random date between N days ago and yesterday - past_datetime(days=30, timezone=None): Random datetime from N days ago to now-1s Closes #775
1 parent b7d77dd commit c93867f

File tree

3 files changed

+138
-1
lines changed

3 files changed

+138
-1
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
Version 19.1.0
22
--------------
33

4-
- Added support for E.164 phone number formatting to the ``Person`` providers ``phone_number()`` method.
4+
- Added support for E.164 phone number formatting to the ``Person`` provider's ``phone_number()`` method.
55
- Added ``secondary_address()`` method to the ``Address`` provider.
6+
- Add ``future_date()``, ``future_datetime()``, ``past_date()`` and ``past_datetime()`` methods for the ``Datetime`` provider. See (`#775 <https://github.com/lk-geimfari/mimesis/issues/775>`_).
67

78
Version 19.0.0
89
--------------
@@ -15,6 +16,8 @@ Version 19.0.0
1516
- Add ``SchemaBuilder`` for generating relational data.
1617
- Add ``ip_v4_cidr()``, ``ip_v6_cidr()`` and ``cloud_region()`` methods for the ``Internet`` provider.
1718

19+
20+
1821
Version 18.0.0
1922
--------------
2023

mimesis/providers/date.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,72 @@ def timestamp(
289289
else:
290290
return int(stamp.timestamp())
291291

292+
def future_date(self, days: int = 30) -> Date:
293+
"""Generates a random date in the future.
294+
295+
:param days: Maximum number of days in the future.
296+
:return: A date object between tomorrow and `days` from now.
297+
"""
298+
today = date.today()
299+
start_date = today + timedelta(days=1)
300+
end_date = today + timedelta(days=days)
301+
delta_days = self.random.randint(0, (end_date - start_date).days)
302+
return start_date + timedelta(days=delta_days)
303+
304+
def future_datetime(self, days: int = 30, timezone: str | None = None) -> DateTime:
305+
"""Generates a random datetime in the future.
306+
307+
:param days: Maximum number of days in the future.
308+
:param timezone: Set custom timezone (pytz required).
309+
:return: A datetime object between now and `days` from now.
310+
"""
311+
now = datetime.now()
312+
start_dt = now + timedelta(seconds=1)
313+
end_dt = now + timedelta(days=days)
314+
delta_seconds = self.random.randint(0, int((end_dt - start_dt).total_seconds()))
315+
result = start_dt + timedelta(seconds=delta_seconds)
316+
317+
if timezone:
318+
if not pytz:
319+
raise ImportError("Timezones are supported only with pytz")
320+
tz = pytz.timezone(timezone)
321+
result = tz.localize(result)
322+
323+
return result
324+
325+
def past_date(self, days: int = 30) -> Date:
326+
"""Generates a random date in the past.
327+
328+
:param days: Maximum number of days in the past.
329+
:return: A date object between `days` ago and yesterday.
330+
"""
331+
today = date.today()
332+
start_date = today - timedelta(days=days)
333+
end_date = today - timedelta(days=1)
334+
delta_days = self.random.randint(0, (end_date - start_date).days)
335+
return start_date + timedelta(days=delta_days)
336+
337+
def past_datetime(self, days: int = 30, timezone: str | None = None) -> DateTime:
338+
"""Generates a random datetime in the past.
339+
340+
:param days: Maximum number of days in the past.
341+
:param timezone: Set custom timezone (pytz required).
342+
:return: A datetime object between `days` ago and now.
343+
"""
344+
now = datetime.now()
345+
start_dt = now - timedelta(days=days)
346+
end_dt = now - timedelta(seconds=1)
347+
delta_seconds = self.random.randint(0, int((end_dt - start_dt).total_seconds()))
348+
result = start_dt + timedelta(seconds=delta_seconds)
349+
350+
if timezone:
351+
if not pytz:
352+
raise ImportError("Timezones are supported only with pytz")
353+
tz = pytz.timezone(timezone)
354+
result = tz.localize(result)
355+
356+
return result
357+
292358
def duration(
293359
self,
294360
min_duration: int = 1,

tests/test_providers/test_localized/test_date.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,56 @@ def test_duration_error(self, _datetime):
258258
duration_unit=DurationUnit.WEEKS,
259259
)
260260

261+
@pytest.mark.parametrize("days", [7, 30, 90])
262+
def test_future_date(self, _datetime, days):
263+
today = datetime.date.today()
264+
result = _datetime.future_date(days=days)
265+
assert isinstance(result, datetime.date)
266+
assert result > today
267+
assert result <= today + datetime.timedelta(days=days)
268+
269+
@pytest.mark.parametrize(
270+
"days, timezone",
271+
[
272+
(30, None),
273+
(30, "Europe/Paris"),
274+
],
275+
)
276+
def test_future_datetime(self, _datetime, days, timezone):
277+
now = datetime.datetime.now()
278+
result = _datetime.future_datetime(days=days, timezone=timezone)
279+
assert isinstance(result, datetime.datetime)
280+
assert result.replace(tzinfo=None) > now
281+
if timezone:
282+
assert result.tzinfo is not None
283+
else:
284+
assert result.tzinfo is None
285+
286+
@pytest.mark.parametrize("days", [7, 30, 90])
287+
def test_past_date(self, _datetime, days):
288+
today = datetime.date.today()
289+
result = _datetime.past_date(days=days)
290+
assert isinstance(result, datetime.date)
291+
assert result < today
292+
assert result >= today - datetime.timedelta(days=days)
293+
294+
@pytest.mark.parametrize(
295+
"days, timezone",
296+
[
297+
(30, None),
298+
(30, "Europe/Paris"),
299+
],
300+
)
301+
def test_past_datetime(self, _datetime, days, timezone):
302+
now = datetime.datetime.now()
303+
result = _datetime.past_datetime(days=days, timezone=timezone)
304+
assert isinstance(result, datetime.datetime)
305+
assert result.replace(tzinfo=None) < now
306+
if timezone:
307+
assert result.tzinfo is not None
308+
else:
309+
assert result.tzinfo is None
310+
261311

262312
class TestSeededDatetime:
263313
@pytest.fixture
@@ -338,3 +388,21 @@ def test_duratioh(self, d1, d2):
338388
assert d1.duration(10, 20, DurationUnit.WEEKS) == d2.duration(
339389
10, 20, DurationUnit.WEEKS
340390
)
391+
392+
def test_future_date(self, d1, d2):
393+
assert d1.future_date() == d2.future_date()
394+
assert d1.future_date(days=7) == d2.future_date(days=7)
395+
396+
def test_future_datetime(self, d1, d2):
397+
r1 = d1.future_datetime().replace(microsecond=0)
398+
r2 = d2.future_datetime().replace(microsecond=0)
399+
assert r1 == r2
400+
401+
def test_past_date(self, d1, d2):
402+
assert d1.past_date() == d2.past_date()
403+
assert d1.past_date(days=7) == d2.past_date(days=7)
404+
405+
def test_past_datetime(self, d1, d2):
406+
r1 = d1.past_datetime().replace(microsecond=0)
407+
r2 = d2.past_datetime().replace(microsecond=0)
408+
assert r1 == r2

0 commit comments

Comments
 (0)