# coding=utf-8 # # This file is part of Hypothesis, which may be found at # https://github.com/HypothesisWorks/hypothesis-python # # Most of this work is copyright (C) 2013-2018 David R. MacIver # (david@drmaciver.com), but it contains contributions by others. See # 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 import datetime as dt from hypothesis.internal.conjecture import utils from hypothesis.searchstrategy.strategies import SearchStrategy __all__ = ['DateStrategy', 'DatetimeStrategy', 'TimedeltaStrategy'] def is_pytz_timezone(tz): if not isinstance(tz, dt.tzinfo): return False module = type(tz).__module__ return module == 'pytz' or module.startswith('pytz.') class DatetimeStrategy(SearchStrategy): def __init__(self, min_value, max_value, timezones_strat): assert isinstance(min_value, dt.datetime) assert isinstance(max_value, dt.datetime) assert min_value.tzinfo is None assert max_value.tzinfo is None assert min_value <= max_value assert isinstance(timezones_strat, SearchStrategy) self.min_dt = min_value self.max_dt = max_value self.tz_strat = timezones_strat def _attempt_one_draw(self, data): result = dict() cap_low, cap_high = True, True for name in ('year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond'): low = getattr(self.min_dt if cap_low else dt.datetime.min, name) high = getattr(self.max_dt if cap_high else dt.datetime.max, name) if name == 'year': val = utils.centered_integer_range(data, low, high, 2000) else: val = utils.integer_range(data, low, high) result[name] = val cap_low = cap_low and val == low cap_high = cap_high and val == high tz = data.draw(self.tz_strat) try: result = dt.datetime(**result) if is_pytz_timezone(tz): # Can't just construct; see http://pytz.sourceforge.net return tz.normalize(tz.localize(result)) return result.replace(tzinfo=tz) except (ValueError, OverflowError): return None def do_draw(self, data): for _ in range(3): result = self._attempt_one_draw(data) if result is not None: return result data.note_event('3 attempts to create a datetime between %r and %r ' 'with timezone from %r failed.' % (self.min_dt, self.max_dt, self.tz_strat)) data.mark_invalid() class DateStrategy(SearchStrategy): def __init__(self, min_value, max_value): assert isinstance(min_value, dt.date) assert isinstance(max_value, dt.date) assert min_value < max_value self.min_value = min_value self.days_apart = (max_value - min_value).days self.center = (dt.date(2000, 1, 1) - min_value).days def do_draw(self, data): return self.min_value + dt.timedelta(days=utils.centered_integer_range( data, 0, self.days_apart, center=self.center)) class TimedeltaStrategy(SearchStrategy): def __init__(self, min_value, max_value): assert isinstance(min_value, dt.timedelta) assert isinstance(max_value, dt.timedelta) assert min_value < max_value self.min_value = min_value self.max_value = max_value def do_draw(self, data): result = dict() low_bound = True high_bound = True for name in ('days', 'seconds', 'microseconds'): low = getattr( self.min_value if low_bound else dt.timedelta.min, name) high = getattr( self.max_value if high_bound else dt.timedelta.max, name) val = utils.centered_integer_range(data, low, high, 0) result[name] = val low_bound = low_bound and val == low high_bound = high_bound and val == high return dt.timedelta(**result)