datetime.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. # coding=utf-8
  2. #
  3. # This file is part of Hypothesis, which may be found at
  4. # https://github.com/HypothesisWorks/hypothesis-python
  5. #
  6. # Most of this work is copyright (C) 2013-2018 David R. MacIver
  7. # (david@drmaciver.com), but it contains contributions by others. See
  8. # CONTRIBUTING.rst for a full list of people who may hold copyright, and
  9. # consult the git log if you need to determine who owns an individual
  10. # contribution.
  11. #
  12. # This Source Code Form is subject to the terms of the Mozilla Public License,
  13. # v. 2.0. If a copy of the MPL was not distributed with this file, You can
  14. # obtain one at http://mozilla.org/MPL/2.0/.
  15. #
  16. # END HEADER
  17. from __future__ import division, print_function, absolute_import
  18. import datetime as dt
  19. from hypothesis.internal.conjecture import utils
  20. from hypothesis.searchstrategy.strategies import SearchStrategy
  21. __all__ = ['DateStrategy', 'DatetimeStrategy', 'TimedeltaStrategy']
  22. def is_pytz_timezone(tz):
  23. if not isinstance(tz, dt.tzinfo):
  24. return False
  25. module = type(tz).__module__
  26. return module == 'pytz' or module.startswith('pytz.')
  27. class DatetimeStrategy(SearchStrategy):
  28. def __init__(self, min_value, max_value, timezones_strat):
  29. assert isinstance(min_value, dt.datetime)
  30. assert isinstance(max_value, dt.datetime)
  31. assert min_value.tzinfo is None
  32. assert max_value.tzinfo is None
  33. assert min_value <= max_value
  34. assert isinstance(timezones_strat, SearchStrategy)
  35. self.min_dt = min_value
  36. self.max_dt = max_value
  37. self.tz_strat = timezones_strat
  38. def _attempt_one_draw(self, data):
  39. result = dict()
  40. cap_low, cap_high = True, True
  41. for name in ('year', 'month', 'day',
  42. 'hour', 'minute', 'second', 'microsecond'):
  43. low = getattr(self.min_dt if cap_low else dt.datetime.min, name)
  44. high = getattr(self.max_dt if cap_high else dt.datetime.max, name)
  45. if name == 'year':
  46. val = utils.centered_integer_range(data, low, high, 2000)
  47. else:
  48. val = utils.integer_range(data, low, high)
  49. result[name] = val
  50. cap_low = cap_low and val == low
  51. cap_high = cap_high and val == high
  52. tz = data.draw(self.tz_strat)
  53. try:
  54. result = dt.datetime(**result)
  55. if is_pytz_timezone(tz):
  56. # Can't just construct; see http://pytz.sourceforge.net
  57. return tz.normalize(tz.localize(result))
  58. return result.replace(tzinfo=tz)
  59. except (ValueError, OverflowError):
  60. return None
  61. def do_draw(self, data):
  62. for _ in range(3):
  63. result = self._attempt_one_draw(data)
  64. if result is not None:
  65. return result
  66. data.note_event('3 attempts to create a datetime between %r and %r '
  67. 'with timezone from %r failed.' %
  68. (self.min_dt, self.max_dt, self.tz_strat))
  69. data.mark_invalid()
  70. class DateStrategy(SearchStrategy):
  71. def __init__(self, min_value, max_value):
  72. assert isinstance(min_value, dt.date)
  73. assert isinstance(max_value, dt.date)
  74. assert min_value < max_value
  75. self.min_value = min_value
  76. self.days_apart = (max_value - min_value).days
  77. self.center = (dt.date(2000, 1, 1) - min_value).days
  78. def do_draw(self, data):
  79. return self.min_value + dt.timedelta(days=utils.centered_integer_range(
  80. data, 0, self.days_apart, center=self.center))
  81. class TimedeltaStrategy(SearchStrategy):
  82. def __init__(self, min_value, max_value):
  83. assert isinstance(min_value, dt.timedelta)
  84. assert isinstance(max_value, dt.timedelta)
  85. assert min_value < max_value
  86. self.min_value = min_value
  87. self.max_value = max_value
  88. def do_draw(self, data):
  89. result = dict()
  90. low_bound = True
  91. high_bound = True
  92. for name in ('days', 'seconds', 'microseconds'):
  93. low = getattr(
  94. self.min_value if low_bound else dt.timedelta.min, name)
  95. high = getattr(
  96. self.max_value if high_bound else dt.timedelta.max, name)
  97. val = utils.centered_integer_range(data, low, high, 0)
  98. result[name] = val
  99. low_bound = low_bound and val == low
  100. high_bound = high_bound and val == high
  101. return dt.timedelta(**result)