Skip to content

Commit

Permalink
Add dates() and times() strategies to hypothesis.extra.datetime
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Chainz committed Sep 25, 2015
1 parent 9da0f7f commit c717e4f
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 41 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Expand Up @@ -2,3 +2,7 @@
*.swp
*.pyc
venv*
.cache
.hypothesis
docs/_build
*.egg-info
1 change: 1 addition & 0 deletions CONTRIBUTING.rst
Expand Up @@ -69,6 +69,7 @@ The primary author for most of Hypothesis is David R. MacIver (me). However the
people have also contributed work. As well as my thanks, they also have copyright over
their individual contributions.

* `Adam Johnson <https://github.com/adamchainz>`_
* `Adam Sven Johnson <https://www.github.com/pkqk>`_
* `Alex Stapleton <https://www.github.com/public>`_
* `Charles O'Farrell <https://www.github.com/charleso>`_
Expand Down
3 changes: 3 additions & 0 deletions docs/Makefile
Expand Up @@ -56,6 +56,9 @@ html:
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

livehtml:
sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
Expand Down
119 changes: 78 additions & 41 deletions docs/extras.rst
Expand Up @@ -6,7 +6,7 @@ Hypothesis itself does not have any dependencies, but there are some packages th
need additional things installed in order to work.

You can install these dependencies using the setuptools extra feature as e.g.
pip install hypothesis[django]. This will check installation of compatible versions.
``pip install hypothesis[django]``. This will check installation of compatible versions.

You can also just install hypothesis into a project using them, ignore the version
constraints, and hope for the best.
Expand All @@ -20,62 +20,99 @@ you run into a bug with any of these please specify the dependency version.
hypothesis[datetime]
--------------------

As might be expected, this provides a strategy which generates instances of
datetime. It depends on pytz to work.
As might be expected, this provides strategies for which generating instances
of objects from the ``datetime`` module: ``datetime``\s, ``date``\s, and
``time``\s. It depends on ``pytz`` to work.

It should work with just about any version of pytz. pytz has a very stable API
and Hypothesis works around a bug or two in older versions.
It should work with just about any version of ``pytz``. ``pytz`` has a very
stable API and Hypothesis works around a bug or two in older versions.

It lives in the hypothesis.extra.datetime package:
It lives in the ``hypothesis.extra.datetime`` package.

.. code-block:: pycon

