Skip to content

Commit bb954fe

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 bb954fe

File tree

3 files changed

+141
-1
lines changed

3 files changed

+141
-1
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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.
66

77
Version 19.0.0
@@ -14,6 +14,8 @@ Version 19.0.0
1414
- Add ``jwt``, ``api_key`` and ``certificate_fingerprint`` methods for the ``Cryptographic`` provider.
1515
- Add ``SchemaBuilder`` for generating relational data.
1616
- Add ``ip_v4_cidr()``, ``ip_v6_cidr()`` and ``cloud_region()`` methods for the ``Internet`` provider.
17+
- 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>`_).
18+
1719

1820
Version 18.0.0
1921
--------------

mimesis/providers/date.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,76 @@ 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(
305+
self, days: int = 30, timezone: str | None = None
306+
) -> DateTime:
307+
"""Generates a random datetime in the future.
308+
309+
:param days: Maximum number of days in the future.
310+
:param timezone: Set custom timezone (pytz required).
311+
:return: A datetime object between now and `days` from now.
312+
"""
313+
now = datetime.now()
314+
start_dt = now + timedelta(seconds=1)
315+
end_dt = now + timedelta(days=days)
316+
delta_seconds = self.random.randint(0, int((end_dt - start_dt).total_seconds()))
317+
result = start_dt + timedelta(seconds=delta_seconds)
318+
319+
if timezone:
320+
if not pytz:
321+
raise ImportError("Timezones are supported only with pytz")
322+
tz = pytz.timezone(timezone)
323+
result = tz.localize(result)
324+
325+
return result
326+
327+
def past_date(self, days: int = 30) -> Date:
328+
"""Generates a random date in the past.
329+
330+
:param days: Maximum number of days in the past.
331+
:return: A date object between `days` ago and yesterday.
332+
"""
333+
today = date.today()
334+
start_date = today - timedelta(days=days)
335+
end_date = today - timedelta(days=1)
336+
delta_days = self.random.randint(0, (end_date - start_date).days)
337+
return start_date + timedelta(days=delta_days)
338+
339+
def past_datetime(
340+
self, days: int = 30, timezone: str | None = None
341+
) -> DateTime:
342+
"""Generates a random datetime in the past.
343+
344+
:param days: Maximum number of days in the past.
345+
:param timezone: Set custom timezone (pytz required).
346+
:return: A datetime object between `days` ago and now.
347+
"""
348+
now = datetime.now()
349+
start_dt = now - timedelta(days=days)
350+
end_dt = now - timedelta(seconds=1)
351+
delta_seconds = self.random.randint(0, int((end_dt - start_dt).total_seconds()))
352+
result = start_dt + timedelta(seconds=delta_seconds)
353+
354+
if timezone:
355+
if not pytz:
356+
raise ImportError("Timezones are supported only with pytz")
357+
tz = pytz.timezone(timezone)
358+
result = tz.localize(result)
359+
360+
return result
361+
292362
def duration(
293363
self,
294364
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)