aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/python-dateutil/py2/tests
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2024-03-18 15:57:16 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2024-03-18 17:36:01 +0300
commit5f76bcc9d8a7d0ff624c12731027acf9a54dd5a8 (patch)
tree15ab8c78ee8a8e4e743139621d634551298db94e /contrib/python/python-dateutil/py2/tests
parentebc6526bccdf9d2304b9eef3a0a9eaba8e7e38f7 (diff)
downloadydb-5f76bcc9d8a7d0ff624c12731027acf9a54dd5a8.tar.gz
Intermediate changes
Diffstat (limited to 'contrib/python/python-dateutil/py2/tests')
-rw-r--r--contrib/python/python-dateutil/py2/tests/__init__.py0
-rw-r--r--contrib/python/python-dateutil/py2/tests/_common.py233
-rw-r--r--contrib/python/python-dateutil/py2/tests/conftest.py41
-rw-r--r--contrib/python/python-dateutil/py2/tests/property/test_isoparse_prop.py27
-rw-r--r--contrib/python/python-dateutil/py2/tests/property/test_parser_prop.py22
-rw-r--r--contrib/python/python-dateutil/py2/tests/property/test_tz_prop.py35
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_easter.py93
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_import_star.py33
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_imports.py240
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_internals.py91
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_isoparser.py509
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_parser.py964
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_relativedelta.py767
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_rrule.py4914
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_tz.py2811
-rw-r--r--contrib/python/python-dateutil/py2/tests/test_utils.py52
-rw-r--r--contrib/python/python-dateutil/py2/tests/ya.make2
17 files changed, 10832 insertions, 2 deletions
diff --git a/contrib/python/python-dateutil/py2/tests/__init__.py b/contrib/python/python-dateutil/py2/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/__init__.py
diff --git a/contrib/python/python-dateutil/py2/tests/_common.py b/contrib/python/python-dateutil/py2/tests/_common.py
new file mode 100644
index 0000000000..b8d2047374
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/_common.py
@@ -0,0 +1,233 @@
+from __future__ import unicode_literals
+import os
+import time
+import subprocess
+import warnings
+import tempfile
+import pickle
+
+import pytest
+
+
+class PicklableMixin(object):
+ def _get_nobj_bytes(self, obj, dump_kwargs, load_kwargs):
+ """
+ Pickle and unpickle an object using ``pickle.dumps`` / ``pickle.loads``
+ """
+ pkl = pickle.dumps(obj, **dump_kwargs)
+ return pickle.loads(pkl, **load_kwargs)
+
+ def _get_nobj_file(self, obj, dump_kwargs, load_kwargs):
+ """
+ Pickle and unpickle an object using ``pickle.dump`` / ``pickle.load`` on
+ a temporary file.
+ """
+ with tempfile.TemporaryFile('w+b') as pkl:
+ pickle.dump(obj, pkl, **dump_kwargs)
+ pkl.seek(0) # Reset the file to the beginning to read it
+ nobj = pickle.load(pkl, **load_kwargs)
+
+ return nobj
+
+ def assertPicklable(self, obj, singleton=False, asfile=False,
+ dump_kwargs=None, load_kwargs=None):
+ """
+ Assert that an object can be pickled and unpickled. This assertion
+ assumes that the desired behavior is that the unpickled object compares
+ equal to the original object, but is not the same object.
+ """
+ get_nobj = self._get_nobj_file if asfile else self._get_nobj_bytes
+ dump_kwargs = dump_kwargs or {}
+ load_kwargs = load_kwargs or {}
+
+ nobj = get_nobj(obj, dump_kwargs, load_kwargs)
+ if not singleton:
+ self.assertIsNot(obj, nobj)
+ self.assertEqual(obj, nobj)
+
+
+class TZContextBase(object):
+ """
+ Base class for a context manager which allows changing of time zones.
+
+ Subclasses may define a guard variable to either block or or allow time
+ zone changes by redefining ``_guard_var_name`` and ``_guard_allows_change``.
+ The default is that the guard variable must be affirmatively set.
+
+ Subclasses must define ``get_current_tz`` and ``set_current_tz``.
+ """
+ _guard_var_name = "DATEUTIL_MAY_CHANGE_TZ"
+ _guard_allows_change = True
+
+ def __init__(self, tzval):
+ self.tzval = tzval
+ self._old_tz = None
+
+ @classmethod
+ def tz_change_allowed(cls):
+ """
+ Class method used to query whether or not this class allows time zone
+ changes.
+ """
+ guard = bool(os.environ.get(cls._guard_var_name, False))
+
+ # _guard_allows_change gives the "default" behavior - if True, the
+ # guard is overcoming a block. If false, the guard is causing a block.
+ # Whether tz_change is allowed is therefore the XNOR of the two.
+ return guard == cls._guard_allows_change
+
+ @classmethod
+ def tz_change_disallowed_message(cls):
+ """ Generate instructions on how to allow tz changes """
+ msg = ('Changing time zone not allowed. Set {envar} to {gval} '
+ 'if you would like to allow this behavior')
+
+ return msg.format(envar=cls._guard_var_name,
+ gval=cls._guard_allows_change)
+
+ def __enter__(self):
+ if not self.tz_change_allowed():
+ msg = self.tz_change_disallowed_message()
+ pytest.skip(msg)
+
+ # If this is used outside of a test suite, we still want an error.
+ raise ValueError(msg) # pragma: no cover
+
+ self._old_tz = self.get_current_tz()
+ self.set_current_tz(self.tzval)
+
+ def __exit__(self, type, value, traceback):
+ if self._old_tz is not None:
+ self.set_current_tz(self._old_tz)
+
+ self._old_tz = None
+
+ def get_current_tz(self):
+ raise NotImplementedError
+
+ def set_current_tz(self):
+ raise NotImplementedError
+
+
+class TZEnvContext(TZContextBase):
+ """
+ Context manager that temporarily sets the `TZ` variable (for use on
+ *nix-like systems). Because the effect is local to the shell anyway, this
+ will apply *unless* a guard is set.
+
+ If you do not want the TZ environment variable set, you may set the
+ ``DATEUTIL_MAY_NOT_CHANGE_TZ_VAR`` variable to a truthy value.
+ """
+ _guard_var_name = "DATEUTIL_MAY_NOT_CHANGE_TZ_VAR"
+ _guard_allows_change = False
+
+ def get_current_tz(self):
+ return os.environ.get('TZ', UnsetTz)
+
+ def set_current_tz(self, tzval):
+ if tzval is UnsetTz and 'TZ' in os.environ:
+ del os.environ['TZ']
+ else:
+ os.environ['TZ'] = tzval
+
+ time.tzset()
+
+
+class TZWinContext(TZContextBase):
+ """
+ Context manager for changing local time zone on Windows.
+
+ Because the effect of this is system-wide and global, it may have
+ unintended side effect. Set the ``DATEUTIL_MAY_CHANGE_TZ`` environment
+ variable to a truthy value before using this context manager.
+ """
+ def get_current_tz(self):
+ p = subprocess.Popen(['tzutil', '/g'], stdout=subprocess.PIPE)
+
+ ctzname, err = p.communicate()
+ ctzname = ctzname.decode() # Popen returns
+
+ if p.returncode:
+ raise OSError('Failed to get current time zone: ' + err)
+
+ return ctzname
+
+ def set_current_tz(self, tzname):
+ p = subprocess.Popen('tzutil /s "' + tzname + '"')
+
+ out, err = p.communicate()
+
+ if p.returncode:
+ raise OSError('Failed to set current time zone: ' +
+ (err or 'Unknown error.'))
+
+
+###
+# Utility classes
+class NotAValueClass(object):
+ """
+ A class analogous to NaN that has operations defined for any type.
+ """
+ def _op(self, other):
+ return self # Operation with NotAValue returns NotAValue
+
+ def _cmp(self, other):
+ return False
+
+ __add__ = __radd__ = _op
+ __sub__ = __rsub__ = _op
+ __mul__ = __rmul__ = _op
+ __div__ = __rdiv__ = _op
+ __truediv__ = __rtruediv__ = _op
+ __floordiv__ = __rfloordiv__ = _op
+
+ __lt__ = __rlt__ = _op
+ __gt__ = __rgt__ = _op
+ __eq__ = __req__ = _op
+ __le__ = __rle__ = _op
+ __ge__ = __rge__ = _op
+
+
+NotAValue = NotAValueClass()
+
+
+class ComparesEqualClass(object):
+ """
+ A class that is always equal to whatever you compare it to.
+ """
+
+ def __eq__(self, other):
+ return True
+
+ def __ne__(self, other):
+ return False
+
+ def __le__(self, other):
+ return True
+
+ def __ge__(self, other):
+ return True
+
+ def __lt__(self, other):
+ return False
+
+ def __gt__(self, other):
+ return False
+
+ __req__ = __eq__
+ __rne__ = __ne__
+ __rle__ = __le__
+ __rge__ = __ge__
+ __rlt__ = __lt__
+ __rgt__ = __gt__
+
+
+ComparesEqual = ComparesEqualClass()
+
+
+class UnsetTzClass(object):
+ """ Sentinel class for unset time zone variable """
+ pass
+
+
+UnsetTz = UnsetTzClass()
diff --git a/contrib/python/python-dateutil/py2/tests/conftest.py b/contrib/python/python-dateutil/py2/tests/conftest.py
new file mode 100644
index 0000000000..78ed70acb3
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/conftest.py
@@ -0,0 +1,41 @@
+import os
+import pytest
+
+
+# Configure pytest to ignore xfailing tests
+# See: https://stackoverflow.com/a/53198349/467366
+def pytest_collection_modifyitems(items):
+ for item in items:
+ marker_getter = getattr(item, 'get_closest_marker', None)
+
+ # Python 3.3 support
+ if marker_getter is None:
+ marker_getter = item.get_marker
+
+ marker = marker_getter('xfail')
+
+ # Need to query the args because conditional xfail tests still have
+ # the xfail mark even if they are not expected to fail
+ if marker and (not marker.args or marker.args[0]):
+ item.add_marker(pytest.mark.no_cover)
+
+
+def set_tzpath():
+ """
+ Sets the TZPATH variable if it's specified in an environment variable.
+ """
+ tzpath = os.environ.get('DATEUTIL_TZPATH', None)
+
+ if tzpath is None:
+ return
+
+ path_components = tzpath.split(':')
+
+ print("Setting TZPATH to {}".format(path_components))
+
+ from dateutil import tz
+ tz.TZPATHS.clear()
+ tz.TZPATHS.extend(path_components)
+
+
+set_tzpath()
diff --git a/contrib/python/python-dateutil/py2/tests/property/test_isoparse_prop.py b/contrib/python/python-dateutil/py2/tests/property/test_isoparse_prop.py
new file mode 100644
index 0000000000..f8e288f3d6
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/property/test_isoparse_prop.py
@@ -0,0 +1,27 @@
+from hypothesis import given, assume
+from hypothesis import strategies as st
+
+from dateutil import tz
+from dateutil.parser import isoparse
+
+import pytest
+
+# Strategies
+TIME_ZONE_STRATEGY = st.sampled_from([None, tz.UTC] +
+ [tz.gettz(zname) for zname in ('US/Eastern', 'US/Pacific',
+ 'Australia/Sydney', 'Europe/London')])
+ASCII_STRATEGY = st.characters(max_codepoint=127)
+
+
+@pytest.mark.isoparser
+@given(dt=st.datetimes(timezones=TIME_ZONE_STRATEGY), sep=ASCII_STRATEGY)
+def test_timespec_auto(dt, sep):
+ if dt.tzinfo is not None:
+ # Assume offset has no sub-second components
+ assume(dt.utcoffset().total_seconds() % 60 == 0)
+
+ sep = str(sep) # Python 2.7 requires bytes
+ dtstr = dt.isoformat(sep=sep)
+ dt_rt = isoparse(dtstr)
+
+ assert dt_rt == dt
diff --git a/contrib/python/python-dateutil/py2/tests/property/test_parser_prop.py b/contrib/python/python-dateutil/py2/tests/property/test_parser_prop.py
new file mode 100644
index 0000000000..fdfd171e86
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/property/test_parser_prop.py
@@ -0,0 +1,22 @@
+from hypothesis.strategies import integers
+from hypothesis import given
+
+import pytest
+
+from dateutil.parser import parserinfo
+
+
+@pytest.mark.parserinfo
+@given(integers(min_value=100, max_value=9999))
+def test_convertyear(n):
+ assert n == parserinfo().convertyear(n)
+
+
+@pytest.mark.parserinfo
+@given(integers(min_value=-50,
+ max_value=49))
+def test_convertyear_no_specified_century(n):
+ p = parserinfo()
+ new_year = p._year + n
+ result = p.convertyear(new_year % 100, century_specified=False)
+ assert result == new_year
diff --git a/contrib/python/python-dateutil/py2/tests/property/test_tz_prop.py b/contrib/python/python-dateutil/py2/tests/property/test_tz_prop.py
new file mode 100644
index 0000000000..ec6d271dcf
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/property/test_tz_prop.py
@@ -0,0 +1,35 @@
+from datetime import datetime, timedelta
+
+import pytest
+import six
+from hypothesis import assume, given
+from hypothesis import strategies as st
+
+from dateutil import tz as tz
+
+EPOCHALYPSE = datetime.fromtimestamp(2147483647)
+NEGATIVE_EPOCHALYPSE = datetime.fromtimestamp(0) - timedelta(seconds=2147483648)
+
+
+@pytest.mark.gettz
+@pytest.mark.parametrize("gettz_arg", [None, ""])
+# TODO: Remove bounds when GH #590 is resolved
+@given(
+ dt=st.datetimes(
+ min_value=NEGATIVE_EPOCHALYPSE, max_value=EPOCHALYPSE, timezones=st.just(tz.UTC),
+ )
+)
+def test_gettz_returns_local(gettz_arg, dt):
+ act_tz = tz.gettz(gettz_arg)
+ if isinstance(act_tz, tz.tzlocal):
+ return
+
+ dt_act = dt.astimezone(tz.gettz(gettz_arg))
+ if six.PY2:
+ dt_exp = dt.astimezone(tz.tzlocal())
+ else:
+ dt_exp = dt.astimezone()
+
+ assert dt_act == dt_exp
+ assert dt_act.tzname() == dt_exp.tzname()
+ assert dt_act.utcoffset() == dt_exp.utcoffset()
diff --git a/contrib/python/python-dateutil/py2/tests/test_easter.py b/contrib/python/python-dateutil/py2/tests/test_easter.py
new file mode 100644
index 0000000000..cf2ec7f287
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_easter.py
@@ -0,0 +1,93 @@
+from dateutil.easter import easter
+from dateutil.easter import EASTER_WESTERN, EASTER_ORTHODOX, EASTER_JULIAN
+
+from datetime import date
+import pytest
+
+# List of easters between 1990 and 2050
+western_easter_dates = [
+ date(1990, 4, 15), date(1991, 3, 31), date(1992, 4, 19), date(1993, 4, 11),
+ date(1994, 4, 3), date(1995, 4, 16), date(1996, 4, 7), date(1997, 3, 30),
+ date(1998, 4, 12), date(1999, 4, 4),
+
+ date(2000, 4, 23), date(2001, 4, 15), date(2002, 3, 31), date(2003, 4, 20),
+ date(2004, 4, 11), date(2005, 3, 27), date(2006, 4, 16), date(2007, 4, 8),
+ date(2008, 3, 23), date(2009, 4, 12),
+
+ date(2010, 4, 4), date(2011, 4, 24), date(2012, 4, 8), date(2013, 3, 31),
+ date(2014, 4, 20), date(2015, 4, 5), date(2016, 3, 27), date(2017, 4, 16),
+ date(2018, 4, 1), date(2019, 4, 21),
+
+ date(2020, 4, 12), date(2021, 4, 4), date(2022, 4, 17), date(2023, 4, 9),
+ date(2024, 3, 31), date(2025, 4, 20), date(2026, 4, 5), date(2027, 3, 28),
+ date(2028, 4, 16), date(2029, 4, 1),
+
+ date(2030, 4, 21), date(2031, 4, 13), date(2032, 3, 28), date(2033, 4, 17),
+ date(2034, 4, 9), date(2035, 3, 25), date(2036, 4, 13), date(2037, 4, 5),
+ date(2038, 4, 25), date(2039, 4, 10),
+
+ date(2040, 4, 1), date(2041, 4, 21), date(2042, 4, 6), date(2043, 3, 29),
+ date(2044, 4, 17), date(2045, 4, 9), date(2046, 3, 25), date(2047, 4, 14),
+ date(2048, 4, 5), date(2049, 4, 18), date(2050, 4, 10)
+ ]
+
+orthodox_easter_dates = [
+ date(1990, 4, 15), date(1991, 4, 7), date(1992, 4, 26), date(1993, 4, 18),
+ date(1994, 5, 1), date(1995, 4, 23), date(1996, 4, 14), date(1997, 4, 27),
+ date(1998, 4, 19), date(1999, 4, 11),
+
+ date(2000, 4, 30), date(2001, 4, 15), date(2002, 5, 5), date(2003, 4, 27),
+ date(2004, 4, 11), date(2005, 5, 1), date(2006, 4, 23), date(2007, 4, 8),
+ date(2008, 4, 27), date(2009, 4, 19),
+
+ date(2010, 4, 4), date(2011, 4, 24), date(2012, 4, 15), date(2013, 5, 5),
+ date(2014, 4, 20), date(2015, 4, 12), date(2016, 5, 1), date(2017, 4, 16),
+ date(2018, 4, 8), date(2019, 4, 28),
+
+ date(2020, 4, 19), date(2021, 5, 2), date(2022, 4, 24), date(2023, 4, 16),
+ date(2024, 5, 5), date(2025, 4, 20), date(2026, 4, 12), date(2027, 5, 2),
+ date(2028, 4, 16), date(2029, 4, 8),
+
+ date(2030, 4, 28), date(2031, 4, 13), date(2032, 5, 2), date(2033, 4, 24),
+ date(2034, 4, 9), date(2035, 4, 29), date(2036, 4, 20), date(2037, 4, 5),
+ date(2038, 4, 25), date(2039, 4, 17),
+
+ date(2040, 5, 6), date(2041, 4, 21), date(2042, 4, 13), date(2043, 5, 3),
+ date(2044, 4, 24), date(2045, 4, 9), date(2046, 4, 29), date(2047, 4, 21),
+ date(2048, 4, 5), date(2049, 4, 25), date(2050, 4, 17)
+]
+
+# A random smattering of Julian dates.
+# Pulled values from http://www.kevinlaughery.com/east4099.html
+julian_easter_dates = [
+ date( 326, 4, 3), date( 375, 4, 5), date( 492, 4, 5), date( 552, 3, 31),
+ date( 562, 4, 9), date( 569, 4, 21), date( 597, 4, 14), date( 621, 4, 19),
+ date( 636, 3, 31), date( 655, 3, 29), date( 700, 4, 11), date( 725, 4, 8),
+ date( 750, 3, 29), date( 782, 4, 7), date( 835, 4, 18), date( 849, 4, 14),
+ date( 867, 3, 30), date( 890, 4, 12), date( 922, 4, 21), date( 934, 4, 6),
+ date(1049, 3, 26), date(1058, 4, 19), date(1113, 4, 6), date(1119, 3, 30),
+ date(1242, 4, 20), date(1255, 3, 28), date(1257, 4, 8), date(1258, 3, 24),
+ date(1261, 4, 24), date(1278, 4, 17), date(1333, 4, 4), date(1351, 4, 17),
+ date(1371, 4, 6), date(1391, 3, 26), date(1402, 3, 26), date(1412, 4, 3),
+ date(1439, 4, 5), date(1445, 3, 28), date(1531, 4, 9), date(1555, 4, 14)
+]
+
+
+@pytest.mark.parametrize("easter_date", western_easter_dates)
+def test_easter_western(easter_date):
+ assert easter_date == easter(easter_date.year, EASTER_WESTERN)
+
+
+@pytest.mark.parametrize("easter_date", orthodox_easter_dates)
+def test_easter_orthodox(easter_date):
+ assert easter_date == easter(easter_date.year, EASTER_ORTHODOX)
+
+
+@pytest.mark.parametrize("easter_date", julian_easter_dates)
+def test_easter_julian(easter_date):
+ assert easter_date == easter(easter_date.year, EASTER_JULIAN)
+
+
+def test_easter_bad_method():
+ with pytest.raises(ValueError):
+ easter(1975, 4)
diff --git a/contrib/python/python-dateutil/py2/tests/test_import_star.py b/contrib/python/python-dateutil/py2/tests/test_import_star.py
new file mode 100644
index 0000000000..2fb7098128
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_import_star.py
@@ -0,0 +1,33 @@
+"""Test for the "import *" functionality.
+
+As import * can be only done at module level, it has been added in a separate file
+"""
+import pytest
+
+prev_locals = list(locals())
+from dateutil import *
+new_locals = {name:value for name,value in locals().items()
+ if name not in prev_locals}
+new_locals.pop('prev_locals')
+
+
+@pytest.mark.import_star
+def test_imported_modules():
+ """ Test that `from dateutil import *` adds modules in __all__ locally """
+ import dateutil.easter
+ import dateutil.parser
+ import dateutil.relativedelta
+ import dateutil.rrule
+ import dateutil.tz
+ import dateutil.utils
+ import dateutil.zoneinfo
+
+ assert dateutil.easter == new_locals.pop("easter")
+ assert dateutil.parser == new_locals.pop("parser")
+ assert dateutil.relativedelta == new_locals.pop("relativedelta")
+ assert dateutil.rrule == new_locals.pop("rrule")
+ assert dateutil.tz == new_locals.pop("tz")
+ assert dateutil.utils == new_locals.pop("utils")
+ assert dateutil.zoneinfo == new_locals.pop("zoneinfo")
+
+ assert not new_locals
diff --git a/contrib/python/python-dateutil/py2/tests/test_imports.py b/contrib/python/python-dateutil/py2/tests/test_imports.py
new file mode 100644
index 0000000000..7d0749ec54
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_imports.py
@@ -0,0 +1,240 @@
+import sys
+import unittest
+import pytest
+import six
+
+MODULE_TYPE = type(sys)
+
+
+# Tests live in datetutil/test which cause a RuntimeWarning for Python2 builds.
+# But since we expect lazy imports tests to fail for Python < 3.7 we'll ignore those
+# warnings with this filter.
+
+if six.PY2:
+ filter_import_warning = pytest.mark.filterwarnings("ignore::RuntimeWarning")
+else:
+
+ def filter_import_warning(f):
+ return f
+
+
+@pytest.fixture(scope="function")
+def clean_import():
+ """Create a somewhat clean import base for lazy import tests"""
+ du_modules = {
+ mod_name: mod
+ for mod_name, mod in sys.modules.items()
+ if mod_name.startswith("dateutil")
+ }
+
+ other_modules = {
+ mod_name for mod_name in sys.modules if mod_name not in du_modules
+ }
+
+ for mod_name in du_modules:
+ del sys.modules[mod_name]
+
+ yield
+
+ # Delete anything that wasn't in the origin sys.modules list
+ for mod_name in list(sys.modules):
+ if mod_name not in other_modules:
+ del sys.modules[mod_name]
+
+ # Restore original modules
+ for mod_name, mod in du_modules.items():
+ sys.modules[mod_name] = mod
+
+
+@filter_import_warning
+@pytest.mark.parametrize(
+ "module",
+ ["easter", "parser", "relativedelta", "rrule", "tz", "utils", "zoneinfo"],
+)
+def test_lazy_import(clean_import, module):
+ """Test that dateutil.[submodule] works for py version > 3.7"""
+
+ import dateutil, importlib
+
+ if sys.version_info < (3, 7):
+ pytest.xfail("Lazy loading does not work for Python < 3.7")
+
+ mod_obj = getattr(dateutil, module, None)
+ assert isinstance(mod_obj, MODULE_TYPE)
+
+ mod_imported = importlib.import_module("dateutil.%s" % module)
+ assert mod_obj is mod_imported
+
+
+HOST_IS_WINDOWS = sys.platform.startswith('win')
+
+
+def test_import_version_str():
+ """ Test that dateutil.__version__ can be imported"""
+ from dateutil import __version__
+
+
+def test_import_version_root():
+ import dateutil
+ assert hasattr(dateutil, '__version__')
+
+
+# Test that dateutil.easter-related imports work properly
+def test_import_easter_direct():
+ import dateutil.easter
+
+
+def test_import_easter_from():
+ from dateutil import easter
+
+
+def test_import_easter_start():
+ from dateutil.easter import easter
+
+
+# Test that dateutil.parser-related imports work properly
+def test_import_parser_direct():
+ import dateutil.parser
+
+
+def test_import_parser_from():
+ from dateutil import parser
+
+
+def test_import_parser_all():
+ # All interface
+ from dateutil.parser import parse
+ from dateutil.parser import parserinfo
+
+ # Other public classes
+ from dateutil.parser import parser
+
+ for var in (parse, parserinfo, parser):
+ assert var is not None
+
+
+# Test that dateutil.relativedelta-related imports work properly
+def test_import_relative_delta_direct():
+ import dateutil.relativedelta
+
+
+def test_import_relative_delta_from():
+ from dateutil import relativedelta
+
+def test_import_relative_delta_all():
+ from dateutil.relativedelta import relativedelta
+ from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU
+
+ for var in (relativedelta, MO, TU, WE, TH, FR, SA, SU):
+ assert var is not None
+
+ # In the public interface but not in all
+ from dateutil.relativedelta import weekday
+ assert weekday is not None
+
+
+# Test that dateutil.rrule related imports work properly
+def test_import_rrule_direct():
+ import dateutil.rrule
+
+
+def test_import_rrule_from():
+ from dateutil import rrule
+
+
+def test_import_rrule_all():
+ from dateutil.rrule import rrule
+ from dateutil.rrule import rruleset
+ from dateutil.rrule import rrulestr
+ from dateutil.rrule import YEARLY, MONTHLY, WEEKLY, DAILY
+ from dateutil.rrule import HOURLY, MINUTELY, SECONDLY
+ from dateutil.rrule import MO, TU, WE, TH, FR, SA, SU
+
+ rr_all = (rrule, rruleset, rrulestr,
+ YEARLY, MONTHLY, WEEKLY, DAILY,
+ HOURLY, MINUTELY, SECONDLY,
+ MO, TU, WE, TH, FR, SA, SU)
+
+ for var in rr_all:
+ assert var is not None
+
+ # In the public interface but not in all
+ from dateutil.rrule import weekday
+ assert weekday is not None
+
+
+# Test that dateutil.tz related imports work properly
+def test_import_tztest_direct():
+ import dateutil.tz
+
+
+def test_import_tz_from():
+ from dateutil import tz
+
+
+def test_import_tz_all():
+ from dateutil.tz import tzutc
+ from dateutil.tz import tzoffset
+ from dateutil.tz import tzlocal
+ from dateutil.tz import tzfile
+ from dateutil.tz import tzrange
+ from dateutil.tz import tzstr
+ from dateutil.tz import tzical
+ from dateutil.tz import gettz
+ from dateutil.tz import tzwin
+ from dateutil.tz import tzwinlocal
+ from dateutil.tz import UTC
+ from dateutil.tz import datetime_ambiguous
+ from dateutil.tz import datetime_exists
+ from dateutil.tz import resolve_imaginary
+
+ tz_all = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
+ "tzstr", "tzical", "gettz", "datetime_ambiguous",
+ "datetime_exists", "resolve_imaginary", "UTC"]
+
+ tz_all += ["tzwin", "tzwinlocal"] if sys.platform.startswith("win") else []
+ lvars = locals()
+
+ for var in tz_all:
+ assert lvars[var] is not None
+
+# Test that dateutil.tzwin related imports work properly
+@pytest.mark.skipif(not HOST_IS_WINDOWS, reason="Requires Windows")
+def test_import_tz_windows_direct():
+ import dateutil.tzwin
+
+
+@pytest.mark.skipif(not HOST_IS_WINDOWS, reason="Requires Windows")
+def test_import_tz_windows_from():
+ from dateutil import tzwin
+
+
+@pytest.mark.skipif(not HOST_IS_WINDOWS, reason="Requires Windows")
+def test_import_tz_windows_star():
+ from dateutil.tzwin import tzwin
+ from dateutil.tzwin import tzwinlocal
+
+ tzwin_all = [tzwin, tzwinlocal]
+
+ for var in tzwin_all:
+ assert var is not None
+
+
+# Test imports of Zone Info
+def test_import_zone_info_direct():
+ import dateutil.zoneinfo
+
+
+def test_import_zone_info_from():
+ from dateutil import zoneinfo
+
+
+def test_import_zone_info_star():
+ from dateutil.zoneinfo import gettz
+ from dateutil.zoneinfo import gettz_db_metadata
+ from dateutil.zoneinfo import rebuild
+
+ zi_all = (gettz, gettz_db_metadata, rebuild)
+
+ for var in zi_all:
+ assert var is not None
diff --git a/contrib/python/python-dateutil/py2/tests/test_internals.py b/contrib/python/python-dateutil/py2/tests/test_internals.py
new file mode 100644
index 0000000000..b32e6723fc
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_internals.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for implementation details, not necessarily part of the user-facing
+API.
+
+The motivating case for these tests is #483, where we want to smoke-test
+code that may be difficult to reach through the standard API calls.
+"""
+
+import sys
+import pytest
+import warnings
+
+from dateutil.parser._parser import _ymd
+from dateutil import tz
+
+IS_PY32 = sys.version_info[0:2] == (3, 2)
+
+
+@pytest.mark.smoke
+def test_YMD_could_be_day():
+ ymd = _ymd('foo bar 124 baz')
+
+ ymd.append(2, 'M')
+ assert ymd.has_month
+ assert not ymd.has_year
+ assert ymd.could_be_day(4)
+ assert not ymd.could_be_day(-6)
+ assert not ymd.could_be_day(32)
+
+ # Assumes leap year
+ assert ymd.could_be_day(29)
+
+ ymd.append(1999)
+ assert ymd.has_year
+ assert not ymd.could_be_day(29)
+
+ ymd.append(16, 'D')
+ assert ymd.has_day
+ assert not ymd.could_be_day(1)
+
+ ymd = _ymd('foo bar 124 baz')
+ ymd.append(1999)
+ assert ymd.could_be_day(31)
+
+
+###
+# Test that private interfaces in _parser are deprecated properly
+@pytest.mark.skipif(IS_PY32, reason='pytest.warns not supported on Python 3.2')
+def test_parser_private_warns():
+ from dateutil.parser import _timelex, _tzparser
+ from dateutil.parser import _parsetz
+
+ with pytest.warns(DeprecationWarning):
+ _tzparser()
+
+ with pytest.warns(DeprecationWarning):
+ _timelex('2014-03-03')
+
+ with pytest.warns(DeprecationWarning):
+ _parsetz('+05:00')
+
+
+@pytest.mark.skipif(IS_PY32, reason='pytest.warns not supported on Python 3.2')
+def test_parser_parser_private_not_warns():
+ from dateutil.parser._parser import _timelex, _tzparser
+ from dateutil.parser._parser import _parsetz
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ _tzparser()
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ _timelex('2014-03-03')
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ _parsetz('+05:00')
+
+
+@pytest.mark.tzstr
+def test_tzstr_internal_timedeltas():
+ with pytest.warns(tz.DeprecatedTzFormatWarning):
+ tz1 = tz.tzstr("EST5EDT,5,4,0,7200,11,-3,0,7200")
+
+ with pytest.warns(tz.DeprecatedTzFormatWarning):
+ tz2 = tz.tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200")
+
+ assert tz1._start_delta != tz2._start_delta
+ assert tz1._end_delta != tz2._end_delta
diff --git a/contrib/python/python-dateutil/py2/tests/test_isoparser.py b/contrib/python/python-dateutil/py2/tests/test_isoparser.py
new file mode 100644
index 0000000000..c33881f3b0
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_isoparser.py
@@ -0,0 +1,509 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from datetime import datetime, timedelta, date, time
+import itertools as it
+
+from dateutil import tz
+from dateutil.tz import UTC
+from dateutil.parser import isoparser, isoparse
+
+import pytest
+import six
+
+
+def _generate_tzoffsets(limited):
+ def _mkoffset(hmtuple, fmt):
+ h, m = hmtuple
+ m_td = (-1 if h < 0 else 1) * m
+
+ tzo = tz.tzoffset(None, timedelta(hours=h, minutes=m_td))
+ return tzo, fmt.format(h, m)
+
+ out = []
+ if not limited:
+ # The subset that's just hours
+ hm_out_h = [(h, 0) for h in (-23, -5, 0, 5, 23)]
+ out.extend([_mkoffset(hm, '{:+03d}') for hm in hm_out_h])
+
+ # Ones that have hours and minutes
+ hm_out = [] + hm_out_h
+ hm_out += [(-12, 15), (11, 30), (10, 2), (5, 15), (-5, 30)]
+ else:
+ hm_out = [(-5, -0)]
+
+ fmts = ['{:+03d}:{:02d}', '{:+03d}{:02d}']
+ out += [_mkoffset(hm, fmt) for hm in hm_out for fmt in fmts]
+
+ # Also add in UTC and naive
+ out.append((UTC, 'Z'))
+ out.append((None, ''))
+
+ return out
+
+FULL_TZOFFSETS = _generate_tzoffsets(False)
+FULL_TZOFFSETS_AWARE = [x for x in FULL_TZOFFSETS if x[1]]
+TZOFFSETS = _generate_tzoffsets(True)
+
+DATES = [datetime(1996, 1, 1), datetime(2017, 1, 1)]
+@pytest.mark.parametrize('dt', tuple(DATES))
+def test_year_only(dt):
+ dtstr = dt.strftime('%Y')
+
+ assert isoparse(dtstr) == dt
+
+DATES += [datetime(2000, 2, 1), datetime(2017, 4, 1)]
+@pytest.mark.parametrize('dt', tuple(DATES))
+def test_year_month(dt):
+ fmt = '%Y-%m'
+ dtstr = dt.strftime(fmt)
+
+ assert isoparse(dtstr) == dt
+
+DATES += [datetime(2016, 2, 29), datetime(2018, 3, 15)]
+YMD_FMTS = ('%Y%m%d', '%Y-%m-%d')
+@pytest.mark.parametrize('dt', tuple(DATES))
+@pytest.mark.parametrize('fmt', YMD_FMTS)
+def test_year_month_day(dt, fmt):
+ dtstr = dt.strftime(fmt)
+
+ assert isoparse(dtstr) == dt
+
+def _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset,
+ microsecond_precision=None):
+ tzi, offset_str = tzoffset
+ fmt = date_fmt + 'T' + time_fmt
+ dt = dt.replace(tzinfo=tzi)
+ dtstr = dt.strftime(fmt)
+
+ if microsecond_precision is not None:
+ if not fmt.endswith('%f'): # pragma: nocover
+ raise ValueError('Time format has no microseconds!')
+
+ if microsecond_precision != 6:
+ dtstr = dtstr[: -(6 - microsecond_precision)]
+ elif microsecond_precision > 6: # pragma: nocover
+ raise ValueError("Precision must be 1-6")
+
+ dtstr += offset_str
+
+ assert isoparse(dtstr) == dt
+
+DATETIMES = [datetime(1998, 4, 16, 12),
+ datetime(2019, 11, 18, 23),
+ datetime(2014, 12, 16, 4)]
+@pytest.mark.parametrize('dt', tuple(DATETIMES))
+@pytest.mark.parametrize('date_fmt', YMD_FMTS)
+@pytest.mark.parametrize('tzoffset', TZOFFSETS)
+def test_ymd_h(dt, date_fmt, tzoffset):
+ _isoparse_date_and_time(dt, date_fmt, '%H', tzoffset)
+
+DATETIMES = [datetime(2012, 1, 6, 9, 37)]
+@pytest.mark.parametrize('dt', tuple(DATETIMES))
+@pytest.mark.parametrize('date_fmt', YMD_FMTS)
+@pytest.mark.parametrize('time_fmt', ('%H%M', '%H:%M'))
+@pytest.mark.parametrize('tzoffset', TZOFFSETS)
+def test_ymd_hm(dt, date_fmt, time_fmt, tzoffset):
+ _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset)
+
+DATETIMES = [datetime(2003, 9, 2, 22, 14, 2),
+ datetime(2003, 8, 8, 14, 9, 14),
+ datetime(2003, 4, 7, 6, 14, 59)]
+HMS_FMTS = ('%H%M%S', '%H:%M:%S')
+@pytest.mark.parametrize('dt', tuple(DATETIMES))
+@pytest.mark.parametrize('date_fmt', YMD_FMTS)
+@pytest.mark.parametrize('time_fmt', HMS_FMTS)
+@pytest.mark.parametrize('tzoffset', TZOFFSETS)
+def test_ymd_hms(dt, date_fmt, time_fmt, tzoffset):
+ _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset)
+
+DATETIMES = [datetime(2017, 11, 27, 6, 14, 30, 123456)]
+@pytest.mark.parametrize('dt', tuple(DATETIMES))
+@pytest.mark.parametrize('date_fmt', YMD_FMTS)
+@pytest.mark.parametrize('time_fmt', (x + sep + '%f' for x in HMS_FMTS
+ for sep in '.,'))
+@pytest.mark.parametrize('tzoffset', TZOFFSETS)
+@pytest.mark.parametrize('precision', list(range(3, 7)))
+def test_ymd_hms_micro(dt, date_fmt, time_fmt, tzoffset, precision):
+ # Truncate the microseconds to the desired precision for the representation
+ dt = dt.replace(microsecond=int(round(dt.microsecond, precision-6)))
+
+ _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset, precision)
+
+###
+# Truncation of extra digits beyond microsecond precision
+@pytest.mark.parametrize('dt_str', [
+ '2018-07-03T14:07:00.123456000001',
+ '2018-07-03T14:07:00.123456999999',
+])
+def test_extra_subsecond_digits(dt_str):
+ assert isoparse(dt_str) == datetime(2018, 7, 3, 14, 7, 0, 123456)
+
+@pytest.mark.parametrize('tzoffset', FULL_TZOFFSETS)
+def test_full_tzoffsets(tzoffset):
+ dt = datetime(2017, 11, 27, 6, 14, 30, 123456)
+ date_fmt = '%Y-%m-%d'
+ time_fmt = '%H:%M:%S.%f'
+
+ _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset)
+
+@pytest.mark.parametrize('dt_str', [
+ '2014-04-11T00',
+ '2014-04-10T24',
+ '2014-04-11T00:00',
+ '2014-04-10T24:00',
+ '2014-04-11T00:00:00',
+ '2014-04-10T24:00:00',
+ '2014-04-11T00:00:00.000',
+ '2014-04-10T24:00:00.000',
+ '2014-04-11T00:00:00.000000',
+ '2014-04-10T24:00:00.000000']
+)
+def test_datetime_midnight(dt_str):
+ assert isoparse(dt_str) == datetime(2014, 4, 11, 0, 0, 0, 0)
+
+@pytest.mark.parametrize('datestr', [
+ '2014-01-01',
+ '20140101',
+])
+@pytest.mark.parametrize('sep', [' ', 'a', 'T', '_', '-'])
+def test_isoparse_sep_none(datestr, sep):
+ isostr = datestr + sep + '14:33:09'
+ assert isoparse(isostr) == datetime(2014, 1, 1, 14, 33, 9)
+
+##
+# Uncommon date formats
+TIME_ARGS = ('time_args',
+ ((None, time(0), None), ) + tuple(('%H:%M:%S.%f', _t, _tz)
+ for _t, _tz in it.product([time(0), time(9, 30), time(14, 47)],
+ TZOFFSETS)))
+
+@pytest.mark.parametrize('isocal,dt_expected',[
+ ((2017, 10), datetime(2017, 3, 6)),
+ ((2020, 1), datetime(2019, 12, 30)), # ISO year != Cal year
+ ((2004, 53), datetime(2004, 12, 27)), # Only half the week is in 2014
+])
+def test_isoweek(isocal, dt_expected):
+ # TODO: Figure out how to parametrize this on formats, too
+ for fmt in ('{:04d}-W{:02d}', '{:04d}W{:02d}'):
+ dtstr = fmt.format(*isocal)
+ assert isoparse(dtstr) == dt_expected
+
+@pytest.mark.parametrize('isocal,dt_expected',[
+ ((2016, 13, 7), datetime(2016, 4, 3)),
+ ((2004, 53, 7), datetime(2005, 1, 2)), # ISO year != Cal year
+ ((2009, 1, 2), datetime(2008, 12, 30)), # ISO year < Cal year
+ ((2009, 53, 6), datetime(2010, 1, 2)) # ISO year > Cal year
+])
+def test_isoweek_day(isocal, dt_expected):
+ # TODO: Figure out how to parametrize this on formats, too
+ for fmt in ('{:04d}-W{:02d}-{:d}', '{:04d}W{:02d}{:d}'):
+ dtstr = fmt.format(*isocal)
+ assert isoparse(dtstr) == dt_expected
+
+@pytest.mark.parametrize('isoord,dt_expected', [
+ ((2004, 1), datetime(2004, 1, 1)),
+ ((2016, 60), datetime(2016, 2, 29)),
+ ((2017, 60), datetime(2017, 3, 1)),
+ ((2016, 366), datetime(2016, 12, 31)),
+ ((2017, 365), datetime(2017, 12, 31))
+])
+def test_iso_ordinal(isoord, dt_expected):
+ for fmt in ('{:04d}-{:03d}', '{:04d}{:03d}'):
+ dtstr = fmt.format(*isoord)
+
+ assert isoparse(dtstr) == dt_expected
+
+
+###
+# Acceptance of bytes
+@pytest.mark.parametrize('isostr,dt', [
+ (b'2014', datetime(2014, 1, 1)),
+ (b'20140204', datetime(2014, 2, 4)),
+ (b'2014-02-04', datetime(2014, 2, 4)),
+ (b'2014-02-04T12', datetime(2014, 2, 4, 12)),
+ (b'2014-02-04T12:30', datetime(2014, 2, 4, 12, 30)),
+ (b'2014-02-04T12:30:15', datetime(2014, 2, 4, 12, 30, 15)),
+ (b'2014-02-04T12:30:15.224', datetime(2014, 2, 4, 12, 30, 15, 224000)),
+ (b'20140204T123015.224', datetime(2014, 2, 4, 12, 30, 15, 224000)),
+ (b'2014-02-04T12:30:15.224Z', datetime(2014, 2, 4, 12, 30, 15, 224000,
+ UTC)),
+ (b'2014-02-04T12:30:15.224z', datetime(2014, 2, 4, 12, 30, 15, 224000,
+ UTC)),
+ (b'2014-02-04T12:30:15.224+05:00',
+ datetime(2014, 2, 4, 12, 30, 15, 224000,
+ tzinfo=tz.tzoffset(None, timedelta(hours=5))))])
+def test_bytes(isostr, dt):
+ assert isoparse(isostr) == dt
+
+
+###
+# Invalid ISO strings
+@pytest.mark.parametrize('isostr,exception', [
+ ('201', ValueError), # ISO string too short
+ ('2012-0425', ValueError), # Inconsistent date separators
+ ('201204-25', ValueError), # Inconsistent date separators
+ ('20120425T0120:00', ValueError), # Inconsistent time separators
+ ('20120425T01:2000', ValueError), # Inconsistent time separators
+ ('14:3015', ValueError), # Inconsistent time separator
+ ('20120425T012500-334', ValueError), # Wrong microsecond separator
+ ('2001-1', ValueError), # YYYY-M not valid
+ ('2012-04-9', ValueError), # YYYY-MM-D not valid
+ ('201204', ValueError), # YYYYMM not valid
+ ('20120411T03:30+', ValueError), # Time zone too short
+ ('20120411T03:30+1234567', ValueError), # Time zone too long
+ ('20120411T03:30-25:40', ValueError), # Time zone invalid
+ ('2012-1a', ValueError), # Invalid month
+ ('20120411T03:30+00:60', ValueError), # Time zone invalid minutes
+ ('20120411T03:30+00:61', ValueError), # Time zone invalid minutes
+ ('20120411T033030.123456012:00', # No sign in time zone
+ ValueError),
+ ('2012-W00', ValueError), # Invalid ISO week
+ ('2012-W55', ValueError), # Invalid ISO week
+ ('2012-W01-0', ValueError), # Invalid ISO week day
+ ('2012-W01-8', ValueError), # Invalid ISO week day
+ ('2013-000', ValueError), # Invalid ordinal day
+ ('2013-366', ValueError), # Invalid ordinal day
+ ('2013366', ValueError), # Invalid ordinal day
+ ('2014-03-12Т12:30:14', ValueError), # Cyrillic T
+ ('2014-04-21T24:00:01', ValueError), # Invalid use of 24 for midnight
+ ('2014_W01-1', ValueError), # Invalid separator
+ ('2014W01-1', ValueError), # Inconsistent use of dashes
+ ('2014-W011', ValueError), # Inconsistent use of dashes
+
+])
+def test_iso_raises(isostr, exception):
+ with pytest.raises(exception):
+ isoparse(isostr)
+
+
+@pytest.mark.parametrize('sep_act, valid_sep, exception', [
+ ('T', 'C', ValueError),
+ ('C', 'T', ValueError),
+])
+def test_iso_with_sep_raises(sep_act, valid_sep, exception):
+ parser = isoparser(sep=valid_sep)
+ isostr = '2012-04-25' + sep_act + '01:25:00'
+ with pytest.raises(exception):
+ parser.isoparse(isostr)
+
+
+###
+# Test ISOParser constructor
+@pytest.mark.parametrize('sep', [' ', '9', '🍛'])
+def test_isoparser_invalid_sep(sep):
+ with pytest.raises(ValueError):
+ isoparser(sep=sep)
+
+
+# This only fails on Python 3
+@pytest.mark.xfail(not six.PY2, reason="Fails on Python 3 only")
+def test_isoparser_byte_sep():
+ dt = datetime(2017, 12, 6, 12, 30, 45)
+ dt_str = dt.isoformat(sep=str('T'))
+
+ dt_rt = isoparser(sep=b'T').isoparse(dt_str)
+
+ assert dt == dt_rt
+
+
+###
+# Test parse_tzstr
+@pytest.mark.parametrize('tzoffset', FULL_TZOFFSETS)
+def test_parse_tzstr(tzoffset):
+ dt = datetime(2017, 11, 27, 6, 14, 30, 123456)
+ date_fmt = '%Y-%m-%d'
+ time_fmt = '%H:%M:%S.%f'
+
+ _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset)
+
+
+@pytest.mark.parametrize('tzstr', [
+ '-00:00', '+00:00', '+00', '-00', '+0000', '-0000'
+])
+@pytest.mark.parametrize('zero_as_utc', [True, False])
+def test_parse_tzstr_zero_as_utc(tzstr, zero_as_utc):
+ tzi = isoparser().parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
+ assert tzi == UTC
+ assert (type(tzi) == tz.tzutc) == zero_as_utc
+
+
+@pytest.mark.parametrize('tzstr,exception', [
+ ('00:00', ValueError), # No sign
+ ('05:00', ValueError), # No sign
+ ('_00:00', ValueError), # Invalid sign
+ ('+25:00', ValueError), # Offset too large
+ ('00:0000', ValueError), # String too long
+])
+def test_parse_tzstr_fails(tzstr, exception):
+ with pytest.raises(exception):
+ isoparser().parse_tzstr(tzstr)
+
+###
+# Test parse_isodate
+def __make_date_examples():
+ dates_no_day = [
+ date(1999, 12, 1),
+ date(2016, 2, 1)
+ ]
+
+ if not six.PY2:
+ # strftime does not support dates before 1900 in Python 2
+ dates_no_day.append(date(1000, 11, 1))
+
+ # Only one supported format for dates with no day
+ o = zip(dates_no_day, it.repeat('%Y-%m'))
+
+ dates_w_day = [
+ date(1969, 12, 31),
+ date(1900, 1, 1),
+ date(2016, 2, 29),
+ date(2017, 11, 14)
+ ]
+
+ dates_w_day_fmts = ('%Y%m%d', '%Y-%m-%d')
+ o = it.chain(o, it.product(dates_w_day, dates_w_day_fmts))
+
+ return list(o)
+
+
+@pytest.mark.parametrize('d,dt_fmt', __make_date_examples())
+@pytest.mark.parametrize('as_bytes', [True, False])
+def test_parse_isodate(d, dt_fmt, as_bytes):
+ d_str = d.strftime(dt_fmt)
+ if isinstance(d_str, six.text_type) and as_bytes:
+ d_str = d_str.encode('ascii')
+ elif isinstance(d_str, bytes) and not as_bytes:
+ d_str = d_str.decode('ascii')
+
+ iparser = isoparser()
+ assert iparser.parse_isodate(d_str) == d
+
+
+@pytest.mark.parametrize('isostr,exception', [
+ ('243', ValueError), # ISO string too short
+ ('2014-0423', ValueError), # Inconsistent date separators
+ ('201404-23', ValueError), # Inconsistent date separators
+ ('2014日03月14', ValueError), # Not ASCII
+ ('2013-02-29', ValueError), # Not a leap year
+ ('2014/12/03', ValueError), # Wrong separators
+ ('2014-04-19T', ValueError), # Unknown components
+ ('201202', ValueError), # Invalid format
+])
+def test_isodate_raises(isostr, exception):
+ with pytest.raises(exception):
+ isoparser().parse_isodate(isostr)
+
+
+def test_parse_isodate_error_text():
+ with pytest.raises(ValueError) as excinfo:
+ isoparser().parse_isodate('2014-0423')
+
+ # ensure the error message does not contain b' prefixes
+ if six.PY2:
+ expected_error = "String contains unknown ISO components: u'2014-0423'"
+ else:
+ expected_error = "String contains unknown ISO components: '2014-0423'"
+ assert expected_error == str(excinfo.value)
+
+
+###
+# Test parse_isotime
+def __make_time_examples():
+ outputs = []
+
+ # HH
+ time_h = [time(0), time(8), time(22)]
+ time_h_fmts = ['%H']
+
+ outputs.append(it.product(time_h, time_h_fmts))
+
+ # HHMM / HH:MM
+ time_hm = [time(0, 0), time(0, 30), time(8, 47), time(16, 1)]
+ time_hm_fmts = ['%H%M', '%H:%M']
+
+ outputs.append(it.product(time_hm, time_hm_fmts))
+
+ # HHMMSS / HH:MM:SS
+ time_hms = [time(0, 0, 0), time(0, 15, 30),
+ time(8, 2, 16), time(12, 0), time(16, 2), time(20, 45)]
+
+ time_hms_fmts = ['%H%M%S', '%H:%M:%S']
+
+ outputs.append(it.product(time_hms, time_hms_fmts))
+
+ # HHMMSS.ffffff / HH:MM:SS.ffffff
+ time_hmsu = [time(0, 0, 0, 0), time(4, 15, 3, 247993),
+ time(14, 21, 59, 948730),
+ time(23, 59, 59, 999999)]
+
+ time_hmsu_fmts = ['%H%M%S.%f', '%H:%M:%S.%f']
+
+ outputs.append(it.product(time_hmsu, time_hmsu_fmts))
+
+ outputs = list(map(list, outputs))
+
+ # Time zones
+ ex_naive = list(it.chain.from_iterable(x[0:2] for x in outputs))
+ o = it.product(ex_naive, TZOFFSETS) # ((time, fmt), (tzinfo, offsetstr))
+ o = ((t.replace(tzinfo=tzi), fmt + off_str)
+ for (t, fmt), (tzi, off_str) in o)
+
+ outputs.append(o)
+
+ return list(it.chain.from_iterable(outputs))
+
+
+@pytest.mark.parametrize('time_val,time_fmt', __make_time_examples())
+@pytest.mark.parametrize('as_bytes', [True, False])
+def test_isotime(time_val, time_fmt, as_bytes):
+ tstr = time_val.strftime(time_fmt)
+ if isinstance(tstr, six.text_type) and as_bytes:
+ tstr = tstr.encode('ascii')
+ elif isinstance(tstr, bytes) and not as_bytes:
+ tstr = tstr.decode('ascii')
+
+ iparser = isoparser()
+
+ assert iparser.parse_isotime(tstr) == time_val
+
+
+@pytest.mark.parametrize('isostr', [
+ '24:00',
+ '2400',
+ '24:00:00',
+ '240000',
+ '24:00:00.000',
+ '24:00:00,000',
+ '24:00:00.000000',
+ '24:00:00,000000',
+])
+def test_isotime_midnight(isostr):
+ iparser = isoparser()
+ assert iparser.parse_isotime(isostr) == time(0, 0, 0, 0)
+
+
+@pytest.mark.parametrize('isostr,exception', [
+ ('3', ValueError), # ISO string too short
+ ('14時30分15秒', ValueError), # Not ASCII
+ ('14_30_15', ValueError), # Invalid separators
+ ('1430:15', ValueError), # Inconsistent separator use
+ ('25', ValueError), # Invalid hours
+ ('25:15', ValueError), # Invalid hours
+ ('14:60', ValueError), # Invalid minutes
+ ('14:59:61', ValueError), # Invalid seconds
+ ('14:30:15.34468305:00', ValueError), # No sign in time zone
+ ('14:30:15+', ValueError), # Time zone too short
+ ('14:30:15+1234567', ValueError), # Time zone invalid
+ ('14:59:59+25:00', ValueError), # Invalid tz hours
+ ('14:59:59+12:62', ValueError), # Invalid tz minutes
+ ('14:59:30_344583', ValueError), # Invalid microsecond separator
+ ('24:01', ValueError), # 24 used for non-midnight time
+ ('24:00:01', ValueError), # 24 used for non-midnight time
+ ('24:00:00.001', ValueError), # 24 used for non-midnight time
+ ('24:00:00.000001', ValueError), # 24 used for non-midnight time
+])
+def test_isotime_raises(isostr, exception):
+ iparser = isoparser()
+ with pytest.raises(exception):
+ iparser.parse_isotime(isostr)
diff --git a/contrib/python/python-dateutil/py2/tests/test_parser.py b/contrib/python/python-dateutil/py2/tests/test_parser.py
new file mode 100644
index 0000000000..08a34dafbc
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_parser.py
@@ -0,0 +1,964 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import itertools
+from datetime import datetime, timedelta
+import unittest
+import sys
+
+from dateutil import tz
+from dateutil.tz import tzoffset
+from dateutil.parser import parse, parserinfo
+from dateutil.parser import ParserError
+from dateutil.parser import UnknownTimezoneWarning
+
+from ._common import TZEnvContext
+
+from six import assertRaisesRegex, PY2
+from io import StringIO
+
+import pytest
+
+# Platform info
+IS_WIN = sys.platform.startswith('win')
+
+PLATFORM_HAS_DASH_D = False
+try:
+ if datetime.now().strftime('%-d'):
+ PLATFORM_HAS_DASH_D = True
+except ValueError:
+ pass
+
+
+@pytest.fixture(params=[True, False])
+def fuzzy(request):
+ """Fixture to pass fuzzy=True or fuzzy=False to parse"""
+ return request.param
+
+
+# Parser test cases using no keyword arguments. Format: (parsable_text, expected_datetime, assertion_message)
+PARSER_TEST_CASES = [
+ ("Thu Sep 25 10:36:28 2003", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
+ ("Thu Sep 25 2003", datetime(2003, 9, 25), "date command format strip"),
+ ("2003-09-25T10:49:41", datetime(2003, 9, 25, 10, 49, 41), "iso format strip"),
+ ("2003-09-25T10:49", datetime(2003, 9, 25, 10, 49), "iso format strip"),
+ ("2003-09-25T10", datetime(2003, 9, 25, 10), "iso format strip"),
+ ("2003-09-25", datetime(2003, 9, 25), "iso format strip"),
+ ("20030925T104941", datetime(2003, 9, 25, 10, 49, 41), "iso stripped format strip"),
+ ("20030925T1049", datetime(2003, 9, 25, 10, 49, 0), "iso stripped format strip"),
+ ("20030925T10", datetime(2003, 9, 25, 10), "iso stripped format strip"),
+ ("20030925", datetime(2003, 9, 25), "iso stripped format strip"),
+ ("2003-09-25 10:49:41,502", datetime(2003, 9, 25, 10, 49, 41, 502000), "python logger format"),
+ ("199709020908", datetime(1997, 9, 2, 9, 8), "no separator"),
+ ("19970902090807", datetime(1997, 9, 2, 9, 8, 7), "no separator"),
+ ("09-25-2003", datetime(2003, 9, 25), "date with dash"),
+ ("25-09-2003", datetime(2003, 9, 25), "date with dash"),
+ ("10-09-2003", datetime(2003, 10, 9), "date with dash"),
+ ("10-09-03", datetime(2003, 10, 9), "date with dash"),
+ ("2003.09.25", datetime(2003, 9, 25), "date with dot"),
+ ("09.25.2003", datetime(2003, 9, 25), "date with dot"),
+ ("25.09.2003", datetime(2003, 9, 25), "date with dot"),
+ ("10.09.2003", datetime(2003, 10, 9), "date with dot"),
+ ("10.09.03", datetime(2003, 10, 9), "date with dot"),
+ ("2003/09/25", datetime(2003, 9, 25), "date with slash"),
+ ("09/25/2003", datetime(2003, 9, 25), "date with slash"),
+ ("25/09/2003", datetime(2003, 9, 25), "date with slash"),
+ ("10/09/2003", datetime(2003, 10, 9), "date with slash"),
+ ("10/09/03", datetime(2003, 10, 9), "date with slash"),
+ ("2003 09 25", datetime(2003, 9, 25), "date with space"),
+ ("09 25 2003", datetime(2003, 9, 25), "date with space"),
+ ("25 09 2003", datetime(2003, 9, 25), "date with space"),
+ ("10 09 2003", datetime(2003, 10, 9), "date with space"),
+ ("10 09 03", datetime(2003, 10, 9), "date with space"),
+ ("25 09 03", datetime(2003, 9, 25), "date with space"),
+ ("03 25 Sep", datetime(2003, 9, 25), "strangely ordered date"),
+ ("25 03 Sep", datetime(2025, 9, 3), "strangely ordered date"),
+ (" July 4 , 1976 12:01:02 am ", datetime(1976, 7, 4, 0, 1, 2), "extra space"),
+ ("Wed, July 10, '96", datetime(1996, 7, 10, 0, 0), "random format"),
+ ("1996.July.10 AD 12:08 PM", datetime(1996, 7, 10, 12, 8), "random format"),
+ ("July 4, 1976", datetime(1976, 7, 4), "random format"),
+ ("7 4 1976", datetime(1976, 7, 4), "random format"),
+ ("4 jul 1976", datetime(1976, 7, 4), "random format"),
+ ("4 Jul 1976", datetime(1976, 7, 4), "'%-d %b %Y' format"),
+ ("7-4-76", datetime(1976, 7, 4), "random format"),
+ ("19760704", datetime(1976, 7, 4), "random format"),
+ ("0:01:02 on July 4, 1976", datetime(1976, 7, 4, 0, 1, 2), "random format"),
+ ("July 4, 1976 12:01:02 am", datetime(1976, 7, 4, 0, 1, 2), "random format"),
+ ("Mon Jan 2 04:24:27 1995", datetime(1995, 1, 2, 4, 24, 27), "random format"),
+ ("04.04.95 00:22", datetime(1995, 4, 4, 0, 22), "random format"),
+ ("Jan 1 1999 11:23:34.578", datetime(1999, 1, 1, 11, 23, 34, 578000), "random format"),
+ ("950404 122212", datetime(1995, 4, 4, 12, 22, 12), "random format"),
+ ("3rd of May 2001", datetime(2001, 5, 3), "random format"),
+ ("5th of March 2001", datetime(2001, 3, 5), "random format"),
+ ("1st of May 2003", datetime(2003, 5, 1), "random format"),
+ ('0099-01-01T00:00:00', datetime(99, 1, 1, 0, 0), "99 ad"),
+ ('0031-01-01T00:00:00', datetime(31, 1, 1, 0, 0), "31 ad"),
+ ("20080227T21:26:01.123456789", datetime(2008, 2, 27, 21, 26, 1, 123456), "high precision seconds"),
+ ('13NOV2017', datetime(2017, 11, 13), "dBY (See GH360)"),
+ ('0003-03-04', datetime(3, 3, 4), "pre 12 year same month (See GH PR #293)"),
+ ('December.0031.30', datetime(31, 12, 30), "BYd corner case (GH#687)"),
+
+ # Cases with legacy h/m/s format, candidates for deprecation (GH#886)
+ ("2016-12-21 04.2h", datetime(2016, 12, 21, 4, 12), "Fractional Hours"),
+]
+# Check that we don't have any duplicates
+assert len(set([x[0] for x in PARSER_TEST_CASES])) == len(PARSER_TEST_CASES)
+
+
+@pytest.mark.parametrize("parsable_text,expected_datetime,assertion_message", PARSER_TEST_CASES)
+def test_parser(parsable_text, expected_datetime, assertion_message):
+ assert parse(parsable_text) == expected_datetime, assertion_message
+
+
+# Parser test cases using datetime(2003, 9, 25) as a default.
+# Format: (parsable_text, expected_datetime, assertion_message)
+PARSER_DEFAULT_TEST_CASES = [
+ ("Thu Sep 25 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
+ ("Thu Sep 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
+ ("Thu 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
+ ("Sep 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
+ ("10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
+ ("10:36", datetime(2003, 9, 25, 10, 36), "date command format strip"),
+ ("Sep 2003", datetime(2003, 9, 25), "date command format strip"),
+ ("Sep", datetime(2003, 9, 25), "date command format strip"),
+ ("2003", datetime(2003, 9, 25), "date command format strip"),
+ ("10h36m28.5s", datetime(2003, 9, 25, 10, 36, 28, 500000), "hour with letters"),
+ ("10h36m28s", datetime(2003, 9, 25, 10, 36, 28), "hour with letters strip"),
+ ("10h36m", datetime(2003, 9, 25, 10, 36), "hour with letters strip"),
+ ("10h", datetime(2003, 9, 25, 10), "hour with letters strip"),
+ ("10 h 36", datetime(2003, 9, 25, 10, 36), "hour with letters strip"),
+ ("10 h 36.5", datetime(2003, 9, 25, 10, 36, 30), "hour with letter strip"),
+ ("36 m 5", datetime(2003, 9, 25, 0, 36, 5), "hour with letters spaces"),
+ ("36 m 5 s", datetime(2003, 9, 25, 0, 36, 5), "minute with letters spaces"),
+ ("36 m 05", datetime(2003, 9, 25, 0, 36, 5), "minute with letters spaces"),
+ ("36 m 05 s", datetime(2003, 9, 25, 0, 36, 5), "minutes with letters spaces"),
+ ("10h am", datetime(2003, 9, 25, 10), "hour am pm"),
+ ("10h pm", datetime(2003, 9, 25, 22), "hour am pm"),
+ ("10am", datetime(2003, 9, 25, 10), "hour am pm"),
+ ("10pm", datetime(2003, 9, 25, 22), "hour am pm"),
+ ("10:00 am", datetime(2003, 9, 25, 10), "hour am pm"),
+ ("10:00 pm", datetime(2003, 9, 25, 22), "hour am pm"),
+ ("10:00am", datetime(2003, 9, 25, 10), "hour am pm"),
+ ("10:00pm", datetime(2003, 9, 25, 22), "hour am pm"),
+ ("10:00a.m", datetime(2003, 9, 25, 10), "hour am pm"),
+ ("10:00p.m", datetime(2003, 9, 25, 22), "hour am pm"),
+ ("10:00a.m.", datetime(2003, 9, 25, 10), "hour am pm"),
+ ("10:00p.m.", datetime(2003, 9, 25, 22), "hour am pm"),
+ ("Wed", datetime(2003, 10, 1), "weekday alone"),
+ ("Wednesday", datetime(2003, 10, 1), "long weekday"),
+ ("October", datetime(2003, 10, 25), "long month"),
+ ("31-Dec-00", datetime(2000, 12, 31), "zero year"),
+ ("0:01:02", datetime(2003, 9, 25, 0, 1, 2), "random format"),
+ ("12h 01m02s am", datetime(2003, 9, 25, 0, 1, 2), "random format"),
+ ("12:08 PM", datetime(2003, 9, 25, 12, 8), "random format"),
+ ("01h02m03", datetime(2003, 9, 25, 1, 2, 3), "random format"),
+ ("01h02", datetime(2003, 9, 25, 1, 2), "random format"),
+ ("01h02s", datetime(2003, 9, 25, 1, 0, 2), "random format"),
+ ("01m02", datetime(2003, 9, 25, 0, 1, 2), "random format"),
+ ("01m02h", datetime(2003, 9, 25, 2, 1), "random format"),
+ ("2004 10 Apr 11h30m", datetime(2004, 4, 10, 11, 30), "random format")
+]
+# Check that we don't have any duplicates
+assert len(set([x[0] for x in PARSER_DEFAULT_TEST_CASES])) == len(PARSER_DEFAULT_TEST_CASES)
+
+
+@pytest.mark.parametrize("parsable_text,expected_datetime,assertion_message", PARSER_DEFAULT_TEST_CASES)
+def test_parser_default(parsable_text, expected_datetime, assertion_message):
+ assert parse(parsable_text, default=datetime(2003, 9, 25)) == expected_datetime, assertion_message
+
+
+@pytest.mark.parametrize('sep', ['-', '.', '/', ' '])
+def test_parse_dayfirst(sep):
+ expected = datetime(2003, 9, 10)
+ fmt = sep.join(['%d', '%m', '%Y'])
+ dstr = expected.strftime(fmt)
+ result = parse(dstr, dayfirst=True)
+ assert result == expected
+
+
+@pytest.mark.parametrize('sep', ['-', '.', '/', ' '])
+def test_parse_yearfirst(sep):
+ expected = datetime(2010, 9, 3)
+ fmt = sep.join(['%Y', '%m', '%d'])
+ dstr = expected.strftime(fmt)
+ result = parse(dstr, yearfirst=True)
+ assert result == expected
+
+
+@pytest.mark.parametrize('dstr,expected', [
+ ("Thu Sep 25 10:36:28 BRST 2003", datetime(2003, 9, 25, 10, 36, 28)),
+ ("1996.07.10 AD at 15:08:56 PDT", datetime(1996, 7, 10, 15, 8, 56)),
+ ("Tuesday, April 12, 1952 AD 3:30:42pm PST",
+ datetime(1952, 4, 12, 15, 30, 42)),
+ ("November 5, 1994, 8:15:30 am EST", datetime(1994, 11, 5, 8, 15, 30)),
+ ("1994-11-05T08:15:30-05:00", datetime(1994, 11, 5, 8, 15, 30)),
+ ("1994-11-05T08:15:30Z", datetime(1994, 11, 5, 8, 15, 30)),
+ ("1976-07-04T00:01:02Z", datetime(1976, 7, 4, 0, 1, 2)),
+ ("1986-07-05T08:15:30z", datetime(1986, 7, 5, 8, 15, 30)),
+ ("Tue Apr 4 00:22:12 PDT 1995", datetime(1995, 4, 4, 0, 22, 12)),
+])
+def test_parse_ignoretz(dstr, expected):
+ result = parse(dstr, ignoretz=True)
+ assert result == expected
+
+
+_brsttz = tzoffset("BRST", -10800)
+
+
+@pytest.mark.parametrize('dstr,expected', [
+ ("20030925T104941-0300",
+ datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)),
+ ("Thu, 25 Sep 2003 10:49:41 -0300",
+ datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)),
+ ("2003-09-25T10:49:41.5-03:00",
+ datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)),
+ ("2003-09-25T10:49:41-03:00",
+ datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)),
+ ("20030925T104941.5-0300",
+ datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)),
+])
+def test_parse_with_tzoffset(dstr, expected):
+ # In these cases, we are _not_ passing a tzinfos arg
+ result = parse(dstr)
+ assert result == expected
+
+
+class TestFormat(object):
+
+ def test_ybd(self):
+ # If we have a 4-digit year, a non-numeric month (abbreviated or not),
+ # and a day (1 or 2 digits), then there is no ambiguity as to which
+ # token is a year/month/day. This holds regardless of what order the
+ # terms are in and for each of the separators below.
+
+ seps = ['-', ' ', '/', '.']
+
+ year_tokens = ['%Y']
+ month_tokens = ['%b', '%B']
+ day_tokens = ['%d']
+ if PLATFORM_HAS_DASH_D:
+ day_tokens.append('%-d')
+
+ prods = itertools.product(year_tokens, month_tokens, day_tokens)
+ perms = [y for x in prods for y in itertools.permutations(x)]
+ unambig_fmts = [sep.join(perm) for sep in seps for perm in perms]
+
+ actual = datetime(2003, 9, 25)
+
+ for fmt in unambig_fmts:
+ dstr = actual.strftime(fmt)
+ res = parse(dstr)
+ assert res == actual
+
+ # TODO: some redundancy with PARSER_TEST_CASES cases
+ @pytest.mark.parametrize("fmt,dstr", [
+ ("%a %b %d %Y", "Thu Sep 25 2003"),
+ ("%b %d %Y", "Sep 25 2003"),
+ ("%Y-%m-%d", "2003-09-25"),
+ ("%Y%m%d", "20030925"),
+ ("%Y-%b-%d", "2003-Sep-25"),
+ ("%d-%b-%Y", "25-Sep-2003"),
+ ("%b-%d-%Y", "Sep-25-2003"),
+ ("%m-%d-%Y", "09-25-2003"),
+ ("%d-%m-%Y", "25-09-2003"),
+ ("%Y.%m.%d", "2003.09.25"),
+ ("%Y.%b.%d", "2003.Sep.25"),
+ ("%d.%b.%Y", "25.Sep.2003"),
+ ("%b.%d.%Y", "Sep.25.2003"),
+ ("%m.%d.%Y", "09.25.2003"),
+ ("%d.%m.%Y", "25.09.2003"),
+ ("%Y/%m/%d", "2003/09/25"),
+ ("%Y/%b/%d", "2003/Sep/25"),
+ ("%d/%b/%Y", "25/Sep/2003"),
+ ("%b/%d/%Y", "Sep/25/2003"),
+ ("%m/%d/%Y", "09/25/2003"),
+ ("%d/%m/%Y", "25/09/2003"),
+ ("%Y %m %d", "2003 09 25"),
+ ("%Y %b %d", "2003 Sep 25"),
+ ("%d %b %Y", "25 Sep 2003"),
+ ("%m %d %Y", "09 25 2003"),
+ ("%d %m %Y", "25 09 2003"),
+ ("%y %d %b", "03 25 Sep",),
+ ])
+ def test_strftime_formats_2003Sep25(self, fmt, dstr):
+ expected = datetime(2003, 9, 25)
+
+ # First check that the format strings behave as expected
+ # (not strictly necessary, but nice to have)
+ assert expected.strftime(fmt) == dstr
+
+ res = parse(dstr)
+ assert res == expected
+
+
+class TestInputTypes(object):
+ def test_empty_string_invalid(self):
+ with pytest.raises(ParserError):
+ parse('')
+
+ def test_none_invalid(self):
+ with pytest.raises(TypeError):
+ parse(None)
+
+ def test_int_invalid(self):
+ with pytest.raises(TypeError):
+ parse(13)
+
+ def test_duck_typing(self):
+ # We want to support arbitrary classes that implement the stream
+ # interface.
+
+ class StringPassThrough(object):
+ def __init__(self, stream):
+ self.stream = stream
+
+ def read(self, *args, **kwargs):
+ return self.stream.read(*args, **kwargs)
+
+ dstr = StringPassThrough(StringIO('2014 January 19'))
+
+ res = parse(dstr)
+ expected = datetime(2014, 1, 19)
+ assert res == expected
+
+ def test_parse_stream(self):
+ dstr = StringIO('2014 January 19')
+
+ res = parse(dstr)
+ expected = datetime(2014, 1, 19)
+ assert res == expected
+
+ def test_parse_str(self):
+ # Parser should be able to handle bytestring and unicode
+ uni_str = '2014-05-01 08:00:00'
+ bytes_str = uni_str.encode()
+
+ res = parse(bytes_str)
+ expected = parse(uni_str)
+ assert res == expected
+
+ def test_parse_bytes(self):
+ res = parse(b'2014 January 19')
+ expected = datetime(2014, 1, 19)
+ assert res == expected
+
+ def test_parse_bytearray(self):
+ # GH#417
+ res = parse(bytearray(b'2014 January 19'))
+ expected = datetime(2014, 1, 19)
+ assert res == expected
+
+
+class TestTzinfoInputTypes(object):
+ def assert_equal_same_tz(self, dt1, dt2):
+ assert dt1 == dt2
+ assert dt1.tzinfo is dt2.tzinfo
+
+ def test_tzinfo_dict_could_return_none(self):
+ dstr = "2017-02-03 12:40 BRST"
+ result = parse(dstr, tzinfos={"BRST": None})
+ expected = datetime(2017, 2, 3, 12, 40)
+ self.assert_equal_same_tz(result, expected)
+
+ def test_tzinfos_callable_could_return_none(self):
+ dstr = "2017-02-03 12:40 BRST"
+ result = parse(dstr, tzinfos=lambda *args: None)
+ expected = datetime(2017, 2, 3, 12, 40)
+ self.assert_equal_same_tz(result, expected)
+
+ def test_invalid_tzinfo_input(self):
+ dstr = "2014 January 19 09:00 UTC"
+ # Pass an absurd tzinfos object
+ tzinfos = {"UTC": ValueError}
+ with pytest.raises(TypeError):
+ parse(dstr, tzinfos=tzinfos)
+
+ def test_valid_tzinfo_tzinfo_input(self):
+ dstr = "2014 January 19 09:00 UTC"
+ tzinfos = {"UTC": tz.UTC}
+ expected = datetime(2014, 1, 19, 9, tzinfo=tz.UTC)
+ res = parse(dstr, tzinfos=tzinfos)
+ self.assert_equal_same_tz(res, expected)
+
+ def test_valid_tzinfo_unicode_input(self):
+ dstr = "2014 January 19 09:00 UTC"
+ tzinfos = {u"UTC": u"UTC+0"}
+ expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzstr("UTC+0"))
+ res = parse(dstr, tzinfos=tzinfos)
+ self.assert_equal_same_tz(res, expected)
+
+ def test_valid_tzinfo_callable_input(self):
+ dstr = "2014 January 19 09:00 UTC"
+
+ def tzinfos(*args, **kwargs):
+ return u"UTC+0"
+
+ expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzstr("UTC+0"))
+ res = parse(dstr, tzinfos=tzinfos)
+ self.assert_equal_same_tz(res, expected)
+
+ def test_valid_tzinfo_int_input(self):
+ dstr = "2014 January 19 09:00 UTC"
+ tzinfos = {u"UTC": -28800}
+ expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzoffset(u"UTC", -28800))
+ res = parse(dstr, tzinfos=tzinfos)
+ self.assert_equal_same_tz(res, expected)
+
+
+class ParserTest(unittest.TestCase):
+
+ @classmethod
+ def setup_class(cls):
+ cls.tzinfos = {"BRST": -10800}
+ cls.brsttz = tzoffset("BRST", -10800)
+ cls.default = datetime(2003, 9, 25)
+
+ # Parser should be able to handle bytestring and unicode
+ cls.uni_str = '2014-05-01 08:00:00'
+ cls.str_str = cls.uni_str.encode()
+
+ def testParserParseStr(self):
+ from dateutil.parser import parser
+
+ assert parser().parse(self.str_str) == parser().parse(self.uni_str)
+
+ def testParseUnicodeWords(self):
+
+ class rus_parserinfo(parserinfo):
+ MONTHS = [("янв", "Январь"),
+ ("фев", "Февраль"),
+ ("мар", "Март"),
+ ("апр", "Апрель"),
+ ("май", "Май"),
+ ("июн", "Июнь"),
+ ("июл", "Июль"),
+ ("авг", "Август"),
+ ("сен", "Сентябрь"),
+ ("окт", "Октябрь"),
+ ("ноя", "Ноябрь"),
+ ("дек", "Декабрь")]
+
+ expected = datetime(2015, 9, 10, 10, 20)
+ res = parse('10 Сентябрь 2015 10:20', parserinfo=rus_parserinfo())
+ assert res == expected
+
+ def testParseWithNulls(self):
+ # This relies on the from __future__ import unicode_literals, because
+ # explicitly specifying a unicode literal is a syntax error in Py 3.2
+ # May want to switch to u'...' if we ever drop Python 3.2 support.
+ pstring = '\x00\x00August 29, 1924'
+
+ assert parse(pstring) == datetime(1924, 8, 29)
+
+ def testDateCommandFormat(self):
+ self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003",
+ tzinfos=self.tzinfos),
+ datetime(2003, 9, 25, 10, 36, 28,
+ tzinfo=self.brsttz))
+
+ def testDateCommandFormatReversed(self):
+ self.assertEqual(parse("2003 10:36:28 BRST 25 Sep Thu",
+ tzinfos=self.tzinfos),
+ datetime(2003, 9, 25, 10, 36, 28,
+ tzinfo=self.brsttz))
+
+ def testDateCommandFormatWithLong(self):
+ if PY2:
+ self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003",
+ tzinfos={"BRST": long(-10800)}),
+ datetime(2003, 9, 25, 10, 36, 28,
+ tzinfo=self.brsttz))
+
+ def testISOFormatStrip2(self):
+ self.assertEqual(parse("2003-09-25T10:49:41+03:00"),
+ datetime(2003, 9, 25, 10, 49, 41,
+ tzinfo=tzoffset(None, 10800)))
+
+ def testISOStrippedFormatStrip2(self):
+ self.assertEqual(parse("20030925T104941+0300"),
+ datetime(2003, 9, 25, 10, 49, 41,
+ tzinfo=tzoffset(None, 10800)))
+
+ def testAMPMNoHour(self):
+ with pytest.raises(ParserError):
+ parse("AM")
+
+ with pytest.raises(ParserError):
+ parse("Jan 20, 2015 PM")
+
+ def testAMPMRange(self):
+ with pytest.raises(ParserError):
+ parse("13:44 AM")
+
+ with pytest.raises(ParserError):
+ parse("January 25, 1921 23:13 PM")
+
+ def testPertain(self):
+ self.assertEqual(parse("Sep 03", default=self.default),
+ datetime(2003, 9, 3))
+ self.assertEqual(parse("Sep of 03", default=self.default),
+ datetime(2003, 9, 25))
+
+ def testFuzzy(self):
+ s = "Today is 25 of September of 2003, exactly " \
+ "at 10:49:41 with timezone -03:00."
+ self.assertEqual(parse(s, fuzzy=True),
+ datetime(2003, 9, 25, 10, 49, 41,
+ tzinfo=self.brsttz))
+
+ def testFuzzyWithTokens(self):
+ s1 = "Today is 25 of September of 2003, exactly " \
+ "at 10:49:41 with timezone -03:00."
+ self.assertEqual(parse(s1, fuzzy_with_tokens=True),
+ (datetime(2003, 9, 25, 10, 49, 41,
+ tzinfo=self.brsttz),
+ ('Today is ', 'of ', ', exactly at ',
+ ' with timezone ', '.')))
+
+ s2 = "http://biz.yahoo.com/ipo/p/600221.html"
+ self.assertEqual(parse(s2, fuzzy_with_tokens=True),
+ (datetime(2060, 2, 21, 0, 0, 0),
+ ('http://biz.yahoo.com/ipo/p/', '.html')))
+
+ def testFuzzyAMPMProblem(self):
+ # Sometimes fuzzy parsing results in AM/PM flag being set without
+ # hours - if it's fuzzy it should ignore that.
+ s1 = "I have a meeting on March 1, 1974."
+ s2 = "On June 8th, 2020, I am going to be the first man on Mars"
+
+ # Also don't want any erroneous AM or PMs changing the parsed time
+ s3 = "Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003"
+ s4 = "Meet me at 3:00AM on December 3rd, 2003 at the AM/PM on Sunset"
+
+ self.assertEqual(parse(s1, fuzzy=True), datetime(1974, 3, 1))
+ self.assertEqual(parse(s2, fuzzy=True), datetime(2020, 6, 8))
+ self.assertEqual(parse(s3, fuzzy=True), datetime(2003, 12, 3, 3))
+ self.assertEqual(parse(s4, fuzzy=True), datetime(2003, 12, 3, 3))
+
+ def testFuzzyIgnoreAMPM(self):
+ s1 = "Jan 29, 1945 14:45 AM I going to see you there?"
+ with pytest.warns(UnknownTimezoneWarning):
+ res = parse(s1, fuzzy=True)
+ self.assertEqual(res, datetime(1945, 1, 29, 14, 45))
+
+ def testRandomFormat24(self):
+ self.assertEqual(parse("0:00 PM, PST", default=self.default,
+ ignoretz=True),
+ datetime(2003, 9, 25, 12, 0))
+
+ def testRandomFormat26(self):
+ with pytest.warns(UnknownTimezoneWarning):
+ res = parse("5:50 A.M. on June 13, 1990")
+
+ self.assertEqual(res, datetime(1990, 6, 13, 5, 50))
+
+ def testUnspecifiedDayFallback(self):
+ # Test that for an unspecified day, the fallback behavior is correct.
+ self.assertEqual(parse("April 2009", default=datetime(2010, 1, 31)),
+ datetime(2009, 4, 30))
+
+ def testUnspecifiedDayFallbackFebNoLeapYear(self):
+ self.assertEqual(parse("Feb 2007", default=datetime(2010, 1, 31)),
+ datetime(2007, 2, 28))
+
+ def testUnspecifiedDayFallbackFebLeapYear(self):
+ self.assertEqual(parse("Feb 2008", default=datetime(2010, 1, 31)),
+ datetime(2008, 2, 29))
+
+ def testErrorType01(self):
+ with pytest.raises(ParserError):
+ parse('shouldfail')
+
+ def testCorrectErrorOnFuzzyWithTokens(self):
+ assertRaisesRegex(self, ParserError, 'Unknown string format',
+ parse, '04/04/32/423', fuzzy_with_tokens=True)
+ assertRaisesRegex(self, ParserError, 'Unknown string format',
+ parse, '04/04/04 +32423', fuzzy_with_tokens=True)
+ assertRaisesRegex(self, ParserError, 'Unknown string format',
+ parse, '04/04/0d4', fuzzy_with_tokens=True)
+
+ def testIncreasingCTime(self):
+ # This test will check 200 different years, every month, every day,
+ # every hour, every minute, every second, and every weekday, using
+ # a delta of more or less 1 year, 1 month, 1 day, 1 minute and
+ # 1 second.
+ delta = timedelta(days=365+31+1, seconds=1+60+60*60)
+ dt = datetime(1900, 1, 1, 0, 0, 0, 0)
+ for i in range(200):
+ assert parse(dt.ctime()) == dt
+ dt += delta
+
+ def testIncreasingISOFormat(self):
+ delta = timedelta(days=365+31+1, seconds=1+60+60*60)
+ dt = datetime(1900, 1, 1, 0, 0, 0, 0)
+ for i in range(200):
+ assert parse(dt.isoformat()) == dt
+ dt += delta
+
+ def testMicrosecondsPrecisionError(self):
+ # Skip found out that sad precision problem. :-(
+ dt1 = parse("00:11:25.01")
+ dt2 = parse("00:12:10.01")
+ assert dt1.microsecond == 10000
+ assert dt2.microsecond == 10000
+
+ def testMicrosecondPrecisionErrorReturns(self):
+ # One more precision issue, discovered by Eric Brown. This should
+ # be the last one, as we're no longer using floating points.
+ for ms in [100001, 100000, 99999, 99998,
+ 10001, 10000, 9999, 9998,
+ 1001, 1000, 999, 998,
+ 101, 100, 99, 98]:
+ dt = datetime(2008, 2, 27, 21, 26, 1, ms)
+ assert parse(dt.isoformat()) == dt
+
+ def testCustomParserInfo(self):
+ # Custom parser info wasn't working, as Michael Elsdörfer discovered.
+ from dateutil.parser import parserinfo, parser
+
+ class myparserinfo(parserinfo):
+ MONTHS = parserinfo.MONTHS[:]
+ MONTHS[0] = ("Foo", "Foo")
+ myparser = parser(myparserinfo())
+ dt = myparser.parse("01/Foo/2007")
+ assert dt == datetime(2007, 1, 1)
+
+ def testCustomParserShortDaynames(self):
+ # Horacio Hoyos discovered that day names shorter than 3 characters,
+ # for example two letter German day name abbreviations, don't work:
+ # https://github.com/dateutil/dateutil/issues/343
+ from dateutil.parser import parserinfo, parser
+
+ class GermanParserInfo(parserinfo):
+ WEEKDAYS = [("Mo", "Montag"),
+ ("Di", "Dienstag"),
+ ("Mi", "Mittwoch"),
+ ("Do", "Donnerstag"),
+ ("Fr", "Freitag"),
+ ("Sa", "Samstag"),
+ ("So", "Sonntag")]
+
+ myparser = parser(GermanParserInfo())
+ dt = myparser.parse("Sa 21. Jan 2017")
+ self.assertEqual(dt, datetime(2017, 1, 21))
+
+ def testNoYearFirstNoDayFirst(self):
+ dtstr = '090107'
+
+ # Should be MMDDYY
+ self.assertEqual(parse(dtstr),
+ datetime(2007, 9, 1))
+
+ self.assertEqual(parse(dtstr, yearfirst=False, dayfirst=False),
+ datetime(2007, 9, 1))
+
+ def testYearFirst(self):
+ dtstr = '090107'
+
+ # Should be MMDDYY
+ self.assertEqual(parse(dtstr, yearfirst=True),
+ datetime(2009, 1, 7))
+
+ self.assertEqual(parse(dtstr, yearfirst=True, dayfirst=False),
+ datetime(2009, 1, 7))
+
+ def testDayFirst(self):
+ dtstr = '090107'
+
+ # Should be DDMMYY
+ self.assertEqual(parse(dtstr, dayfirst=True),
+ datetime(2007, 1, 9))
+
+ self.assertEqual(parse(dtstr, yearfirst=False, dayfirst=True),
+ datetime(2007, 1, 9))
+
+ def testDayFirstYearFirst(self):
+ dtstr = '090107'
+ # Should be YYDDMM
+ self.assertEqual(parse(dtstr, yearfirst=True, dayfirst=True),
+ datetime(2009, 7, 1))
+
+ def testUnambiguousYearFirst(self):
+ dtstr = '2015 09 25'
+ self.assertEqual(parse(dtstr, yearfirst=True),
+ datetime(2015, 9, 25))
+
+ def testUnambiguousDayFirst(self):
+ dtstr = '2015 09 25'
+ self.assertEqual(parse(dtstr, dayfirst=True),
+ datetime(2015, 9, 25))
+
+ def testUnambiguousDayFirstYearFirst(self):
+ dtstr = '2015 09 25'
+ self.assertEqual(parse(dtstr, dayfirst=True, yearfirst=True),
+ datetime(2015, 9, 25))
+
+ def test_mstridx(self):
+ # See GH408
+ dtstr = '2015-15-May'
+ self.assertEqual(parse(dtstr),
+ datetime(2015, 5, 15))
+
+ def test_idx_check(self):
+ dtstr = '2017-07-17 06:15:'
+ # Pre-PR, the trailing colon will cause an IndexError at 824-825
+ # when checking `i < len_l` and then accessing `l[i+1]`
+ res = parse(dtstr, fuzzy=True)
+ assert res == datetime(2017, 7, 17, 6, 15)
+
+ def test_hmBY(self):
+ # See GH#483
+ dtstr = '02:17NOV2017'
+ res = parse(dtstr, default=self.default)
+ assert res == datetime(2017, 11, self.default.day, 2, 17)
+
+ def test_validate_hour(self):
+ # See GH353
+ invalid = "201A-01-01T23:58:39.239769+03:00"
+ with pytest.raises(ParserError):
+ parse(invalid)
+
+ def test_era_trailing_year(self):
+ dstr = 'AD2001'
+ res = parse(dstr)
+ assert res.year == 2001, res
+
+ def test_includes_timestr(self):
+ timestr = "2020-13-97T44:61:83"
+
+ try:
+ parse(timestr)
+ except ParserError as e:
+ assert e.args[1] == timestr
+ else:
+ pytest.fail("Failed to raise ParserError")
+
+
+class TestOutOfBounds(object):
+
+ def test_no_year_zero(self):
+ with pytest.raises(ParserError):
+ parse("0000 Jun 20")
+
+ def test_out_of_bound_day(self):
+ with pytest.raises(ParserError):
+ parse("Feb 30, 2007")
+
+ def test_illegal_month_error(self):
+ with pytest.raises(ParserError):
+ parse("0-100")
+
+ def test_day_sanity(self, fuzzy):
+ dstr = "2014-15-25"
+ with pytest.raises(ParserError):
+ parse(dstr, fuzzy=fuzzy)
+
+ def test_minute_sanity(self, fuzzy):
+ dstr = "2014-02-28 22:64"
+ with pytest.raises(ParserError):
+ parse(dstr, fuzzy=fuzzy)
+
+ def test_hour_sanity(self, fuzzy):
+ dstr = "2014-02-28 25:16 PM"
+ with pytest.raises(ParserError):
+ parse(dstr, fuzzy=fuzzy)
+
+ def test_second_sanity(self, fuzzy):
+ dstr = "2014-02-28 22:14:64"
+ with pytest.raises(ParserError):
+ parse(dstr, fuzzy=fuzzy)
+
+
+class TestParseUnimplementedCases(object):
+ @pytest.mark.xfail
+ def test_somewhat_ambiguous_string(self):
+ # Ref: github issue #487
+ # The parser is choosing the wrong part for hour
+ # causing datetime to raise an exception.
+ dtstr = '1237 PM BRST Mon Oct 30 2017'
+ res = parse(dtstr, tzinfo=self.tzinfos)
+ assert res == datetime(2017, 10, 30, 12, 37, tzinfo=self.tzinfos)
+
+ @pytest.mark.xfail
+ def test_YmdH_M_S(self):
+ # found in nasdaq's ftp data
+ dstr = '1991041310:19:24'
+ expected = datetime(1991, 4, 13, 10, 19, 24)
+ res = parse(dstr)
+ assert res == expected, (res, expected)
+
+ @pytest.mark.xfail
+ def test_first_century(self):
+ dstr = '0031 Nov 03'
+ expected = datetime(31, 11, 3)
+ res = parse(dstr)
+ assert res == expected, res
+
+ @pytest.mark.xfail
+ def test_era_trailing_year_with_dots(self):
+ dstr = 'A.D.2001'
+ res = parse(dstr)
+ assert res.year == 2001, res
+
+ @pytest.mark.xfail
+ def test_ad_nospace(self):
+ expected = datetime(6, 5, 19)
+ for dstr in [' 6AD May 19', ' 06AD May 19',
+ ' 006AD May 19', ' 0006AD May 19']:
+ res = parse(dstr)
+ assert res == expected, (dstr, res)
+
+ @pytest.mark.xfail
+ def test_four_letter_day(self):
+ dstr = 'Frid Dec 30, 2016'
+ expected = datetime(2016, 12, 30)
+ res = parse(dstr)
+ assert res == expected
+
+ @pytest.mark.xfail
+ def test_non_date_number(self):
+ dstr = '1,700'
+ with pytest.raises(ParserError):
+ parse(dstr)
+
+ @pytest.mark.xfail
+ def test_on_era(self):
+ # This could be classified as an "eras" test, but the relevant part
+ # about this is the ` on `
+ dstr = '2:15 PM on January 2nd 1973 A.D.'
+ expected = datetime(1973, 1, 2, 14, 15)
+ res = parse(dstr)
+ assert res == expected
+
+ @pytest.mark.xfail
+ def test_extraneous_year(self):
+ # This was found in the wild at insidertrading.org
+ dstr = "2011 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d NOVEMBER 7, 2012"
+ res = parse(dstr, fuzzy_with_tokens=True)
+ expected = datetime(2012, 11, 7)
+ assert res == expected
+
+ @pytest.mark.xfail
+ def test_extraneous_year_tokens(self):
+ # This was found in the wild at insidertrading.org
+ # Unlike in the case above, identifying the first "2012" as the year
+ # would not be a problem, but inferring that the latter 2012 is hhmm
+ # is a problem.
+ dstr = "2012 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d NOVEMBER 7, 2012"
+ expected = datetime(2012, 11, 7)
+ (res, tokens) = parse(dstr, fuzzy_with_tokens=True)
+ assert res == expected
+ assert tokens == ("2012 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d ",)
+
+ @pytest.mark.xfail
+ def test_extraneous_year2(self):
+ # This was found in the wild at insidertrading.org
+ dstr = ("Berylson Amy Smith 1998 Grantor Retained Annuity Trust "
+ "u/d/t November 2, 1998 f/b/o Jennifer L Berylson")
+ res = parse(dstr, fuzzy_with_tokens=True)
+ expected = datetime(1998, 11, 2)
+ assert res == expected
+
+ @pytest.mark.xfail
+ def test_extraneous_year3(self):
+ # This was found in the wild at insidertrading.org
+ dstr = "SMITH R & WEISS D 94 CHILD TR FBO M W SMITH UDT 12/1/1994"
+ res = parse(dstr, fuzzy_with_tokens=True)
+ expected = datetime(1994, 12, 1)
+ assert res == expected
+
+ @pytest.mark.xfail
+ def test_unambiguous_YYYYMM(self):
+ # 171206 can be parsed as YYMMDD. However, 201712 cannot be parsed
+ # as instance of YYMMDD and parser could fallback to YYYYMM format.
+ dstr = "201712"
+ res = parse(dstr)
+ expected = datetime(2017, 12, 1)
+ assert res == expected
+
+ @pytest.mark.xfail
+ def test_extraneous_numerical_content(self):
+ # ref: https://github.com/dateutil/dateutil/issues/1029
+ # parser interprets price and percentage as parts of the date
+ dstr = "£14.99 (25% off, until April 20)"
+ res = parse(dstr, fuzzy=True, default=datetime(2000, 1, 1))
+ expected = datetime(2000, 4, 20)
+ assert res == expected
+
+
+@pytest.mark.skipif(IS_WIN, reason="Windows does not use TZ var")
+class TestTZVar(object):
+ def test_parse_unambiguous_nonexistent_local(self):
+ # When dates are specified "EST" even when they should be "EDT" in the
+ # local time zone, we should still assign the local time zone
+ with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'):
+ dt_exp = datetime(2011, 8, 1, 12, 30, tzinfo=tz.tzlocal())
+ dt = parse('2011-08-01T12:30 EST')
+
+ assert dt.tzname() == 'EDT'
+ assert dt == dt_exp
+
+ def test_tzlocal_in_gmt(self):
+ # GH #318
+ with TZEnvContext('GMT0BST,M3.5.0,M10.5.0'):
+ # This is an imaginary datetime in tz.tzlocal() but should still
+ # parse using the GMT-as-alias-for-UTC rule
+ dt = parse('2004-05-01T12:00 GMT')
+ dt_exp = datetime(2004, 5, 1, 12, tzinfo=tz.UTC)
+
+ assert dt == dt_exp
+
+ def test_tzlocal_parse_fold(self):
+ # One manifestion of GH #318
+ with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'):
+ dt_exp = datetime(2011, 11, 6, 1, 30, tzinfo=tz.tzlocal())
+ dt_exp = tz.enfold(dt_exp, fold=1)
+ dt = parse('2011-11-06T01:30 EST')
+
+ # Because this is ambiguous, until `tz.tzlocal() is tz.tzlocal()`
+ # we'll just check the attributes we care about rather than
+ # dt == dt_exp
+ assert dt.tzname() == dt_exp.tzname()
+ assert dt.replace(tzinfo=None) == dt_exp.replace(tzinfo=None)
+ assert getattr(dt, 'fold') == getattr(dt_exp, 'fold')
+ assert dt.astimezone(tz.UTC) == dt_exp.astimezone(tz.UTC)
+
+
+def test_parse_tzinfos_fold():
+ NYC = tz.gettz('America/New_York')
+ tzinfos = {'EST': NYC, 'EDT': NYC}
+
+ dt_exp = tz.enfold(datetime(2011, 11, 6, 1, 30, tzinfo=NYC), fold=1)
+ dt = parse('2011-11-06T01:30 EST', tzinfos=tzinfos)
+
+ assert dt == dt_exp
+ assert dt.tzinfo is dt_exp.tzinfo
+ assert getattr(dt, 'fold') == getattr(dt_exp, 'fold')
+ assert dt.astimezone(tz.UTC) == dt_exp.astimezone(tz.UTC)
+
+
+@pytest.mark.parametrize('dtstr,dt', [
+ ('5.6h', datetime(2003, 9, 25, 5, 36)),
+ ('5.6m', datetime(2003, 9, 25, 0, 5, 36)),
+ # '5.6s' never had a rounding problem, test added for completeness
+ ('5.6s', datetime(2003, 9, 25, 0, 0, 5, 600000))
+])
+def test_rounding_floatlike_strings(dtstr, dt):
+ assert parse(dtstr, default=datetime(2003, 9, 25)) == dt
+
+
+@pytest.mark.parametrize('value', ['1: test', 'Nan'])
+def test_decimal_error(value):
+ # GH 632, GH 662 - decimal.Decimal raises some non-ParserError exception
+ # when constructed with an invalid value
+ with pytest.raises(ParserError):
+ parse(value)
+
+def test_parsererror_repr():
+ # GH 991 — the __repr__ was not properly indented and so was never defined.
+ # This tests the current behavior of the ParserError __repr__, but the
+ # precise format is not guaranteed to be stable and may change even in
+ # minor versions. This test exists to avoid regressions.
+ s = repr(ParserError("Problem with string: %s", "2019-01-01"))
+
+ assert s == "ParserError('Problem with string: %s', '2019-01-01')"
diff --git a/contrib/python/python-dateutil/py2/tests/test_relativedelta.py b/contrib/python/python-dateutil/py2/tests/test_relativedelta.py
new file mode 100644
index 0000000000..5204c293b7
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_relativedelta.py
@@ -0,0 +1,767 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from ._common import NotAValue
+
+import calendar
+from datetime import datetime, date, timedelta
+import unittest
+
+import pytest
+
+from dateutil.relativedelta import relativedelta, MO, TU, WE, FR, SU
+
+
+class RelativeDeltaTest(unittest.TestCase):
+ now = datetime(2003, 9, 17, 20, 54, 47, 282310)
+ today = date(2003, 9, 17)
+
+ def testInheritance(self):
+ # Ensure that relativedelta is inheritance-friendly.
+ class rdChildClass(relativedelta):
+ pass
+
+ ccRD = rdChildClass(years=1, months=1, days=1, leapdays=1, weeks=1,
+ hours=1, minutes=1, seconds=1, microseconds=1)
+
+ rd = relativedelta(years=1, months=1, days=1, leapdays=1, weeks=1,
+ hours=1, minutes=1, seconds=1, microseconds=1)
+
+ self.assertEqual(type(ccRD + rd), type(ccRD),
+ msg='Addition does not inherit type.')
+
+ self.assertEqual(type(ccRD - rd), type(ccRD),
+ msg='Subtraction does not inherit type.')
+
+ self.assertEqual(type(-ccRD), type(ccRD),
+ msg='Negation does not inherit type.')
+
+ self.assertEqual(type(ccRD * 5.0), type(ccRD),
+ msg='Multiplication does not inherit type.')
+
+ self.assertEqual(type(ccRD / 5.0), type(ccRD),
+ msg='Division does not inherit type.')
+
+ def testMonthEndMonthBeginning(self):
+ self.assertEqual(relativedelta(datetime(2003, 1, 31, 23, 59, 59),
+ datetime(2003, 3, 1, 0, 0, 0)),
+ relativedelta(months=-1, seconds=-1))
+
+ self.assertEqual(relativedelta(datetime(2003, 3, 1, 0, 0, 0),
+ datetime(2003, 1, 31, 23, 59, 59)),
+ relativedelta(months=1, seconds=1))
+
+ def testMonthEndMonthBeginningLeapYear(self):
+ self.assertEqual(relativedelta(datetime(2012, 1, 31, 23, 59, 59),
+ datetime(2012, 3, 1, 0, 0, 0)),
+ relativedelta(months=-1, seconds=-1))
+
+ self.assertEqual(relativedelta(datetime(2003, 3, 1, 0, 0, 0),
+ datetime(2003, 1, 31, 23, 59, 59)),
+ relativedelta(months=1, seconds=1))
+
+ def testNextMonth(self):
+ self.assertEqual(self.now+relativedelta(months=+1),
+ datetime(2003, 10, 17, 20, 54, 47, 282310))
+
+ def testNextMonthPlusOneWeek(self):
+ self.assertEqual(self.now+relativedelta(months=+1, weeks=+1),
+ datetime(2003, 10, 24, 20, 54, 47, 282310))
+
+ def testNextMonthPlusOneWeek10am(self):
+ self.assertEqual(self.today +
+ relativedelta(months=+1, weeks=+1, hour=10),
+ datetime(2003, 10, 24, 10, 0))
+
+ def testNextMonthPlusOneWeek10amDiff(self):
+ self.assertEqual(relativedelta(datetime(2003, 10, 24, 10, 0),
+ self.today),
+ relativedelta(months=+1, days=+7, hours=+10))
+
+ def testOneMonthBeforeOneYear(self):
+ self.assertEqual(self.now+relativedelta(years=+1, months=-1),
+ datetime(2004, 8, 17, 20, 54, 47, 282310))
+
+ def testMonthsOfDiffNumOfDays(self):
+ self.assertEqual(date(2003, 1, 27)+relativedelta(months=+1),
+ date(2003, 2, 27))
+ self.assertEqual(date(2003, 1, 31)+relativedelta(months=+1),
+ date(2003, 2, 28))
+ self.assertEqual(date(2003, 1, 31)+relativedelta(months=+2),
+ date(2003, 3, 31))
+
+ def testMonthsOfDiffNumOfDaysWithYears(self):
+ self.assertEqual(date(2000, 2, 28)+relativedelta(years=+1),
+ date(2001, 2, 28))
+ self.assertEqual(date(2000, 2, 29)+relativedelta(years=+1),
+ date(2001, 2, 28))
+
+ self.assertEqual(date(1999, 2, 28)+relativedelta(years=+1),
+ date(2000, 2, 28))
+ self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1),
+ date(2000, 3, 1))
+ self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1),
+ date(2000, 3, 1))
+
+ self.assertEqual(date(2001, 2, 28)+relativedelta(years=-1),
+ date(2000, 2, 28))
+ self.assertEqual(date(2001, 3, 1)+relativedelta(years=-1),
+ date(2000, 3, 1))
+
+ def testNextFriday(self):
+ self.assertEqual(self.today+relativedelta(weekday=FR),
+ date(2003, 9, 19))
+
+ def testNextFridayInt(self):
+ self.assertEqual(self.today+relativedelta(weekday=calendar.FRIDAY),
+ date(2003, 9, 19))
+
+ def testLastFridayInThisMonth(self):
+ self.assertEqual(self.today+relativedelta(day=31, weekday=FR(-1)),
+ date(2003, 9, 26))
+
+ def testLastDayOfFebruary(self):
+ self.assertEqual(date(2021, 2, 1) + relativedelta(day=31),
+ date(2021, 2, 28))
+
+ def testLastDayOfFebruaryLeapYear(self):
+ self.assertEqual(date(2020, 2, 1) + relativedelta(day=31),
+ date(2020, 2, 29))
+
+ def testNextWednesdayIsToday(self):
+ self.assertEqual(self.today+relativedelta(weekday=WE),
+ date(2003, 9, 17))
+
+ def testNextWednesdayNotToday(self):
+ self.assertEqual(self.today+relativedelta(days=+1, weekday=WE),
+ date(2003, 9, 24))
+
+ def testAddMoreThan12Months(self):
+ self.assertEqual(date(2003, 12, 1) + relativedelta(months=+13),
+ date(2005, 1, 1))
+
+ def testAddNegativeMonths(self):
+ self.assertEqual(date(2003, 1, 1) + relativedelta(months=-2),
+ date(2002, 11, 1))
+
+ def test15thISOYearWeek(self):
+ self.assertEqual(date(2003, 1, 1) +
+ relativedelta(day=4, weeks=+14, weekday=MO(-1)),
+ date(2003, 4, 7))
+
+ def testMillenniumAge(self):
+ self.assertEqual(relativedelta(self.now, date(2001, 1, 1)),
+ relativedelta(years=+2, months=+8, days=+16,
+ hours=+20, minutes=+54, seconds=+47,
+ microseconds=+282310))
+
+ def testJohnAge(self):
+ self.assertEqual(relativedelta(self.now,
+ datetime(1978, 4, 5, 12, 0)),
+ relativedelta(years=+25, months=+5, days=+12,
+ hours=+8, minutes=+54, seconds=+47,
+ microseconds=+282310))
+
+ def testJohnAgeWithDate(self):
+ self.assertEqual(relativedelta(self.today,
+ datetime(1978, 4, 5, 12, 0)),
+ relativedelta(years=+25, months=+5, days=+11,
+ hours=+12))
+
+ def testYearDay(self):
+ self.assertEqual(date(2003, 1, 1)+relativedelta(yearday=260),
+ date(2003, 9, 17))
+ self.assertEqual(date(2002, 1, 1)+relativedelta(yearday=260),
+ date(2002, 9, 17))
+ self.assertEqual(date(2000, 1, 1)+relativedelta(yearday=260),
+ date(2000, 9, 16))
+ self.assertEqual(self.today+relativedelta(yearday=261),
+ date(2003, 9, 18))
+
+ def testYearDayBug(self):
+ # Tests a problem reported by Adam Ryan.
+ self.assertEqual(date(2010, 1, 1)+relativedelta(yearday=15),
+ date(2010, 1, 15))
+
+ def testNonLeapYearDay(self):
+ self.assertEqual(date(2003, 1, 1)+relativedelta(nlyearday=260),
+ date(2003, 9, 17))
+ self.assertEqual(date(2002, 1, 1)+relativedelta(nlyearday=260),
+ date(2002, 9, 17))
+ self.assertEqual(date(2000, 1, 1)+relativedelta(nlyearday=260),
+ date(2000, 9, 17))
+ self.assertEqual(self.today+relativedelta(yearday=261),
+ date(2003, 9, 18))
+
+ def testAddition(self):
+ self.assertEqual(relativedelta(days=10) +
+ relativedelta(years=1, months=2, days=3, hours=4,
+ minutes=5, microseconds=6),
+ relativedelta(years=1, months=2, days=13, hours=4,
+ minutes=5, microseconds=6))
+
+ def testAbsoluteAddition(self):
+ self.assertEqual(relativedelta() + relativedelta(day=0, hour=0),
+ relativedelta(day=0, hour=0))
+ self.assertEqual(relativedelta(day=0, hour=0) + relativedelta(),
+ relativedelta(day=0, hour=0))
+
+ def testAdditionToDatetime(self):
+ self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1),
+ datetime(2000, 1, 2))
+
+ def testRightAdditionToDatetime(self):
+ self.assertEqual(relativedelta(days=1) + datetime(2000, 1, 1),
+ datetime(2000, 1, 2))
+
+ def testAdditionInvalidType(self):
+ with self.assertRaises(TypeError):
+ relativedelta(days=3) + 9
+
+ def testAdditionUnsupportedType(self):
+ # For unsupported types that define their own comparators, etc.
+ self.assertIs(relativedelta(days=1) + NotAValue, NotAValue)
+
+ def testAdditionFloatValue(self):
+ self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=float(1)),
+ datetime(2000, 1, 2))
+ self.assertEqual(datetime(2000, 1, 1) + relativedelta(months=float(1)),
+ datetime(2000, 2, 1))
+ self.assertEqual(datetime(2000, 1, 1) + relativedelta(years=float(1)),
+ datetime(2001, 1, 1))
+
+ def testAdditionFloatFractionals(self):
+ self.assertEqual(datetime(2000, 1, 1, 0) +
+ relativedelta(days=float(0.5)),
+ datetime(2000, 1, 1, 12))
+ self.assertEqual(datetime(2000, 1, 1, 0, 0) +
+ relativedelta(hours=float(0.5)),
+ datetime(2000, 1, 1, 0, 30))
+ self.assertEqual(datetime(2000, 1, 1, 0, 0, 0) +
+ relativedelta(minutes=float(0.5)),
+ datetime(2000, 1, 1, 0, 0, 30))
+ self.assertEqual(datetime(2000, 1, 1, 0, 0, 0, 0) +
+ relativedelta(seconds=float(0.5)),
+ datetime(2000, 1, 1, 0, 0, 0, 500000))
+ self.assertEqual(datetime(2000, 1, 1, 0, 0, 0, 0) +
+ relativedelta(microseconds=float(500000.25)),
+ datetime(2000, 1, 1, 0, 0, 0, 500000))
+
+ def testSubtraction(self):
+ self.assertEqual(relativedelta(days=10) -
+ relativedelta(years=1, months=2, days=3, hours=4,
+ minutes=5, microseconds=6),
+ relativedelta(years=-1, months=-2, days=7, hours=-4,
+ minutes=-5, microseconds=-6))
+
+ def testRightSubtractionFromDatetime(self):
+ self.assertEqual(datetime(2000, 1, 2) - relativedelta(days=1),
+ datetime(2000, 1, 1))
+
+ def testSubractionWithDatetime(self):
+ self.assertRaises(TypeError, lambda x, y: x - y,
+ (relativedelta(days=1), datetime(2000, 1, 1)))
+
+ def testSubtractionInvalidType(self):
+ with self.assertRaises(TypeError):
+ relativedelta(hours=12) - 14
+
+ def testSubtractionUnsupportedType(self):
+ self.assertIs(relativedelta(days=1) + NotAValue, NotAValue)
+
+ def testMultiplication(self):
+ self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1) * 28,
+ datetime(2000, 1, 29))
+ self.assertEqual(datetime(2000, 1, 1) + 28 * relativedelta(days=1),
+ datetime(2000, 1, 29))
+
+ def testMultiplicationUnsupportedType(self):
+ self.assertIs(relativedelta(days=1) * NotAValue, NotAValue)
+
+ def testDivision(self):
+ self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=28) / 28,
+ datetime(2000, 1, 2))
+
+ def testDivisionUnsupportedType(self):
+ self.assertIs(relativedelta(days=1) / NotAValue, NotAValue)
+
+ def testBoolean(self):
+ self.assertFalse(relativedelta(days=0))
+ self.assertTrue(relativedelta(days=1))
+
+ def testAbsoluteValueNegative(self):
+ rd_base = relativedelta(years=-1, months=-5, days=-2, hours=-3,
+ minutes=-5, seconds=-2, microseconds=-12)
+ rd_expected = relativedelta(years=1, months=5, days=2, hours=3,
+ minutes=5, seconds=2, microseconds=12)
+ self.assertEqual(abs(rd_base), rd_expected)
+
+ def testAbsoluteValuePositive(self):
+ rd_base = relativedelta(years=1, months=5, days=2, hours=3,
+ minutes=5, seconds=2, microseconds=12)
+ rd_expected = rd_base
+
+ self.assertEqual(abs(rd_base), rd_expected)
+
+ def testComparison(self):
+ d1 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1,
+ minutes=1, seconds=1, microseconds=1)
+ d2 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1,
+ minutes=1, seconds=1, microseconds=1)
+ d3 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1,
+ minutes=1, seconds=1, microseconds=2)
+
+ self.assertEqual(d1, d2)
+ self.assertNotEqual(d1, d3)
+
+ def testInequalityTypeMismatch(self):
+ # Different type
+ self.assertFalse(relativedelta(year=1) == 19)
+
+ def testInequalityUnsupportedType(self):
+ self.assertIs(relativedelta(hours=3) == NotAValue, NotAValue)
+
+ def testInequalityWeekdays(self):
+ # Different weekdays
+ no_wday = relativedelta(year=1997, month=4)
+ wday_mo_1 = relativedelta(year=1997, month=4, weekday=MO(+1))
+ wday_mo_2 = relativedelta(year=1997, month=4, weekday=MO(+2))
+ wday_tu = relativedelta(year=1997, month=4, weekday=TU)
+
+ self.assertTrue(wday_mo_1 == wday_mo_1)
+
+ self.assertFalse(no_wday == wday_mo_1)
+ self.assertFalse(wday_mo_1 == no_wday)
+
+ self.assertFalse(wday_mo_1 == wday_mo_2)
+ self.assertFalse(wday_mo_2 == wday_mo_1)
+
+ self.assertFalse(wday_mo_1 == wday_tu)
+ self.assertFalse(wday_tu == wday_mo_1)
+
+ def testMonthOverflow(self):
+ self.assertEqual(relativedelta(months=273),
+ relativedelta(years=22, months=9))
+
+ def testWeeks(self):
+ # Test that the weeks property is working properly.
+ rd = relativedelta(years=4, months=2, weeks=8, days=6)
+ self.assertEqual((rd.weeks, rd.days), (8, 8 * 7 + 6))
+
+ rd.weeks = 3
+ self.assertEqual((rd.weeks, rd.days), (3, 3 * 7 + 6))
+
+ def testRelativeDeltaRepr(self):
+ self.assertEqual(repr(relativedelta(years=1, months=-1, days=15)),
+ 'relativedelta(years=+1, months=-1, days=+15)')
+
+ self.assertEqual(repr(relativedelta(months=14, seconds=-25)),
+ 'relativedelta(years=+1, months=+2, seconds=-25)')
+
+ self.assertEqual(repr(relativedelta(month=3, hour=3, weekday=SU(3))),
+ 'relativedelta(month=3, weekday=SU(+3), hour=3)')
+
+ def testRelativeDeltaFractionalYear(self):
+ with self.assertRaises(ValueError):
+ relativedelta(years=1.5)
+
+ def testRelativeDeltaFractionalMonth(self):
+ with self.assertRaises(ValueError):
+ relativedelta(months=1.5)
+
+ def testRelativeDeltaInvalidDatetimeObject(self):
+ with self.assertRaises(TypeError):
+ relativedelta(dt1='2018-01-01', dt2='2018-01-02')
+
+ with self.assertRaises(TypeError):
+ relativedelta(dt1=datetime(2018, 1, 1), dt2='2018-01-02')
+
+ with self.assertRaises(TypeError):
+ relativedelta(dt1='2018-01-01', dt2=datetime(2018, 1, 2))
+
+ def testRelativeDeltaFractionalAbsolutes(self):
+ # Fractional absolute values will soon be unsupported,
+ # check for the deprecation warning.
+ with pytest.warns(DeprecationWarning):
+ relativedelta(year=2.86)
+
+ with pytest.warns(DeprecationWarning):
+ relativedelta(month=1.29)
+
+ with pytest.warns(DeprecationWarning):
+ relativedelta(day=0.44)
+
+ with pytest.warns(DeprecationWarning):
+ relativedelta(hour=23.98)
+
+ with pytest.warns(DeprecationWarning):
+ relativedelta(minute=45.21)
+
+ with pytest.warns(DeprecationWarning):
+ relativedelta(second=13.2)
+
+ with pytest.warns(DeprecationWarning):
+ relativedelta(microsecond=157221.93)
+
+ def testRelativeDeltaFractionalRepr(self):
+ rd = relativedelta(years=3, months=-2, days=1.25)
+
+ self.assertEqual(repr(rd),
+ 'relativedelta(years=+3, months=-2, days=+1.25)')
+
+ rd = relativedelta(hours=0.5, seconds=9.22)
+ self.assertEqual(repr(rd),
+ 'relativedelta(hours=+0.5, seconds=+9.22)')
+
+ def testRelativeDeltaFractionalWeeks(self):
+ # Equivalent to days=8, hours=18
+ rd = relativedelta(weeks=1.25)
+ d1 = datetime(2009, 9, 3, 0, 0)
+ self.assertEqual(d1 + rd,
+ datetime(2009, 9, 11, 18))
+
+ def testRelativeDeltaFractionalDays(self):
+ rd1 = relativedelta(days=1.48)
+
+ d1 = datetime(2009, 9, 3, 0, 0)
+ self.assertEqual(d1 + rd1,
+ datetime(2009, 9, 4, 11, 31, 12))
+
+ rd2 = relativedelta(days=1.5)
+ self.assertEqual(d1 + rd2,
+ datetime(2009, 9, 4, 12, 0, 0))
+
+ def testRelativeDeltaFractionalHours(self):
+ rd = relativedelta(days=1, hours=12.5)
+ d1 = datetime(2009, 9, 3, 0, 0)
+ self.assertEqual(d1 + rd,
+ datetime(2009, 9, 4, 12, 30, 0))
+
+ def testRelativeDeltaFractionalMinutes(self):
+ rd = relativedelta(hours=1, minutes=30.5)
+ d1 = datetime(2009, 9, 3, 0, 0)
+ self.assertEqual(d1 + rd,
+ datetime(2009, 9, 3, 1, 30, 30))
+
+ def testRelativeDeltaFractionalSeconds(self):
+ rd = relativedelta(hours=5, minutes=30, seconds=30.5)
+ d1 = datetime(2009, 9, 3, 0, 0)
+ self.assertEqual(d1 + rd,
+ datetime(2009, 9, 3, 5, 30, 30, 500000))
+
+ def testRelativeDeltaFractionalPositiveOverflow(self):
+ # Equivalent to (days=1, hours=14)
+ rd1 = relativedelta(days=1.5, hours=2)
+ d1 = datetime(2009, 9, 3, 0, 0)
+ self.assertEqual(d1 + rd1,
+ datetime(2009, 9, 4, 14, 0, 0))
+
+ # Equivalent to (days=1, hours=14, minutes=45)
+ rd2 = relativedelta(days=1.5, hours=2.5, minutes=15)
+ d1 = datetime(2009, 9, 3, 0, 0)
+ self.assertEqual(d1 + rd2,
+ datetime(2009, 9, 4, 14, 45))
+
+ # Carry back up - equivalent to (days=2, hours=2, minutes=0, seconds=1)
+ rd3 = relativedelta(days=1.5, hours=13, minutes=59.5, seconds=31)
+ self.assertEqual(d1 + rd3,
+ datetime(2009, 9, 5, 2, 0, 1))
+
+ def testRelativeDeltaFractionalNegativeDays(self):
+ # Equivalent to (days=-1, hours=-1)
+ rd1 = relativedelta(days=-1.5, hours=11)
+ d1 = datetime(2009, 9, 3, 12, 0)
+ self.assertEqual(d1 + rd1,
+ datetime(2009, 9, 2, 11, 0, 0))
+
+ # Equivalent to (days=-1, hours=-9)
+ rd2 = relativedelta(days=-1.25, hours=-3)
+ self.assertEqual(d1 + rd2,
+ datetime(2009, 9, 2, 3))
+
+ def testRelativeDeltaNormalizeFractionalDays(self):
+ # Equivalent to (days=2, hours=18)
+ rd1 = relativedelta(days=2.75)
+
+ self.assertEqual(rd1.normalized(), relativedelta(days=2, hours=18))
+
+ # Equivalent to (days=1, hours=11, minutes=31, seconds=12)
+ rd2 = relativedelta(days=1.48)
+
+ self.assertEqual(rd2.normalized(),
+ relativedelta(days=1, hours=11, minutes=31, seconds=12))
+
+ def testRelativeDeltaNormalizeFractionalDays2(self):
+ # Equivalent to (hours=1, minutes=30)
+ rd1 = relativedelta(hours=1.5)
+
+ self.assertEqual(rd1.normalized(), relativedelta(hours=1, minutes=30))
+
+ # Equivalent to (hours=3, minutes=17, seconds=5, microseconds=100)
+ rd2 = relativedelta(hours=3.28472225)
+
+ self.assertEqual(rd2.normalized(),
+ relativedelta(hours=3, minutes=17, seconds=5, microseconds=100))
+
+ def testRelativeDeltaNormalizeFractionalMinutes(self):
+ # Equivalent to (minutes=15, seconds=36)
+ rd1 = relativedelta(minutes=15.6)
+
+ self.assertEqual(rd1.normalized(),
+ relativedelta(minutes=15, seconds=36))
+
+ # Equivalent to (minutes=25, seconds=20, microseconds=25000)
+ rd2 = relativedelta(minutes=25.33375)
+
+ self.assertEqual(rd2.normalized(),
+ relativedelta(minutes=25, seconds=20, microseconds=25000))
+
+ def testRelativeDeltaNormalizeFractionalSeconds(self):
+ # Equivalent to (seconds=45, microseconds=25000)
+ rd1 = relativedelta(seconds=45.025)
+ self.assertEqual(rd1.normalized(),
+ relativedelta(seconds=45, microseconds=25000))
+
+ def testRelativeDeltaFractionalPositiveOverflow2(self):
+ # Equivalent to (days=1, hours=14)
+ rd1 = relativedelta(days=1.5, hours=2)
+ self.assertEqual(rd1.normalized(),
+ relativedelta(days=1, hours=14))
+
+ # Equivalent to (days=1, hours=14, minutes=45)
+ rd2 = relativedelta(days=1.5, hours=2.5, minutes=15)
+ self.assertEqual(rd2.normalized(),
+ relativedelta(days=1, hours=14, minutes=45))
+
+ # Carry back up - equivalent to:
+ # (days=2, hours=2, minutes=0, seconds=2, microseconds=3)
+ rd3 = relativedelta(days=1.5, hours=13, minutes=59.50045,
+ seconds=31.473, microseconds=500003)
+ self.assertEqual(rd3.normalized(),
+ relativedelta(days=2, hours=2, minutes=0,
+ seconds=2, microseconds=3))
+
+ def testRelativeDeltaFractionalNegativeOverflow(self):
+ # Equivalent to (days=-1)
+ rd1 = relativedelta(days=-0.5, hours=-12)
+ self.assertEqual(rd1.normalized(),
+ relativedelta(days=-1))
+
+ # Equivalent to (days=-1)
+ rd2 = relativedelta(days=-1.5, hours=12)
+ self.assertEqual(rd2.normalized(),
+ relativedelta(days=-1))
+
+ # Equivalent to (days=-1, hours=-14, minutes=-45)
+ rd3 = relativedelta(days=-1.5, hours=-2.5, minutes=-15)
+ self.assertEqual(rd3.normalized(),
+ relativedelta(days=-1, hours=-14, minutes=-45))
+
+ # Equivalent to (days=-1, hours=-14, minutes=+15)
+ rd4 = relativedelta(days=-1.5, hours=-2.5, minutes=45)
+ self.assertEqual(rd4.normalized(),
+ relativedelta(days=-1, hours=-14, minutes=+15))
+
+ # Carry back up - equivalent to:
+ # (days=-2, hours=-2, minutes=0, seconds=-2, microseconds=-3)
+ rd3 = relativedelta(days=-1.5, hours=-13, minutes=-59.50045,
+ seconds=-31.473, microseconds=-500003)
+ self.assertEqual(rd3.normalized(),
+ relativedelta(days=-2, hours=-2, minutes=0,
+ seconds=-2, microseconds=-3))
+
+ def testInvalidYearDay(self):
+ with self.assertRaises(ValueError):
+ relativedelta(yearday=367)
+
+ def testAddTimedeltaToUnpopulatedRelativedelta(self):
+ td = timedelta(
+ days=1,
+ seconds=1,
+ microseconds=1,
+ milliseconds=1,
+ minutes=1,
+ hours=1,
+ weeks=1
+ )
+
+ expected = relativedelta(
+ weeks=1,
+ days=1,
+ hours=1,
+ minutes=1,
+ seconds=1,
+ microseconds=1001
+ )
+
+ self.assertEqual(expected, relativedelta() + td)
+
+ def testAddTimedeltaToPopulatedRelativeDelta(self):
+ td = timedelta(
+ days=1,
+ seconds=1,
+ microseconds=1,
+ milliseconds=1,
+ minutes=1,
+ hours=1,
+ weeks=1
+ )
+
+ rd = relativedelta(
+ year=1,
+ month=1,
+ day=1,
+ hour=1,
+ minute=1,
+ second=1,
+ microsecond=1,
+ years=1,
+ months=1,
+ days=1,
+ weeks=1,
+ hours=1,
+ minutes=1,
+ seconds=1,
+ microseconds=1
+ )
+
+ expected = relativedelta(
+ year=1,
+ month=1,
+ day=1,
+ hour=1,
+ minute=1,
+ second=1,
+ microsecond=1,
+ years=1,
+ months=1,
+ weeks=2,
+ days=2,
+ hours=2,
+ minutes=2,
+ seconds=2,
+ microseconds=1002,
+ )
+
+ self.assertEqual(expected, rd + td)
+
+ def testHashable(self):
+ try:
+ {relativedelta(minute=1): 'test'}
+ except:
+ self.fail("relativedelta() failed to hash!")
+
+ def testDayOfMonthPlus(self):
+ assert [
+ date(2021, 1, 28) + relativedelta(months=1),
+ date(2021, 2, 27) + relativedelta(months=1),
+ date(2021, 4, 29) + relativedelta(months=1),
+ date(2021, 5, 30) + relativedelta(months=1),
+ ] == [
+ date(2021, 2, 28),
+ date(2021, 3, 27),
+ date(2021, 5, 29),
+ date(2021, 6, 30),
+ ]
+
+ def testLastDayOfMonthPlus(self):
+ assert [
+ date(2021, 1, 31) + relativedelta(months=1),
+ date(2021, 1, 30) + relativedelta(months=1),
+ date(2021, 1, 29) + relativedelta(months=1),
+ date(2021, 1, 28) + relativedelta(months=1),
+ date(2021, 2, 28) + relativedelta(months=1),
+ date(2021, 4, 30) + relativedelta(months=1),
+ date(2021, 5, 31) + relativedelta(months=1),
+ ] == [
+ date(2021, 2, 28),
+ date(2021, 2, 28),
+ date(2021, 2, 28),
+ date(2021, 2, 28),
+ date(2021, 3, 28),
+ date(2021, 5, 30),
+ date(2021, 6, 30),
+ ]
+
+ def testDayOfMonthMinus(self):
+ assert [
+ date(2021, 2, 27) - relativedelta(months=1),
+ date(2021, 3, 30) - relativedelta(months=1),
+ date(2021, 3, 29) - relativedelta(months=1),
+ date(2021, 3, 28) - relativedelta(months=1),
+ date(2021, 5, 30) - relativedelta(months=1),
+ date(2021, 6, 29) - relativedelta(months=1),
+ ] == [
+ date(2021, 1, 27),
+ date(2021, 2, 28),
+ date(2021, 2, 28),
+ date(2021, 2, 28),
+ date(2021, 4, 30),
+ date(2021, 5, 29),
+ ]
+
+ def testLastDayOfMonthMinus(self):
+ assert [
+ date(2021, 2, 28) - relativedelta(months=1),
+ date(2021, 3, 31) - relativedelta(months=1),
+ date(2021, 5, 31) - relativedelta(months=1),
+ date(2021, 6, 30) - relativedelta(months=1),
+ ] == [
+ date(2021, 1, 28),
+ date(2021, 2, 28),
+ date(2021, 4, 30),
+ date(2021, 5, 30),
+ ]
+
+class RelativeDeltaWeeksPropertyGetterTest(unittest.TestCase):
+ """Test the weeks property getter"""
+
+ def test_one_day(self):
+ rd = relativedelta(days=1)
+ self.assertEqual(rd.days, 1)
+ self.assertEqual(rd.weeks, 0)
+
+ def test_minus_one_day(self):
+ rd = relativedelta(days=-1)
+ self.assertEqual(rd.days, -1)
+ self.assertEqual(rd.weeks, 0)
+
+ def test_height_days(self):
+ rd = relativedelta(days=8)
+ self.assertEqual(rd.days, 8)
+ self.assertEqual(rd.weeks, 1)
+
+ def test_minus_height_days(self):
+ rd = relativedelta(days=-8)
+ self.assertEqual(rd.days, -8)
+ self.assertEqual(rd.weeks, -1)
+
+
+class RelativeDeltaWeeksPropertySetterTest(unittest.TestCase):
+ """Test the weeks setter which makes a "smart" update of the days attribute"""
+
+ def test_one_day_set_one_week(self):
+ rd = relativedelta(days=1)
+ rd.weeks = 1 # add 7 days
+ self.assertEqual(rd.days, 8)
+ self.assertEqual(rd.weeks, 1)
+
+ def test_minus_one_day_set_one_week(self):
+ rd = relativedelta(days=-1)
+ rd.weeks = 1 # add 7 days
+ self.assertEqual(rd.days, 6)
+ self.assertEqual(rd.weeks, 0)
+
+ def test_height_days_set_minus_one_week(self):
+ rd = relativedelta(days=8)
+ rd.weeks = -1 # change from 1 week, 1 day to -1 week, 1 day
+ self.assertEqual(rd.days, -6)
+ self.assertEqual(rd.weeks, 0)
+
+ def test_minus_height_days_set_minus_one_week(self):
+ rd = relativedelta(days=-8)
+ rd.weeks = -1 # does not change anything
+ self.assertEqual(rd.days, -8)
+ self.assertEqual(rd.weeks, -1)
+
+
+# vim:ts=4:sw=4:et
diff --git a/contrib/python/python-dateutil/py2/tests/test_rrule.py b/contrib/python/python-dateutil/py2/tests/test_rrule.py
new file mode 100644
index 0000000000..52673ecc26
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_rrule.py
@@ -0,0 +1,4914 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from datetime import datetime, date
+import unittest
+from six import PY2
+
+from dateutil import tz
+from dateutil.rrule import (
+ rrule, rruleset, rrulestr,
+ YEARLY, MONTHLY, WEEKLY, DAILY,
+ HOURLY, MINUTELY, SECONDLY,
+ MO, TU, WE, TH, FR, SA, SU
+)
+
+from freezegun import freeze_time
+
+import pytest
+
+
+@pytest.mark.rrule
+class RRuleTest(unittest.TestCase):
+ def _rrulestr_reverse_test(self, rule):
+ """
+ Call with an `rrule` and it will test that `str(rrule)` generates a
+ string which generates the same `rrule` as the input when passed to
+ `rrulestr()`
+ """
+ rr_str = str(rule)
+ rrulestr_rrule = rrulestr(rr_str)
+
+ self.assertEqual(list(rule), list(rrulestr_rrule))
+
+ def testStrAppendRRULEToken(self):
+ # `_rrulestr_reverse_test` does not check if the "RRULE:" prefix
+ # property is appended properly, so give it a dedicated test
+ self.assertEqual(str(rrule(YEARLY,
+ count=5,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=5")
+
+ rr_str = (
+ 'DTSTART:19970105T083000\nRRULE:FREQ=YEARLY;INTERVAL=2'
+ )
+ self.assertEqual(str(rrulestr(rr_str)), rr_str)
+
+ def testYearly(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1998, 9, 2, 9, 0),
+ datetime(1999, 9, 2, 9, 0)])
+
+ def testYearlyInterval(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1999, 9, 2, 9, 0),
+ datetime(2001, 9, 2, 9, 0)])
+
+ def testYearlyIntervalLarge(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ interval=100,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(2097, 9, 2, 9, 0),
+ datetime(2197, 9, 2, 9, 0)])
+
+ def testYearlyByMonth(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 2, 9, 0),
+ datetime(1998, 3, 2, 9, 0),
+ datetime(1999, 1, 2, 9, 0)])
+
+ def testYearlyByMonthDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 10, 1, 9, 0),
+ datetime(1997, 10, 3, 9, 0)])
+
+ def testYearlyByMonthAndMonthDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 5, 9, 0),
+ datetime(1998, 1, 7, 9, 0),
+ datetime(1998, 3, 5, 9, 0)])
+
+ def testYearlyByWeekDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testYearlyByNWeekDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 25, 9, 0),
+ datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 12, 31, 9, 0)])
+
+ def testYearlyByNWeekDayLarge(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byweekday=(TU(3), TH(-3)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 11, 9, 0),
+ datetime(1998, 1, 20, 9, 0),
+ datetime(1998, 12, 17, 9, 0)])
+
+ def testYearlyByMonthAndWeekDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 1, 8, 9, 0)])
+
+ def testYearlyByMonthAndNWeekDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 1, 29, 9, 0),
+ datetime(1998, 3, 3, 9, 0)])
+
+ def testYearlyByMonthAndNWeekDayLarge(self):
+ # This is interesting because the TH(-3) ends up before
+ # the TU(3).
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(3), TH(-3)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 15, 9, 0),
+ datetime(1998, 1, 20, 9, 0),
+ datetime(1998, 3, 12, 9, 0)])
+
+ def testYearlyByMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 2, 3, 9, 0),
+ datetime(1998, 3, 3, 9, 0)])
+
+ def testYearlyByMonthAndMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 3, 3, 9, 0),
+ datetime(2001, 3, 1, 9, 0)])
+
+ def testYearlyByYearDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 9, 0),
+ datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0)])
+
+ def testYearlyByYearDayNeg(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 9, 0),
+ datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0)])
+
+ def testYearlyByMonthAndYearDay(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0),
+ datetime(1999, 4, 10, 9, 0),
+ datetime(1999, 7, 19, 9, 0)])
+
+ def testYearlyByMonthAndYearDayNeg(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0),
+ datetime(1999, 4, 10, 9, 0),
+ datetime(1999, 7, 19, 9, 0)])
+
+ def testYearlyByWeekNo(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 5, 11, 9, 0),
+ datetime(1998, 5, 12, 9, 0),
+ datetime(1998, 5, 13, 9, 0)])
+
+ def testYearlyByWeekNoAndWeekDay(self):
+ # That's a nice one. The first days of week number one
+ # may be in the last year.
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 29, 9, 0),
+ datetime(1999, 1, 4, 9, 0),
+ datetime(2000, 1, 3, 9, 0)])
+
+ def testYearlyByWeekNoAndWeekDayLarge(self):
+ # Another nice test. The last days of week number 52/53
+ # may be in the next year.
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 9, 0),
+ datetime(1998, 12, 27, 9, 0),
+ datetime(2000, 1, 2, 9, 0)])
+
+ def testYearlyByWeekNoAndWeekDayLast(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 9, 0),
+ datetime(1999, 1, 3, 9, 0),
+ datetime(2000, 1, 2, 9, 0)])
+
+ def testYearlyByEaster(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 12, 9, 0),
+ datetime(1999, 4, 4, 9, 0),
+ datetime(2000, 4, 23, 9, 0)])
+
+ def testYearlyByEasterPos(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 13, 9, 0),
+ datetime(1999, 4, 5, 9, 0),
+ datetime(2000, 4, 24, 9, 0)])
+
+ def testYearlyByEasterNeg(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 11, 9, 0),
+ datetime(1999, 4, 3, 9, 0),
+ datetime(2000, 4, 22, 9, 0)])
+
+ def testYearlyByWeekNoAndWeekDay53(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 12, 28, 9, 0),
+ datetime(2004, 12, 27, 9, 0),
+ datetime(2009, 12, 28, 9, 0)])
+
+ def testYearlyByHour(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0),
+ datetime(1998, 9, 2, 6, 0),
+ datetime(1998, 9, 2, 18, 0)])
+
+ def testYearlyByMinute(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6),
+ datetime(1997, 9, 2, 9, 18),
+ datetime(1998, 9, 2, 9, 6)])
+
+ def testYearlyBySecond(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 6),
+ datetime(1997, 9, 2, 9, 0, 18),
+ datetime(1998, 9, 2, 9, 0, 6)])
+
+ def testYearlyByHourAndMinute(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6),
+ datetime(1997, 9, 2, 18, 18),
+ datetime(1998, 9, 2, 6, 6)])
+
+ def testYearlyByHourAndSecond(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0, 6),
+ datetime(1997, 9, 2, 18, 0, 18),
+ datetime(1998, 9, 2, 6, 0, 6)])
+
+ def testYearlyByMinuteAndSecond(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6, 6),
+ datetime(1997, 9, 2, 9, 6, 18),
+ datetime(1997, 9, 2, 9, 18, 6)])
+
+ def testYearlyByHourAndMinuteAndSecond(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6, 6),
+ datetime(1997, 9, 2, 18, 6, 18),
+ datetime(1997, 9, 2, 18, 18, 6)])
+
+ def testYearlyBySetPos(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonthday=15,
+ byhour=(6, 18),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 11, 15, 18, 0),
+ datetime(1998, 2, 15, 6, 0),
+ datetime(1998, 11, 15, 18, 0)])
+
+ def testMonthly(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 10, 2, 9, 0),
+ datetime(1997, 11, 2, 9, 0)])
+
+ def testMonthlyInterval(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 11, 2, 9, 0),
+ datetime(1998, 1, 2, 9, 0)])
+
+ def testMonthlyIntervalLarge(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ interval=18,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1999, 3, 2, 9, 0),
+ datetime(2000, 9, 2, 9, 0)])
+
+ def testMonthlyByMonth(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 2, 9, 0),
+ datetime(1998, 3, 2, 9, 0),
+ datetime(1999, 1, 2, 9, 0)])
+
+ def testMonthlyByMonthDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 10, 1, 9, 0),
+ datetime(1997, 10, 3, 9, 0)])
+
+ def testMonthlyByMonthAndMonthDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 5, 9, 0),
+ datetime(1998, 1, 7, 9, 0),
+ datetime(1998, 3, 5, 9, 0)])
+
+ def testMonthlyByWeekDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ # Third Monday of the month
+ self.assertEqual(rrule(MONTHLY,
+ byweekday=(MO(+3)),
+ dtstart=datetime(1997, 9, 1)).between(datetime(1997, 9, 1),
+ datetime(1997, 12, 1)),
+ [datetime(1997, 9, 15, 0, 0),
+ datetime(1997, 10, 20, 0, 0),
+ datetime(1997, 11, 17, 0, 0)])
+
+ def testMonthlyByNWeekDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 25, 9, 0),
+ datetime(1997, 10, 7, 9, 0)])
+
+ def testMonthlyByNWeekDayLarge(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byweekday=(TU(3), TH(-3)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 11, 9, 0),
+ datetime(1997, 9, 16, 9, 0),
+ datetime(1997, 10, 16, 9, 0)])
+
+ def testMonthlyByMonthAndWeekDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 1, 8, 9, 0)])
+
+ def testMonthlyByMonthAndNWeekDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 1, 29, 9, 0),
+ datetime(1998, 3, 3, 9, 0)])
+
+ def testMonthlyByMonthAndNWeekDayLarge(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(3), TH(-3)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 15, 9, 0),
+ datetime(1998, 1, 20, 9, 0),
+ datetime(1998, 3, 12, 9, 0)])
+
+ def testMonthlyByMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 2, 3, 9, 0),
+ datetime(1998, 3, 3, 9, 0)])
+
+ def testMonthlyByMonthAndMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 3, 3, 9, 0),
+ datetime(2001, 3, 1, 9, 0)])
+
+ def testMonthlyByYearDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 9, 0),
+ datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0)])
+
+ def testMonthlyByYearDayNeg(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 9, 0),
+ datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0)])
+
+ def testMonthlyByMonthAndYearDay(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0),
+ datetime(1999, 4, 10, 9, 0),
+ datetime(1999, 7, 19, 9, 0)])
+
+ def testMonthlyByMonthAndYearDayNeg(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0),
+ datetime(1999, 4, 10, 9, 0),
+ datetime(1999, 7, 19, 9, 0)])
+
+ def testMonthlyByWeekNo(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 5, 11, 9, 0),
+ datetime(1998, 5, 12, 9, 0),
+ datetime(1998, 5, 13, 9, 0)])
+
+ def testMonthlyByWeekNoAndWeekDay(self):
+ # That's a nice one. The first days of week number one
+ # may be in the last year.
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 29, 9, 0),
+ datetime(1999, 1, 4, 9, 0),
+ datetime(2000, 1, 3, 9, 0)])
+
+ def testMonthlyByWeekNoAndWeekDayLarge(self):
+ # Another nice test. The last days of week number 52/53
+ # may be in the next year.
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 9, 0),
+ datetime(1998, 12, 27, 9, 0),
+ datetime(2000, 1, 2, 9, 0)])
+
+ def testMonthlyByWeekNoAndWeekDayLast(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 9, 0),
+ datetime(1999, 1, 3, 9, 0),
+ datetime(2000, 1, 2, 9, 0)])
+
+ def testMonthlyByWeekNoAndWeekDay53(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 12, 28, 9, 0),
+ datetime(2004, 12, 27, 9, 0),
+ datetime(2009, 12, 28, 9, 0)])
+
+ def testMonthlyByEaster(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 12, 9, 0),
+ datetime(1999, 4, 4, 9, 0),
+ datetime(2000, 4, 23, 9, 0)])
+
+ def testMonthlyByEasterPos(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 13, 9, 0),
+ datetime(1999, 4, 5, 9, 0),
+ datetime(2000, 4, 24, 9, 0)])
+
+ def testMonthlyByEasterNeg(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 11, 9, 0),
+ datetime(1999, 4, 3, 9, 0),
+ datetime(2000, 4, 22, 9, 0)])
+
+ def testMonthlyByHour(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0),
+ datetime(1997, 10, 2, 6, 0),
+ datetime(1997, 10, 2, 18, 0)])
+
+ def testMonthlyByMinute(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6),
+ datetime(1997, 9, 2, 9, 18),
+ datetime(1997, 10, 2, 9, 6)])
+
+ def testMonthlyBySecond(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 6),
+ datetime(1997, 9, 2, 9, 0, 18),
+ datetime(1997, 10, 2, 9, 0, 6)])
+
+ def testMonthlyByHourAndMinute(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6),
+ datetime(1997, 9, 2, 18, 18),
+ datetime(1997, 10, 2, 6, 6)])
+
+ def testMonthlyByHourAndSecond(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0, 6),
+ datetime(1997, 9, 2, 18, 0, 18),
+ datetime(1997, 10, 2, 6, 0, 6)])
+
+ def testMonthlyByMinuteAndSecond(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6, 6),
+ datetime(1997, 9, 2, 9, 6, 18),
+ datetime(1997, 9, 2, 9, 18, 6)])
+
+ def testMonthlyByHourAndMinuteAndSecond(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6, 6),
+ datetime(1997, 9, 2, 18, 6, 18),
+ datetime(1997, 9, 2, 18, 18, 6)])
+
+ def testMonthlyBySetPos(self):
+ self.assertEqual(list(rrule(MONTHLY,
+ count=3,
+ bymonthday=(13, 17),
+ byhour=(6, 18),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 13, 18, 0),
+ datetime(1997, 9, 17, 6, 0),
+ datetime(1997, 10, 13, 18, 0)])
+
+ def testWeekly(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testWeeklyInterval(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 16, 9, 0),
+ datetime(1997, 9, 30, 9, 0)])
+
+ def testWeeklyIntervalLarge(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ interval=20,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1998, 1, 20, 9, 0),
+ datetime(1998, 6, 9, 9, 0)])
+
+ def testWeeklyByMonth(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 1, 13, 9, 0),
+ datetime(1998, 1, 20, 9, 0)])
+
+ def testWeeklyByMonthDay(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 10, 1, 9, 0),
+ datetime(1997, 10, 3, 9, 0)])
+
+ def testWeeklyByMonthAndMonthDay(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 5, 9, 0),
+ datetime(1998, 1, 7, 9, 0),
+ datetime(1998, 3, 5, 9, 0)])
+
+ def testWeeklyByWeekDay(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testWeeklyByNWeekDay(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testWeeklyByMonthAndWeekDay(self):
+ # This test is interesting, because it crosses the year
+ # boundary in a weekly period to find day '1' as a
+ # valid recurrence.
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 1, 8, 9, 0)])
+
+ def testWeeklyByMonthAndNWeekDay(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 1, 8, 9, 0)])
+
+ def testWeeklyByMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 2, 3, 9, 0),
+ datetime(1998, 3, 3, 9, 0)])
+
+ def testWeeklyByMonthAndMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 3, 3, 9, 0),
+ datetime(2001, 3, 1, 9, 0)])
+
+ def testWeeklyByYearDay(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 9, 0),
+ datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0)])
+
+ def testWeeklyByYearDayNeg(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 9, 0),
+ datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0)])
+
+ def testWeeklyByMonthAndYearDay(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=4,
+ bymonth=(1, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 7, 19, 9, 0),
+ datetime(1999, 1, 1, 9, 0),
+ datetime(1999, 7, 19, 9, 0)])
+
+ def testWeeklyByMonthAndYearDayNeg(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=4,
+ bymonth=(1, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 7, 19, 9, 0),
+ datetime(1999, 1, 1, 9, 0),
+ datetime(1999, 7, 19, 9, 0)])
+
+ def testWeeklyByWeekNo(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 5, 11, 9, 0),
+ datetime(1998, 5, 12, 9, 0),
+ datetime(1998, 5, 13, 9, 0)])
+
+ def testWeeklyByWeekNoAndWeekDay(self):
+ # That's a nice one. The first days of week number one
+ # may be in the last year.
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 29, 9, 0),
+ datetime(1999, 1, 4, 9, 0),
+ datetime(2000, 1, 3, 9, 0)])
+
+ def testWeeklyByWeekNoAndWeekDayLarge(self):
+ # Another nice test. The last days of week number 52/53
+ # may be in the next year.
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 9, 0),
+ datetime(1998, 12, 27, 9, 0),
+ datetime(2000, 1, 2, 9, 0)])
+
+ def testWeeklyByWeekNoAndWeekDayLast(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 9, 0),
+ datetime(1999, 1, 3, 9, 0),
+ datetime(2000, 1, 2, 9, 0)])
+
+ def testWeeklyByWeekNoAndWeekDay53(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 12, 28, 9, 0),
+ datetime(2004, 12, 27, 9, 0),
+ datetime(2009, 12, 28, 9, 0)])
+
+ def testWeeklyByEaster(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 12, 9, 0),
+ datetime(1999, 4, 4, 9, 0),
+ datetime(2000, 4, 23, 9, 0)])
+
+ def testWeeklyByEasterPos(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 13, 9, 0),
+ datetime(1999, 4, 5, 9, 0),
+ datetime(2000, 4, 24, 9, 0)])
+
+ def testWeeklyByEasterNeg(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 11, 9, 0),
+ datetime(1999, 4, 3, 9, 0),
+ datetime(2000, 4, 22, 9, 0)])
+
+ def testWeeklyByHour(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0),
+ datetime(1997, 9, 9, 6, 0),
+ datetime(1997, 9, 9, 18, 0)])
+
+ def testWeeklyByMinute(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6),
+ datetime(1997, 9, 2, 9, 18),
+ datetime(1997, 9, 9, 9, 6)])
+
+ def testWeeklyBySecond(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 6),
+ datetime(1997, 9, 2, 9, 0, 18),
+ datetime(1997, 9, 9, 9, 0, 6)])
+
+ def testWeeklyByHourAndMinute(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6),
+ datetime(1997, 9, 2, 18, 18),
+ datetime(1997, 9, 9, 6, 6)])
+
+ def testWeeklyByHourAndSecond(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0, 6),
+ datetime(1997, 9, 2, 18, 0, 18),
+ datetime(1997, 9, 9, 6, 0, 6)])
+
+ def testWeeklyByMinuteAndSecond(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6, 6),
+ datetime(1997, 9, 2, 9, 6, 18),
+ datetime(1997, 9, 2, 9, 18, 6)])
+
+ def testWeeklyByHourAndMinuteAndSecond(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6, 6),
+ datetime(1997, 9, 2, 18, 6, 18),
+ datetime(1997, 9, 2, 18, 18, 6)])
+
+ def testWeeklyBySetPos(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ byweekday=(TU, TH),
+ byhour=(6, 18),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0),
+ datetime(1997, 9, 4, 6, 0),
+ datetime(1997, 9, 9, 18, 0)])
+
+ def testDaily(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0)])
+
+ def testDailyInterval(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 6, 9, 0)])
+
+ def testDailyIntervalLarge(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ interval=92,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 12, 3, 9, 0),
+ datetime(1998, 3, 5, 9, 0)])
+
+ def testDailyByMonth(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 1, 2, 9, 0),
+ datetime(1998, 1, 3, 9, 0)])
+
+ def testDailyByMonthDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 10, 1, 9, 0),
+ datetime(1997, 10, 3, 9, 0)])
+
+ def testDailyByMonthAndMonthDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 5, 9, 0),
+ datetime(1998, 1, 7, 9, 0),
+ datetime(1998, 3, 5, 9, 0)])
+
+ def testDailyByWeekDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testDailyByNWeekDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testDailyByMonthAndWeekDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 1, 8, 9, 0)])
+
+ def testDailyByMonthAndNWeekDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 1, 8, 9, 0)])
+
+ def testDailyByMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 2, 3, 9, 0),
+ datetime(1998, 3, 3, 9, 0)])
+
+ def testDailyByMonthAndMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 3, 3, 9, 0),
+ datetime(2001, 3, 1, 9, 0)])
+
+ def testDailyByYearDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 9, 0),
+ datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0)])
+
+ def testDailyByYearDayNeg(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 9, 0),
+ datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 4, 10, 9, 0),
+ datetime(1998, 7, 19, 9, 0)])
+
+ def testDailyByMonthAndYearDay(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=4,
+ bymonth=(1, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 7, 19, 9, 0),
+ datetime(1999, 1, 1, 9, 0),
+ datetime(1999, 7, 19, 9, 0)])
+
+ def testDailyByMonthAndYearDayNeg(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=4,
+ bymonth=(1, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 9, 0),
+ datetime(1998, 7, 19, 9, 0),
+ datetime(1999, 1, 1, 9, 0),
+ datetime(1999, 7, 19, 9, 0)])
+
+ def testDailyByWeekNo(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 5, 11, 9, 0),
+ datetime(1998, 5, 12, 9, 0),
+ datetime(1998, 5, 13, 9, 0)])
+
+ def testDailyByWeekNoAndWeekDay(self):
+ # That's a nice one. The first days of week number one
+ # may be in the last year.
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 29, 9, 0),
+ datetime(1999, 1, 4, 9, 0),
+ datetime(2000, 1, 3, 9, 0)])
+
+ def testDailyByWeekNoAndWeekDayLarge(self):
+ # Another nice test. The last days of week number 52/53
+ # may be in the next year.
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 9, 0),
+ datetime(1998, 12, 27, 9, 0),
+ datetime(2000, 1, 2, 9, 0)])
+
+ def testDailyByWeekNoAndWeekDayLast(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 9, 0),
+ datetime(1999, 1, 3, 9, 0),
+ datetime(2000, 1, 2, 9, 0)])
+
+ def testDailyByWeekNoAndWeekDay53(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 12, 28, 9, 0),
+ datetime(2004, 12, 27, 9, 0),
+ datetime(2009, 12, 28, 9, 0)])
+
+ def testDailyByEaster(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 12, 9, 0),
+ datetime(1999, 4, 4, 9, 0),
+ datetime(2000, 4, 23, 9, 0)])
+
+ def testDailyByEasterPos(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 13, 9, 0),
+ datetime(1999, 4, 5, 9, 0),
+ datetime(2000, 4, 24, 9, 0)])
+
+ def testDailyByEasterNeg(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 11, 9, 0),
+ datetime(1999, 4, 3, 9, 0),
+ datetime(2000, 4, 22, 9, 0)])
+
+ def testDailyByHour(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0),
+ datetime(1997, 9, 3, 6, 0),
+ datetime(1997, 9, 3, 18, 0)])
+
+ def testDailyByMinute(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6),
+ datetime(1997, 9, 2, 9, 18),
+ datetime(1997, 9, 3, 9, 6)])
+
+ def testDailyBySecond(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 6),
+ datetime(1997, 9, 2, 9, 0, 18),
+ datetime(1997, 9, 3, 9, 0, 6)])
+
+ def testDailyByHourAndMinute(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6),
+ datetime(1997, 9, 2, 18, 18),
+ datetime(1997, 9, 3, 6, 6)])
+
+ def testDailyByHourAndSecond(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0, 6),
+ datetime(1997, 9, 2, 18, 0, 18),
+ datetime(1997, 9, 3, 6, 0, 6)])
+
+ def testDailyByMinuteAndSecond(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6, 6),
+ datetime(1997, 9, 2, 9, 6, 18),
+ datetime(1997, 9, 2, 9, 18, 6)])
+
+ def testDailyByHourAndMinuteAndSecond(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6, 6),
+ datetime(1997, 9, 2, 18, 6, 18),
+ datetime(1997, 9, 2, 18, 18, 6)])
+
+ def testDailyBySetPos(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(15, 45),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 15),
+ datetime(1997, 9, 3, 6, 45),
+ datetime(1997, 9, 3, 18, 15)])
+
+ def testHourly(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 2, 10, 0),
+ datetime(1997, 9, 2, 11, 0)])
+
+ def testHourlyInterval(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 2, 11, 0),
+ datetime(1997, 9, 2, 13, 0)])
+
+ def testHourlyIntervalLarge(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ interval=769,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 10, 4, 10, 0),
+ datetime(1997, 11, 5, 11, 0)])
+
+ def testHourlyByMonth(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 1, 0),
+ datetime(1998, 1, 1, 2, 0)])
+
+ def testHourlyByMonthDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 3, 0, 0),
+ datetime(1997, 9, 3, 1, 0),
+ datetime(1997, 9, 3, 2, 0)])
+
+ def testHourlyByMonthAndMonthDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 5, 0, 0),
+ datetime(1998, 1, 5, 1, 0),
+ datetime(1998, 1, 5, 2, 0)])
+
+ def testHourlyByWeekDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 2, 10, 0),
+ datetime(1997, 9, 2, 11, 0)])
+
+ def testHourlyByNWeekDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 2, 10, 0),
+ datetime(1997, 9, 2, 11, 0)])
+
+ def testHourlyByMonthAndWeekDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 1, 0),
+ datetime(1998, 1, 1, 2, 0)])
+
+ def testHourlyByMonthAndNWeekDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 1, 0),
+ datetime(1998, 1, 1, 2, 0)])
+
+ def testHourlyByMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 1, 0),
+ datetime(1998, 1, 1, 2, 0)])
+
+ def testHourlyByMonthAndMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 1, 0),
+ datetime(1998, 1, 1, 2, 0)])
+
+ def testHourlyByYearDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 0, 0),
+ datetime(1997, 12, 31, 1, 0),
+ datetime(1997, 12, 31, 2, 0),
+ datetime(1997, 12, 31, 3, 0)])
+
+ def testHourlyByYearDayNeg(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 0, 0),
+ datetime(1997, 12, 31, 1, 0),
+ datetime(1997, 12, 31, 2, 0),
+ datetime(1997, 12, 31, 3, 0)])
+
+ def testHourlyByMonthAndYearDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 0, 0),
+ datetime(1998, 4, 10, 1, 0),
+ datetime(1998, 4, 10, 2, 0),
+ datetime(1998, 4, 10, 3, 0)])
+
+ def testHourlyByMonthAndYearDayNeg(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 0, 0),
+ datetime(1998, 4, 10, 1, 0),
+ datetime(1998, 4, 10, 2, 0),
+ datetime(1998, 4, 10, 3, 0)])
+
+ def testHourlyByWeekNo(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 5, 11, 0, 0),
+ datetime(1998, 5, 11, 1, 0),
+ datetime(1998, 5, 11, 2, 0)])
+
+ def testHourlyByWeekNoAndWeekDay(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 29, 0, 0),
+ datetime(1997, 12, 29, 1, 0),
+ datetime(1997, 12, 29, 2, 0)])
+
+ def testHourlyByWeekNoAndWeekDayLarge(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 0, 0),
+ datetime(1997, 12, 28, 1, 0),
+ datetime(1997, 12, 28, 2, 0)])
+
+ def testHourlyByWeekNoAndWeekDayLast(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 0, 0),
+ datetime(1997, 12, 28, 1, 0),
+ datetime(1997, 12, 28, 2, 0)])
+
+ def testHourlyByWeekNoAndWeekDay53(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 12, 28, 0, 0),
+ datetime(1998, 12, 28, 1, 0),
+ datetime(1998, 12, 28, 2, 0)])
+
+ def testHourlyByEaster(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 12, 0, 0),
+ datetime(1998, 4, 12, 1, 0),
+ datetime(1998, 4, 12, 2, 0)])
+
+ def testHourlyByEasterPos(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 13, 0, 0),
+ datetime(1998, 4, 13, 1, 0),
+ datetime(1998, 4, 13, 2, 0)])
+
+ def testHourlyByEasterNeg(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 11, 0, 0),
+ datetime(1998, 4, 11, 1, 0),
+ datetime(1998, 4, 11, 2, 0)])
+
+ def testHourlyByHour(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0),
+ datetime(1997, 9, 3, 6, 0),
+ datetime(1997, 9, 3, 18, 0)])
+
+ def testHourlyByMinute(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6),
+ datetime(1997, 9, 2, 9, 18),
+ datetime(1997, 9, 2, 10, 6)])
+
+ def testHourlyBySecond(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 6),
+ datetime(1997, 9, 2, 9, 0, 18),
+ datetime(1997, 9, 2, 10, 0, 6)])
+
+ def testHourlyByHourAndMinute(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6),
+ datetime(1997, 9, 2, 18, 18),
+ datetime(1997, 9, 3, 6, 6)])
+
+ def testHourlyByHourAndSecond(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0, 6),
+ datetime(1997, 9, 2, 18, 0, 18),
+ datetime(1997, 9, 3, 6, 0, 6)])
+
+ def testHourlyByMinuteAndSecond(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6, 6),
+ datetime(1997, 9, 2, 9, 6, 18),
+ datetime(1997, 9, 2, 9, 18, 6)])
+
+ def testHourlyByHourAndMinuteAndSecond(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6, 6),
+ datetime(1997, 9, 2, 18, 6, 18),
+ datetime(1997, 9, 2, 18, 18, 6)])
+
+ def testHourlyBySetPos(self):
+ self.assertEqual(list(rrule(HOURLY,
+ count=3,
+ byminute=(15, 45),
+ bysecond=(15, 45),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 15, 45),
+ datetime(1997, 9, 2, 9, 45, 15),
+ datetime(1997, 9, 2, 10, 15, 45)])
+
+ def testMinutely(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 2, 9, 1),
+ datetime(1997, 9, 2, 9, 2)])
+
+ def testMinutelyInterval(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 2, 9, 2),
+ datetime(1997, 9, 2, 9, 4)])
+
+ def testMinutelyIntervalLarge(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ interval=1501,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 10, 1),
+ datetime(1997, 9, 4, 11, 2)])
+
+ def testMinutelyByMonth(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 0, 1),
+ datetime(1998, 1, 1, 0, 2)])
+
+ def testMinutelyByMonthDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 3, 0, 0),
+ datetime(1997, 9, 3, 0, 1),
+ datetime(1997, 9, 3, 0, 2)])
+
+ def testMinutelyByMonthAndMonthDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 5, 0, 0),
+ datetime(1998, 1, 5, 0, 1),
+ datetime(1998, 1, 5, 0, 2)])
+
+ def testMinutelyByWeekDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 2, 9, 1),
+ datetime(1997, 9, 2, 9, 2)])
+
+ def testMinutelyByNWeekDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 2, 9, 1),
+ datetime(1997, 9, 2, 9, 2)])
+
+ def testMinutelyByMonthAndWeekDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 0, 1),
+ datetime(1998, 1, 1, 0, 2)])
+
+ def testMinutelyByMonthAndNWeekDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 0, 1),
+ datetime(1998, 1, 1, 0, 2)])
+
+ def testMinutelyByMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 0, 1),
+ datetime(1998, 1, 1, 0, 2)])
+
+ def testMinutelyByMonthAndMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0),
+ datetime(1998, 1, 1, 0, 1),
+ datetime(1998, 1, 1, 0, 2)])
+
+ def testMinutelyByYearDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 0, 0),
+ datetime(1997, 12, 31, 0, 1),
+ datetime(1997, 12, 31, 0, 2),
+ datetime(1997, 12, 31, 0, 3)])
+
+ def testMinutelyByYearDayNeg(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 0, 0),
+ datetime(1997, 12, 31, 0, 1),
+ datetime(1997, 12, 31, 0, 2),
+ datetime(1997, 12, 31, 0, 3)])
+
+ def testMinutelyByMonthAndYearDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 0, 0),
+ datetime(1998, 4, 10, 0, 1),
+ datetime(1998, 4, 10, 0, 2),
+ datetime(1998, 4, 10, 0, 3)])
+
+ def testMinutelyByMonthAndYearDayNeg(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 0, 0),
+ datetime(1998, 4, 10, 0, 1),
+ datetime(1998, 4, 10, 0, 2),
+ datetime(1998, 4, 10, 0, 3)])
+
+ def testMinutelyByWeekNo(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 5, 11, 0, 0),
+ datetime(1998, 5, 11, 0, 1),
+ datetime(1998, 5, 11, 0, 2)])
+
+ def testMinutelyByWeekNoAndWeekDay(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 29, 0, 0),
+ datetime(1997, 12, 29, 0, 1),
+ datetime(1997, 12, 29, 0, 2)])
+
+ def testMinutelyByWeekNoAndWeekDayLarge(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 0, 0),
+ datetime(1997, 12, 28, 0, 1),
+ datetime(1997, 12, 28, 0, 2)])
+
+ def testMinutelyByWeekNoAndWeekDayLast(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 0, 0),
+ datetime(1997, 12, 28, 0, 1),
+ datetime(1997, 12, 28, 0, 2)])
+
+ def testMinutelyByWeekNoAndWeekDay53(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 12, 28, 0, 0),
+ datetime(1998, 12, 28, 0, 1),
+ datetime(1998, 12, 28, 0, 2)])
+
+ def testMinutelyByEaster(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 12, 0, 0),
+ datetime(1998, 4, 12, 0, 1),
+ datetime(1998, 4, 12, 0, 2)])
+
+ def testMinutelyByEasterPos(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 13, 0, 0),
+ datetime(1998, 4, 13, 0, 1),
+ datetime(1998, 4, 13, 0, 2)])
+
+ def testMinutelyByEasterNeg(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 11, 0, 0),
+ datetime(1998, 4, 11, 0, 1),
+ datetime(1998, 4, 11, 0, 2)])
+
+ def testMinutelyByHour(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0),
+ datetime(1997, 9, 2, 18, 1),
+ datetime(1997, 9, 2, 18, 2)])
+
+ def testMinutelyByMinute(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6),
+ datetime(1997, 9, 2, 9, 18),
+ datetime(1997, 9, 2, 10, 6)])
+
+ def testMinutelyBySecond(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 6),
+ datetime(1997, 9, 2, 9, 0, 18),
+ datetime(1997, 9, 2, 9, 1, 6)])
+
+ def testMinutelyByHourAndMinute(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6),
+ datetime(1997, 9, 2, 18, 18),
+ datetime(1997, 9, 3, 6, 6)])
+
+ def testMinutelyByHourAndSecond(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0, 6),
+ datetime(1997, 9, 2, 18, 0, 18),
+ datetime(1997, 9, 2, 18, 1, 6)])
+
+ def testMinutelyByMinuteAndSecond(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6, 6),
+ datetime(1997, 9, 2, 9, 6, 18),
+ datetime(1997, 9, 2, 9, 18, 6)])
+
+ def testMinutelyByHourAndMinuteAndSecond(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6, 6),
+ datetime(1997, 9, 2, 18, 6, 18),
+ datetime(1997, 9, 2, 18, 18, 6)])
+
+ def testMinutelyBySetPos(self):
+ self.assertEqual(list(rrule(MINUTELY,
+ count=3,
+ bysecond=(15, 30, 45),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 15),
+ datetime(1997, 9, 2, 9, 0, 45),
+ datetime(1997, 9, 2, 9, 1, 15)])
+
+ def testSecondly(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 0),
+ datetime(1997, 9, 2, 9, 0, 1),
+ datetime(1997, 9, 2, 9, 0, 2)])
+
+ def testSecondlyInterval(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 0),
+ datetime(1997, 9, 2, 9, 0, 2),
+ datetime(1997, 9, 2, 9, 0, 4)])
+
+ def testSecondlyIntervalLarge(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ interval=90061,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 0),
+ datetime(1997, 9, 3, 10, 1, 1),
+ datetime(1997, 9, 4, 11, 2, 2)])
+
+ def testSecondlyByMonth(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0, 0),
+ datetime(1998, 1, 1, 0, 0, 1),
+ datetime(1998, 1, 1, 0, 0, 2)])
+
+ def testSecondlyByMonthDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 3, 0, 0, 0),
+ datetime(1997, 9, 3, 0, 0, 1),
+ datetime(1997, 9, 3, 0, 0, 2)])
+
+ def testSecondlyByMonthAndMonthDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 5, 0, 0, 0),
+ datetime(1998, 1, 5, 0, 0, 1),
+ datetime(1998, 1, 5, 0, 0, 2)])
+
+ def testSecondlyByWeekDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 0),
+ datetime(1997, 9, 2, 9, 0, 1),
+ datetime(1997, 9, 2, 9, 0, 2)])
+
+ def testSecondlyByNWeekDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 0),
+ datetime(1997, 9, 2, 9, 0, 1),
+ datetime(1997, 9, 2, 9, 0, 2)])
+
+ def testSecondlyByMonthAndWeekDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0, 0),
+ datetime(1998, 1, 1, 0, 0, 1),
+ datetime(1998, 1, 1, 0, 0, 2)])
+
+ def testSecondlyByMonthAndNWeekDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0, 0),
+ datetime(1998, 1, 1, 0, 0, 1),
+ datetime(1998, 1, 1, 0, 0, 2)])
+
+ def testSecondlyByMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0, 0),
+ datetime(1998, 1, 1, 0, 0, 1),
+ datetime(1998, 1, 1, 0, 0, 2)])
+
+ def testSecondlyByMonthAndMonthDayAndWeekDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 1, 0, 0, 0),
+ datetime(1998, 1, 1, 0, 0, 1),
+ datetime(1998, 1, 1, 0, 0, 2)])
+
+ def testSecondlyByYearDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 0, 0, 0),
+ datetime(1997, 12, 31, 0, 0, 1),
+ datetime(1997, 12, 31, 0, 0, 2),
+ datetime(1997, 12, 31, 0, 0, 3)])
+
+ def testSecondlyByYearDayNeg(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 31, 0, 0, 0),
+ datetime(1997, 12, 31, 0, 0, 1),
+ datetime(1997, 12, 31, 0, 0, 2),
+ datetime(1997, 12, 31, 0, 0, 3)])
+
+ def testSecondlyByMonthAndYearDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 0, 0, 0),
+ datetime(1998, 4, 10, 0, 0, 1),
+ datetime(1998, 4, 10, 0, 0, 2),
+ datetime(1998, 4, 10, 0, 0, 3)])
+
+ def testSecondlyByMonthAndYearDayNeg(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 10, 0, 0, 0),
+ datetime(1998, 4, 10, 0, 0, 1),
+ datetime(1998, 4, 10, 0, 0, 2),
+ datetime(1998, 4, 10, 0, 0, 3)])
+
+ def testSecondlyByWeekNo(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 5, 11, 0, 0, 0),
+ datetime(1998, 5, 11, 0, 0, 1),
+ datetime(1998, 5, 11, 0, 0, 2)])
+
+ def testSecondlyByWeekNoAndWeekDay(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 29, 0, 0, 0),
+ datetime(1997, 12, 29, 0, 0, 1),
+ datetime(1997, 12, 29, 0, 0, 2)])
+
+ def testSecondlyByWeekNoAndWeekDayLarge(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 0, 0, 0),
+ datetime(1997, 12, 28, 0, 0, 1),
+ datetime(1997, 12, 28, 0, 0, 2)])
+
+ def testSecondlyByWeekNoAndWeekDayLast(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 12, 28, 0, 0, 0),
+ datetime(1997, 12, 28, 0, 0, 1),
+ datetime(1997, 12, 28, 0, 0, 2)])
+
+ def testSecondlyByWeekNoAndWeekDay53(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 12, 28, 0, 0, 0),
+ datetime(1998, 12, 28, 0, 0, 1),
+ datetime(1998, 12, 28, 0, 0, 2)])
+
+ def testSecondlyByEaster(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 12, 0, 0, 0),
+ datetime(1998, 4, 12, 0, 0, 1),
+ datetime(1998, 4, 12, 0, 0, 2)])
+
+ def testSecondlyByEasterPos(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 13, 0, 0, 0),
+ datetime(1998, 4, 13, 0, 0, 1),
+ datetime(1998, 4, 13, 0, 0, 2)])
+
+ def testSecondlyByEasterNeg(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 4, 11, 0, 0, 0),
+ datetime(1998, 4, 11, 0, 0, 1),
+ datetime(1998, 4, 11, 0, 0, 2)])
+
+ def testSecondlyByHour(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0, 0),
+ datetime(1997, 9, 2, 18, 0, 1),
+ datetime(1997, 9, 2, 18, 0, 2)])
+
+ def testSecondlyByMinute(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6, 0),
+ datetime(1997, 9, 2, 9, 6, 1),
+ datetime(1997, 9, 2, 9, 6, 2)])
+
+ def testSecondlyBySecond(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0, 6),
+ datetime(1997, 9, 2, 9, 0, 18),
+ datetime(1997, 9, 2, 9, 1, 6)])
+
+ def testSecondlyByHourAndMinute(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6, 0),
+ datetime(1997, 9, 2, 18, 6, 1),
+ datetime(1997, 9, 2, 18, 6, 2)])
+
+ def testSecondlyByHourAndSecond(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 0, 6),
+ datetime(1997, 9, 2, 18, 0, 18),
+ datetime(1997, 9, 2, 18, 1, 6)])
+
+ def testSecondlyByMinuteAndSecond(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 6, 6),
+ datetime(1997, 9, 2, 9, 6, 18),
+ datetime(1997, 9, 2, 9, 18, 6)])
+
+ def testSecondlyByHourAndMinuteAndSecond(self):
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 18, 6, 6),
+ datetime(1997, 9, 2, 18, 6, 18),
+ datetime(1997, 9, 2, 18, 18, 6)])
+
+ def testSecondlyByHourAndMinuteAndSecondBug(self):
+ # This explores a bug found by Mathieu Bridon.
+ self.assertEqual(list(rrule(SECONDLY,
+ count=3,
+ bysecond=(0,),
+ byminute=(1,),
+ dtstart=datetime(2010, 3, 22, 12, 1))),
+ [datetime(2010, 3, 22, 12, 1),
+ datetime(2010, 3, 22, 13, 1),
+ datetime(2010, 3, 22, 14, 1)])
+
+ def testLongIntegers(self):
+ if PY2: # There are no longs in python3
+ self.assertEqual(list(rrule(MINUTELY,
+ count=long(2),
+ interval=long(2),
+ bymonth=long(2),
+ byweekday=long(3),
+ byhour=long(6),
+ byminute=long(6),
+ bysecond=long(6),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 2, 5, 6, 6, 6),
+ datetime(1998, 2, 12, 6, 6, 6)])
+ self.assertEqual(list(rrule(YEARLY,
+ count=long(2),
+ bymonthday=long(5),
+ byweekno=long(2),
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1998, 1, 5, 9, 0),
+ datetime(2004, 1, 5, 9, 0)])
+
+ def testHourlyBadRRule(self):
+ """
+ When `byhour` is specified with `freq=HOURLY`, there are certain
+ combinations of `dtstart` and `byhour` which result in an rrule with no
+ valid values.
+
+ See https://github.com/dateutil/dateutil/issues/4
+ """
+
+ self.assertRaises(ValueError, rrule, HOURLY,
+ **dict(interval=4, byhour=(7, 11, 15, 19),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testMinutelyBadRRule(self):
+ """
+ See :func:`testHourlyBadRRule` for details.
+ """
+
+ self.assertRaises(ValueError, rrule, MINUTELY,
+ **dict(interval=12, byminute=(10, 11, 25, 39, 50),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testSecondlyBadRRule(self):
+ """
+ See :func:`testHourlyBadRRule` for details.
+ """
+
+ self.assertRaises(ValueError, rrule, SECONDLY,
+ **dict(interval=10, bysecond=(2, 15, 37, 42, 59),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testMinutelyBadComboRRule(self):
+ """
+ Certain values of :param:`interval` in :class:`rrule`, when combined
+ with certain values of :param:`byhour` create rules which apply to no
+ valid dates. The library should detect this case in the iterator and
+ raise a :exception:`ValueError`.
+ """
+
+ # In Python 2.7 you can use a context manager for this.
+ def make_bad_rrule():
+ list(rrule(MINUTELY, interval=120, byhour=(10, 12, 14, 16),
+ count=2, dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ self.assertRaises(ValueError, make_bad_rrule)
+
+ def testSecondlyBadComboRRule(self):
+ """
+ See :func:`testMinutelyBadComboRRule' for details.
+ """
+
+ # In Python 2.7 you can use a context manager for this.
+ def make_bad_minute_rrule():
+ list(rrule(SECONDLY, interval=360, byminute=(10, 28, 49),
+ count=4, dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def make_bad_hour_rrule():
+ list(rrule(SECONDLY, interval=43200, byhour=(2, 10, 18, 23),
+ count=4, dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ self.assertRaises(ValueError, make_bad_minute_rrule)
+ self.assertRaises(ValueError, make_bad_hour_rrule)
+
+ def testBadUntilCountRRule(self):
+ """
+ See rfc-5545 3.3.10 - This checks for the deprecation warning, and will
+ eventually check for an error.
+ """
+ with pytest.warns(DeprecationWarning):
+ rrule(DAILY, dtstart=datetime(1997, 9, 2, 9, 0),
+ count=3, until=datetime(1997, 9, 4, 9, 0))
+
+ def testUntilNotMatching(self):
+ self.assertEqual(list(rrule(DAILY,
+ dtstart=datetime(1997, 9, 2, 9, 0),
+ until=datetime(1997, 9, 5, 8, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0)])
+
+ def testUntilMatching(self):
+ self.assertEqual(list(rrule(DAILY,
+ dtstart=datetime(1997, 9, 2, 9, 0),
+ until=datetime(1997, 9, 4, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0)])
+
+ def testUntilSingle(self):
+ self.assertEqual(list(rrule(DAILY,
+ dtstart=datetime(1997, 9, 2, 9, 0),
+ until=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0)])
+
+ def testUntilEmpty(self):
+ self.assertEqual(list(rrule(DAILY,
+ dtstart=datetime(1997, 9, 2, 9, 0),
+ until=datetime(1997, 9, 1, 9, 0))),
+ [])
+
+ def testUntilWithDate(self):
+ self.assertEqual(list(rrule(DAILY,
+ dtstart=datetime(1997, 9, 2, 9, 0),
+ until=date(1997, 9, 5))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0)])
+
+ def testWkStIntervalMO(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ interval=2,
+ byweekday=(TU, SU),
+ wkst=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 7, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testWkStIntervalSU(self):
+ self.assertEqual(list(rrule(WEEKLY,
+ count=3,
+ interval=2,
+ byweekday=(TU, SU),
+ wkst=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 14, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testDTStartIsDate(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ dtstart=date(1997, 9, 2))),
+ [datetime(1997, 9, 2, 0, 0),
+ datetime(1997, 9, 3, 0, 0),
+ datetime(1997, 9, 4, 0, 0)])
+
+ def testDTStartWithMicroseconds(self):
+ self.assertEqual(list(rrule(DAILY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0, 0, 500000))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0)])
+
+ def testMaxYear(self):
+ self.assertEqual(list(rrule(YEARLY,
+ count=3,
+ bymonth=2,
+ bymonthday=31,
+ dtstart=datetime(9997, 9, 2, 9, 0, 0))),
+ [])
+
+ def testGetItem(self):
+ self.assertEqual(rrule(DAILY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))[0],
+ datetime(1997, 9, 2, 9, 0))
+
+ def testGetItemNeg(self):
+ self.assertEqual(rrule(DAILY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))[-1],
+ datetime(1997, 9, 4, 9, 0))
+
+ def testGetItemSlice(self):
+ self.assertEqual(rrule(DAILY,
+ # count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))[1:2],
+ [datetime(1997, 9, 3, 9, 0)])
+
+ def testGetItemSliceEmpty(self):
+ self.assertEqual(rrule(DAILY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))[:],
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0)])
+
+ def testGetItemSliceStep(self):
+ self.assertEqual(rrule(DAILY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0))[::-2],
+ [datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 2, 9, 0)])
+
+ def testCount(self):
+ self.assertEqual(rrule(DAILY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0)).count(),
+ 3)
+
+ def testCountZero(self):
+ self.assertEqual(rrule(YEARLY,
+ count=0,
+ dtstart=datetime(1997, 9, 2, 9, 0)).count(),
+ 0)
+
+ def testContains(self):
+ rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))
+ self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True)
+
+ def testContainsNot(self):
+ rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))
+ self.assertEqual(datetime(1997, 9, 3, 9, 0) not in rr, False)
+
+ def testBefore(self):
+ self.assertEqual(rrule(DAILY, # count=5
+ dtstart=datetime(1997, 9, 2, 9, 0)).before(datetime(1997, 9, 5, 9, 0)),
+ datetime(1997, 9, 4, 9, 0))
+
+ def testBeforeInc(self):
+ self.assertEqual(rrule(DAILY,
+ #count=5,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ .before(datetime(1997, 9, 5, 9, 0), inc=True),
+ datetime(1997, 9, 5, 9, 0))
+
+ def testAfter(self):
+ self.assertEqual(rrule(DAILY,
+ #count=5,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ .after(datetime(1997, 9, 4, 9, 0)),
+ datetime(1997, 9, 5, 9, 0))
+
+ def testAfterInc(self):
+ self.assertEqual(rrule(DAILY,
+ #count=5,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ .after(datetime(1997, 9, 4, 9, 0), inc=True),
+ datetime(1997, 9, 4, 9, 0))
+
+ def testXAfter(self):
+ self.assertEqual(list(rrule(DAILY,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ .xafter(datetime(1997, 9, 8, 9, 0), count=12)),
+ [datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 10, 9, 0),
+ datetime(1997, 9, 11, 9, 0),
+ datetime(1997, 9, 12, 9, 0),
+ datetime(1997, 9, 13, 9, 0),
+ datetime(1997, 9, 14, 9, 0),
+ datetime(1997, 9, 15, 9, 0),
+ datetime(1997, 9, 16, 9, 0),
+ datetime(1997, 9, 17, 9, 0),
+ datetime(1997, 9, 18, 9, 0),
+ datetime(1997, 9, 19, 9, 0),
+ datetime(1997, 9, 20, 9, 0)])
+
+ def testXAfterInc(self):
+ self.assertEqual(list(rrule(DAILY,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ .xafter(datetime(1997, 9, 8, 9, 0), count=12, inc=True)),
+ [datetime(1997, 9, 8, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 10, 9, 0),
+ datetime(1997, 9, 11, 9, 0),
+ datetime(1997, 9, 12, 9, 0),
+ datetime(1997, 9, 13, 9, 0),
+ datetime(1997, 9, 14, 9, 0),
+ datetime(1997, 9, 15, 9, 0),
+ datetime(1997, 9, 16, 9, 0),
+ datetime(1997, 9, 17, 9, 0),
+ datetime(1997, 9, 18, 9, 0),
+ datetime(1997, 9, 19, 9, 0)])
+
+ def testBetween(self):
+ self.assertEqual(rrule(DAILY,
+ #count=5,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ .between(datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 6, 9, 0)),
+ [datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 5, 9, 0)])
+
+ def testBetweenInc(self):
+ self.assertEqual(rrule(DAILY,
+ #count=5,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ .between(datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 6, 9, 0), inc=True),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 5, 9, 0),
+ datetime(1997, 9, 6, 9, 0)])
+
+ def testCachePre(self):
+ rr = rrule(DAILY, count=15, cache=True,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ self.assertEqual(list(rr),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 5, 9, 0),
+ datetime(1997, 9, 6, 9, 0),
+ datetime(1997, 9, 7, 9, 0),
+ datetime(1997, 9, 8, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 10, 9, 0),
+ datetime(1997, 9, 11, 9, 0),
+ datetime(1997, 9, 12, 9, 0),
+ datetime(1997, 9, 13, 9, 0),
+ datetime(1997, 9, 14, 9, 0),
+ datetime(1997, 9, 15, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testCachePost(self):
+ rr = rrule(DAILY, count=15, cache=True,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ for x in rr: pass
+ self.assertEqual(list(rr),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 5, 9, 0),
+ datetime(1997, 9, 6, 9, 0),
+ datetime(1997, 9, 7, 9, 0),
+ datetime(1997, 9, 8, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 10, 9, 0),
+ datetime(1997, 9, 11, 9, 0),
+ datetime(1997, 9, 12, 9, 0),
+ datetime(1997, 9, 13, 9, 0),
+ datetime(1997, 9, 14, 9, 0),
+ datetime(1997, 9, 15, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testCachePostInternal(self):
+ rr = rrule(DAILY, count=15, cache=True,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ for x in rr: pass
+ self.assertEqual(rr._cache,
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 3, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 5, 9, 0),
+ datetime(1997, 9, 6, 9, 0),
+ datetime(1997, 9, 7, 9, 0),
+ datetime(1997, 9, 8, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 10, 9, 0),
+ datetime(1997, 9, 11, 9, 0),
+ datetime(1997, 9, 12, 9, 0),
+ datetime(1997, 9, 13, 9, 0),
+ datetime(1997, 9, 14, 9, 0),
+ datetime(1997, 9, 15, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testCachePreContains(self):
+ rr = rrule(DAILY, count=3, cache=True,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True)
+
+ def testCachePostContains(self):
+ rr = rrule(DAILY, count=3, cache=True,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ for x in rr: pass
+ self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True)
+
+ def testStr(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=3\n"
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1998, 9, 2, 9, 0),
+ datetime(1999, 9, 2, 9, 0)])
+
+ def testStrWithTZID(self):
+ NYC = tz.gettz('America/New_York')
+ self.assertEqual(list(rrulestr(
+ "DTSTART;TZID=America/New_York:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=3\n"
+ )),
+ [datetime(1997, 9, 2, 9, 0, tzinfo=NYC),
+ datetime(1998, 9, 2, 9, 0, tzinfo=NYC),
+ datetime(1999, 9, 2, 9, 0, tzinfo=NYC)])
+
+ def testStrWithTZIDMapping(self):
+ rrstr = ("DTSTART;TZID=Eastern:19970902T090000\n" +
+ "RRULE:FREQ=YEARLY;COUNT=3")
+
+ NYC = tz.gettz('America/New_York')
+ rr = rrulestr(rrstr, tzids={'Eastern': NYC})
+ exp = [datetime(1997, 9, 2, 9, 0, tzinfo=NYC),
+ datetime(1998, 9, 2, 9, 0, tzinfo=NYC),
+ datetime(1999, 9, 2, 9, 0, tzinfo=NYC)]
+
+ self.assertEqual(list(rr), exp)
+
+ def testStrWithTZIDCallable(self):
+ rrstr = ('DTSTART;TZID=UTC+04:19970902T090000\n' +
+ 'RRULE:FREQ=YEARLY;COUNT=3')
+
+ TZ = tz.tzstr('UTC+04')
+ def parse_tzstr(tzstr):
+ if tzstr is None:
+ raise ValueError('Invalid tzstr')
+
+ return tz.tzstr(tzstr)
+
+ rr = rrulestr(rrstr, tzids=parse_tzstr)
+
+ exp = [datetime(1997, 9, 2, 9, 0, tzinfo=TZ),
+ datetime(1998, 9, 2, 9, 0, tzinfo=TZ),
+ datetime(1999, 9, 2, 9, 0, tzinfo=TZ),]
+
+ self.assertEqual(list(rr), exp)
+
+ def testStrWithTZIDCallableFailure(self):
+ rrstr = ('DTSTART;TZID=America/New_York:19970902T090000\n' +
+ 'RRULE:FREQ=YEARLY;COUNT=3')
+
+ class TzInfoError(Exception):
+ pass
+
+ def tzinfos(tzstr):
+ if tzstr == 'America/New_York':
+ raise TzInfoError('Invalid!')
+ return None
+
+ with self.assertRaises(TzInfoError):
+ rrulestr(rrstr, tzids=tzinfos)
+
+ def testStrWithConflictingTZID(self):
+ # RFC 5545 Section 3.3.5, FORM #2: DATE WITH UTC TIME
+ # https://tools.ietf.org/html/rfc5545#section-3.3.5
+ # The "TZID" property parameter MUST NOT be applied to DATE-TIME
+ with self.assertRaises(ValueError):
+ rrulestr("DTSTART;TZID=America/New_York:19970902T090000Z\n"+
+ "RRULE:FREQ=YEARLY;COUNT=3\n")
+
+ def testStrType(self):
+ self.assertEqual(isinstance(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=3\n"
+ ), rrule), True)
+
+ def testStrForceSetType(self):
+ self.assertEqual(isinstance(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=3\n"
+ , forceset=True), rruleset), True)
+
+ def testStrSetType(self):
+ self.assertEqual(isinstance(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU\n"
+ "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH\n"
+ ), rruleset), True)
+
+ def testStrCase(self):
+ self.assertEqual(list(rrulestr(
+ "dtstart:19970902T090000\n"
+ "rrule:freq=yearly;count=3\n"
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1998, 9, 2, 9, 0),
+ datetime(1999, 9, 2, 9, 0)])
+
+ def testStrSpaces(self):
+ self.assertEqual(list(rrulestr(
+ " DTSTART:19970902T090000 "
+ " RRULE:FREQ=YEARLY;COUNT=3 "
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1998, 9, 2, 9, 0),
+ datetime(1999, 9, 2, 9, 0)])
+
+ def testStrSpacesAndLines(self):
+ self.assertEqual(list(rrulestr(
+ " DTSTART:19970902T090000 \n"
+ " \n"
+ " RRULE:FREQ=YEARLY;COUNT=3 \n"
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1998, 9, 2, 9, 0),
+ datetime(1999, 9, 2, 9, 0)])
+
+ def testStrNoDTStart(self):
+ self.assertEqual(list(rrulestr(
+ "RRULE:FREQ=YEARLY;COUNT=3\n"
+ , dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1998, 9, 2, 9, 0),
+ datetime(1999, 9, 2, 9, 0)])
+
+ def testStrValueOnly(self):
+ self.assertEqual(list(rrulestr(
+ "FREQ=YEARLY;COUNT=3\n"
+ , dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1998, 9, 2, 9, 0),
+ datetime(1999, 9, 2, 9, 0)])
+
+ def testStrUnfold(self):
+ self.assertEqual(list(rrulestr(
+ "FREQ=YEA\n RLY;COUNT=3\n", unfold=True,
+ dtstart=datetime(1997, 9, 2, 9, 0))),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1998, 9, 2, 9, 0),
+ datetime(1999, 9, 2, 9, 0)])
+
+ def testStrSet(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU\n"
+ "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH\n"
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testStrSetDate(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TU\n"
+ "RDATE:19970904T090000\n"
+ "RDATE:19970909T090000\n"
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testStrSetExRule(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n"
+ "EXRULE:FREQ=YEARLY;COUNT=3;BYDAY=TH\n"
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testStrSetExDate(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n"
+ "EXDATE:19970904T090000\n"
+ "EXDATE:19970911T090000\n"
+ "EXDATE:19970918T090000\n"
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testStrSetExDateMultiple(self):
+ rrstr = ("DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n"
+ "EXDATE:19970904T090000,19970911T090000,19970918T090000\n")
+
+ rr = rrulestr(rrstr)
+ assert list(rr) == [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)]
+
+ def testStrSetExDateWithTZID(self):
+ BXL = tz.gettz('Europe/Brussels')
+ rr = rrulestr("DTSTART;TZID=Europe/Brussels:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n"
+ "EXDATE;TZID=Europe/Brussels:19970904T090000\n"
+ "EXDATE;TZID=Europe/Brussels:19970911T090000\n"
+ "EXDATE;TZID=Europe/Brussels:19970918T090000\n")
+
+ assert list(rr) == [datetime(1997, 9, 2, 9, 0, tzinfo=BXL),
+ datetime(1997, 9, 9, 9, 0, tzinfo=BXL),
+ datetime(1997, 9, 16, 9, 0, tzinfo=BXL)]
+
+ def testStrSetExDateValueDateTimeNoTZID(self):
+ rrstr = '\n'.join([
+ "DTSTART:19970902T090000",
+ "RRULE:FREQ=YEARLY;COUNT=4;BYDAY=TU,TH",
+ "EXDATE;VALUE=DATE-TIME:19970902T090000",
+ "EXDATE;VALUE=DATE-TIME:19970909T090000",
+ ])
+
+ rr = rrulestr(rrstr)
+ assert list(rr) == [datetime(1997, 9, 4, 9), datetime(1997, 9, 11, 9)]
+
+ def testStrSetExDateValueMixDateTimeNoTZID(self):
+ rrstr = '\n'.join([
+ "DTSTART:19970902T090000",
+ "RRULE:FREQ=YEARLY;COUNT=4;BYDAY=TU,TH",
+ "EXDATE;VALUE=DATE-TIME:19970902T090000",
+ "EXDATE:19970909T090000",
+ ])
+
+ rr = rrulestr(rrstr)
+ assert list(rr) == [datetime(1997, 9, 4, 9), datetime(1997, 9, 11, 9)]
+
+ def testStrSetExDateValueDateTimeWithTZID(self):
+ BXL = tz.gettz('Europe/Brussels')
+ rrstr = '\n'.join([
+ "DTSTART;VALUE=DATE-TIME;TZID=Europe/Brussels:19970902T090000",
+ "RRULE:FREQ=YEARLY;COUNT=4;BYDAY=TU,TH",
+ "EXDATE;VALUE=DATE-TIME;TZID=Europe/Brussels:19970902T090000",
+ "EXDATE;VALUE=DATE-TIME;TZID=Europe/Brussels:19970909T090000",
+ ])
+
+ rr = rrulestr(rrstr)
+ assert list(rr) == [datetime(1997, 9, 4, 9, tzinfo=BXL),
+ datetime(1997, 9, 11, 9, tzinfo=BXL)]
+
+ def testStrSetExDateValueDate(self):
+ rrstr = '\n'.join([
+ "DTSTART;VALUE=DATE:19970902",
+ "RRULE:FREQ=YEARLY;COUNT=4;BYDAY=TU,TH",
+ "EXDATE;VALUE=DATE:19970902",
+ "EXDATE;VALUE=DATE:19970909",
+ ])
+
+ rr = rrulestr(rrstr)
+ assert list(rr) == [datetime(1997, 9, 4), datetime(1997, 9, 11)]
+
+ def testStrSetDateAndExDate(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RDATE:19970902T090000\n"
+ "RDATE:19970904T090000\n"
+ "RDATE:19970909T090000\n"
+ "RDATE:19970911T090000\n"
+ "RDATE:19970916T090000\n"
+ "RDATE:19970918T090000\n"
+ "EXDATE:19970904T090000\n"
+ "EXDATE:19970911T090000\n"
+ "EXDATE:19970918T090000\n"
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testStrSetDateAndExRule(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RDATE:19970902T090000\n"
+ "RDATE:19970904T090000\n"
+ "RDATE:19970909T090000\n"
+ "RDATE:19970911T090000\n"
+ "RDATE:19970916T090000\n"
+ "RDATE:19970918T090000\n"
+ "EXRULE:FREQ=YEARLY;COUNT=3;BYDAY=TH\n"
+ )),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testStrKeywords(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=3;"
+ "BYMONTH=3;BYWEEKDAY=TH;BYMONTHDAY=3;"
+ "BYHOUR=3;BYMINUTE=3;BYSECOND=3\n"
+ )),
+ [datetime(2033, 3, 3, 3, 3, 3),
+ datetime(2039, 3, 3, 3, 3, 3),
+ datetime(2072, 3, 3, 3, 3, 3)])
+
+ def testStrNWeekDay(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=3;BYDAY=1TU,-1TH\n"
+ )),
+ [datetime(1997, 12, 25, 9, 0),
+ datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 12, 31, 9, 0)])
+
+ def testStrUntil(self):
+ self.assertEqual(list(rrulestr(
+ "DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;"
+ "UNTIL=19990101T000000;BYDAY=1TU,-1TH\n"
+ )),
+ [datetime(1997, 12, 25, 9, 0),
+ datetime(1998, 1, 6, 9, 0),
+ datetime(1998, 12, 31, 9, 0)])
+
+ def testStrValueDatetime(self):
+ rr = rrulestr("DTSTART;VALUE=DATE-TIME:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;COUNT=2")
+
+ self.assertEqual(list(rr), [datetime(1997, 9, 2, 9, 0, 0),
+ datetime(1998, 9, 2, 9, 0, 0)])
+
+ def testStrValueDate(self):
+ rr = rrulestr("DTSTART;VALUE=DATE:19970902\n"
+ "RRULE:FREQ=YEARLY;COUNT=2")
+
+ self.assertEqual(list(rr), [datetime(1997, 9, 2, 0, 0, 0),
+ datetime(1998, 9, 2, 0, 0, 0)])
+
+ def testStrMultipleDTStartComma(self):
+ with pytest.raises(ValueError):
+ rr = rrulestr("DTSTART:19970101T000000,19970202T000000\n"
+ "RRULE:FREQ=YEARLY;COUNT=1")
+
+ def testStrInvalidUntil(self):
+ with self.assertRaises(ValueError):
+ list(rrulestr("DTSTART:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;"
+ "UNTIL=TheCowsComeHome;BYDAY=1TU,-1TH\n"))
+
+ def testStrUntilMustBeUTC(self):
+ with self.assertRaises(ValueError):
+ list(rrulestr("DTSTART;TZID=America/New_York:19970902T090000\n"
+ "RRULE:FREQ=YEARLY;"
+ "UNTIL=19990101T000000;BYDAY=1TU,-1TH\n"))
+
+ def testStrUntilWithTZ(self):
+ NYC = tz.gettz('America/New_York')
+ rr = list(rrulestr("DTSTART;TZID=America/New_York:19970101T000000\n"
+ "RRULE:FREQ=YEARLY;"
+ "UNTIL=19990101T000000Z\n"))
+ self.assertEqual(list(rr), [datetime(1997, 1, 1, 0, 0, 0, tzinfo=NYC),
+ datetime(1998, 1, 1, 0, 0, 0, tzinfo=NYC)])
+
+ def testStrEmptyByDay(self):
+ with self.assertRaises(ValueError):
+ list(rrulestr("DTSTART:19970902T090000\n"
+ "FREQ=WEEKLY;"
+ "BYDAY=;" # This part is invalid
+ "WKST=SU"))
+
+ def testStrInvalidByDay(self):
+ with self.assertRaises(ValueError):
+ list(rrulestr("DTSTART:19970902T090000\n"
+ "FREQ=WEEKLY;"
+ "BYDAY=-1OK;" # This part is invalid
+ "WKST=SU"))
+
+ def testBadBySetPos(self):
+ self.assertRaises(ValueError,
+ rrule, MONTHLY,
+ count=1,
+ bysetpos=0,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+
+ def testBadBySetPosMany(self):
+ self.assertRaises(ValueError,
+ rrule, MONTHLY,
+ count=1,
+ bysetpos=(-1, 0, 1),
+ dtstart=datetime(1997, 9, 2, 9, 0))
+
+ # Tests to ensure that str(rrule) works
+ def testToStrYearly(self):
+ rule = rrule(YEARLY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))
+ self._rrulestr_reverse_test(rule)
+
+ def testToStrYearlyInterval(self):
+ rule = rrule(YEARLY, count=3, interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0))
+ self._rrulestr_reverse_test(rule)
+
+ def testToStrYearlyByMonth(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMonthDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMonthAndMonthDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByWeekDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByNWeekDayLarge(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byweekday=(TU(3), TH(-3)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMonthAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMonthAndNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMonthAndNWeekDayLarge(self):
+ # This is interesting because the TH(-3) ends up before
+ # the TU(3).
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(3), TH(-3)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMonthAndMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByYearDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMonthAndYearDay(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMonthAndYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByWeekNo(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByWeekNoAndWeekDay(self):
+ # That's a nice one. The first days of week number one
+ # may be in the last year.
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByWeekNoAndWeekDayLarge(self):
+ # Another nice test. The last days of week number 52/53
+ # may be in the next year.
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByWeekNoAndWeekDayLast(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByEaster(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByEasterPos(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByEasterNeg(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByWeekNoAndWeekDay53(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByHour(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMinute(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyBySecond(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByHourAndMinute(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByHourAndSecond(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyByHourAndMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrYearlyBySetPos(self):
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=3,
+ bymonthday=15,
+ byhour=(6, 18),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthly(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyInterval(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyIntervalLarge(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ interval=18,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonth(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonthDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonthAndMonthDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ # Third Monday of the month
+ self.assertEqual(rrule(MONTHLY,
+ byweekday=(MO(+3)),
+ dtstart=datetime(1997, 9, 1)).between(datetime(1997,
+ 9,
+ 1),
+ datetime(1997,
+ 12,
+ 1)),
+ [datetime(1997, 9, 15, 0, 0),
+ datetime(1997, 10, 20, 0, 0),
+ datetime(1997, 11, 17, 0, 0)])
+
+ def testToStrMonthlyByNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByNWeekDayLarge(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byweekday=(TU(3), TH(-3)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonthAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonthAndNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonthAndNWeekDayLarge(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(3), TH(-3)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonthAndMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByYearDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonthAndYearDay(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMonthAndYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByWeekNo(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByWeekNoAndWeekDay(self):
+ # That's a nice one. The first days of week number one
+ # may be in the last year.
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByWeekNoAndWeekDayLarge(self):
+ # Another nice test. The last days of week number 52/53
+ # may be in the next year.
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByWeekNoAndWeekDayLast(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByWeekNoAndWeekDay53(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByEaster(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByEasterPos(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByEasterNeg(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByHour(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMinute(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyBySecond(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByHourAndMinute(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByHourAndSecond(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyByHourAndMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMonthlyBySetPos(self):
+ self._rrulestr_reverse_test(rrule(MONTHLY,
+ count=3,
+ bymonthday=(13, 17),
+ byhour=(6, 18),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeekly(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyInterval(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyIntervalLarge(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ interval=20,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMonth(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMonthDay(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMonthAndMonthDay(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByWeekDay(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMonthAndWeekDay(self):
+ # This test is interesting, because it crosses the year
+ # boundary in a weekly period to find day '1' as a
+ # valid recurrence.
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMonthAndNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMonthAndMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByYearDay(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMonthAndYearDay(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=4,
+ bymonth=(1, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMonthAndYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=4,
+ bymonth=(1, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByWeekNo(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByWeekNoAndWeekDay(self):
+ # That's a nice one. The first days of week number one
+ # may be in the last year.
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByWeekNoAndWeekDayLarge(self):
+ # Another nice test. The last days of week number 52/53
+ # may be in the next year.
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByWeekNoAndWeekDayLast(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByWeekNoAndWeekDay53(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByEaster(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByEasterPos(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByEasterNeg(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByHour(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMinute(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyBySecond(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByHourAndMinute(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByHourAndSecond(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyByHourAndMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrWeeklyBySetPos(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ byweekday=(TU, TH),
+ byhour=(6, 18),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDaily(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyInterval(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyIntervalLarge(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ interval=92,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMonth(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMonthDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMonthAndMonthDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByWeekDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMonthAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMonthAndNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMonthAndMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByYearDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMonthAndYearDay(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=4,
+ bymonth=(1, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMonthAndYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=4,
+ bymonth=(1, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByWeekNo(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByWeekNoAndWeekDay(self):
+ # That's a nice one. The first days of week number one
+ # may be in the last year.
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByWeekNoAndWeekDayLarge(self):
+ # Another nice test. The last days of week number 52/53
+ # may be in the next year.
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByWeekNoAndWeekDayLast(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByWeekNoAndWeekDay53(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByEaster(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByEasterPos(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByEasterNeg(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByHour(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMinute(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyBySecond(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByHourAndMinute(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByHourAndSecond(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyByHourAndMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrDailyBySetPos(self):
+ self._rrulestr_reverse_test(rrule(DAILY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(15, 45),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourly(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyInterval(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyIntervalLarge(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ interval=769,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMonth(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMonthDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMonthAndMonthDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByWeekDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMonthAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMonthAndNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMonthAndMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByYearDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMonthAndYearDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMonthAndYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByWeekNo(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByWeekNoAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByWeekNoAndWeekDayLarge(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByWeekNoAndWeekDayLast(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByWeekNoAndWeekDay53(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByEaster(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByEasterPos(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByEasterNeg(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByHour(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMinute(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyBySecond(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByHourAndMinute(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByHourAndSecond(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyByHourAndMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrHourlyBySetPos(self):
+ self._rrulestr_reverse_test(rrule(HOURLY,
+ count=3,
+ byminute=(15, 45),
+ bysecond=(15, 45),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutely(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyInterval(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyIntervalLarge(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ interval=1501,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMonth(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMonthDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMonthAndMonthDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMonthAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMonthAndNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMonthAndMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByYearDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMonthAndYearDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMonthAndYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByWeekNo(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByWeekNoAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByWeekNoAndWeekDayLarge(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByWeekNoAndWeekDayLast(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByWeekNoAndWeekDay53(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByEaster(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByEasterPos(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByEasterNeg(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByHour(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMinute(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyBySecond(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByHourAndMinute(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByHourAndSecond(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyByHourAndMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrMinutelyBySetPos(self):
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=3,
+ bysecond=(15, 30, 45),
+ bysetpos=(3, -3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondly(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyInterval(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ interval=2,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyIntervalLarge(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ interval=90061,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMonth(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMonthDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ bymonthday=(1, 3),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMonthAndMonthDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(5, 7),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByWeekDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMonthAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMonthAndNWeekDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ byweekday=(TU(1), TH(-1)),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMonthAndMonthDayAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ bymonth=(1, 3),
+ bymonthday=(1, 3),
+ byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByYearDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=4,
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=4,
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMonthAndYearDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(1, 100, 200, 365),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMonthAndYearDayNeg(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=4,
+ bymonth=(4, 7),
+ byyearday=(-365, -266, -166, -1),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByWeekNo(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byweekno=20,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByWeekNoAndWeekDay(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byweekno=1,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByWeekNoAndWeekDayLarge(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byweekno=52,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByWeekNoAndWeekDayLast(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byweekno=-1,
+ byweekday=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByWeekNoAndWeekDay53(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byweekno=53,
+ byweekday=MO,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByEaster(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byeaster=0,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByEasterPos(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byeaster=1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByEasterNeg(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byeaster=-1,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByHour(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byhour=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMinute(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyBySecond(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByHourAndMinute(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByHourAndSecond(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byhour=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByHourAndMinuteAndSecond(self):
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ byhour=(6, 18),
+ byminute=(6, 18),
+ bysecond=(6, 18),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrSecondlyByHourAndMinuteAndSecondBug(self):
+ # This explores a bug found by Mathieu Bridon.
+ self._rrulestr_reverse_test(rrule(SECONDLY,
+ count=3,
+ bysecond=(0,),
+ byminute=(1,),
+ dtstart=datetime(2010, 3, 22, 12, 1)))
+
+ def testToStrWithWkSt(self):
+ self._rrulestr_reverse_test(rrule(WEEKLY,
+ count=3,
+ wkst=SU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testToStrLongIntegers(self):
+ if PY2: # There are no longs in python3
+ self._rrulestr_reverse_test(rrule(MINUTELY,
+ count=long(2),
+ interval=long(2),
+ bymonth=long(2),
+ byweekday=long(3),
+ byhour=long(6),
+ byminute=long(6),
+ bysecond=long(6),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ self._rrulestr_reverse_test(rrule(YEARLY,
+ count=long(2),
+ bymonthday=long(5),
+ byweekno=long(2),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+
+ def testReplaceIfSet(self):
+ rr = rrule(YEARLY,
+ count=1,
+ bymonthday=5,
+ dtstart=datetime(1997, 1, 1))
+ newrr = rr.replace(bymonthday=6)
+ self.assertEqual(list(rr), [datetime(1997, 1, 5)])
+ self.assertEqual(list(newrr),
+ [datetime(1997, 1, 6)])
+
+ def testReplaceIfNotSet(self):
+ rr = rrule(YEARLY,
+ count=1,
+ dtstart=datetime(1997, 1, 1))
+ newrr = rr.replace(bymonthday=6)
+ self.assertEqual(list(rr), [datetime(1997, 1, 1)])
+ self.assertEqual(list(newrr),
+ [datetime(1997, 1, 6)])
+
+
+@pytest.mark.rrule
+@freeze_time(datetime(2018, 3, 6, 5, 36, tzinfo=tz.UTC))
+def test_generated_aware_dtstart():
+ dtstart_exp = datetime(2018, 3, 6, 5, 36, tzinfo=tz.UTC)
+ UNTIL = datetime(2018, 3, 6, 8, 0, tzinfo=tz.UTC)
+
+ rule_without_dtstart = rrule(freq=HOURLY, until=UNTIL)
+ rule_with_dtstart = rrule(freq=HOURLY, dtstart=dtstart_exp, until=UNTIL)
+ assert list(rule_without_dtstart) == list(rule_with_dtstart)
+
+
+@pytest.mark.rrule
+@pytest.mark.rrulestr
+@pytest.mark.xfail(reason="rrulestr loses time zone, gh issue #637")
+@freeze_time(datetime(2018, 3, 6, 5, 36, tzinfo=tz.UTC))
+def test_generated_aware_dtstart_rrulestr():
+ rrule_without_dtstart = rrule(freq=HOURLY,
+ until=datetime(2018, 3, 6, 8, 0,
+ tzinfo=tz.UTC))
+ rrule_r = rrulestr(str(rrule_without_dtstart))
+
+ assert list(rrule_r) == list(rrule_without_dtstart)
+
+
+@pytest.mark.rruleset
+class RRuleSetTest(unittest.TestCase):
+ def testSet(self):
+ rrset = rruleset()
+ rrset.rrule(rrule(YEARLY, count=2, byweekday=TU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ rrset.rrule(rrule(YEARLY, count=1, byweekday=TH,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ self.assertEqual(list(rrset),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testSetDate(self):
+ rrset = rruleset()
+ rrset.rrule(rrule(YEARLY, count=1, byweekday=TU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ rrset.rdate(datetime(1997, 9, 4, 9))
+ rrset.rdate(datetime(1997, 9, 9, 9))
+ self.assertEqual(list(rrset),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testSetExRule(self):
+ rrset = rruleset()
+ rrset.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ rrset.exrule(rrule(YEARLY, count=3, byweekday=TH,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ self.assertEqual(list(rrset),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testSetExDate(self):
+ rrset = rruleset()
+ rrset.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ rrset.exdate(datetime(1997, 9, 4, 9))
+ rrset.exdate(datetime(1997, 9, 11, 9))
+ rrset.exdate(datetime(1997, 9, 18, 9))
+ self.assertEqual(list(rrset),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testSetExDateRevOrder(self):
+ rrset = rruleset()
+ rrset.rrule(rrule(MONTHLY, count=5, bymonthday=10,
+ dtstart=datetime(2004, 1, 1, 9, 0)))
+ rrset.exdate(datetime(2004, 4, 10, 9, 0))
+ rrset.exdate(datetime(2004, 2, 10, 9, 0))
+ self.assertEqual(list(rrset),
+ [datetime(2004, 1, 10, 9, 0),
+ datetime(2004, 3, 10, 9, 0),
+ datetime(2004, 5, 10, 9, 0)])
+
+ def testSetDateAndExDate(self):
+ rrset = rruleset()
+ rrset.rdate(datetime(1997, 9, 2, 9))
+ rrset.rdate(datetime(1997, 9, 4, 9))
+ rrset.rdate(datetime(1997, 9, 9, 9))
+ rrset.rdate(datetime(1997, 9, 11, 9))
+ rrset.rdate(datetime(1997, 9, 16, 9))
+ rrset.rdate(datetime(1997, 9, 18, 9))
+ rrset.exdate(datetime(1997, 9, 4, 9))
+ rrset.exdate(datetime(1997, 9, 11, 9))
+ rrset.exdate(datetime(1997, 9, 18, 9))
+ self.assertEqual(list(rrset),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testSetDateAndExRule(self):
+ rrset = rruleset()
+ rrset.rdate(datetime(1997, 9, 2, 9))
+ rrset.rdate(datetime(1997, 9, 4, 9))
+ rrset.rdate(datetime(1997, 9, 9, 9))
+ rrset.rdate(datetime(1997, 9, 11, 9))
+ rrset.rdate(datetime(1997, 9, 16, 9))
+ rrset.rdate(datetime(1997, 9, 18, 9))
+ rrset.exrule(rrule(YEARLY, count=3, byweekday=TH,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ self.assertEqual(list(rrset),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 9, 9, 0),
+ datetime(1997, 9, 16, 9, 0)])
+
+ def testSetCount(self):
+ rrset = rruleset()
+ rrset.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH),
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ rrset.exrule(rrule(YEARLY, count=3, byweekday=TH,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ self.assertEqual(rrset.count(), 3)
+
+ def testSetCachePre(self):
+ rrset = rruleset()
+ rrset.rrule(rrule(YEARLY, count=2, byweekday=TU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ rrset.rrule(rrule(YEARLY, count=1, byweekday=TH,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ self.assertEqual(list(rrset),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testSetCachePost(self):
+ rrset = rruleset(cache=True)
+ rrset.rrule(rrule(YEARLY, count=2, byweekday=TU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ rrset.rrule(rrule(YEARLY, count=1, byweekday=TH,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ for x in rrset: pass
+ self.assertEqual(list(rrset),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testSetCachePostInternal(self):
+ rrset = rruleset(cache=True)
+ rrset.rrule(rrule(YEARLY, count=2, byweekday=TU,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ rrset.rrule(rrule(YEARLY, count=1, byweekday=TH,
+ dtstart=datetime(1997, 9, 2, 9, 0)))
+ for x in rrset: pass
+ self.assertEqual(list(rrset._cache),
+ [datetime(1997, 9, 2, 9, 0),
+ datetime(1997, 9, 4, 9, 0),
+ datetime(1997, 9, 9, 9, 0)])
+
+ def testSetRRuleCount(self):
+ # Test that the count is updated when an rrule is added
+ rrset = rruleset(cache=False)
+ for cache in (True, False):
+ rrset = rruleset(cache=cache)
+ rrset.rrule(rrule(YEARLY, count=2, byweekday=TH,
+ dtstart=datetime(1983, 4, 1)))
+ rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR,
+ dtstart=datetime(1991, 6, 3)))
+
+ # Check the length twice - first one sets a cache, second reads it
+ self.assertEqual(rrset.count(), 6)
+ self.assertEqual(rrset.count(), 6)
+
+ # This should invalidate the cache and force an update
+ rrset.rrule(rrule(MONTHLY, count=3, dtstart=datetime(1994, 1, 3)))
+
+ self.assertEqual(rrset.count(), 9)
+ self.assertEqual(rrset.count(), 9)
+
+ def testSetRDateCount(self):
+ # Test that the count is updated when an rdate is added
+ rrset = rruleset(cache=False)
+ for cache in (True, False):
+ rrset = rruleset(cache=cache)
+ rrset.rrule(rrule(YEARLY, count=2, byweekday=TH,
+ dtstart=datetime(1983, 4, 1)))
+ rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR,
+ dtstart=datetime(1991, 6, 3)))
+
+ # Check the length twice - first one sets a cache, second reads it
+ self.assertEqual(rrset.count(), 6)
+ self.assertEqual(rrset.count(), 6)
+
+ # This should invalidate the cache and force an update
+ rrset.rdate(datetime(1993, 2, 14))
+
+ self.assertEqual(rrset.count(), 7)
+ self.assertEqual(rrset.count(), 7)
+
+ def testSetExRuleCount(self):
+ # Test that the count is updated when an exrule is added
+ rrset = rruleset(cache=False)
+ for cache in (True, False):
+ rrset = rruleset(cache=cache)
+ rrset.rrule(rrule(YEARLY, count=2, byweekday=TH,
+ dtstart=datetime(1983, 4, 1)))
+ rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR,
+ dtstart=datetime(1991, 6, 3)))
+
+ # Check the length twice - first one sets a cache, second reads it
+ self.assertEqual(rrset.count(), 6)
+ self.assertEqual(rrset.count(), 6)
+
+ # This should invalidate the cache and force an update
+ rrset.exrule(rrule(WEEKLY, count=2, interval=2,
+ dtstart=datetime(1991, 6, 14)))
+
+ self.assertEqual(rrset.count(), 4)
+ self.assertEqual(rrset.count(), 4)
+
+ def testSetExDateCount(self):
+ # Test that the count is updated when an rdate is added
+ for cache in (True, False):
+ rrset = rruleset(cache=cache)
+ rrset.rrule(rrule(YEARLY, count=2, byweekday=TH,
+ dtstart=datetime(1983, 4, 1)))
+ rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR,
+ dtstart=datetime(1991, 6, 3)))
+
+ # Check the length twice - first one sets a cache, second reads it
+ self.assertEqual(rrset.count(), 6)
+ self.assertEqual(rrset.count(), 6)
+
+ # This should invalidate the cache and force an update
+ rrset.exdate(datetime(1991, 6, 28))
+
+ self.assertEqual(rrset.count(), 5)
+ self.assertEqual(rrset.count(), 5)
+
+
+class WeekdayTest(unittest.TestCase):
+ def testInvalidNthWeekday(self):
+ with self.assertRaises(ValueError):
+ FR(0)
+
+ def testWeekdayCallable(self):
+ # Calling a weekday instance generates a new weekday instance with the
+ # value of n changed.
+ from dateutil.rrule import weekday
+ self.assertEqual(MO(1), weekday(0, 1))
+
+ # Calling a weekday instance with the identical n returns the original
+ # object
+ FR_3 = weekday(4, 3)
+ self.assertIs(FR_3(3), FR_3)
+
+ def testWeekdayEquality(self):
+ # Two weekday objects are not equal if they have different values for n
+ self.assertNotEqual(TH, TH(-1))
+ self.assertNotEqual(SA(3), SA(2))
+
+ def testWeekdayEqualitySubclass(self):
+ # Two weekday objects equal if their "weekday" and "n" attributes are
+ # available and the same
+ class BasicWeekday(object):
+ def __init__(self, weekday):
+ self.weekday = weekday
+
+ class BasicNWeekday(BasicWeekday):
+ def __init__(self, weekday, n=None):
+ super(BasicNWeekday, self).__init__(weekday)
+ self.n = n
+
+ MO_Basic = BasicWeekday(0)
+
+ self.assertNotEqual(MO, MO_Basic)
+ self.assertNotEqual(MO(1), MO_Basic)
+
+ TU_BasicN = BasicNWeekday(1)
+
+ self.assertEqual(TU, TU_BasicN)
+ self.assertNotEqual(TU(3), TU_BasicN)
+
+ WE_Basic3 = BasicNWeekday(2, 3)
+ self.assertEqual(WE(3), WE_Basic3)
+ self.assertNotEqual(WE(2), WE_Basic3)
+
+ def testWeekdayReprNoN(self):
+ no_n_reprs = ('MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU')
+ no_n_wdays = (MO, TU, WE, TH, FR, SA, SU)
+
+ for repstr, wday in zip(no_n_reprs, no_n_wdays):
+ self.assertEqual(repr(wday), repstr)
+
+ def testWeekdayReprWithN(self):
+ with_n_reprs = ('WE(+1)', 'TH(-2)', 'SU(+3)')
+ with_n_wdays = (WE(1), TH(-2), SU(+3))
+
+ for repstr, wday in zip(with_n_reprs, with_n_wdays):
+ self.assertEqual(repr(wday), repstr)
diff --git a/contrib/python/python-dateutil/py2/tests/test_tz.py b/contrib/python/python-dateutil/py2/tests/test_tz.py
new file mode 100644
index 0000000000..e5e4772d9a
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_tz.py
@@ -0,0 +1,2811 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from ._common import PicklableMixin
+from ._common import TZEnvContext, TZWinContext
+from ._common import ComparesEqual
+
+from datetime import datetime, timedelta
+from datetime import time as dt_time
+from datetime import tzinfo
+from six import PY2
+from io import BytesIO, StringIO
+import unittest
+
+import sys
+import base64
+import copy
+import gc
+import weakref
+
+from functools import partial
+
+IS_WIN = sys.platform.startswith('win')
+
+import pytest
+
+# dateutil imports
+from dateutil.relativedelta import relativedelta, SU, TH
+from dateutil.parser import parse
+from dateutil import tz as tz
+from dateutil import zoneinfo
+
+try:
+ from dateutil import tzwin
+except ImportError as e:
+ if IS_WIN:
+ raise e
+ else:
+ pass
+
+MISSING_TARBALL = ("This test fails if you don't have the dateutil "
+ "timezone file installed. Please read the README")
+
+TZFILE_EST5EDT = b"""
+VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAADrAAAABAAAABCeph5wn7rrYKCGAHCh
+ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e
+S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0
+YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg
+yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db
+wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW
+8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b
+YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g
+BGD9cAVQ4GAGQN9wBzDCYAeNGXAJEKRgCa2U8ArwhmAL4IVwDNmi4A3AZ3AOuYTgD6mD8BCZZuAR
+iWXwEnlI4BNpR/AUWSrgFUkp8BY5DOAXKQvwGCIpYBkI7fAaAgtgGvIKcBvh7WAc0exwHcHPYB6x
+znAfobFgIHYA8CGBk2AiVeLwI2qv4CQ1xPAlSpHgJhWm8Ccqc+An/sNwKQpV4CnepXAq6jfgK76H
+cCzTVGAtnmlwLrM2YC9+S3AwkxhgMWdn8DJy+mAzR0nwNFLcYDUnK/A2Mr5gNwcN8Dgb2uA45u/w
+Ofu84DrG0fA7257gPK/ucD27gOA+j9BwP5ti4EBvsnBBhH9gQk+UcENkYWBEL3ZwRURDYEYPWHBH
+JCVgR/h08EkEB2BJ2FbwSuPpYEu4OPBMzQXgTZga8E6s5+BPd/zwUIzJ4FFhGXBSbKvgU0D7cFRM
+jeBVIN1wVixv4FcAv3BYFYxgWOChcFn1bmBawINwW9VQYFypn/BdtTJgXomB8F+VFGBgaWPwYX4w
+4GJJRfBjXhLgZCkn8GU99OBmEkRwZx3W4GfyJnBo/bjgadIIcGrdmuBrsepwbMa3YG2RzHBupplg
+b3GucHCGe2BxWsrwcmZdYHM6rPB0Rj9gdRqO8HYvW+B2+nDweA894HjaUvB57x/gero08HvPAeB8
+o1Fwfa7j4H6DM3B/jsXgAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
+AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
+AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA
+AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
+AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU
+AEVQVAAAAAABAAAAAQ==
+"""
+
+EUROPE_HELSINKI = b"""
+VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAB1AAAABQAAAA2kc28Yy85RYMy/hdAV
+I+uQFhPckBcDzZAX876QGOOvkBnToJAaw5GQG7y9EBysrhAdnJ8QHoyQEB98gRAgbHIQIVxjECJM
+VBAjPEUQJCw2ECUcJxAmDBgQJwVDkCf1NJAo5SWQKdUWkCrFB5ArtPiQLKTpkC2U2pAuhMuQL3S8
+kDBkrZAxXdkQMnK0EDM9uxA0UpYQNR2dEDYyeBA2/X8QOBuUkDjdYRA5+3aQOr1DEDvbWJA8pl+Q
+Pbs6kD6GQZA/mxyQQGYjkEGEORBCRgWQQ2QbEEQl55BFQ/0QRgXJkEcj3xBH7uYQSQPBEEnOyBBK
+46MQS66qEEzMv5BNjowQTqyhkE9ubhBQjIOQUVeKkFJsZZBTN2yQVExHkFUXTpBWLCmQVvcwkFgV
+RhBY1xKQWfUoEFq29JBb1QoQXKAREF207BBef/MQX5TOEGBf1RBhfeqQYj+3EGNdzJBkH5kQZT2u
+kGYItZBnHZCQZ+iXkGj9cpBpyHmQat1UkGuoW5BsxnEQbYg9kG6mUxBvaB+QcIY1EHFRPBByZhcQ
+czEeEHRF+RB1EQAQdi8VkHbw4hB4DveQeNDEEHnu2ZB6sKYQe867kHyZwpB9rp2QfnmkkH+Of5AC
+AQIDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQD
+BAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAME
+AwQAABdoAAAAACowAQQAABwgAAkAACowAQQAABwgAAlITVQARUVTVABFRVQAAAAAAQEAAAABAQ==
+"""
+
+NEW_YORK = b"""
+VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABcAAADrAAAABAAAABCeph5wn7rrYKCGAHCh
+ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e
+S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0
+YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg
+yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db
+wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW
+8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b
+YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g
+BGD9cAVQ4GEGQN9yBzDCYgeNGXMJEKRjCa2U9ArwhmQL4IV1DNmi5Q3AZ3YOuYTmD6mD9xCZZucR
+iWX4EnlI6BNpR/kUWSrpFUkp+RY5DOoXKQv6GCIpaxkI7fsaAgtsGvIKfBvh7Wwc0ex8HcHPbR6x
+zn0fobFtIHYA/SGBk20iVeL+I2qv7iQ1xP4lSpHuJhWm/ycqc+8n/sOAKQpV8CnepYAq6jfxK76H
+gSzTVHItnmmCLrM2cy9+S4MwkxhzMWdoBDJy+nQzR0oENFLcdTUnLAU2Mr51NwcOBjgb2vY45vAG
+Ofu89jrG0gY72572PK/uhj27gPY+j9CGP5ti9kBvsoZBhH92Qk+UhkNkYXZEL3aHRURDd0XzqQdH
+LV/3R9OLB0kNQfdJs20HSu0j90uciYdM1kB3TXxrh062IndPXE2HUJYEd1E8L4dSdeZ3UxwRh1RV
+yHdU+/OHVjWqd1blEAdYHsb3WMTyB1n+qPdapNQHW96K91yEtgddvmz3XmSYB1+eTvdgTbSHYYdr
+d2ItlodjZ013ZA14h2VHL3dl7VqHZycRd2fNPIdpBvN3aa0eh2rm1XdrljsHbM/x9212HQdur9P3
+b1X/B3CPtfdxNeEHcm+X93MVwwd0T3n3dP7fh3Y4lnd23sGHeBh4d3i+o4d5+Fp3ep6Fh3vYPHd8
+fmeHfbged35eSYd/mAB3AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
+AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
+AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA
+AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
+AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU
+AEVQVAAEslgAAAAAAQWk7AEAAAACB4YfggAAAAMJZ1MDAAAABAtIhoQAAAAFDSsLhQAAAAYPDD8G
+AAAABxDtcocAAAAIEs6mCAAAAAkVn8qJAAAACheA/goAAAALGWIxiwAAAAwdJeoMAAAADSHa5Q0A
+AAAOJZ6djgAAAA8nf9EPAAAAECpQ9ZAAAAARLDIpEQAAABIuE1ySAAAAEzDnJBMAAAAUM7hIlAAA
+ABU2jBAVAAAAFkO3G5YAAAAXAAAAAQAAAAE=
+"""
+
+TZICAL_EST5EDT = """
+BEGIN:VTIMEZONE
+TZID:US-Eastern
+LAST-MODIFIED:19870101T000000Z
+TZURL:http://zones.stds_r_us.net/tz/US-Eastern
+BEGIN:STANDARD
+DTSTART:19671029T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+TZNAME:EST
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EDT
+END:DAYLIGHT
+END:VTIMEZONE
+"""
+
+TZICAL_PST8PDT = """
+BEGIN:VTIMEZONE
+TZID:US-Pacific
+LAST-MODIFIED:19870101T000000Z
+BEGIN:STANDARD
+DTSTART:19671029T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+TZNAME:PST
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+TZNAME:PDT
+END:DAYLIGHT
+END:VTIMEZONE
+"""
+
+EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0))
+EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1))
+
+SUPPORTS_SUB_MINUTE_OFFSETS = sys.version_info >= (3, 6)
+
+
+###
+# Helper functions
+def get_timezone_tuple(dt):
+ """Retrieve a (tzname, utcoffset, dst) tuple for a given DST"""
+ return dt.tzname(), dt.utcoffset(), dt.dst()
+
+
+###
+# Mix-ins
+class context_passthrough(object):
+ def __init__(*args, **kwargs):
+ pass
+
+ def __enter__(*args, **kwargs):
+ pass
+
+ def __exit__(*args, **kwargs):
+ pass
+
+
+class TzFoldMixin(object):
+ """ Mix-in class for testing ambiguous times """
+ def gettz(self, tzname):
+ raise NotImplementedError
+
+ def _get_tzname(self, tzname):
+ return tzname
+
+ def _gettz_context(self, tzname):
+ return context_passthrough()
+
+ def testFoldPositiveUTCOffset(self):
+ # Test that we can resolve ambiguous times
+ tzname = self._get_tzname('Australia/Sydney')
+
+ with self._gettz_context(tzname):
+ SYD = self.gettz(tzname)
+
+ t0_u = datetime(2012, 3, 31, 15, 30, tzinfo=tz.UTC) # AEST
+ t1_u = datetime(2012, 3, 31, 16, 30, tzinfo=tz.UTC) # AEDT
+
+ t0_syd0 = t0_u.astimezone(SYD)
+ t1_syd1 = t1_u.astimezone(SYD)
+
+ self.assertEqual(t0_syd0.replace(tzinfo=None),
+ datetime(2012, 4, 1, 2, 30))
+
+ self.assertEqual(t1_syd1.replace(tzinfo=None),
+ datetime(2012, 4, 1, 2, 30))
+
+ self.assertEqual(t0_syd0.utcoffset(), timedelta(hours=11))
+ self.assertEqual(t1_syd1.utcoffset(), timedelta(hours=10))
+
+ def testGapPositiveUTCOffset(self):
+ # Test that we don't have a problem around gaps.
+ tzname = self._get_tzname('Australia/Sydney')
+
+ with self._gettz_context(tzname):
+ SYD = self.gettz(tzname)
+
+ t0_u = datetime(2012, 10, 6, 15, 30, tzinfo=tz.UTC) # AEST
+ t1_u = datetime(2012, 10, 6, 16, 30, tzinfo=tz.UTC) # AEDT
+
+ t0 = t0_u.astimezone(SYD)
+ t1 = t1_u.astimezone(SYD)
+
+ self.assertEqual(t0.replace(tzinfo=None),
+ datetime(2012, 10, 7, 1, 30))
+
+ self.assertEqual(t1.replace(tzinfo=None),
+ datetime(2012, 10, 7, 3, 30))
+
+ self.assertEqual(t0.utcoffset(), timedelta(hours=10))
+ self.assertEqual(t1.utcoffset(), timedelta(hours=11))
+
+ def testFoldNegativeUTCOffset(self):
+ # Test that we can resolve ambiguous times
+ tzname = self._get_tzname('America/Toronto')
+
+ with self._gettz_context(tzname):
+ TOR = self.gettz(tzname)
+
+ t0_u = datetime(2011, 11, 6, 5, 30, tzinfo=tz.UTC)
+ t1_u = datetime(2011, 11, 6, 6, 30, tzinfo=tz.UTC)
+
+ t0_tor = t0_u.astimezone(TOR)
+ t1_tor = t1_u.astimezone(TOR)
+
+ self.assertEqual(t0_tor.replace(tzinfo=None),
+ datetime(2011, 11, 6, 1, 30))
+
+ self.assertEqual(t1_tor.replace(tzinfo=None),
+ datetime(2011, 11, 6, 1, 30))
+
+ self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname())
+ self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0))
+ self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0))
+
+ def testGapNegativeUTCOffset(self):
+ # Test that we don't have a problem around gaps.
+ tzname = self._get_tzname('America/Toronto')
+
+ with self._gettz_context(tzname):
+ TOR = self.gettz(tzname)
+
+ t0_u = datetime(2011, 3, 13, 6, 30, tzinfo=tz.UTC)
+ t1_u = datetime(2011, 3, 13, 7, 30, tzinfo=tz.UTC)
+
+ t0 = t0_u.astimezone(TOR)
+ t1 = t1_u.astimezone(TOR)
+
+ self.assertEqual(t0.replace(tzinfo=None),
+ datetime(2011, 3, 13, 1, 30))
+
+ self.assertEqual(t1.replace(tzinfo=None),
+ datetime(2011, 3, 13, 3, 30))
+
+ self.assertNotEqual(t0, t1)
+ self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0))
+ self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0))
+
+ def testFoldLondon(self):
+ tzname = self._get_tzname('Europe/London')
+
+ with self._gettz_context(tzname):
+ LON = self.gettz(tzname)
+ UTC = tz.UTC
+
+ t0_u = datetime(2013, 10, 27, 0, 30, tzinfo=UTC) # BST
+ t1_u = datetime(2013, 10, 27, 1, 30, tzinfo=UTC) # GMT
+
+ t0 = t0_u.astimezone(LON)
+ t1 = t1_u.astimezone(LON)
+
+ self.assertEqual(t0.replace(tzinfo=None),
+ datetime(2013, 10, 27, 1, 30))
+
+ self.assertEqual(t1.replace(tzinfo=None),
+ datetime(2013, 10, 27, 1, 30))
+
+ self.assertEqual(t0.utcoffset(), timedelta(hours=1))
+ self.assertEqual(t1.utcoffset(), timedelta(hours=0))
+
+ def testFoldIndependence(self):
+ tzname = self._get_tzname('America/New_York')
+
+ with self._gettz_context(tzname):
+ NYC = self.gettz(tzname)
+ UTC = tz.UTC
+ hour = timedelta(hours=1)
+
+ # Firmly 2015-11-01 0:30 EDT-4
+ pre_dst = datetime(2015, 11, 1, 0, 30, tzinfo=NYC)
+
+ # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5
+ in_dst = pre_dst + hour
+ in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT
+
+ # Doing the arithmetic in UTC creates a date that is unambiguously
+ # 2015-11-01 1:30 EDT-5
+ in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC)
+
+ # Make sure the dates are actually ambiguous
+ self.assertEqual(in_dst, in_dst_via_utc)
+
+ # Make sure we got the right folding behavior
+ self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0)
+
+ # Now check to make sure in_dst's tzname hasn't changed
+ self.assertEqual(in_dst_tzname_0, in_dst.tzname())
+
+ def testInZoneFoldEquality(self):
+ # Two datetimes in the same zone are considered to be equal if their
+ # wall times are equal, even if they have different absolute times.
+
+ tzname = self._get_tzname('America/New_York')
+
+ with self._gettz_context(tzname):
+ NYC = self.gettz(tzname)
+ UTC = tz.UTC
+
+ dt0 = datetime(2011, 11, 6, 1, 30, tzinfo=NYC)
+ dt1 = tz.enfold(dt0, fold=1)
+
+ # Make sure these actually represent different times
+ self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC))
+
+ # Test that they compare equal
+ self.assertEqual(dt0, dt1)
+
+ def _test_ambiguous_time(self, dt, tzid, ambiguous):
+ # This is a test to check that the individual is_ambiguous values
+ # on the _tzinfo subclasses work.
+ tzname = self._get_tzname(tzid)
+
+ with self._gettz_context(tzname):
+ tzi = self.gettz(tzname)
+
+ self.assertEqual(tz.datetime_ambiguous(dt, tz=tzi), ambiguous)
+
+ def testAmbiguousNegativeUTCOffset(self):
+ self._test_ambiguous_time(datetime(2015, 11, 1, 1, 30),
+ 'America/New_York', True)
+
+ def testAmbiguousPositiveUTCOffset(self):
+ self._test_ambiguous_time(datetime(2012, 4, 1, 2, 30),
+ 'Australia/Sydney', True)
+
+ def testUnambiguousNegativeUTCOffset(self):
+ self._test_ambiguous_time(datetime(2015, 11, 1, 2, 30),
+ 'America/New_York', False)
+
+ def testUnambiguousPositiveUTCOffset(self):
+ self._test_ambiguous_time(datetime(2012, 4, 1, 3, 30),
+ 'Australia/Sydney', False)
+
+ def testUnambiguousGapNegativeUTCOffset(self):
+ # Imaginary time
+ self._test_ambiguous_time(datetime(2011, 3, 13, 2, 30),
+ 'America/New_York', False)
+
+ def testUnambiguousGapPositiveUTCOffset(self):
+ # Imaginary time
+ self._test_ambiguous_time(datetime(2012, 10, 7, 2, 30),
+ 'Australia/Sydney', False)
+
+ def _test_imaginary_time(self, dt, tzid, exists):
+ tzname = self._get_tzname(tzid)
+ with self._gettz_context(tzname):
+ tzi = self.gettz(tzname)
+
+ self.assertEqual(tz.datetime_exists(dt, tz=tzi), exists)
+
+ def testImaginaryNegativeUTCOffset(self):
+ self._test_imaginary_time(datetime(2011, 3, 13, 2, 30),
+ 'America/New_York', False)
+
+ def testNotImaginaryNegativeUTCOffset(self):
+ self._test_imaginary_time(datetime(2011, 3, 13, 1, 30),
+ 'America/New_York', True)
+
+ def testImaginaryPositiveUTCOffset(self):
+ self._test_imaginary_time(datetime(2012, 10, 7, 2, 30),
+ 'Australia/Sydney', False)
+
+ def testNotImaginaryPositiveUTCOffset(self):
+ self._test_imaginary_time(datetime(2012, 10, 7, 1, 30),
+ 'Australia/Sydney', True)
+
+ def testNotImaginaryFoldNegativeUTCOffset(self):
+ self._test_imaginary_time(datetime(2015, 11, 1, 1, 30),
+ 'America/New_York', True)
+
+ def testNotImaginaryFoldPositiveUTCOffset(self):
+ self._test_imaginary_time(datetime(2012, 4, 1, 3, 30),
+ 'Australia/Sydney', True)
+
+ @unittest.skip("Known failure in Python 3.6.")
+ def testEqualAmbiguousComparison(self):
+ tzname = self._get_tzname('Australia/Sydney')
+
+ with self._gettz_context(tzname):
+ SYD0 = self.gettz(tzname)
+ SYD1 = self.gettz(tzname)
+
+ t0_u = datetime(2012, 3, 31, 14, 30, tzinfo=tz.UTC) # AEST
+
+ t0_syd0 = t0_u.astimezone(SYD0)
+ t0_syd1 = t0_u.astimezone(SYD1)
+
+ # This is considered an "inter-zone comparison" because it's an
+ # ambiguous datetime.
+ self.assertEqual(t0_syd0, t0_syd1)
+
+
+class TzWinFoldMixin(object):
+ def get_args(self, tzname):
+ return (tzname, )
+
+ class context(object):
+ def __init__(*args, **kwargs):
+ pass
+
+ def __enter__(*args, **kwargs):
+ pass
+
+ def __exit__(*args, **kwargs):
+ pass
+
+ def get_utc_transitions(self, tzi, year, gap):
+ dston, dstoff = tzi.transitions(year)
+ if gap:
+ t_n = dston - timedelta(minutes=30)
+
+ t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.UTC)
+ t1_u = t0_u + timedelta(hours=1)
+ else:
+ # Get 1 hour before the first ambiguous date
+ t_n = dstoff - timedelta(minutes=30)
+
+ t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.UTC)
+ t_n += timedelta(hours=1) # Naive ambiguous date
+ t0_u = t0_u + timedelta(hours=1) # First ambiguous date
+ t1_u = t0_u + timedelta(hours=1) # Second ambiguous date
+
+ return t_n, t0_u, t1_u
+
+ def testFoldPositiveUTCOffset(self):
+ # Test that we can resolve ambiguous times
+ tzname = 'AUS Eastern Standard Time'
+ args = self.get_args(tzname)
+
+ with self.context(tzname):
+ # Calling fromutc() alters the tzfile object
+ SYD = self.tzclass(*args)
+
+ # Get the transition time in UTC from the object, because
+ # Windows doesn't store historical info
+ t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, False)
+
+ # Using fresh tzfiles
+ t0_syd = t0_u.astimezone(SYD)
+ t1_syd = t1_u.astimezone(SYD)
+
+ self.assertEqual(t0_syd.replace(tzinfo=None), t_n)
+
+ self.assertEqual(t1_syd.replace(tzinfo=None), t_n)
+
+ self.assertEqual(t0_syd.utcoffset(), timedelta(hours=11))
+ self.assertEqual(t1_syd.utcoffset(), timedelta(hours=10))
+ self.assertNotEqual(t0_syd.tzname(), t1_syd.tzname())
+
+ def testGapPositiveUTCOffset(self):
+ # Test that we don't have a problem around gaps.
+ tzname = 'AUS Eastern Standard Time'
+ args = self.get_args(tzname)
+
+ with self.context(tzname):
+ SYD = self.tzclass(*args)
+
+ t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, True)
+
+ t0 = t0_u.astimezone(SYD)
+ t1 = t1_u.astimezone(SYD)
+
+ self.assertEqual(t0.replace(tzinfo=None), t_n)
+
+ self.assertEqual(t1.replace(tzinfo=None), t_n + timedelta(hours=2))
+
+ self.assertEqual(t0.utcoffset(), timedelta(hours=10))
+ self.assertEqual(t1.utcoffset(), timedelta(hours=11))
+
+ def testFoldNegativeUTCOffset(self):
+ # Test that we can resolve ambiguous times
+ tzname = 'Eastern Standard Time'
+ args = self.get_args(tzname)
+
+ with self.context(tzname):
+ TOR = self.tzclass(*args)
+
+ t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, False)
+
+ t0_tor = t0_u.astimezone(TOR)
+ t1_tor = t1_u.astimezone(TOR)
+
+ self.assertEqual(t0_tor.replace(tzinfo=None), t_n)
+ self.assertEqual(t1_tor.replace(tzinfo=None), t_n)
+
+ self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname())
+ self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0))
+ self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0))
+
+ def testGapNegativeUTCOffset(self):
+ # Test that we don't have a problem around gaps.
+ tzname = 'Eastern Standard Time'
+ args = self.get_args(tzname)
+
+ with self.context(tzname):
+ TOR = self.tzclass(*args)
+
+ t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, True)
+
+ t0 = t0_u.astimezone(TOR)
+ t1 = t1_u.astimezone(TOR)
+
+ self.assertEqual(t0.replace(tzinfo=None),
+ t_n)
+
+ self.assertEqual(t1.replace(tzinfo=None),
+ t_n + timedelta(hours=2))
+
+ self.assertNotEqual(t0.tzname(), t1.tzname())
+ self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0))
+ self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0))
+
+ def testFoldIndependence(self):
+ tzname = 'Eastern Standard Time'
+ args = self.get_args(tzname)
+
+ with self.context(tzname):
+ NYC = self.tzclass(*args)
+ UTC = tz.UTC
+ hour = timedelta(hours=1)
+
+ # Firmly 2015-11-01 0:30 EDT-4
+ t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2015, False)
+
+ pre_dst = (t_n - hour).replace(tzinfo=NYC)
+
+ # Currently, there's no way around the fact that this resolves to an
+ # ambiguous date, which defaults to EST. I'm not hard-coding in the
+ # answer, though, because the preferred behavior would be that this
+ # results in a time on the EDT side.
+
+ # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5
+ in_dst = pre_dst + hour
+ in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT
+
+ # Doing the arithmetic in UTC creates a date that is unambiguously
+ # 2015-11-01 1:30 EDT-5
+ in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC)
+
+ # Make sure we got the right folding behavior
+ self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0)
+
+ # Now check to make sure in_dst's tzname hasn't changed
+ self.assertEqual(in_dst_tzname_0, in_dst.tzname())
+
+ def testInZoneFoldEquality(self):
+ # Two datetimes in the same zone are considered to be equal if their
+ # wall times are equal, even if they have different absolute times.
+ tzname = 'Eastern Standard Time'
+ args = self.get_args(tzname)
+
+ with self.context(tzname):
+ NYC = self.tzclass(*args)
+ UTC = tz.UTC
+
+ t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2011, False)
+
+ dt0 = t_n.replace(tzinfo=NYC)
+ dt1 = tz.enfold(dt0, fold=1)
+
+ # Make sure these actually represent different times
+ self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC))
+
+ # Test that they compare equal
+ self.assertEqual(dt0, dt1)
+
+###
+# Test Cases
+class TzUTCTest(unittest.TestCase):
+ def testSingleton(self):
+ UTC_0 = tz.tzutc()
+ UTC_1 = tz.tzutc()
+
+ self.assertIs(UTC_0, UTC_1)
+
+ def testOffset(self):
+ ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc())
+
+ self.assertEqual(ct.utcoffset(), timedelta(seconds=0))
+
+ def testDst(self):
+ ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc())
+
+ self.assertEqual(ct.dst(), timedelta(seconds=0))
+
+ def testTzName(self):
+ ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc())
+ self.assertEqual(ct.tzname(), 'UTC')
+
+ def testEquality(self):
+ UTC0 = tz.tzutc()
+ UTC1 = tz.tzutc()
+
+ self.assertEqual(UTC0, UTC1)
+
+ def testInequality(self):
+ UTC = tz.tzutc()
+ UTCp4 = tz.tzoffset('UTC+4', 14400)
+
+ self.assertNotEqual(UTC, UTCp4)
+
+ def testInequalityInteger(self):
+ self.assertFalse(tz.tzutc() == 7)
+ self.assertNotEqual(tz.tzutc(), 7)
+
+ def testInequalityUnsupported(self):
+ self.assertEqual(tz.tzutc(), ComparesEqual)
+
+ def testRepr(self):
+ UTC = tz.tzutc()
+ self.assertEqual(repr(UTC), 'tzutc()')
+
+ def testTimeOnlyUTC(self):
+ # https://github.com/dateutil/dateutil/issues/132
+ # tzutc doesn't care
+ tz_utc = tz.tzutc()
+ self.assertEqual(dt_time(13, 20, tzinfo=tz_utc).utcoffset(),
+ timedelta(0))
+
+ def testAmbiguity(self):
+ # Pick an arbitrary datetime, this should always return False.
+ dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzutc())
+
+ self.assertFalse(tz.datetime_ambiguous(dt))
+
+
+@pytest.mark.tzoffset
+class TzOffsetTest(unittest.TestCase):
+ def testTimedeltaOffset(self):
+ est = tz.tzoffset('EST', timedelta(hours=-5))
+ est_s = tz.tzoffset('EST', -18000)
+
+ self.assertEqual(est, est_s)
+
+ def testTzNameNone(self):
+ gmt5 = tz.tzoffset(None, -18000) # -5:00
+ self.assertIs(datetime(2003, 10, 26, 0, 0, tzinfo=gmt5).tzname(),
+ None)
+
+ def testTimeOnlyOffset(self):
+ # tzoffset doesn't care
+ tz_offset = tz.tzoffset('+3', 3600)
+ self.assertEqual(dt_time(13, 20, tzinfo=tz_offset).utcoffset(),
+ timedelta(seconds=3600))
+
+ def testTzOffsetRepr(self):
+ tname = 'EST'
+ tzo = tz.tzoffset(tname, -5 * 3600)
+ self.assertEqual(repr(tzo), "tzoffset(" + repr(tname) + ", -18000)")
+
+ def testEquality(self):
+ utc = tz.tzoffset('UTC', 0)
+ gmt = tz.tzoffset('GMT', 0)
+
+ self.assertEqual(utc, gmt)
+
+ def testUTCEquality(self):
+ utc = tz.UTC
+ o_utc = tz.tzoffset('UTC', 0)
+
+ self.assertEqual(utc, o_utc)
+ self.assertEqual(o_utc, utc)
+
+ def testInequalityInvalid(self):
+ tzo = tz.tzoffset('-3', -3 * 3600)
+ self.assertFalse(tzo == -3)
+ self.assertNotEqual(tzo, -3)
+
+ def testInequalityUnsupported(self):
+ tzo = tz.tzoffset('-5', -5 * 3600)
+
+ self.assertTrue(tzo == ComparesEqual)
+ self.assertFalse(tzo != ComparesEqual)
+ self.assertEqual(tzo, ComparesEqual)
+
+ def testAmbiguity(self):
+ # Pick an arbitrary datetime, this should always return False.
+ dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzoffset("EST", -5 * 3600))
+
+ self.assertFalse(tz.datetime_ambiguous(dt))
+
+ def testTzOffsetInstance(self):
+ tz1 = tz.tzoffset.instance('EST', timedelta(hours=-5))
+ tz2 = tz.tzoffset.instance('EST', timedelta(hours=-5))
+
+ assert tz1 is not tz2
+
+ def testTzOffsetSingletonDifferent(self):
+ tz1 = tz.tzoffset('EST', timedelta(hours=-5))
+ tz2 = tz.tzoffset('EST', -18000)
+
+ assert tz1 is tz2
+
+
+@pytest.mark.smoke
+@pytest.mark.tzoffset
+def test_tzoffset_weakref():
+ UTC1 = tz.tzoffset('UTC', 0)
+ UTC_ref = weakref.ref(tz.tzoffset('UTC', 0))
+ UTC1 is UTC_ref()
+ del UTC1
+ gc.collect()
+
+ assert UTC_ref() is not None # Should be in the strong cache
+ assert UTC_ref() is tz.tzoffset('UTC', 0)
+
+ # Fill the strong cache with other items
+ for offset in range(5,15):
+ tz.tzoffset('RandomZone', offset)
+
+ gc.collect()
+ assert UTC_ref() is None
+ assert UTC_ref() is not tz.tzoffset('UTC', 0)
+
+
+@pytest.mark.tzoffset
+@pytest.mark.parametrize('args', [
+ ('UTC', 0),
+ ('EST', -18000),
+ ('EST', timedelta(hours=-5)),
+ (None, timedelta(hours=3)),
+])
+def test_tzoffset_singleton(args):
+ tz1 = tz.tzoffset(*args)
+ tz2 = tz.tzoffset(*args)
+
+ assert tz1 is tz2
+
+
+@pytest.mark.tzoffset
+@pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS,
+ reason='Sub-minute offsets not supported')
+def test_tzoffset_sub_minute():
+ delta = timedelta(hours=12, seconds=30)
+ test_datetime = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta))
+ assert test_datetime.utcoffset() == delta
+
+
+@pytest.mark.tzoffset
+@pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS,
+ reason='Sub-minute offsets supported')
+def test_tzoffset_sub_minute_rounding():
+ delta = timedelta(hours=12, seconds=30)
+ test_date = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta))
+ assert test_date.utcoffset() == timedelta(hours=12, minutes=1)
+
+
+@pytest.mark.tzlocal
+class TzLocalTest(unittest.TestCase):
+ def testEquality(self):
+ tz1 = tz.tzlocal()
+ tz2 = tz.tzlocal()
+
+ # Explicitly calling == and != here to ensure the operators work
+ self.assertTrue(tz1 == tz2)
+ self.assertFalse(tz1 != tz2)
+
+ def testInequalityFixedOffset(self):
+ tzl = tz.tzlocal()
+ tzos = tz.tzoffset('LST', tzl._std_offset.total_seconds())
+ tzod = tz.tzoffset('LDT', tzl._std_offset.total_seconds())
+
+ self.assertFalse(tzl == tzos)
+ self.assertFalse(tzl == tzod)
+ self.assertTrue(tzl != tzos)
+ self.assertTrue(tzl != tzod)
+
+ def testInequalityInvalid(self):
+ tzl = tz.tzlocal()
+
+ self.assertTrue(tzl != 1)
+ self.assertFalse(tzl == 1)
+
+ # TODO: Use some sort of universal local mocking so that it's clear
+ # that we're expecting tzlocal to *not* be Pacific/Kiritimati
+ LINT = tz.gettz('Pacific/Kiritimati')
+ self.assertTrue(tzl != LINT)
+ self.assertFalse(tzl == LINT)
+
+ def testInequalityUnsupported(self):
+ tzl = tz.tzlocal()
+
+ self.assertTrue(tzl == ComparesEqual)
+ self.assertFalse(tzl != ComparesEqual)
+
+ def testRepr(self):
+ tzl = tz.tzlocal()
+
+ self.assertEqual(repr(tzl), 'tzlocal()')
+
+
+@pytest.mark.parametrize('args,kwargs', [
+ (('EST', -18000), {}),
+ (('EST', timedelta(hours=-5)), {}),
+ (('EST',), {'offset': -18000}),
+ (('EST',), {'offset': timedelta(hours=-5)}),
+ (tuple(), {'name': 'EST', 'offset': -18000})
+])
+def test_tzoffset_is(args, kwargs):
+ tz_ref = tz.tzoffset('EST', -18000)
+ assert tz.tzoffset(*args, **kwargs) is tz_ref
+
+
+def test_tzoffset_is_not():
+ assert tz.tzoffset('EDT', -14400) is not tz.tzoffset('EST', -18000)
+
+
+@pytest.mark.tzlocal
+@unittest.skipIf(IS_WIN, "requires Unix")
+class TzLocalNixTest(unittest.TestCase, TzFoldMixin):
+ # This is a set of tests for `tzlocal()` on *nix systems
+
+ # POSIX string indicating change to summer time on the 2nd Sunday in March
+ # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007)
+ TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2'
+
+ # POSIX string for AEST/AEDT (valid >= 2008)
+ TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3'
+
+ # POSIX string for BST/GMT
+ TZ_LON = 'GMT0BST,M3.5.0,M10.5.0'
+
+ # POSIX string for UTC
+ UTC = 'UTC'
+
+ def gettz(self, tzname):
+ # Actual time zone changes are handled by the _gettz_context function
+ return tz.tzlocal()
+
+ def _gettz_context(self, tzname):
+ tzname_map = {'Australia/Sydney': self.TZ_AEST,
+ 'America/Toronto': self.TZ_EST,
+ 'America/New_York': self.TZ_EST,
+ 'Europe/London': self.TZ_LON}
+
+ return TZEnvContext(tzname_map.get(tzname, tzname))
+
+ def _testTzFunc(self, tzval, func, std_val, dst_val):
+ """
+ This generates tests about how the behavior of a function ``func``
+ changes between STD and DST (e.g. utcoffset, tzname, dst).
+
+ It assume that DST starts the 2nd Sunday in March and ends the 1st
+ Sunday in November
+ """
+ with TZEnvContext(tzval):
+ dt1 = datetime(2015, 2, 1, 12, 0, tzinfo=tz.tzlocal()) # STD
+ dt2 = datetime(2015, 5, 1, 12, 0, tzinfo=tz.tzlocal()) # DST
+
+ self.assertEqual(func(dt1), std_val)
+ self.assertEqual(func(dt2), dst_val)
+
+ def _testTzName(self, tzval, std_name, dst_name):
+ func = datetime.tzname
+
+ self._testTzFunc(tzval, func, std_name, dst_name)
+
+ def testTzNameDST(self):
+ # Test tzname in a zone with DST
+ self._testTzName(self.TZ_EST, 'EST', 'EDT')
+
+ def testTzNameUTC(self):
+ # Test tzname in a zone without DST
+ self._testTzName(self.UTC, 'UTC', 'UTC')
+
+ def _testOffset(self, tzval, std_off, dst_off):
+ func = datetime.utcoffset
+
+ self._testTzFunc(tzval, func, std_off, dst_off)
+
+ def testOffsetDST(self):
+ self._testOffset(self.TZ_EST, timedelta(hours=-5), timedelta(hours=-4))
+
+ def testOffsetUTC(self):
+ self._testOffset(self.UTC, timedelta(0), timedelta(0))
+
+ def _testDST(self, tzval, dst_dst):
+ func = datetime.dst
+ std_dst = timedelta(0)
+
+ self._testTzFunc(tzval, func, std_dst, dst_dst)
+
+ def testDSTDST(self):
+ self._testDST(self.TZ_EST, timedelta(hours=1))
+
+ def testDSTUTC(self):
+ self._testDST(self.UTC, timedelta(0))
+
+ def testTimeOnlyOffsetLocalUTC(self):
+ with TZEnvContext(self.UTC):
+ self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(),
+ timedelta(0))
+
+ def testTimeOnlyOffsetLocalDST(self):
+ with TZEnvContext(self.TZ_EST):
+ self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(),
+ None)
+
+ def testTimeOnlyDSTLocalUTC(self):
+ with TZEnvContext(self.UTC):
+ self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(),
+ timedelta(0))
+
+ def testTimeOnlyDSTLocalDST(self):
+ with TZEnvContext(self.TZ_EST):
+ self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(),
+ None)
+
+ def testUTCEquality(self):
+ with TZEnvContext(self.UTC):
+ assert tz.tzlocal() == tz.UTC
+
+
+# TODO: Maybe a better hack than this?
+def mark_tzlocal_nix(f):
+ marks = [
+ pytest.mark.tzlocal,
+ pytest.mark.skipif(IS_WIN, reason='requires Unix'),
+ ]
+
+ for mark in reversed(marks):
+ f = mark(f)
+
+ return f
+
+
+@mark_tzlocal_nix
+@pytest.mark.parametrize('tzvar', ['UTC', 'GMT0', 'UTC0'])
+def test_tzlocal_utc_equal(tzvar):
+ with TZEnvContext(tzvar):
+ assert tz.tzlocal() == tz.UTC
+
+
+@mark_tzlocal_nix
+@pytest.mark.parametrize('tzvar', [
+ 'Europe/London', 'America/New_York',
+ 'GMT0BST', 'EST5EDT'])
+def test_tzlocal_utc_unequal(tzvar):
+ with TZEnvContext(tzvar):
+ assert tz.tzlocal() != tz.UTC
+
+
+@mark_tzlocal_nix
+def test_tzlocal_local_time_trim_colon():
+ with TZEnvContext(':/etc/localtime'):
+ assert tz.gettz() is not None
+
+
+@mark_tzlocal_nix
+@pytest.mark.parametrize('tzvar, tzoff', [
+ ('EST5', tz.tzoffset('EST', -18000)),
+ ('GMT0', tz.tzoffset('GMT', 0)),
+ ('YAKT-9', tz.tzoffset('YAKT', timedelta(hours=9))),
+ ('JST-9', tz.tzoffset('JST', timedelta(hours=9))),
+])
+def test_tzlocal_offset_equal(tzvar, tzoff):
+ with TZEnvContext(tzvar):
+ # Including both to test both __eq__ and __ne__
+ assert tz.tzlocal() == tzoff
+ assert not (tz.tzlocal() != tzoff)
+
+
+@mark_tzlocal_nix
+@pytest.mark.parametrize('tzvar, tzoff', [
+ ('EST5EDT', tz.tzoffset('EST', -18000)),
+ ('GMT0BST', tz.tzoffset('GMT', 0)),
+ ('EST5', tz.tzoffset('EST', -14400)),
+ ('YAKT-9', tz.tzoffset('JST', timedelta(hours=9))),
+ ('JST-9', tz.tzoffset('YAKT', timedelta(hours=9))),
+])
+def test_tzlocal_offset_unequal(tzvar, tzoff):
+ with TZEnvContext(tzvar):
+ # Including both to test both __eq__ and __ne__
+ assert tz.tzlocal() != tzoff
+ assert not (tz.tzlocal() == tzoff)
+
+
+@pytest.mark.gettz
+class GettzTest(unittest.TestCase, TzFoldMixin):
+ gettz = staticmethod(tz.gettz)
+
+ def testGettz(self):
+ # bug 892569
+ str(self.gettz('UTC'))
+
+ def testGetTzEquality(self):
+ self.assertEqual(self.gettz('UTC'), self.gettz('UTC'))
+
+ def testTimeOnlyGettz(self):
+ # gettz returns None
+ tz_get = self.gettz('Europe/Minsk')
+ self.assertIs(dt_time(13, 20, tzinfo=tz_get).utcoffset(), None)
+
+ def testTimeOnlyGettzDST(self):
+ # gettz returns None
+ tz_get = self.gettz('Europe/Minsk')
+ self.assertIs(dt_time(13, 20, tzinfo=tz_get).dst(), None)
+
+ def testTimeOnlyGettzTzName(self):
+ tz_get = self.gettz('Europe/Minsk')
+ self.assertIs(dt_time(13, 20, tzinfo=tz_get).tzname(), None)
+
+ def testTimeOnlyFormatZ(self):
+ tz_get = self.gettz('Europe/Minsk')
+ t = dt_time(13, 20, tzinfo=tz_get)
+
+ self.assertEqual(t.strftime('%H%M%Z'), '1320')
+
+ def testPortugalDST(self):
+ # In 1996, Portugal changed from CET to WET
+ PORTUGAL = self.gettz('Portugal')
+
+ t_cet = datetime(1996, 3, 31, 1, 59, tzinfo=PORTUGAL)
+
+ self.assertEqual(t_cet.tzname(), 'CET')
+ self.assertEqual(t_cet.utcoffset(), timedelta(hours=1))
+ self.assertEqual(t_cet.dst(), timedelta(0))
+
+ t_west = datetime(1996, 3, 31, 2, 1, tzinfo=PORTUGAL)
+
+ self.assertEqual(t_west.tzname(), 'WEST')
+ self.assertEqual(t_west.utcoffset(), timedelta(hours=1))
+ self.assertEqual(t_west.dst(), timedelta(hours=1))
+
+ def testGettzCacheTzFile(self):
+ NYC1 = tz.gettz('America/New_York')
+ NYC2 = tz.gettz('America/New_York')
+
+ assert NYC1 is NYC2
+
+ def testGettzCacheTzLocal(self):
+ local1 = tz.gettz()
+ local2 = tz.gettz()
+
+ assert local1 is not local2
+
+
+@pytest.mark.gettz
+def test_gettz_same_result_for_none_and_empty_string():
+ local_from_none = tz.gettz()
+ local_from_empty_string = tz.gettz("")
+ assert local_from_none is not None
+ assert local_from_empty_string is not None
+ assert local_from_none == local_from_empty_string
+
+
+@pytest.mark.gettz
+@pytest.mark.parametrize('badzone', [
+ 'Fake.Region/Abcdefghijklmnop', # Violates several tz project name rules
+])
+def test_gettz_badzone(badzone):
+ # Make sure passing a bad TZ string to gettz returns None (GH #800)
+ tzi = tz.gettz(badzone)
+ assert tzi is None
+
+
+@pytest.mark.gettz
+def test_gettz_badzone_unicode():
+ # Make sure a unicode string can be passed to TZ (GH #802)
+ # When fixed, combine this with test_gettz_badzone
+ tzi = tz.gettz('🐼')
+ assert tzi is None
+
+
+@pytest.mark.gettz
+@pytest.mark.parametrize(
+ "badzone,exc_reason",
+ [
+ pytest.param(
+ b"America/New_York",
+ ".*should be str, not bytes.*",
+ id="bytes on Python 3",
+ marks=[
+ pytest.mark.skipif(
+ PY2, reason="bytes arguments accepted in Python 2"
+ )
+ ],
+ ),
+ pytest.param(
+ object(),
+ None,
+ id="no startswith()",
+ marks=[
+ pytest.mark.xfail(reason="AttributeError instead of TypeError",
+ raises=AttributeError),
+ ],
+ ),
+ ],
+)
+def test_gettz_zone_wrong_type(badzone, exc_reason):
+ with pytest.raises(TypeError, match=exc_reason):
+ tz.gettz(badzone)
+
+
+@pytest.mark.gettz
+@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached')
+def test_gettz_cache_clear():
+ NYC1 = tz.gettz('America/New_York')
+ tz.gettz.cache_clear()
+
+ NYC2 = tz.gettz('America/New_York')
+
+ assert NYC1 is not NYC2
+
+@pytest.mark.gettz
+@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached')
+def test_gettz_set_cache_size():
+ tz.gettz.cache_clear()
+ tz.gettz.set_cache_size(3)
+
+ MONACO_ref = weakref.ref(tz.gettz('Europe/Monaco'))
+ EASTER_ref = weakref.ref(tz.gettz('Pacific/Easter'))
+ CURRIE_ref = weakref.ref(tz.gettz('Australia/Currie'))
+
+ gc.collect()
+
+ assert MONACO_ref() is not None
+ assert EASTER_ref() is not None
+ assert CURRIE_ref() is not None
+
+ tz.gettz.set_cache_size(2)
+ gc.collect()
+
+ assert MONACO_ref() is None
+
+@pytest.mark.xfail(IS_WIN, reason="Windows does not use system zoneinfo")
+@pytest.mark.smoke
+@pytest.mark.gettz
+def test_gettz_weakref():
+ tz.gettz.cache_clear()
+ tz.gettz.set_cache_size(2)
+ NYC1 = tz.gettz('America/New_York')
+ NYC_ref = weakref.ref(tz.gettz('America/New_York'))
+
+ assert NYC1 is NYC_ref()
+
+ del NYC1
+ gc.collect()
+
+ assert NYC_ref() is not None # Should still be in the strong cache
+ assert tz.gettz('America/New_York') is NYC_ref()
+
+ # Populate strong cache with other timezones
+ tz.gettz('Europe/Monaco')
+ tz.gettz('Pacific/Easter')
+ tz.gettz('Australia/Currie')
+
+ gc.collect()
+ assert NYC_ref() is None # Should have been pushed out
+ assert tz.gettz('America/New_York') is not NYC_ref()
+
+class ZoneInfoGettzTest(GettzTest):
+ def gettz(self, name):
+ zoneinfo_file = zoneinfo.get_zonefile_instance()
+ return zoneinfo_file.get(name)
+
+ def testZoneInfoFileStart1(self):
+ tz = self.gettz("EST5EDT")
+ self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST",
+ MISSING_TARBALL)
+ self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT")
+
+ def testZoneInfoFileEnd1(self):
+ tzc = self.gettz("EST5EDT")
+ self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(),
+ "EDT", MISSING_TARBALL)
+
+ end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc), fold=1)
+ self.assertEqual(end_est.tzname(), "EST")
+
+ def testZoneInfoOffsetSignal(self):
+ utc = self.gettz("UTC")
+ nyc = self.gettz("America/New_York")
+ self.assertNotEqual(utc, None, MISSING_TARBALL)
+ self.assertNotEqual(nyc, None)
+ t0 = datetime(2007, 11, 4, 0, 30, tzinfo=nyc)
+ t1 = t0.astimezone(utc)
+ t2 = t1.astimezone(nyc)
+ self.assertEqual(t0, t2)
+ self.assertEqual(nyc.dst(t0), timedelta(hours=1))
+
+ def testZoneInfoCopy(self):
+ # copy.copy() called on a ZoneInfo file was returning the same instance
+ CHI = self.gettz('America/Chicago')
+ CHI_COPY = copy.copy(CHI)
+
+ self.assertIsNot(CHI, CHI_COPY)
+ self.assertEqual(CHI, CHI_COPY)
+
+ def testZoneInfoDeepCopy(self):
+ CHI = self.gettz('America/Chicago')
+ CHI_COPY = copy.deepcopy(CHI)
+
+ self.assertIsNot(CHI, CHI_COPY)
+ self.assertEqual(CHI, CHI_COPY)
+
+ def testZoneInfoInstanceCaching(self):
+ zif_0 = zoneinfo.get_zonefile_instance()
+ zif_1 = zoneinfo.get_zonefile_instance()
+
+ self.assertIs(zif_0, zif_1)
+
+ def testZoneInfoNewInstance(self):
+ zif_0 = zoneinfo.get_zonefile_instance()
+ zif_1 = zoneinfo.get_zonefile_instance(new_instance=True)
+ zif_2 = zoneinfo.get_zonefile_instance()
+
+ self.assertIsNot(zif_0, zif_1)
+ self.assertIs(zif_1, zif_2)
+
+ def testZoneInfoDeprecated(self):
+ with pytest.warns(DeprecationWarning):
+ zoneinfo.gettz('US/Eastern')
+
+ def testZoneInfoMetadataDeprecated(self):
+ with pytest.warns(DeprecationWarning):
+ zoneinfo.gettz_db_metadata()
+
+
+class TZRangeTest(unittest.TestCase, TzFoldMixin):
+ TZ_EST = tz.tzrange('EST', timedelta(hours=-5),
+ 'EDT', timedelta(hours=-4),
+ start=relativedelta(month=3, day=1, hour=2,
+ weekday=SU(+2)),
+ end=relativedelta(month=11, day=1, hour=1,
+ weekday=SU(+1)))
+
+ TZ_AEST = tz.tzrange('AEST', timedelta(hours=10),
+ 'AEDT', timedelta(hours=11),
+ start=relativedelta(month=10, day=1, hour=2,
+ weekday=SU(+1)),
+ end=relativedelta(month=4, day=1, hour=2,
+ weekday=SU(+1)))
+
+ TZ_LON = tz.tzrange('GMT', timedelta(hours=0),
+ 'BST', timedelta(hours=1),
+ start=relativedelta(month=3, day=31, weekday=SU(-1),
+ hours=2),
+ end=relativedelta(month=10, day=31, weekday=SU(-1),
+ hours=1))
+ # POSIX string for UTC
+ UTC = 'UTC'
+
+ def gettz(self, tzname):
+ tzname_map = {'Australia/Sydney': self.TZ_AEST,
+ 'America/Toronto': self.TZ_EST,
+ 'America/New_York': self.TZ_EST,
+ 'Europe/London': self.TZ_LON}
+
+ return tzname_map[tzname]
+
+ def testRangeCmp1(self):
+ self.assertEqual(tz.tzstr("EST5EDT"),
+ tz.tzrange("EST", -18000, "EDT", -14400,
+ relativedelta(hours=+2,
+ month=4, day=1,
+ weekday=SU(+1)),
+ relativedelta(hours=+1,
+ month=10, day=31,
+ weekday=SU(-1))))
+
+ def testRangeCmp2(self):
+ self.assertEqual(tz.tzstr("EST5EDT"),
+ tz.tzrange("EST", -18000, "EDT"))
+
+ def testRangeOffsets(self):
+ TZR = tz.tzrange('EST', -18000, 'EDT', -14400,
+ start=relativedelta(hours=2, month=4, day=1,
+ weekday=SU(+2)),
+ end=relativedelta(hours=1, month=10, day=31,
+ weekday=SU(-1)))
+
+ dt_std = datetime(2014, 4, 11, 12, 0, tzinfo=TZR) # STD
+ dt_dst = datetime(2016, 4, 11, 12, 0, tzinfo=TZR) # DST
+
+ dst_zero = timedelta(0)
+ dst_hour = timedelta(hours=1)
+
+ std_offset = timedelta(hours=-5)
+ dst_offset = timedelta(hours=-4)
+
+ # Check dst()
+ self.assertEqual(dt_std.dst(), dst_zero)
+ self.assertEqual(dt_dst.dst(), dst_hour)
+
+ # Check utcoffset()
+ self.assertEqual(dt_std.utcoffset(), std_offset)
+ self.assertEqual(dt_dst.utcoffset(), dst_offset)
+
+ # Check tzname
+ self.assertEqual(dt_std.tzname(), 'EST')
+ self.assertEqual(dt_dst.tzname(), 'EDT')
+
+ def testTimeOnlyRangeFixed(self):
+ # This is a fixed-offset zone, so tzrange allows this
+ tz_range = tz.tzrange('dflt', stdoffset=timedelta(hours=-3))
+ self.assertEqual(dt_time(13, 20, tzinfo=tz_range).utcoffset(),
+ timedelta(hours=-3))
+
+ def testTimeOnlyRange(self):
+ # tzrange returns None because this zone has DST
+ tz_range = tz.tzrange('EST', timedelta(hours=-5),
+ 'EDT', timedelta(hours=-4))
+ self.assertIs(dt_time(13, 20, tzinfo=tz_range).utcoffset(), None)
+
+ def testBrokenIsDstHandling(self):
+ # tzrange._isdst() was using a date() rather than a datetime().
+ # Issue reported by Lennart Regebro.
+ dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.UTC)
+ self.assertEqual(dt.astimezone(tz=tz.gettz("GMT+2")),
+ datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2")))
+
+ def testRangeTimeDelta(self):
+ # Test that tzrange can be specified with a timedelta instead of an int.
+ EST5EDT_td = tz.tzrange('EST', timedelta(hours=-5),
+ 'EDT', timedelta(hours=-4))
+
+ EST5EDT_sec = tz.tzrange('EST', -18000,
+ 'EDT', -14400)
+
+ self.assertEqual(EST5EDT_td, EST5EDT_sec)
+
+ def testRangeEquality(self):
+ TZR1 = tz.tzrange('EST', -18000, 'EDT', -14400)
+
+ # Standard abbreviation different
+ TZR2 = tz.tzrange('ET', -18000, 'EDT', -14400)
+ self.assertNotEqual(TZR1, TZR2)
+
+ # DST abbreviation different
+ TZR3 = tz.tzrange('EST', -18000, 'EMT', -14400)
+ self.assertNotEqual(TZR1, TZR3)
+
+ # STD offset different
+ TZR4 = tz.tzrange('EST', -14000, 'EDT', -14400)
+ self.assertNotEqual(TZR1, TZR4)
+
+ # DST offset different
+ TZR5 = tz.tzrange('EST', -18000, 'EDT', -18000)
+ self.assertNotEqual(TZR1, TZR5)
+
+ # Start delta different
+ TZR6 = tz.tzrange('EST', -18000, 'EDT', -14400,
+ start=relativedelta(hours=+1, month=3,
+ day=1, weekday=SU(+2)))
+ self.assertNotEqual(TZR1, TZR6)
+
+ # End delta different
+ TZR7 = tz.tzrange('EST', -18000, 'EDT', -14400,
+ end=relativedelta(hours=+1, month=11,
+ day=1, weekday=SU(+2)))
+ self.assertNotEqual(TZR1, TZR7)
+
+ def testRangeInequalityUnsupported(self):
+ TZR = tz.tzrange('EST', -18000, 'EDT', -14400)
+
+ self.assertFalse(TZR == 4)
+ self.assertTrue(TZR == ComparesEqual)
+ self.assertFalse(TZR != ComparesEqual)
+
+
+@pytest.mark.tzstr
+class TZStrTest(unittest.TestCase, TzFoldMixin):
+ # POSIX string indicating change to summer time on the 2nd Sunday in March
+ # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007)
+ TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2'
+
+ # POSIX string for AEST/AEDT (valid >= 2008)
+ TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3'
+
+ # POSIX string for GMT/BST
+ TZ_LON = 'GMT0BST,M3.5.0,M10.5.0'
+
+ def gettz(self, tzname):
+ # Actual time zone changes are handled by the _gettz_context function
+ tzname_map = {'Australia/Sydney': self.TZ_AEST,
+ 'America/Toronto': self.TZ_EST,
+ 'America/New_York': self.TZ_EST,
+ 'Europe/London': self.TZ_LON}
+
+ return tz.tzstr(tzname_map[tzname])
+
+ def testStrStr(self):
+ # Test that tz.tzstr() won't throw an error if given a str instead
+ # of a unicode literal.
+ self.assertEqual(datetime(2003, 4, 6, 1, 59,
+ tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EST")
+ self.assertEqual(datetime(2003, 4, 6, 2, 00,
+ tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EDT")
+
+ def testStrInequality(self):
+ TZS1 = tz.tzstr('EST5EDT4')
+
+ # Standard abbreviation different
+ TZS2 = tz.tzstr('ET5EDT4')
+ self.assertNotEqual(TZS1, TZS2)
+
+ # DST abbreviation different
+ TZS3 = tz.tzstr('EST5EMT')
+ self.assertNotEqual(TZS1, TZS3)
+
+ # STD offset different
+ TZS4 = tz.tzstr('EST4EDT4')
+ self.assertNotEqual(TZS1, TZS4)
+
+ # DST offset different
+ TZS5 = tz.tzstr('EST5EDT3')
+ self.assertNotEqual(TZS1, TZS5)
+
+ def testStrInequalityStartEnd(self):
+ TZS1 = tz.tzstr('EST5EDT4')
+
+ # Start delta different
+ TZS2 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M10-5-0/02:00')
+ self.assertNotEqual(TZS1, TZS2)
+
+ # End delta different
+ TZS3 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M11-5-0/02:00')
+ self.assertNotEqual(TZS1, TZS3)
+
+ def testPosixOffset(self):
+ TZ1 = tz.tzstr('UTC-3')
+ self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ1).utcoffset(),
+ timedelta(hours=-3))
+
+ TZ2 = tz.tzstr('UTC-3', posix_offset=True)
+ self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ2).utcoffset(),
+ timedelta(hours=+3))
+
+ def testStrInequalityUnsupported(self):
+ TZS = tz.tzstr('EST5EDT')
+
+ self.assertFalse(TZS == 4)
+ self.assertTrue(TZS == ComparesEqual)
+ self.assertFalse(TZS != ComparesEqual)
+
+ def testTzStrRepr(self):
+ TZS1 = tz.tzstr('EST5EDT4')
+ TZS2 = tz.tzstr('EST')
+
+ self.assertEqual(repr(TZS1), "tzstr(" + repr('EST5EDT4') + ")")
+ self.assertEqual(repr(TZS2), "tzstr(" + repr('EST') + ")")
+
+ def testTzStrFailure(self):
+ with self.assertRaises(ValueError):
+ tz.tzstr('InvalidString;439999')
+
+ def testTzStrSingleton(self):
+ tz1 = tz.tzstr('EST5EDT')
+ tz2 = tz.tzstr('CST4CST')
+ tz3 = tz.tzstr('EST5EDT')
+
+ self.assertIsNot(tz1, tz2)
+ self.assertIs(tz1, tz3)
+
+ def testTzStrSingletonPosix(self):
+ tz_t1 = tz.tzstr('GMT+3', posix_offset=True)
+ tz_f1 = tz.tzstr('GMT+3', posix_offset=False)
+
+ tz_t2 = tz.tzstr('GMT+3', posix_offset=True)
+ tz_f2 = tz.tzstr('GMT+3', posix_offset=False)
+
+ self.assertIs(tz_t1, tz_t2)
+ self.assertIsNot(tz_t1, tz_f1)
+
+ self.assertIs(tz_f1, tz_f2)
+
+ def testTzStrInstance(self):
+ tz1 = tz.tzstr('EST5EDT')
+ tz2 = tz.tzstr.instance('EST5EDT')
+ tz3 = tz.tzstr.instance('EST5EDT')
+
+ assert tz1 is not tz2
+ assert tz2 is not tz3
+
+ # Ensure that these still are all the same zone
+ assert tz1 == tz2 == tz3
+
+
+@pytest.mark.smoke
+@pytest.mark.tzstr
+def test_tzstr_weakref():
+ tz_t1 = tz.tzstr('EST5EDT')
+ tz_t2_ref = weakref.ref(tz.tzstr('EST5EDT'))
+ assert tz_t1 is tz_t2_ref()
+
+ del tz_t1
+ gc.collect()
+
+ assert tz_t2_ref() is not None
+ assert tz.tzstr('EST5EDT') is tz_t2_ref()
+
+ for offset in range(5,15):
+ tz.tzstr('GMT+{}'.format(offset))
+ gc.collect()
+
+ assert tz_t2_ref() is None
+ assert tz.tzstr('EST5EDT') is not tz_t2_ref()
+
+
+@pytest.mark.tzstr
+@pytest.mark.parametrize('tz_str,expected', [
+ # From https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
+ ('', tz.tzrange(None)), # TODO: Should change this so tz.tzrange('') works
+ ('EST+5EDT,M3.2.0/2,M11.1.0/12',
+ tz.tzrange('EST', -18000, 'EDT', -14400,
+ start=relativedelta(month=3, day=1, weekday=SU(2), hours=2),
+ end=relativedelta(month=11, day=1, weekday=SU(1), hours=11))),
+ ('WART4WARST,J1/0,J365/25', # This is DST all year, Western Argentina Summer Time
+ tz.tzrange('WART', timedelta(hours=-4), 'WARST',
+ start=relativedelta(month=1, day=1, hours=0),
+ end=relativedelta(month=12, day=31, days=1))),
+ ('IST-2IDT,M3.4.4/26,M10.5.0', # Israel Standard / Daylight Time
+ tz.tzrange('IST', timedelta(hours=2), 'IDT',
+ start=relativedelta(month=3, day=1, weekday=TH(4), days=1, hours=2),
+ end=relativedelta(month=10, day=31, weekday=SU(-1), hours=1))),
+ ('WGT3WGST,M3.5.0/2,M10.5.0/1',
+ tz.tzrange('WGT', timedelta(hours=-3), 'WGST',
+ start=relativedelta(month=3, day=31, weekday=SU(-1), hours=2),
+ end=relativedelta(month=10, day=31, weekday=SU(-1), hours=0))),
+
+ # Different offset specifications
+ ('WGT0300WGST',
+ tz.tzrange('WGT', timedelta(hours=-3), 'WGST')),
+ ('WGT03:00WGST',
+ tz.tzrange('WGT', timedelta(hours=-3), 'WGST')),
+ ('AEST-1100AEDT',
+ tz.tzrange('AEST', timedelta(hours=11), 'AEDT')),
+ ('AEST-11:00AEDT',
+ tz.tzrange('AEST', timedelta(hours=11), 'AEDT')),
+
+ # Different time formats
+ ('EST5EDT,M3.2.0/4:00,M11.1.0/3:00',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT',
+ start=relativedelta(month=3, day=1, weekday=SU(2), hours=4),
+ end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))),
+ ('EST5EDT,M3.2.0/04:00,M11.1.0/03:00',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT',
+ start=relativedelta(month=3, day=1, weekday=SU(2), hours=4),
+ end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))),
+ ('EST5EDT,M3.2.0/0400,M11.1.0/0300',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT',
+ start=relativedelta(month=3, day=1, weekday=SU(2), hours=4),
+ end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))),
+])
+def test_valid_GNU_tzstr(tz_str, expected):
+ tzi = tz.tzstr(tz_str)
+
+ assert tzi == expected
+
+
+@pytest.mark.tzstr
+@pytest.mark.parametrize('tz_str, expected', [
+ ('EST5EDT,5,4,0,7200,11,3,0,7200',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT',
+ start=relativedelta(month=5, day=1, weekday=SU(+4), hours=+2),
+ end=relativedelta(month=11, day=1, weekday=SU(+3), hours=+1))),
+ ('EST5EDT,5,-4,0,7200,11,3,0,7200',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT',
+ start=relativedelta(hours=+2, month=5, day=31, weekday=SU(-4)),
+ end=relativedelta(hours=+1, month=11, day=1, weekday=SU(+3)))),
+ ('EST5EDT,5,4,0,7200,11,-3,0,7200',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT',
+ start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
+ end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
+ ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT',
+ start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
+ end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
+ ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT',
+ start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
+ end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
+ ('EST5EDT,5,4,0,7200,11,-3,0,7200,-3600',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-6),
+ start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
+ end=relativedelta(hours=+3, month=11, day=31, weekday=SU(-3)))),
+ ('EST5EDT,5,4,0,7200,11,-3,0,7200,+7200',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-3),
+ start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
+ end=relativedelta(hours=0, month=11, day=31, weekday=SU(-3)))),
+ ('EST5EDT,5,4,0,7200,11,-3,0,7200,+3600',
+ tz.tzrange('EST', timedelta(hours=-5), 'EDT',
+ start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
+ end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
+])
+def test_valid_dateutil_format(tz_str, expected):
+ # This tests the dateutil-specific format that is used widely in the tests
+ # and examples. It is unclear where this format originated from.
+ with pytest.warns(tz.DeprecatedTzFormatWarning):
+ tzi = tz.tzstr.instance(tz_str)
+
+ assert tzi == expected
+
+
+@pytest.mark.tzstr
+@pytest.mark.parametrize('tz_str', [
+ 'hdfiughdfuig,dfughdfuigpu87ñ::',
+ ',dfughdfuigpu87ñ::',
+ '-1:WART4WARST,J1,J365/25',
+ 'WART4WARST,J1,J365/-25',
+ 'IST-2IDT,M3.4.-1/26,M10.5.0',
+ 'IST-2IDT,M3,2000,1/26,M10,5,0'
+])
+def test_invalid_GNU_tzstr(tz_str):
+ with pytest.raises(ValueError):
+ tz.tzstr(tz_str)
+
+
+# Different representations of the same default rule set
+DEFAULT_TZSTR_RULES_EQUIV_2003 = [
+ 'EST5EDT',
+ 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00',
+ 'EST5EDT4,95/02:00:00,298/02:00',
+ 'EST5EDT4,J96/02:00:00,J299/02:00',
+ 'EST5EDT4,J96/02:00:00,J299/02'
+]
+
+
+@pytest.mark.tzstr
+@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003)
+def test_tzstr_default_start(tz_str):
+ tzi = tz.tzstr(tz_str)
+ dt_std = datetime(2003, 4, 6, 1, 59, tzinfo=tzi)
+ dt_dst = datetime(2003, 4, 6, 2, 00, tzinfo=tzi)
+
+ assert get_timezone_tuple(dt_std) == EST_TUPLE
+ assert get_timezone_tuple(dt_dst) == EDT_TUPLE
+
+
+@pytest.mark.tzstr
+@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003)
+def test_tzstr_default_end(tz_str):
+ tzi = tz.tzstr(tz_str)
+ dt_dst = datetime(2003, 10, 26, 0, 59, tzinfo=tzi)
+ dt_dst_ambig = datetime(2003, 10, 26, 1, 00, tzinfo=tzi)
+ dt_std_ambig = tz.enfold(dt_dst_ambig, fold=1)
+ dt_std = datetime(2003, 10, 26, 2, 00, tzinfo=tzi)
+
+ assert get_timezone_tuple(dt_dst) == EDT_TUPLE
+ assert get_timezone_tuple(dt_dst_ambig) == EDT_TUPLE
+ assert get_timezone_tuple(dt_std_ambig) == EST_TUPLE
+ assert get_timezone_tuple(dt_std) == EST_TUPLE
+
+
+@pytest.mark.tzstr
+@pytest.mark.parametrize('tzstr_1', ['EST5EDT',
+ 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00'])
+@pytest.mark.parametrize('tzstr_2', ['EST5EDT',
+ 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00'])
+def test_tzstr_default_cmp(tzstr_1, tzstr_2):
+ tz1 = tz.tzstr(tzstr_1)
+ tz2 = tz.tzstr(tzstr_2)
+
+ assert tz1 == tz2
+
+class TZICalTest(unittest.TestCase, TzFoldMixin):
+ def _gettz_str_tuple(self, tzname):
+ TZ_EST = (
+ 'BEGIN:VTIMEZONE',
+ 'TZID:US-Eastern',
+ 'BEGIN:STANDARD',
+ 'DTSTART:19971029T020000',
+ 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11',
+ 'TZOFFSETFROM:-0400',
+ 'TZOFFSETTO:-0500',
+ 'TZNAME:EST',
+ 'END:STANDARD',
+ 'BEGIN:DAYLIGHT',
+ 'DTSTART:19980301T020000',
+ 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03',
+ 'TZOFFSETFROM:-0500',
+ 'TZOFFSETTO:-0400',
+ 'TZNAME:EDT',
+ 'END:DAYLIGHT',
+ 'END:VTIMEZONE'
+ )
+
+ TZ_PST = (
+ 'BEGIN:VTIMEZONE',
+ 'TZID:US-Pacific',
+ 'BEGIN:STANDARD',
+ 'DTSTART:19971029T020000',
+ 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11',
+ 'TZOFFSETFROM:-0700',
+ 'TZOFFSETTO:-0800',
+ 'TZNAME:PST',
+ 'END:STANDARD',
+ 'BEGIN:DAYLIGHT',
+ 'DTSTART:19980301T020000',
+ 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03',
+ 'TZOFFSETFROM:-0800',
+ 'TZOFFSETTO:-0700',
+ 'TZNAME:PDT',
+ 'END:DAYLIGHT',
+ 'END:VTIMEZONE'
+ )
+
+ TZ_AEST = (
+ 'BEGIN:VTIMEZONE',
+ 'TZID:Australia-Sydney',
+ 'BEGIN:STANDARD',
+ 'DTSTART:19980301T030000',
+ 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=04',
+ 'TZOFFSETFROM:+1100',
+ 'TZOFFSETTO:+1000',
+ 'TZNAME:AEST',
+ 'END:STANDARD',
+ 'BEGIN:DAYLIGHT',
+ 'DTSTART:19971029T020000',
+ 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=10',
+ 'TZOFFSETFROM:+1000',
+ 'TZOFFSETTO:+1100',
+ 'TZNAME:AEDT',
+ 'END:DAYLIGHT',
+ 'END:VTIMEZONE'
+ )
+
+ TZ_LON = (
+ 'BEGIN:VTIMEZONE',
+ 'TZID:Europe-London',
+ 'BEGIN:STANDARD',
+ 'DTSTART:19810301T030000',
+ 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10;BYHOUR=02',
+ 'TZOFFSETFROM:+0100',
+ 'TZOFFSETTO:+0000',
+ 'TZNAME:GMT',
+ 'END:STANDARD',
+ 'BEGIN:DAYLIGHT',
+ 'DTSTART:19961001T030000',
+ 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=03;BYHOUR=01',
+ 'TZOFFSETFROM:+0000',
+ 'TZOFFSETTO:+0100',
+ 'TZNAME:BST',
+ 'END:DAYLIGHT',
+ 'END:VTIMEZONE'
+ )
+
+ tzname_map = {'Australia/Sydney': TZ_AEST,
+ 'America/Toronto': TZ_EST,
+ 'America/New_York': TZ_EST,
+ 'America/Los_Angeles': TZ_PST,
+ 'Europe/London': TZ_LON}
+
+ return tzname_map[tzname]
+
+ def _gettz_str(self, tzname):
+ return '\n'.join(self._gettz_str_tuple(tzname))
+
+ def _tzstr_dtstart_with_params(self, tzname, param_str):
+ # Adds parameters to the DTSTART values of a given tzstr
+ tz_str_tuple = self._gettz_str_tuple(tzname)
+
+ out_tz = []
+ for line in tz_str_tuple:
+ if line.startswith('DTSTART'):
+ name, value = line.split(':', 1)
+ line = name + ';' + param_str + ':' + value
+
+ out_tz.append(line)
+
+ return '\n'.join(out_tz)
+
+ def gettz(self, tzname):
+ tz_str = self._gettz_str(tzname)
+
+ tzc = tz.tzical(StringIO(tz_str)).get()
+
+ return tzc
+
+ def testRepr(self):
+ instr = StringIO(TZICAL_PST8PDT)
+ instr.name = 'StringIO(PST8PDT)'
+ tzc = tz.tzical(instr)
+
+ self.assertEqual(repr(tzc), "tzical(" + repr(instr.name) + ")")
+
+ # Test performance
+ def _test_us_zone(self, tzc, func, values, start):
+ if start:
+ dt1 = datetime(2003, 3, 9, 1, 59)
+ dt2 = datetime(2003, 3, 9, 2, 00)
+ fold = [0, 0]
+ else:
+ dt1 = datetime(2003, 11, 2, 0, 59)
+ dt2 = datetime(2003, 11, 2, 1, 00)
+ fold = [0, 1]
+
+ dts = (tz.enfold(dt.replace(tzinfo=tzc), fold=f)
+ for dt, f in zip((dt1, dt2), fold))
+
+ for value, dt in zip(values, dts):
+ self.assertEqual(func(dt), value)
+
+ def _test_multi_zones(self, tzstrs, tzids, func, values, start):
+ tzic = tz.tzical(StringIO('\n'.join(tzstrs)))
+ for tzid, vals in zip(tzids, values):
+ tzc = tzic.get(tzid)
+
+ self._test_us_zone(tzc, func, vals, start)
+
+ def _prepare_EST(self):
+ tz_str = self._gettz_str('America/New_York')
+ return tz.tzical(StringIO(tz_str)).get()
+
+ def _testEST(self, start, test_type, tzc=None):
+ if tzc is None:
+ tzc = self._prepare_EST()
+
+ argdict = {
+ 'name': (datetime.tzname, ('EST', 'EDT')),
+ 'offset': (datetime.utcoffset, (timedelta(hours=-5),
+ timedelta(hours=-4))),
+ 'dst': (datetime.dst, (timedelta(hours=0),
+ timedelta(hours=1)))
+ }
+
+ func, values = argdict[test_type]
+
+ if not start:
+ values = reversed(values)
+
+ self._test_us_zone(tzc, func, values, start=start)
+
+ def testESTStartName(self):
+ self._testEST(start=True, test_type='name')
+
+ def testESTEndName(self):
+ self._testEST(start=False, test_type='name')
+
+ def testESTStartOffset(self):
+ self._testEST(start=True, test_type='offset')
+
+ def testESTEndOffset(self):
+ self._testEST(start=False, test_type='offset')
+
+ def testESTStartDST(self):
+ self._testEST(start=True, test_type='dst')
+
+ def testESTEndDST(self):
+ self._testEST(start=False, test_type='dst')
+
+ def testESTValueDatetime(self):
+ # Violating one-test-per-test rule because we're not set up to do
+ # parameterized tests and the manual proliferation is getting a bit
+ # out of hand.
+ tz_str = self._tzstr_dtstart_with_params('America/New_York',
+ 'VALUE=DATE-TIME')
+
+ tzc = tz.tzical(StringIO(tz_str)).get()
+
+ for start in (True, False):
+ for test_type in ('name', 'offset', 'dst'):
+ self._testEST(start=start, test_type=test_type, tzc=tzc)
+
+ def _testMultizone(self, start, test_type):
+ tzstrs = (self._gettz_str('America/New_York'),
+ self._gettz_str('America/Los_Angeles'))
+ tzids = ('US-Eastern', 'US-Pacific')
+
+ argdict = {
+ 'name': (datetime.tzname, (('EST', 'EDT'),
+ ('PST', 'PDT'))),
+ 'offset': (datetime.utcoffset, ((timedelta(hours=-5),
+ timedelta(hours=-4)),
+ (timedelta(hours=-8),
+ timedelta(hours=-7)))),
+ 'dst': (datetime.dst, ((timedelta(hours=0),
+ timedelta(hours=1)),
+ (timedelta(hours=0),
+ timedelta(hours=1))))
+ }
+
+ func, values = argdict[test_type]
+
+ if not start:
+ values = map(reversed, values)
+
+ self._test_multi_zones(tzstrs, tzids, func, values, start)
+
+ def testMultiZoneStartName(self):
+ self._testMultizone(start=True, test_type='name')
+
+ def testMultiZoneEndName(self):
+ self._testMultizone(start=False, test_type='name')
+
+ def testMultiZoneStartOffset(self):
+ self._testMultizone(start=True, test_type='offset')
+
+ def testMultiZoneEndOffset(self):
+ self._testMultizone(start=False, test_type='offset')
+
+ def testMultiZoneStartDST(self):
+ self._testMultizone(start=True, test_type='dst')
+
+ def testMultiZoneEndDST(self):
+ self._testMultizone(start=False, test_type='dst')
+
+ def testMultiZoneKeys(self):
+ est_str = self._gettz_str('America/New_York')
+ pst_str = self._gettz_str('America/Los_Angeles')
+ tzic = tz.tzical(StringIO('\n'.join((est_str, pst_str))))
+
+ # Sort keys because they are in a random order, being dictionary keys
+ keys = sorted(tzic.keys())
+
+ self.assertEqual(keys, ['US-Eastern', 'US-Pacific'])
+
+ # Test error conditions
+ def testEmptyString(self):
+ with self.assertRaises(ValueError):
+ tz.tzical(StringIO(""))
+
+ def testMultiZoneGet(self):
+ tzic = tz.tzical(StringIO(TZICAL_EST5EDT + TZICAL_PST8PDT))
+
+ with self.assertRaises(ValueError):
+ tzic.get()
+
+ def testDtstartDate(self):
+ tz_str = self._tzstr_dtstart_with_params('America/New_York',
+ 'VALUE=DATE')
+ with self.assertRaises(ValueError):
+ tz.tzical(StringIO(tz_str))
+
+ def testDtstartTzid(self):
+ tz_str = self._tzstr_dtstart_with_params('America/New_York',
+ 'TZID=UTC')
+ with self.assertRaises(ValueError):
+ tz.tzical(StringIO(tz_str))
+
+ def testDtstartBadParam(self):
+ tz_str = self._tzstr_dtstart_with_params('America/New_York',
+ 'FOO=BAR')
+ with self.assertRaises(ValueError):
+ tz.tzical(StringIO(tz_str))
+
+ # Test Parsing
+ def testGap(self):
+ tzic = tz.tzical(StringIO('\n'.join((TZICAL_EST5EDT, TZICAL_PST8PDT))))
+
+ keys = sorted(tzic.keys())
+ self.assertEqual(keys, ['US-Eastern', 'US-Pacific'])
+
+
+class TZTest(unittest.TestCase):
+ def testFileStart1(self):
+ tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
+ self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzc).tzname(), "EST")
+ self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzc).tzname(), "EDT")
+
+ def testFileEnd1(self):
+ tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
+ self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(),
+ "EDT")
+ end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc))
+ self.assertEqual(end_est.tzname(), "EST")
+
+ def testFileLastTransition(self):
+ # After the last transition, it goes to standard time in perpetuity
+ tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
+ self.assertEqual(datetime(2037, 10, 25, 0, 59, tzinfo=tzc).tzname(),
+ "EDT")
+
+ last_date = tz.enfold(datetime(2037, 10, 25, 1, 00, tzinfo=tzc), fold=1)
+ self.assertEqual(last_date.tzname(),
+ "EST")
+
+ self.assertEqual(datetime(2038, 5, 25, 12, 0, tzinfo=tzc).tzname(),
+ "EST")
+
+ def testInvalidFile(self):
+ # Should throw a ValueError if an invalid file is passed
+ with self.assertRaises(ValueError):
+ tz.tzfile(BytesIO(b'BadFile'))
+
+ def testFilestreamWithNameRepr(self):
+ # If fileobj is a filestream with a "name" attribute this name should
+ # be reflected in the tz object's repr
+ fileobj = BytesIO(base64.b64decode(TZFILE_EST5EDT))
+ fileobj.name = 'foo'
+ tzc = tz.tzfile(fileobj)
+ self.assertEqual(repr(tzc), 'tzfile(' + repr('foo') + ')')
+
+ def testLeapCountDecodesProperly(self):
+ # This timezone has leapcnt, and failed to decode until
+ # Eugene Oden notified about the issue.
+
+ # As leap information is currently unused (and unstored) by tzfile() we
+ # can only indirectly test this: Take advantage of tzfile() not closing
+ # the input file if handed in as an opened file and assert that the
+ # full file content has been read by tzfile(). Note: For this test to
+ # work NEW_YORK must be in TZif version 1 format i.e. no more data
+ # after TZif v1 header + data has been read
+ fileobj = BytesIO(base64.b64decode(NEW_YORK))
+ tz.tzfile(fileobj)
+ # we expect no remaining file content now, i.e. zero-length; if there's
+ # still data we haven't read the file format correctly
+ remaining_tzfile_content = fileobj.read()
+ self.assertEqual(len(remaining_tzfile_content), 0)
+
+ def testIsStd(self):
+ # NEW_YORK tzfile contains this isstd information:
+ isstd_expected = (0, 0, 0, 1)
+ tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK)))
+ # gather the actual information as parsed by the tzfile class
+ isstd = []
+ for ttinfo in tzc._ttinfo_list:
+ # ttinfo objects contain boolean values
+ isstd.append(int(ttinfo.isstd))
+ # ttinfo list may contain more entries than isstd file content
+ isstd = tuple(isstd[:len(isstd_expected)])
+ self.assertEqual(
+ isstd_expected, isstd,
+ "isstd UTC/local indicators parsed: %s != tzfile contents: %s"
+ % (isstd, isstd_expected))
+
+ def testGMTHasNoDaylight(self):
+ # tz.tzstr("GMT+2") improperly considered daylight saving time.
+ # Issue reported by Lennart Regebro.
+ dt = datetime(2007, 8, 6, 4, 10)
+ self.assertEqual(tz.gettz("GMT+2").dst(dt), timedelta(0))
+
+ def testGMTOffset(self):
+ # GMT and UTC offsets have inverted signal when compared to the
+ # usual TZ variable handling.
+ dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.UTC)
+ self.assertEqual(dt.astimezone(tz=tz.tzstr("GMT+2")),
+ datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2")))
+ self.assertEqual(dt.astimezone(tz=tz.gettz("UTC-2")),
+ datetime(2007, 8, 6, 2, 10, tzinfo=tz.tzstr("UTC-2")))
+
+ @unittest.skipIf(IS_WIN, "requires Unix")
+ def testTZSetDoesntCorrupt(self):
+ # if we start in non-UTC then tzset UTC make sure parse doesn't get
+ # confused
+ with TZEnvContext('UTC'):
+ # this should parse to UTC timezone not the original timezone
+ dt = parse('2014-07-20T12:34:56+00:00')
+ self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00')
+
+
+@pytest.mark.tzfile
+@pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS,
+ reason='Sub-minute offsets not supported')
+def test_tzfile_sub_minute_offset():
+ # If user running python 3.6 or newer, exact offset is used
+ tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
+ offset = timedelta(hours=1, minutes=39, seconds=52)
+ assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset
+
+
+@pytest.mark.tzfile
+@pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS,
+ reason='Sub-minute offsets supported.')
+def test_sub_minute_rounding_tzfile():
+ # This timezone has an offset of 5992 seconds in 1900-01-01.
+ # For python version pre-3.6, this will be rounded
+ tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
+ offset = timedelta(hours=1, minutes=40)
+ assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset
+
+
+@pytest.mark.tzfile
+def test_samoa_transition():
+ # utcoffset() was erroneously returning +14:00 an hour early (GH #812)
+ APIA = tz.gettz('Pacific/Apia')
+ dt = datetime(2011, 12, 29, 23, 59, tzinfo=APIA)
+ assert dt.utcoffset() == timedelta(hours=-10)
+
+ # Make sure the transition actually works, too
+ dt_after = (dt.astimezone(tz.UTC) + timedelta(minutes=1)).astimezone(APIA)
+ assert dt_after == datetime(2011, 12, 31, tzinfo=APIA)
+ assert dt_after.utcoffset() == timedelta(hours=14)
+
+
+@unittest.skipUnless(IS_WIN, "Requires Windows")
+class TzWinTest(unittest.TestCase, TzWinFoldMixin):
+ def setUp(self):
+ self.tzclass = tzwin.tzwin
+
+ def testTzResLoadName(self):
+ # This may not work right on non-US locales.
+ tzr = tzwin.tzres()
+ self.assertEqual(tzr.load_name(112), "Eastern Standard Time")
+
+ def testTzResNameFromString(self):
+ tzr = tzwin.tzres()
+ self.assertEqual(tzr.name_from_string('@tzres.dll,-221'),
+ 'Alaskan Daylight Time')
+
+ self.assertEqual(tzr.name_from_string('Samoa Daylight Time'),
+ 'Samoa Daylight Time')
+
+ with self.assertRaises(ValueError):
+ tzr.name_from_string('@tzres.dll,100')
+
+ def testIsdstZoneWithNoDaylightSaving(self):
+ tz = tzwin.tzwin("UTC")
+ dt = parse("2013-03-06 19:08:15")
+ self.assertFalse(tz._isdst(dt))
+
+ def testOffset(self):
+ tz = tzwin.tzwin("Cape Verde Standard Time")
+ self.assertEqual(tz.utcoffset(datetime(1995, 5, 21, 12, 9, 13)),
+ timedelta(-1, 82800))
+
+ def testTzwinName(self):
+ # https://github.com/dateutil/dateutil/issues/143
+ tw = tz.tzwin('Eastern Standard Time')
+
+ # Cover the transitions for at least two years.
+ ESTs = 'Eastern Standard Time'
+ EDTs = 'Eastern Daylight Time'
+ transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs),
+ (datetime(2015, 3, 8, 3, 1), EDTs),
+ (datetime(2015, 11, 1, 0, 59), EDTs),
+ (datetime(2015, 11, 1, 3, 1), ESTs),
+ (datetime(2016, 3, 13, 0, 59), ESTs),
+ (datetime(2016, 3, 13, 3, 1), EDTs),
+ (datetime(2016, 11, 6, 0, 59), EDTs),
+ (datetime(2016, 11, 6, 3, 1), ESTs)]
+
+ for t_date, expected in transition_dates:
+ self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected)
+
+ def testTzwinRepr(self):
+ tw = tz.tzwin('Yakutsk Standard Time')
+ self.assertEqual(repr(tw), 'tzwin(' +
+ repr('Yakutsk Standard Time') + ')')
+
+ def testTzWinEquality(self):
+ # https://github.com/dateutil/dateutil/issues/151
+ tzwin_names = ('Eastern Standard Time',
+ 'West Pacific Standard Time',
+ 'Yakutsk Standard Time',
+ 'Iran Standard Time',
+ 'UTC')
+
+ for tzwin_name in tzwin_names:
+ # Get two different instances to compare
+ tw1 = tz.tzwin(tzwin_name)
+ tw2 = tz.tzwin(tzwin_name)
+
+ self.assertEqual(tw1, tw2)
+
+ def testTzWinInequality(self):
+ # https://github.com/dateutil/dateutil/issues/151
+ # Note these last two currently differ only in their name.
+ tzwin_names = (('Eastern Standard Time', 'Yakutsk Standard Time'),
+ ('Greenwich Standard Time', 'GMT Standard Time'),
+ ('GMT Standard Time', 'UTC'),
+ ('E. South America Standard Time',
+ 'Argentina Standard Time'))
+
+ for tzwn1, tzwn2 in tzwin_names:
+ # Get two different instances to compare
+ tw1 = tz.tzwin(tzwn1)
+ tw2 = tz.tzwin(tzwn2)
+
+ self.assertNotEqual(tw1, tw2)
+
+ def testTzWinEqualityInvalid(self):
+ # Compare to objects that do not implement comparison with this
+ # (should default to False)
+ UTC = tz.UTC
+ EST = tz.tzwin('Eastern Standard Time')
+
+ self.assertFalse(EST == UTC)
+ self.assertFalse(EST == 1)
+ self.assertFalse(UTC == EST)
+
+ self.assertTrue(EST != UTC)
+ self.assertTrue(EST != 1)
+
+ def testTzWinInequalityUnsupported(self):
+ # Compare it to an object that is promiscuous about equality, but for
+ # which tzwin does not implement an equality operator.
+ EST = tz.tzwin('Eastern Standard Time')
+ self.assertTrue(EST == ComparesEqual)
+ self.assertFalse(EST != ComparesEqual)
+
+ def testTzwinTimeOnlyDST(self):
+ # For zones with DST, .dst() should return None
+ tw_est = tz.tzwin('Eastern Standard Time')
+ self.assertIs(dt_time(14, 10, tzinfo=tw_est).dst(), None)
+
+ # This zone has no DST, so .dst() can return 0
+ tw_sast = tz.tzwin('South Africa Standard Time')
+ self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).dst(),
+ timedelta(0))
+
+ def testTzwinTimeOnlyUTCOffset(self):
+ # For zones with DST, .utcoffset() should return None
+ tw_est = tz.tzwin('Eastern Standard Time')
+ self.assertIs(dt_time(14, 10, tzinfo=tw_est).utcoffset(), None)
+
+ # This zone has no DST, so .utcoffset() returns standard offset
+ tw_sast = tz.tzwin('South Africa Standard Time')
+ self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).utcoffset(),
+ timedelta(hours=2))
+
+ def testTzwinTimeOnlyTZName(self):
+ # For zones with DST, the name defaults to standard time
+ tw_est = tz.tzwin('Eastern Standard Time')
+ self.assertEqual(dt_time(14, 10, tzinfo=tw_est).tzname(),
+ 'Eastern Standard Time')
+
+ # For zones with no DST, this should work normally.
+ tw_sast = tz.tzwin('South Africa Standard Time')
+ self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).tzname(),
+ 'South Africa Standard Time')
+
+
+@unittest.skipUnless(IS_WIN, "Requires Windows")
+class TzWinLocalTest(unittest.TestCase, TzWinFoldMixin):
+
+ def setUp(self):
+ self.tzclass = tzwin.tzwinlocal
+ self.context = TZWinContext
+
+ def get_args(self, tzname):
+ return ()
+
+ def testLocal(self):
+ # Not sure how to pin a local time zone, so for now we're just going
+ # to run this and make sure it doesn't raise an error
+ # See GitHub Issue #135: https://github.com/dateutil/dateutil/issues/135
+ datetime.now(tzwin.tzwinlocal())
+
+ def testTzwinLocalUTCOffset(self):
+ with TZWinContext('Eastern Standard Time'):
+ tzwl = tzwin.tzwinlocal()
+ self.assertEqual(datetime(2014, 3, 11, tzinfo=tzwl).utcoffset(),
+ timedelta(hours=-4))
+
+ def testTzwinLocalName(self):
+ # https://github.com/dateutil/dateutil/issues/143
+ ESTs = 'Eastern Standard Time'
+ EDTs = 'Eastern Daylight Time'
+ transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs),
+ (datetime(2015, 3, 8, 3, 1), EDTs),
+ (datetime(2015, 11, 1, 0, 59), EDTs),
+ (datetime(2015, 11, 1, 3, 1), ESTs),
+ (datetime(2016, 3, 13, 0, 59), ESTs),
+ (datetime(2016, 3, 13, 3, 1), EDTs),
+ (datetime(2016, 11, 6, 0, 59), EDTs),
+ (datetime(2016, 11, 6, 3, 1), ESTs)]
+
+ with TZWinContext('Eastern Standard Time'):
+ tw = tz.tzwinlocal()
+
+ for t_date, expected in transition_dates:
+ self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected)
+
+ def testTzWinLocalRepr(self):
+ tw = tz.tzwinlocal()
+ self.assertEqual(repr(tw), 'tzwinlocal()')
+
+ def testTzwinLocalRepr(self):
+ # https://github.com/dateutil/dateutil/issues/143
+ with TZWinContext('Eastern Standard Time'):
+ tw = tz.tzwinlocal()
+
+ self.assertEqual(str(tw), 'tzwinlocal(' +
+ repr('Eastern Standard Time') + ')')
+
+ with TZWinContext('Pacific Standard Time'):
+ tw = tz.tzwinlocal()
+
+ self.assertEqual(str(tw), 'tzwinlocal(' +
+ repr('Pacific Standard Time') + ')')
+
+ def testTzwinLocalEquality(self):
+ tw_est = tz.tzwin('Eastern Standard Time')
+ tw_pst = tz.tzwin('Pacific Standard Time')
+
+ with TZWinContext('Eastern Standard Time'):
+ twl1 = tz.tzwinlocal()
+ twl2 = tz.tzwinlocal()
+
+ self.assertEqual(twl1, twl2)
+ self.assertEqual(twl1, tw_est)
+ self.assertNotEqual(twl1, tw_pst)
+
+ with TZWinContext('Pacific Standard Time'):
+ twl1 = tz.tzwinlocal()
+ twl2 = tz.tzwinlocal()
+ tw = tz.tzwin('Pacific Standard Time')
+
+ self.assertEqual(twl1, twl2)
+ self.assertEqual(twl1, tw)
+ self.assertEqual(twl1, tw_pst)
+ self.assertNotEqual(twl1, tw_est)
+
+ def testTzwinLocalTimeOnlyDST(self):
+ # For zones with DST, .dst() should return None
+ with TZWinContext('Eastern Standard Time'):
+ twl = tz.tzwinlocal()
+ self.assertIs(dt_time(14, 10, tzinfo=twl).dst(), None)
+
+ # This zone has no DST, so .dst() can return 0
+ with TZWinContext('South Africa Standard Time'):
+ twl = tz.tzwinlocal()
+ self.assertEqual(dt_time(14, 10, tzinfo=twl).dst(), timedelta(0))
+
+ def testTzwinLocalTimeOnlyUTCOffset(self):
+ # For zones with DST, .utcoffset() should return None
+ with TZWinContext('Eastern Standard Time'):
+ twl = tz.tzwinlocal()
+ self.assertIs(dt_time(14, 10, tzinfo=twl).utcoffset(), None)
+
+ # This zone has no DST, so .utcoffset() returns standard offset
+ with TZWinContext('South Africa Standard Time'):
+ twl = tz.tzwinlocal()
+ self.assertEqual(dt_time(14, 10, tzinfo=twl).utcoffset(),
+ timedelta(hours=2))
+
+ def testTzwinLocalTimeOnlyTZName(self):
+ # For zones with DST, the name defaults to standard time
+ with TZWinContext('Eastern Standard Time'):
+ twl = tz.tzwinlocal()
+ self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(),
+ 'Eastern Standard Time')
+
+ # For zones with no DST, this should work normally.
+ with TZWinContext('South Africa Standard Time'):
+ twl = tz.tzwinlocal()
+ self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(),
+ 'South Africa Standard Time')
+
+
+class TzPickleTest(PicklableMixin, unittest.TestCase):
+ _asfile = False
+
+ def setUp(self):
+ self.assertPicklable = partial(self.assertPicklable,
+ asfile=self._asfile)
+
+ def testPickleTzUTC(self):
+ self.assertPicklable(tz.tzutc(), singleton=True)
+
+ def testPickleTzOffsetZero(self):
+ self.assertPicklable(tz.tzoffset('UTC', 0), singleton=True)
+
+ def testPickleTzOffsetPos(self):
+ self.assertPicklable(tz.tzoffset('UTC+1', 3600), singleton=True)
+
+ def testPickleTzOffsetNeg(self):
+ self.assertPicklable(tz.tzoffset('UTC-1', -3600), singleton=True)
+
+ @pytest.mark.tzlocal
+ def testPickleTzLocal(self):
+ self.assertPicklable(tz.tzlocal())
+
+ def testPickleTzFileEST5EDT(self):
+ tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
+ self.assertPicklable(tzc)
+
+ def testPickleTzFileEurope_Helsinki(self):
+ tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
+ self.assertPicklable(tzc)
+
+ def testPickleTzFileNew_York(self):
+ tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK)))
+ self.assertPicklable(tzc)
+
+ @unittest.skip("Known failure")
+ def testPickleTzICal(self):
+ tzc = tz.tzical(StringIO(TZICAL_EST5EDT)).get()
+ self.assertPicklable(tzc)
+
+ def testPickleTzGettz(self):
+ self.assertPicklable(tz.gettz('America/New_York'))
+
+ def testPickleZoneFileGettz(self):
+ zoneinfo_file = zoneinfo.get_zonefile_instance()
+ tzi = zoneinfo_file.get('America/New_York')
+ self.assertIsNot(tzi, None)
+ self.assertPicklable(tzi)
+
+
+class TzPickleFileTest(TzPickleTest):
+ """ Run all the TzPickleTest tests, using a temporary file """
+ _asfile = True
+
+
+class DatetimeAmbiguousTest(unittest.TestCase):
+ """ Test the datetime_exists / datetime_ambiguous functions """
+
+ def testNoTzSpecified(self):
+ with self.assertRaises(ValueError):
+ tz.datetime_ambiguous(datetime(2016, 4, 1, 2, 9))
+
+ def _get_no_support_tzinfo_class(self, dt_start, dt_end, dst_only=False):
+ # Generates a class of tzinfo with no support for is_ambiguous
+ # where dates between dt_start and dt_end are ambiguous.
+
+ class FoldingTzInfo(tzinfo):
+ def utcoffset(self, dt):
+ if not dst_only:
+ dt_n = dt.replace(tzinfo=None)
+
+ if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0):
+ return timedelta(hours=-1)
+
+ return timedelta(hours=0)
+
+ def dst(self, dt):
+ dt_n = dt.replace(tzinfo=None)
+
+ if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0):
+ return timedelta(hours=1)
+ else:
+ return timedelta(0)
+
+ return FoldingTzInfo
+
+ def _get_no_support_tzinfo(self, dt_start, dt_end, dst_only=False):
+ return self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)()
+
+ def testNoSupportAmbiguityFoldNaive(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_no_support_tzinfo(dt_start, dt_end)
+
+ self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
+ tz=tzi))
+
+ def testNoSupportAmbiguityFoldAware(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_no_support_tzinfo(dt_start, dt_end)
+
+ self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30,
+ tzinfo=tzi)))
+
+ def testNoSupportAmbiguityUnambiguousNaive(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_no_support_tzinfo(dt_start, dt_end)
+
+ self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
+ tz=tzi))
+
+ def testNoSupportAmbiguityUnambiguousAware(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_no_support_tzinfo(dt_start, dt_end)
+
+ self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30,
+ tzinfo=tzi)))
+
+ def testNoSupportAmbiguityFoldDSTOnly(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True)
+
+ self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
+ tz=tzi))
+
+ def testNoSupportAmbiguityUnambiguousDSTOnly(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True)
+
+ self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
+ tz=tzi))
+
+ def testSupportAmbiguityFoldNaive(self):
+ tzi = tz.gettz('US/Eastern')
+
+ dt = datetime(2011, 11, 6, 1, 30)
+
+ self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi))
+
+ def testSupportAmbiguityFoldAware(self):
+ tzi = tz.gettz('US/Eastern')
+
+ dt = datetime(2011, 11, 6, 1, 30, tzinfo=tzi)
+
+ self.assertTrue(tz.datetime_ambiguous(dt))
+
+ def testSupportAmbiguityUnambiguousAware(self):
+ tzi = tz.gettz('US/Eastern')
+
+ dt = datetime(2011, 11, 6, 4, 30)
+
+ self.assertFalse(tz.datetime_ambiguous(dt, tz=tzi))
+
+ def testSupportAmbiguityUnambiguousNaive(self):
+ tzi = tz.gettz('US/Eastern')
+
+ dt = datetime(2011, 11, 6, 4, 30, tzinfo=tzi)
+
+ self.assertFalse(tz.datetime_ambiguous(dt))
+
+ def _get_ambig_error_tzinfo(self, dt_start, dt_end, dst_only=False):
+ cTzInfo = self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)
+
+ # Takes the wrong number of arguments and raises an error anyway.
+ class FoldTzInfoRaises(cTzInfo):
+ def is_ambiguous(self, dt, other_arg):
+ raise NotImplementedError('This is not implemented')
+
+ return FoldTzInfoRaises()
+
+ def testIncompatibleAmbiguityFoldNaive(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
+
+ self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
+ tz=tzi))
+
+ def testIncompatibleAmbiguityFoldAware(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
+
+ self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30,
+ tzinfo=tzi)))
+
+ def testIncompatibleAmbiguityUnambiguousNaive(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
+
+ self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
+ tz=tzi))
+
+ def testIncompatibleAmbiguityUnambiguousAware(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
+
+ self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30,
+ tzinfo=tzi)))
+
+ def testIncompatibleAmbiguityFoldDSTOnly(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True)
+
+ self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
+ tz=tzi))
+
+ def testIncompatibleAmbiguityUnambiguousDSTOnly(self):
+ dt_start = datetime(2018, 9, 1, 1, 0)
+ dt_end = datetime(2018, 9, 1, 2, 0)
+
+ tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True)
+
+ self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
+ tz=tzi))
+
+ def testSpecifiedTzOverridesAttached(self):
+ # If a tz is specified, the datetime will be treated as naive.
+
+ # This is not ambiguous in the local zone
+ dt = datetime(2011, 11, 6, 1, 30, tzinfo=tz.gettz('Australia/Sydney'))
+
+ self.assertFalse(tz.datetime_ambiguous(dt))
+
+ tzi = tz.gettz('US/Eastern')
+ self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi))
+
+
+class DatetimeExistsTest(unittest.TestCase):
+ def testNoTzSpecified(self):
+ with self.assertRaises(ValueError):
+ tz.datetime_exists(datetime(2016, 4, 1, 2, 9))
+
+ def testInGapNaive(self):
+ tzi = tz.gettz('Australia/Sydney')
+
+ dt = datetime(2012, 10, 7, 2, 30)
+
+ self.assertFalse(tz.datetime_exists(dt, tz=tzi))
+
+ def testInGapAware(self):
+ tzi = tz.gettz('Australia/Sydney')
+
+ dt = datetime(2012, 10, 7, 2, 30, tzinfo=tzi)
+
+ self.assertFalse(tz.datetime_exists(dt))
+
+ def testExistsNaive(self):
+ tzi = tz.gettz('Australia/Sydney')
+
+ dt = datetime(2012, 10, 7, 10, 30)
+
+ self.assertTrue(tz.datetime_exists(dt, tz=tzi))
+
+ def testExistsAware(self):
+ tzi = tz.gettz('Australia/Sydney')
+
+ dt = datetime(2012, 10, 7, 10, 30, tzinfo=tzi)
+
+ self.assertTrue(tz.datetime_exists(dt))
+
+ def testSpecifiedTzOverridesAttached(self):
+ EST = tz.gettz('US/Eastern')
+ AEST = tz.gettz('Australia/Sydney')
+
+ dt = datetime(2012, 10, 7, 2, 30, tzinfo=EST) # This time exists
+
+ self.assertFalse(tz.datetime_exists(dt, tz=AEST))
+
+
+class TestEnfold:
+ def test_enter_fold_default(self):
+ dt = tz.enfold(datetime(2020, 1, 19, 3, 32))
+
+ assert dt.fold == 1
+
+ def test_enter_fold(self):
+ dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
+
+ assert dt.fold == 1
+
+ def test_exit_fold(self):
+ dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=0)
+
+ # Before Python 3.6, dt.fold won't exist if fold is 0.
+ assert getattr(dt, 'fold', 0) == 0
+
+ def test_defold(self):
+ dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
+
+ dt2 = tz.enfold(dt, fold=0)
+
+ assert getattr(dt2, 'fold', 0) == 0
+
+ def test_fold_replace_args(self):
+ # This test can be dropped when Python < 3.6 is dropped, since it
+ # is mainly to cover the `replace` method on _DatetimeWithFold
+ dt = tz.enfold(datetime(1950, 1, 2, 12, 30, 15, 8), fold=1)
+
+ dt2 = dt.replace(1952, 2, 3, 13, 31, 16, 9)
+ assert dt2 == tz.enfold(datetime(1952, 2, 3, 13, 31, 16, 9), fold=1)
+ assert dt2.fold == 1
+
+ def test_fold_replace_exception_duplicate_args(self):
+ dt = tz.enfold(datetime(1999, 1, 3), fold=1)
+
+ with pytest.raises(TypeError):
+ dt.replace(1950, year=2000)
+
+
+@pytest.mark.tz_resolve_imaginary
+class ImaginaryDateTest(unittest.TestCase):
+ def testCanberraForward(self):
+ tzi = tz.gettz('Australia/Canberra')
+ dt = datetime(2018, 10, 7, 2, 30, tzinfo=tzi)
+ dt_act = tz.resolve_imaginary(dt)
+ dt_exp = datetime(2018, 10, 7, 3, 30, tzinfo=tzi)
+ self.assertEqual(dt_act, dt_exp)
+
+ def testLondonForward(self):
+ tzi = tz.gettz('Europe/London')
+ dt = datetime(2018, 3, 25, 1, 30, tzinfo=tzi)
+ dt_act = tz.resolve_imaginary(dt)
+ dt_exp = datetime(2018, 3, 25, 2, 30, tzinfo=tzi)
+ self.assertEqual(dt_act, dt_exp)
+
+ def testKeivForward(self):
+ tzi = tz.gettz('Europe/Kiev')
+ dt = datetime(2018, 3, 25, 3, 30, tzinfo=tzi)
+ dt_act = tz.resolve_imaginary(dt)
+ dt_exp = datetime(2018, 3, 25, 4, 30, tzinfo=tzi)
+ self.assertEqual(dt_act, dt_exp)
+
+
+@pytest.mark.tz_resolve_imaginary
+@pytest.mark.parametrize('dt', [
+ datetime(2017, 11, 5, 1, 30, tzinfo=tz.gettz('America/New_York')),
+ datetime(2018, 10, 28, 1, 30, tzinfo=tz.gettz('Europe/London')),
+ datetime(2017, 4, 2, 2, 30, tzinfo=tz.gettz('Australia/Sydney')),
+])
+def test_resolve_imaginary_ambiguous(dt):
+ assert tz.resolve_imaginary(dt) is dt
+
+ dt_f = tz.enfold(dt)
+ assert dt is not dt_f
+ assert tz.resolve_imaginary(dt_f) is dt_f
+
+
+@pytest.mark.tz_resolve_imaginary
+@pytest.mark.parametrize('dt', [
+ datetime(2017, 6, 2, 12, 30, tzinfo=tz.gettz('America/New_York')),
+ datetime(2018, 4, 2, 9, 30, tzinfo=tz.gettz('Europe/London')),
+ datetime(2017, 2, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')),
+ datetime(2017, 12, 2, 12, 30, tzinfo=tz.gettz('America/New_York')),
+ datetime(2018, 12, 2, 9, 30, tzinfo=tz.gettz('Europe/London')),
+ datetime(2017, 6, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')),
+ datetime(2025, 9, 25, 1, 17, tzinfo=tz.UTC),
+ datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzoffset('EST', -18000)),
+ datetime(2019, 3, 4, tzinfo=None)
+])
+def test_resolve_imaginary_existing(dt):
+ assert tz.resolve_imaginary(dt) is dt
+
+
+def __get_kiritimati_resolve_imaginary_test():
+ # In the 2018d release of the IANA database, the Kiritimati "imaginary day"
+ # data was corrected, so if the system zoneinfo is older than 2018d, the
+ # Kiritimati test will fail.
+
+ tzi = tz.gettz('Pacific/Kiritimati')
+ new_version = False
+ if not tz.datetime_exists(datetime(1995, 1, 1, 12, 30), tzi):
+ zif = zoneinfo.get_zonefile_instance()
+ if zif.metadata is not None:
+ new_version = zif.metadata['tzversion'] >= '2018d'
+
+ if new_version:
+ tzi = zif.get('Pacific/Kiritimati')
+ else:
+ new_version = True
+
+ if new_version:
+ dates = (datetime(1994, 12, 31, 12, 30), datetime(1995, 1, 1, 12, 30))
+ else:
+ dates = (datetime(1995, 1, 1, 12, 30), datetime(1995, 1, 2, 12, 30))
+
+ return (tzi, ) + dates
+
+
+resolve_imaginary_tests = [
+ (tz.gettz('Europe/London'),
+ datetime(2018, 3, 25, 1, 30), datetime(2018, 3, 25, 2, 30)),
+ (tz.gettz('America/New_York'),
+ datetime(2017, 3, 12, 2, 30), datetime(2017, 3, 12, 3, 30)),
+ (tz.gettz('Australia/Sydney'),
+ datetime(2014, 10, 5, 2, 0), datetime(2014, 10, 5, 3, 0)),
+ __get_kiritimati_resolve_imaginary_test(),
+]
+
+
+if SUPPORTS_SUB_MINUTE_OFFSETS:
+ resolve_imaginary_tests.append(
+ (tz.gettz('Africa/Monrovia'),
+ datetime(1972, 1, 7, 0, 30), datetime(1972, 1, 7, 1, 14, 30)))
+
+
+@pytest.mark.tz_resolve_imaginary
+@pytest.mark.parametrize('tzi, dt, dt_exp', resolve_imaginary_tests)
+def test_resolve_imaginary(tzi, dt, dt_exp):
+ dt = dt.replace(tzinfo=tzi)
+ dt_exp = dt_exp.replace(tzinfo=tzi)
+
+ dt_r = tz.resolve_imaginary(dt)
+ assert dt_r == dt_exp
+ assert dt_r.tzname() == dt_exp.tzname()
+ assert dt_r.utcoffset() == dt_exp.utcoffset()
diff --git a/contrib/python/python-dateutil/py2/tests/test_utils.py b/contrib/python/python-dateutil/py2/tests/test_utils.py
new file mode 100644
index 0000000000..fe1bfdcb84
--- /dev/null
+++ b/contrib/python/python-dateutil/py2/tests/test_utils.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from datetime import timedelta, datetime
+
+from dateutil import tz
+from dateutil import utils
+from dateutil.tz import UTC
+from dateutil.utils import within_delta
+
+from freezegun import freeze_time
+
+NYC = tz.gettz("America/New_York")
+
+
+@freeze_time(datetime(2014, 12, 15, 1, 21, 33, 4003))
+def test_utils_today():
+ assert utils.today() == datetime(2014, 12, 15, 0, 0, 0)
+
+
+@freeze_time(datetime(2014, 12, 15, 12), tz_offset=5)
+def test_utils_today_tz_info():
+ assert utils.today(NYC) == datetime(2014, 12, 15, 0, 0, 0, tzinfo=NYC)
+
+
+@freeze_time(datetime(2014, 12, 15, 23), tz_offset=5)
+def test_utils_today_tz_info_different_day():
+ assert utils.today(UTC) == datetime(2014, 12, 16, 0, 0, 0, tzinfo=UTC)
+
+
+def test_utils_default_tz_info_naive():
+ dt = datetime(2014, 9, 14, 9, 30)
+ assert utils.default_tzinfo(dt, NYC).tzinfo is NYC
+
+
+def test_utils_default_tz_info_aware():
+ dt = datetime(2014, 9, 14, 9, 30, tzinfo=UTC)
+ assert utils.default_tzinfo(dt, NYC).tzinfo is UTC
+
+
+def test_utils_within_delta():
+ d1 = datetime(2016, 1, 1, 12, 14, 1, 9)
+ d2 = d1.replace(microsecond=15)
+
+ assert within_delta(d1, d2, timedelta(seconds=1))
+ assert not within_delta(d1, d2, timedelta(microseconds=1))
+
+
+def test_utils_within_delta_with_negative_delta():
+ d1 = datetime(2016, 1, 1)
+ d2 = datetime(2015, 12, 31)
+
+ assert within_delta(d2, d1, timedelta(days=-1))
diff --git a/contrib/python/python-dateutil/py2/tests/ya.make b/contrib/python/python-dateutil/py2/tests/ya.make
index 5b3ae41227..81fb4c8915 100644
--- a/contrib/python/python-dateutil/py2/tests/ya.make
+++ b/contrib/python/python-dateutil/py2/tests/ya.make
@@ -11,8 +11,6 @@ ENV(LANG=ru_RU.UTF-8)
# because we cannot change TZ in arcadia CI
ENV(DATEUTIL_MAY_NOT_CHANGE_TZ_VAR=1)
-SRCDIR(contrib/python/python-dateutil/py2/dateutil/test)
-
TEST_SRCS(
property/test_isoparse_prop.py
property/test_parser_prop.py