Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FutureDatetime and PastDatetime providers #779

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mimesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Development,
File,
Food,
FutureDatetime,
Generic,
Hardware,
Internet,
Expand All @@ -46,6 +47,7 @@
'Development',
'File',
'Food',
'FutureDatetime',
'Hardware',
'Internet',
'Numbers',
Expand Down
3 changes: 2 additions & 1 deletion mimesis/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from mimesis.providers.clothing import Clothing
from mimesis.providers.code import Code
from mimesis.providers.cryptographic import Cryptographic
from mimesis.providers.date import Datetime
from mimesis.providers.date import Datetime, FutureDatetime
from mimesis.providers.development import Development
from mimesis.providers.file import File
from mimesis.providers.food import Food
Expand Down Expand Up @@ -44,6 +44,7 @@
'Development',
'File',
'Food',
'FutureDatetime',
'Hardware',
'Internet',
'Numbers',
Expand Down
130 changes: 127 additions & 3 deletions mimesis/providers/date.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from mimesis.providers.base import BaseDataProvider
from mimesis.typing import Date, DateTime, Time

__all__ = ['Datetime']
__all__ = ['Datetime', 'FutureDatetime']


class Datetime(BaseDataProvider):
Expand Down Expand Up @@ -50,7 +50,6 @@ def bulk_create_datetimes(date_start: DateTime,
See datetime module documentation for more:
https://docs.python.org/3.7/library/datetime.html#timedelta-objects


:param date_start: Begin of the range.
:param date_end: End of the range.
:param kwargs: Keyword arguments for datetime.timedelta
Expand Down Expand Up @@ -135,7 +134,7 @@ def date(self, start: int = 2000, end: int = 2019) -> Date:

:param start: Minimum value of year.
:param end: Maximum value of year.
:return: Formatted date.
:return: Date.
"""
year = self.random.randint(start, end)
month = self.random.randint(1, 12)
Expand Down Expand Up @@ -255,3 +254,128 @@ def timestamp(self, posix: bool = True, **kwargs) -> Union[str, int]:
return timegm(stamp.utctimetuple())

return stamp.strftime('%Y-%m-%dT%H:%M:%SZ')


class FutureDatetime(BaseDataProvider):
"""Class for generating data related to the date and time in the future."""

def __init__(self, days: int = 1, weeks: int = 0, hours: int = 0,
minutes: int = 0, seconds: int = 0, *args, **kwargs):
"""Initialize attributes.

:param days: Number of days to add to the current date.
:param weeks: Number of weeks to add to the current date.
:param hours: Number of hours to add to the current date.
:param minutes: Number of minutes to add to the current date.
:param seconds: Number of seconds to add to the current date.
"""
super().__init__(*args, **kwargs)
self.future = datetime.now() + timedelta(
days=days, weeks=weeks, hours=hours, minutes=minutes,
seconds=seconds)
self._dt = Datetime(*args, **kwargs)
self.day_of_week = self._dt.day_of_week
self.month = self._dt.month
self.century = self._dt.century
self.periodicity = self._dt.periodicity
self.time = self._dt.time
self.formatted_time = self._dt.formatted_time
self.day_of_month = self._dt.day_of_month
self.timezone = self._dt.timezone
self.gmt_offset = self._dt.gmt_offset
self.timestamp = self._dt.timestamp
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lk-geimfari @sobolevn I had to switch to composition instead of inheritance because of the incompatibility with the overloaded methods.

I know this section is a bit tricky. It can be replaced with:

        self._dt = Datetime(*args, **kwargs)
        for method in (
            m for m in dir(Datetime)
            if callable(getattr(Datetime, m))
            and not m.startswith('_')
            and m not in dir(FutureDatetime)
        ):
            setattr(self, method, getattr(self._dt, method))

which it automatically adds the methods not overridden, but it is more black magic and less explicit. What do you prefer?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ceccoemi I think that we need to implement only some methods in this provider, not all of them. We must decide which methods can be really useful in FutureDatetime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lk-geimfari ok, if you don't want all the methods then it's really easy.

I propose to keep date, formatted_date, datetime, formatted_datetime and timestamp only.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lk-geimfari see my last commit!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ceccoemi I agree with your propose.


bulk_create_datetimes = staticmethod(Datetime.bulk_create_datetimes)

class Meta:
"""Class for metadata."""

name = 'future_datetime'

def _validate_future_year(self, year: int) -> int:
"""Check if the year is after the year of the current future.

:param year: Year to validate.
:raise ValueError: If ``year`` is before the year of the
current future
"""
if year < self.future.year:
raise ValueError(f'Year {year} is before the current future')

def week_date(self, end: int = None) -> str:
"""Get week number with year from the year of the current future.

:param end: To end.
:raises ValueError: When ``end`` is before the year of the
current future.
:return: Week number.
"""
if not end:
end = self.future.year + 1
else:
self._validate_future_year(end)
return self._dt.week_date(start=self.future.year, end=end)

def year(self, maximum: int = None) -> int:
"""Generate a random year from the year of the current future.

:param maximum: Maximum value.
:raises ValueError: When ``maximum`` is before the year of the
current future.
:return: Year.
"""
if not maximum:
maximum = self.future.year + 65
else:
self._validate_future_year(maximum)
return self._dt.year(minimum=self.future.year, maximum=maximum)

def date(self, end: int = None) -> Date:
"""Generate random date object from the year of the current future.

:param end: Maximum value of year.
:raises ValueError: When ``end`` is before the year of the
current future.
:return: Formatted date.
"""
if not end:
end = self.future.year + 19
else:
self._validate_future_year(end)
return self._dt.date(start=self.future.year, end=end)

def formatted_date(self, fmt: str = '', **kwargs) -> str:
"""Generate random date as string.

:param fmt: The format of date, if None then use standard
accepted in the current locale.
:param kwargs: Keyword arguments for :meth:`~Datetime.date()`
:return: Formatted date.
"""
return self._dt.formatted_date(fmt=fmt, start=self.future.year)

def datetime(self, end: int = None,
timezone: Optional[str] = None) -> DateTime:
"""Generate random datetime from the year of the current future.

:param end: Maximum value of year.
:raises ValueError: When ``end`` is before the year of the
current future.
:param timezone: Set custom timezone (pytz required).
:return: Datetime
"""
if not end:
end = self.future.year + 19
else:
self._validate_future_year(end)
return self._dt.datetime(
start=self.future.year, end=end, timezone=timezone)

def formatted_datetime(self, fmt: str = '', **kwargs) -> str:
"""Generate datetime string in human readable format.

:param fmt: Custom format (default is format for current locale)
:param kwargs: Keyword arguments for :meth:`~Datetime.datetime()`
:return: Formatted datetime string.
"""
return self._dt.formatted_datetime(fmt=fmt, start=self.future.year)
4 changes: 2 additions & 2 deletions tests/test_providers/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def test_datetime(self, _datetime, start, end, timezone):
],
)
def test_formatted_datetime(self, _datetime, start, end):
dt_str = _datetime.formatted_date(fmt='%Y', start=start, end=end)
dt_str = _datetime.formatted_datetime(fmt='%Y', start=start, end=end)
assert isinstance(dt_str, str)
assert start <= int(dt_str) <= end

Expand Down Expand Up @@ -213,7 +213,7 @@ def test_timestamp(self, d1, d2):
assert d1.timestamp(posix=False) == d2.timestamp(posix=False)

def test_formatted_datetime(self, d1, d2):
assert d1.formatted_date() == d2.formatted_date()
assert d1.formatted_datetime() == d2.formatted_datetime()

def test_week_date(self, d1, d2):
assert d1.week_date() == d2.week_date()
Expand Down
116 changes: 116 additions & 0 deletions tests/test_providers/test_future_datetime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
import datetime
import re

import pytest

from mimesis import Datetime, FutureDatetime
from mimesis.data import GMT_OFFSETS, TIMEZONES

from . import patterns


class TestFutureDatetime(object):

@pytest.fixture
def future_dt(self):
return FutureDatetime()

def test_str(self, dt):
assert re.match(patterns.DATA_PROVIDER_STR_REGEX, str(dt))

def test_week_date(self, future_dt):
result = future_dt.week_date()
result = result.replace('-', ' ').replace('W', '')
year, week = result.split(' ')
assert int(year) >= future_dt.future.year
assert int(year) <= future_dt.future.year + 1
assert int(week) <= 52

with pytest.raises(ValueError):
future_dt.week_date(end=datetime.MINYEAR)

def test_year(self, future_dt):
result = future_dt.year()
assert result >= future_dt.future.year
assert result <= future_dt.future.year + 65

with pytest.raises(ValueError):
future_dt.year(maximum=datetime.MINYEAR)

def test_date(self, future_dt):
date_object = future_dt.date()
assert isinstance(date_object, datetime.date)
assert date_object.year >= future_dt.future.year
assert date_object.year <= future_dt.future.year + 19

with pytest.raises(ValueError):
future_dt.date(end=datetime.MINYEAR)

def test_formatted_date(self, future_dt):
fmt_date = future_dt.formatted_date('%Y', end=datetime.MAXYEAR)
assert int(fmt_date) >= future_dt.future.year
assert isinstance(fmt_date, str)

@pytest.mark.parametrize(
'end, timezone', [
(datetime.MAXYEAR, 'Europe/Paris'),
(datetime.MAXYEAR, None),
],
)
def test_datetime(self, future_dt, end, timezone):
dt_obj = future_dt.datetime(end=end, timezone=timezone)

assert future_dt.future.year <= dt_obj.year <= datetime.MAXYEAR
assert isinstance(dt_obj, datetime.datetime)

with pytest.raises(ValueError):
future_dt.datetime(end=datetime.MINYEAR)

def test_formatted_datetime(self, future_dt):
dt_obj = future_dt.formatted_datetime('%Y', end=datetime.MAXYEAR)
assert int(dt_obj) >= future_dt.future.year
assert isinstance(dt_obj, str)

def test_is_subclass(self, future_dt):
datetime_methods = [method for method in dir(Datetime)
if callable(getattr(Datetime, method))]
assert len(datetime_methods) > 0
for method in datetime_methods:
assert method in dir(future_dt)


class TestSeededFutureDatetime(object):

@pytest.fixture
def d1(self, seed):
return FutureDatetime(seed=seed)

@pytest.fixture
def d2(self, seed):
return FutureDatetime(seed=seed)

def test_week_date(self, d1, d2):
assert d1.week_date() == d2.week_date()
assert d1.week_date(end=datetime.MAXYEAR) == \
d2.week_date(end=datetime.MAXYEAR)

def test_year(self, d1, d2):
assert d1.year() == d2.year()
assert d1.year(maximum=datetime.MAXYEAR) == \
d2.year(maximum=datetime.MAXYEAR)

def test_date(self, d1, d2):
assert d1.date() == d2.date()
assert d1.date(end=datetime.MAXYEAR) == d2.date(end=datetime.MAXYEAR)

def test_formatted_date(self, d1, d2):
assert d1.formatted_date() == d2.formatted_date()

def test_datetime(self, d1, d2):
assert d1.datetime() == d2.datetime()
assert d1.datetime(end=datetime.MAXYEAR) == \
d2.datetime(end=datetime.MAXYEAR)

def test_formatted_datetime(self, d1, d2):
assert d1.formatted_datetime() == d2.formatted_datetime()