>>> from hypothesis.extra.datetime import datetimes
>>> datetimes().example()
datetime.datetime(1705, 1, 20, 0, 32, 0, 973139, tzinfo=<DstTzInfo 'Israel...
>>> datetimes().example()
datetime.datetime(7274, 6, 9, 23, 0, 31, 75498, tzinfo=<DstTzInfo 'America...
.. method:: datetimes(allow_naive=None, timezones=None, min_year=None, \
max_year=None)

As you can see, it produces years from quite a wide range. If you want to
narrow it down you can ask for a more specific range of years:
This strategy generates ``datetime`` objects. For example:

.. code-block:: pycon
.. code-block:: pycon
>>> datetimes(min_year=2001, max_year=2010).example()
datetime.datetime(2010, 7, 7, 0, 15, 0, 614034, tzinfo=<DstTzInfo 'Pacif...
>>> datetimes(min_year=2001, max_year=2010).example()
datetime.datetime(2006, 9, 26, 22, 0, 0, 220365, tzinfo=<DstTzInfo 'Asia...
>>> from hypothesis.extra.datetime import datetimes
>>> datetimes().example()
datetime.datetime(1705, 1, 20, 0, 32, 0, 973139, tzinfo=<DstTzInfo 'Israel...
>>> datetimes().example()
datetime.datetime(7274, 6, 9, 23, 0, 31, 75498, tzinfo=<DstTzInfo 'America...
You can also specify timezones:
As you can see, it produces years from quite a wide range. If you want to
narrow it down you can ask for a more specific range of years:

.. code-block:: pycon
.. code-block:: pycon
>>> import pytz
>>> pytz.all_timezones[:3]
['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa']
>>> datetimes(timezones=pytz.all_timezones[:3]).example()
datetime.datetime(6257, 8, 21, 13, 6, 24, 8751, tzinfo=<DstTzInfo 'Africa/Accra' GMT0:00:00 STD>)
>>> datetimes(timezones=pytz.all_timezones[:3]).example()
datetime.datetime(7851, 2, 3, 0, 0, 0, 767400, tzinfo=<DstTzInfo 'Africa/Accra' GMT0:00:00 STD>)
>>> datetimes(timezones=pytz.all_timezones[:3]).example()
datetime.datetime(8262, 6, 22, 16, 0, 0, 154235, tzinfo=<DstTzInfo 'Africa/Abidjan' GMT0:00:00 STD>)
>>> datetimes(min_year=2001, max_year=2010).example()
datetime.datetime(2010, 7, 7, 0, 15, 0, 614034, tzinfo=<DstTzInfo 'Pacif...
>>> datetimes(min_year=2001, max_year=2010).example()
datetime.datetime(2006, 9, 26, 22, 0, 0, 220365, tzinfo=<DstTzInfo 'Asia...
If the set of timezones is empty you will get a naive datetime:
You can also specify timezones:

.. code-block:: pycon
.. code-block:: pycon
>>> datetimes(timezones=[]).example()
datetime.datetime(918, 11, 26, 2, 0, 35, 916439)
>>> import pytz
>>> pytz.all_timezones[:3]
['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa']
>>> datetimes(timezones=pytz.all_timezones[:3]).example()
datetime.datetime(6257, 8, 21, 13, 6, 24, 8751, tzinfo=<DstTzInfo 'Africa/Accra' GMT0:00:00 STD>)
>>> datetimes(timezones=pytz.all_timezones[:3]).example()
datetime.datetime(7851, 2, 3, 0, 0, 0, 767400, tzinfo=<DstTzInfo 'Africa/Accra' GMT0:00:00 STD>)
>>> datetimes(timezones=pytz.all_timezones[:3]).example()
datetime.datetime(8262, 6, 22, 16, 0, 0, 154235, tzinfo=<DstTzInfo 'Africa/Abidjan' GMT0:00:00 STD>)
You can also explicitly get a mix of naive and non-naive datetimes if you
want:
If the set of timezones is empty you will get a naive datetime:

.. code-block:: pycon
.. code-block:: pycon
>>> datetimes(timezones=[]).example()
datetime.datetime(918, 11, 26, 2, 0, 35, 916439)
You can also explicitly get a mix of naive and non-naive datetimes if you
want:

.. code-block:: pycon
>>> datetimes(allow_naive=True).example()
datetime.datetime(2433, 3, 20, 0, 0, 44, 460383, tzinfo=<DstTzInfo 'Asia/Hovd' HOVT+7:00:00 STD>)
>>> datetimes(allow_naive=True).example()
datetime.datetime(7003, 1, 22, 0, 0, 52, 401259)
.. method:: dates(min_year=None, max_year=None)

This strategy generates ``date`` objects. For example:

.. code-block:: pycon
>>> from hypothesis.extra.datetime import dates
>>> dates().example()
datetime.date(1687, 3, 23)
>>> dates().example()
datetime.date(9565, 5, 2)
Again, you can restrict the range with the ``min_year`` and ``max_year``
arguments.


.. method:: times()

This strategy generates ``time`` objects. For example:

.. code-block:: pycon
>>> from hypothesis.extra.datetime import times
>>> times().example()
datetime.time(0, 15, 55, 188712)
>>> times().example()
datetime.time(9, 0, 47, 959374)
>>> datetimes(allow_naive=True).example()
datetime.datetime(2433, 3, 20, 0, 0, 44, 460383, tzinfo=<DstTzInfo 'Asia/Hovd' HOVT+7:00:00 STD>)
>>> datetimes(allow_naive=True).example()
datetime.datetime(7003, 1, 22, 0, 0, 52, 401259)
-----------------------
hypothesis[fakefactory]
Expand Down
25 changes: 25 additions & 0 deletions src/hypothesis/extra/datetime.py
Expand Up @@ -278,6 +278,31 @@ def datetimes(allow_naive=None, timezones=None, min_year=None, max_year=None):
)


@defines_strategy
def dates(min_year=None, max_year=None):
"""Return a strategy for generating dates."""
return datetimes(
allow_naive=True, timezones=[],
min_year=min_year, max_year=max_year,
).map(datetime_to_date)


def datetime_to_date(dt):
return dt.date()


@defines_strategy
def times(allow_naive=None, timezones=None):
"""Return a strategy for generating times."""
return datetimes(
allow_naive=allow_naive, timezones=timezones,
).map(datetime_to_time)


def datetime_to_time(dt):
return dt.timetz()


@strategy.extend_static(dt.datetime)
def datetime_strategy(cls, settings):
return datetimes()
Expand Down
75 changes: 75 additions & 0 deletions tests/datetime/test_dates.py
@@ -0,0 +1,75 @@
# coding=utf-8

# This file is part of Hypothesis (https://github.com/DRMacIver/hypothesis)

# Most of this work is copyright (C) 2013-2015 David R. MacIver
# (david@drmaciver.com), but it contains contributions by others. See
# https://github.com/DRMacIver/hypothesis/blob/master/CONTRIBUTING.rst for a
# full list of people who may hold copyright, and consult the git log if you
# need to determine who owns an individual contribution.

# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at http://mozilla.org/MPL/2.0/.

# END HEADER

from __future__ import division, print_function, absolute_import

from random import Random
from datetime import MAXYEAR

import pytest
import hypothesis.settings as hs
from hypothesis.strategytests import strategy_test_suite
from hypothesis.extra.datetime import dates
from hypothesis.internal.debug import minimal
from hypothesis.internal.compat import hrange
from hypothesis.searchstrategy.strategies import BadData

hs.Settings.default.max_examples = 1000


TestStandardDescriptorFeatures1 = strategy_test_suite(dates())


def test_can_find_after_the_year_2000():
assert minimal(dates(), lambda x: x.year > 2000).year == 2001


def test_can_find_before_the_year_2000():
assert minimal(dates(), lambda x: x.year < 2000).year == 1999


def test_can_find_each_month():
for i in hrange(1, 12):
minimal(dates(), lambda x: x.month == i)


def test_min_year_is_respected():
assert minimal(dates(min_year=2003)).year == 2003


def test_max_year_is_respected():
assert minimal(dates(max_year=1998)).year == 1998


def test_year_bounds_are_respected_in_deserialization():
s = dates()
r = Random(1)
template = s.draw_template(r, s.draw_parameter(r))
year = s.reify(template).year
basic = s.to_basic(template)
above = dates(min_year=year + 1)
below = dates(max_year=year - 1)
with pytest.raises(BadData):
above.from_basic(basic)
with pytest.raises(BadData):
below.from_basic(basic)


def test_can_draw_times_in_the_final_year():
last_year = dates(min_year=MAXYEAR)
r = Random(1)
for _ in hrange(1000):
last_year.reify(last_year.draw_and_produce(r))
99 changes: 99 additions & 0 deletions tests/datetime/test_times.py
@@ -0,0 +1,99 @@
# coding=utf-8

# This file is part of Hypothesis (https://github.com/DRMacIver/hypothesis)

# Most of this work is copyright (C) 2013-2015 David R. MacIver
# (david@drmaciver.com), but it contains contributions by others. See
# https://github.com/DRMacIver/hypothesis/blob/master/CONTRIBUTING.rst for a
# full list of people who may hold copyright, and consult the git log if you
# need to determine who owns an individual contribution.

# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at http://mozilla.org/MPL/2.0/.

# END HEADER

from __future__ import division, print_function, absolute_import

from random import Random

import pytz
import pytest
import hypothesis.settings as hs
from hypothesis import given, assume
from hypothesis.strategytests import strategy_test_suite
from hypothesis.extra.datetime import times
from hypothesis.internal.debug import minimal
from hypothesis.searchstrategy.strategies import BadData

hs.Settings.default.max_examples = 1000


TestStandardDescriptorFeatures1 = strategy_test_suite(times())


def test_can_find_midnight():
minimal(
times(),
lambda x: (x.hour == 0 and x.minute == 0 and x.second == 0),
)


def test_can_find_non_midnight():
assert minimal(times(), lambda x: x.hour != 0).hour == 1


def test_can_find_off_the_minute():
minimal(times(), lambda x: x.second == 0)


def test_can_find_on_the_minute():
minimal(times(), lambda x: x.second != 0)


def test_simplifies_towards_midnight():
d = minimal(times())
assert d.hour == 0
assert d.minute == 0
assert d.second == 0
assert d.microsecond == 0


def test_can_generate_naive_time():
minimal(times(allow_naive=True), lambda d: not d.tzinfo)


def test_can_generate_non_naive_time():
assert minimal(
times(allow_naive=True), lambda d: d.tzinfo).tzinfo == pytz.UTC


def test_can_generate_non_utc():
minimal(
times(),
lambda d: assume(d.tzinfo) and d.tzinfo.zone != u'UTC')


@given(times(timezones=[]))
def test_naive_times_are_naive(dt):
assert not dt.tzinfo


@given(times(allow_naive=False))
def test_timezone_aware_times_are_timezone_aware(dt):
assert dt.tzinfo


def test_restricts_to_allowed_set_of_timezones():
timezones = list(map(pytz.timezone, list(pytz.all_timezones)[:3]))
x = minimal(times(timezones=timezones))
assert any(tz.zone == x.tzinfo.zone for tz in timezones)


def test_timezones_are_checked_in_deserialization():
s = times()
r = Random(1)
basic = s.to_basic(s.draw_template(r, s.draw_parameter(r)))
with pytest.raises(BadData):
times(timezones=[]).from_basic(basic)

0 comments on commit c717e4f

Please sign in to comment.