From 47f7d99954e5b71a00df55625bb3f01e53c4b732 Mon Sep 17 00:00:00 2001
From: angelblue05 <angelblue.dev@gmail.com>
Date: Sun, 30 Dec 2018 19:30:50 -0600
Subject: [PATCH] Add dateutil library

---
 resources/lib/libraries/__init__.py           |    1 +
 resources/lib/libraries/dateutil/LICENSE      |   54 +
 resources/lib/libraries/dateutil/NEWS         |  701 +++
 resources/lib/libraries/dateutil/README.rst   |  158 +
 resources/lib/libraries/dateutil/__init__.py  |    8 +
 resources/lib/libraries/dateutil/_common.py   |   43 +
 resources/lib/libraries/dateutil/easter.py    |   89 +
 .../lib/libraries/dateutil/parser/__init__.py |   60 +
 .../lib/libraries/dateutil/parser/_parser.py  | 1578 ++++++
 .../libraries/dateutil/parser/isoparser.py    |  406 ++
 .../lib/libraries/dateutil/relativedelta.py   |  590 ++
 resources/lib/libraries/dateutil/rrule.py     | 1672 ++++++
 resources/lib/libraries/dateutil/six.py       |  891 +++
 .../lib/libraries/dateutil/test/__init__.py   |    0
 .../lib/libraries/dateutil/test/_common.py    |  275 +
 .../test/property/test_isoparse_prop.py       |   27 +
 .../test/property/test_parser_prop.py         |   22 +
 .../libraries/dateutil/test/test_easter.py    |   95 +
 .../dateutil/test/test_import_star.py         |   33 +
 .../libraries/dateutil/test/test_imports.py   |  166 +
 .../libraries/dateutil/test/test_internals.py |   95 +
 .../libraries/dateutil/test/test_isoparser.py |  482 ++
 .../libraries/dateutil/test/test_parser.py    | 1114 ++++
 .../dateutil/test/test_relativedelta.py       |  678 +++
 .../lib/libraries/dateutil/test/test_rrule.py | 4842 +++++++++++++++++
 .../lib/libraries/dateutil/test/test_tz.py    | 2603 +++++++++
 .../lib/libraries/dateutil/test/test_utils.py |   53 +
 .../lib/libraries/dateutil/tz/__init__.py     |   17 +
 .../lib/libraries/dateutil/tz/_common.py      |  415 ++
 .../lib/libraries/dateutil/tz/_factories.py   |   49 +
 resources/lib/libraries/dateutil/tz/tz.py     | 1785 ++++++
 resources/lib/libraries/dateutil/tz/win.py    |  331 ++
 resources/lib/libraries/dateutil/tzwin.py     |    2 +
 resources/lib/libraries/dateutil/utils.py     |   71 +
 .../libraries/dateutil/zoneinfo/__init__.py   |  167 +
 .../zoneinfo/dateutil-zoneinfo.tar.gz         |  Bin 0 -> 139130 bytes
 .../libraries/dateutil/zoneinfo/rebuild.py    |   53 +
 37 files changed, 19626 insertions(+)
 create mode 100644 resources/lib/libraries/dateutil/LICENSE
 create mode 100644 resources/lib/libraries/dateutil/NEWS
 create mode 100644 resources/lib/libraries/dateutil/README.rst
 create mode 100644 resources/lib/libraries/dateutil/__init__.py
 create mode 100644 resources/lib/libraries/dateutil/_common.py
 create mode 100644 resources/lib/libraries/dateutil/easter.py
 create mode 100644 resources/lib/libraries/dateutil/parser/__init__.py
 create mode 100644 resources/lib/libraries/dateutil/parser/_parser.py
 create mode 100644 resources/lib/libraries/dateutil/parser/isoparser.py
 create mode 100644 resources/lib/libraries/dateutil/relativedelta.py
 create mode 100644 resources/lib/libraries/dateutil/rrule.py
 create mode 100644 resources/lib/libraries/dateutil/six.py
 create mode 100644 resources/lib/libraries/dateutil/test/__init__.py
 create mode 100644 resources/lib/libraries/dateutil/test/_common.py
 create mode 100644 resources/lib/libraries/dateutil/test/property/test_isoparse_prop.py
 create mode 100644 resources/lib/libraries/dateutil/test/property/test_parser_prop.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_easter.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_import_star.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_imports.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_internals.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_isoparser.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_parser.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_relativedelta.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_rrule.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_tz.py
 create mode 100644 resources/lib/libraries/dateutil/test/test_utils.py
 create mode 100644 resources/lib/libraries/dateutil/tz/__init__.py
 create mode 100644 resources/lib/libraries/dateutil/tz/_common.py
 create mode 100644 resources/lib/libraries/dateutil/tz/_factories.py
 create mode 100644 resources/lib/libraries/dateutil/tz/tz.py
 create mode 100644 resources/lib/libraries/dateutil/tz/win.py
 create mode 100644 resources/lib/libraries/dateutil/tzwin.py
 create mode 100644 resources/lib/libraries/dateutil/utils.py
 create mode 100644 resources/lib/libraries/dateutil/zoneinfo/__init__.py
 create mode 100644 resources/lib/libraries/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz
 create mode 100644 resources/lib/libraries/dateutil/zoneinfo/rebuild.py

diff --git a/resources/lib/libraries/__init__.py b/resources/lib/libraries/__init__.py
index 20b15530..a81e44db 100644
--- a/resources/lib/libraries/__init__.py
+++ b/resources/lib/libraries/__init__.py
@@ -1 +1,2 @@
 import requests
+import dateutil
diff --git a/resources/lib/libraries/dateutil/LICENSE b/resources/lib/libraries/dateutil/LICENSE
new file mode 100644
index 00000000..1e65815c
--- /dev/null
+++ b/resources/lib/libraries/dateutil/LICENSE
@@ -0,0 +1,54 @@
+Copyright 2017- Paul Ganssle <paul@ganssle.io>
+Copyright 2017- dateutil contributors (see AUTHORS file)
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+The above license applies to all contributions after 2017-12-01, as well as
+all contributions that have been re-licensed (see AUTHORS file for the list of
+contributors who have re-licensed their code).
+--------------------------------------------------------------------------------
+dateutil - Extensions to the standard Python datetime module.
+
+Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
+Copyright (c) 2012-2014 - Tomi Pieviläinen <tomi.pievilainen@iki.fi>
+Copyright (c) 2014-2016 - Yaron de Leeuw <me@jarondl.net>
+Copyright (c) 2015-     - Paul Ganssle <paul@ganssle.io>
+Copyright (c) 2015-     - dateutil contributors (see AUTHORS file)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the names of its
+      contributors may be used to endorse or promote products derived from
+      this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The above BSD License Applies to all code, even that also covered by Apache 2.0.
\ No newline at end of file
diff --git a/resources/lib/libraries/dateutil/NEWS b/resources/lib/libraries/dateutil/NEWS
new file mode 100644
index 00000000..a30cdaab
--- /dev/null
+++ b/resources/lib/libraries/dateutil/NEWS
@@ -0,0 +1,701 @@
+Version 2.7.3 (2018-05-09)
+==========================
+
+Data updates
+------------
+
+- Update tzdata to 2018e. (gh pr #710)
+
+
+Bugfixes
+--------
+
+- Fixed an issue where decimal.Decimal would cast `NaN` or infinite value in a
+  parser.parse, which will raise decimal.Decimal-specific errors. Reported and
+  fixed by @amureki (gh issue #662, gh pr #679).
+- Fixed a ValueError being thrown if tzinfos call explicity returns ``None``.
+  Reported by @pganssle (gh issue #661) Fixed by @parsethis (gh pr #681)
+- Fixed incorrect parsing of certain dates earlier than 100 AD when repesented
+  in the form "%B.%Y.%d", e.g. "December.0031.30". (gh issue #687, pr #700)
+- Fixed a bug where automatically generated DTSTART was naive even if a
+  specified UNTIL had a time zone. Automatically generated DTSTART will now
+  take on the timezone of an UNTIL date, if provided. Reported by @href (gh
+  issue #652). Fixed by @absreim (gh pr #693).
+
+
+Documentation changes
+---------------------
+
+- Corrected link syntax and updated URL to https for ISO year week number
+  notation in relativedelta examples. (gh issue #670, pr #711)
+- Add doctest examples to tzfile documentation. Done by @weatherpattern and
+  @pganssle (gh pr #671)
+- Updated the documentation for relativedelta. Removed references to tuple
+  arguments for weekday, explained effect of weekday(_, 1) and better explained
+  the order of operations that relativedelta applies. Fixed by @kvn219
+  @huangy22 and @ElliotJH (gh pr #673)
+- Added changelog to documentation. (gh issue #692, gh pr #707)
+- Changed order of keywords in rrule docstring. Reported and fixed by
+  @rmahajan14 (gh issue #686, gh pr #695).
+- Added documentation for ``dateutil.tz.gettz``. Reported by @pganssle (gh
+  issue #647). Fixed by @weatherpattern (gh pr #704)
+- Cleaned up malformed RST in the ``tz`` documentation. (gh issue #702, gh pr
+  #706)
+- Changed the default theme to sphinx_rtd_theme, and changed the sphinx
+  configuration to go along with that. (gh pr #707)
+- Reorganized ``dateutil.tz`` documentation and fixed issue with the
+  ``dateutil.tz`` docstring. (gh pr #714)
+
+
+Misc
+----
+
+- GH #674, GH #688, GH #699
+
+
+Version 2.7.2 (2018-03-26)
+==========================
+
+Bugfixes
+--------
+
+- Fixed an issue with the setup script running in non-UTF-8 environment.
+  Reported and fixed by @gergondet (gh pr #651)
+
+
+Misc
+----
+
+- GH #655
+
+
+Version 2.7.1 (2018-03-24)
+===========================
+
+Data updates
+------------
+
+- Updated tzdata version to 2018d.
+
+
+Bugfixes
+--------
+
+- Fixed issue where parser.parse would occasionally raise
+  decimal.Decimal-specific error types rather than ValueError. Reported by
+  @amureki (gh issue #632). Fixed by @pganssle (gh pr #636).
+- Improve error message when rrule's dtstart and until are not both naive or
+  both aware. Reported and fixed by @ryanpetrello (gh issue #633, gh pr #634)
+
+
+Misc
+----
+
+- GH #644, GH #648
+
+
+Version 2.7.0
+=============
+- Dropped support for Python 2.6 (gh pr #362 by @jdufresne)
+- Dropped support for Python 3.2 (gh pr #626)
+- Updated zoneinfo file to 2018c (gh pr #616)
+- Changed licensing scheme so all new contributions are dual licensed under
+  Apache 2.0 and BSD. (gh pr #542, issue #496)
+- Added __all__ variable to the root package. Reported by @tebriel
+  (gh issue #406), fixed by @mariocj89 (gh pr #494)
+- Added python_requires to setup.py so that pip will distribute the right
+  version of dateutil. Fixed by @jakec-github (gh issue #537, pr #552)
+- Added the utils submodule, for miscellaneous utilities.
+- Added within_delta function to utils - added by @justanr (gh issue #432,
+  gh pr #437)
+- Added today function to utils (gh pr #474)
+- Added default_tzinfo function to utils (gh pr #475), solving an issue
+  reported by @nealmcb (gh issue #94)
+- Added dedicated ISO 8601 parsing function isoparse (gh issue #424).
+  Initial implementation by @pganssle in gh pr #489 and #622, with a
+  pre-release fix by @kirit93 (gh issue #546, gh pr #573). 
+- Moved parser module into parser/_parser.py and officially deprecated the use
+  of several private functions and classes from that module. (gh pr #501, #515)
+- Tweaked parser error message to include rejected string format, added by
+  @pbiering (gh pr #300)
+- Add support for parsing bytesarray, reported by @uckelman (gh issue #417) and
+  fixed by @uckelman and @pganssle (gh pr #514)
+- Started raising a warning when the parser finds a timezone string that it
+  cannot construct a tzinfo instance for (rather than succeeding with no
+  indication of an error). Reported and fixed by @jbrockmendel (gh pr #540)
+- Dropped the use of assert in the parser. Fixed by @jbrockmendel (gh pr #502)
+- Fixed to assertion logic in parser to support dates like '2015-15-May',
+  reported and fixed by @jbrockmendel (gh pr #409)
+- Fixed IndexError in parser on dates with trailing colons, reported and fixed
+  by @jbrockmendel (gh pr #420)
+- Fixed bug where hours were not validated, leading to improper parse. Reported
+  by @heappro (gh pr #353), fixed by @jbrockmendel (gh pr #482)
+- Fixed problem parsing strings in %b-%Y-%d format. Reported and fixed by
+  @jbrockmendel (gh pr #481)
+- Fixed problem parsing strings in the %d%B%y format. Reported by @asishm
+  (gh issue #360), fixed by @jbrockmendel (gh pr #483)
+- Fixed problem parsing certain unambiguous strings when year <99 (gh pr #510).
+  Reported by @alexwlchan (gh issue #293).
+- Fixed issue with parsing an unambiguous string representation of an ambiguous
+  datetime such that if possible the correct value for fold is set. Fixes
+  issue reported by @JordonPhillips and @pganssle (gh issue #318, #320,
+  gh pr #517)
+- Fixed issue with improper rounding of fractional components. Reported by
+  @dddmello (gh issue #427), fixed by @m-dz (gh pr #570)
+- Performance improvement to parser from removing certain min() calls. Reported
+  and fixed by @jbrockmendel (gh pr #589)
+- Significantly refactored parser code by @jbrockmendel (gh prs #419, #436,
+  #490, #498, #539) and @pganssle (gh prs #435, #468)
+- Implementated of __hash__ for relativedelta and weekday, reported and fixed
+  by @mrigor (gh pr #389) 
+- Implemented __abs__ for relativedelta. Reported by @binnisb and @pferreir
+  (gh issue #350, pr #472)
+- Fixed relativedelta.weeks property getter and setter to work for both
+  negative and positive values. Reported and fixed by @souliane (gh issue #459,
+  pr #460)
+- Fixed issue where passing whole number floats to the months or years
+  arguments of the relativedelta constructor would lead to errors during
+  addition. Reported by @arouanet (gh pr #411), fixed by @lkollar (gh pr #553)
+- Added a pre-built tz.UTC object representing UTC (gh pr #497)
+- Added a cache to tz.gettz so that by default it will return the same object
+  for identical inputs. This will change the semantics of certain operations
+  between datetimes constructed with tzinfo=tz.gettz(...). (gh pr #628)
+- Changed the behavior of tz.tzutc to return a singleton (gh pr #497, #504)
+- Changed the behavior of tz.tzoffset to return the same object when passed the
+  same inputs, with a corresponding performance improvement (gh pr #504)
+- Changed the behavior of tz.tzstr to return the same object when passed the
+  same inputs. (gh pr #628)
+- Added .instance alternate constructors for tz.tzoffset and tz.tzstr, to
+  allow the construction of a new instance if desired. (gh pr #628)
+- Added the tz.gettz.nocache function to allow explicit retrieval of a new
+  instance of the relevant tzinfo. (gh pr #628)
+- Expand definition of tz.tzlocal equality so that the local zone is allow
+  equality with tzoffset and tzutc. (gh pr #598)
+- Deprecated the idiosyncratic tzstr format mentioned in several examples but
+  evidently designed exclusively for dateutil, and very likely not used by
+  any current users. (gh issue #595, gh pr #606)
+- Added the tz.resolve_imaginary function, which generates a real date from
+  an imaginary one, if necessary. Implemented by @Cheukting (gh issue #339,
+  gh pr #607)
+- Fixed issue where the tz.tzstr constructor would erroneously succeed if
+  passed an invalid value for tzstr. Fixed by @pablogsal (gh issue #259,
+  gh pr #581)
+- Fixed issue with tz.gettz for TZ variables that start with a colon. Reported
+  and fixed by @lapointexavier (gh pr #601)
+- Added a lock to tz.tzical's cache. Reported and fixed by @Unrud (gh pr #430) 
+- Fixed an issue with fold support on certain Python 3 implementations that
+  used the pre-3.6 pure Python implementation of datetime.replace, most
+  notably pypy3 (gh pr #446).
+- Added support for VALUE=DATE-TIME for DTSTART in rrulestr. Reported by @potuz
+  (gh issue #401) and fixed by @Unrud (gh pr #429)
+- Started enforcing that within VTIMEZONE, the VALUE parameter can only be
+  omitted or DATE-TIME, per RFC 5545. Reported by @Unrud (gh pr #439)
+- Added support for TZID parameter for DTSTART in rrulestr. Reported and
+  fixed by @ryanpetrello (gh issue #614, gh pr #624)
+- Added 'RRULE:' prefix to rrule strings generated by rrule.__str__, in
+  compliance with the RFC. Reported by @AndrewPashkin (gh issue #86), fixed by
+  @jarondl and @mlorant (gh pr #450)
+- Switched to setuptools_scm for version management, automatically calculating
+  a version number from the git metadata. Reported by @jreback (gh issue #511),
+  implemented by @Sulley38 (gh pr #564)
+- Switched setup.py to use find_packages, and started testing against pip
+  installed versions of dateutil in CI. Fixed issue with parser import
+  discovered by @jreback in pandas-dev/pandas#18141. (gh issue #507, pr #509)
+- Switched test suite to using pytest (gh pr #495)
+- Switched CI over to use tox. Fixed by @gaborbernat (gh pr #549)
+- Added a test-only dependency on freezegun. (gh pr #474)
+- Reduced number of CI builds on Appveyor. Fixed by @kirit93 (gh issue #529,
+  gh pr #579)
+- Made xfails strict by default, so that an xpass is a failure. (gh pr #567)
+- Added a documentation generation stage to tox and CI. (gh pr #568)
+- Added an explicit warning when running python setup.py explaining how to run
+  the test suites with pytest. Fixed by @lkollar. (gh issue #544, gh pr #548)
+- Added requirements-dev.txt for test dependency management (gh pr #499, #516)
+- Fixed code coverage metrics to account for Windows builds (gh pr #526)
+- Fixed code coverage metrics to NOT count xfails. Fixed by @gaborbernat
+  (gh issue #519, gh pr #563)
+- Style improvement to zoneinfo.tzfile that was confusing to static type
+  checkers. Reported and fixed by @quodlibetor (gh pr #485)
+- Several unused imports were removed by @jdufresne. (gh pr #486)
+- Switched ``isinstance(*, collections.Callable)`` to callable, which is available
+  on all supported Python versions. Implemented by @jdufresne (gh pr #612)
+- Added CONTRIBUTING.md (gh pr #533)
+- Added AUTHORS.md (gh pr #542)
+- Corrected setup.py metadata to reflect author vs. maintainer, (gh issue #477,
+  gh pr #538)
+- Corrected README to reflect that tests are now run in pytest. Reported and
+  fixed by @m-dz (gh issue #556, gh pr #557)
+- Updated all references to RFC 2445 (iCalendar) to point to RFC 5545. Fixed
+  by @mariocj89 (gh issue #543, gh pr #555)
+- Corrected parse documentation to reflect proper integer offset units,
+  reported and fixed by @abrugh (gh pr #458)
+- Fixed dangling parenthesis in tzoffset documentation (gh pr #461)
+- Started including the license file in wheels. Reported and fixed by
+  @jdufresne (gh pr #476)
+- Indendation fixes to parser docstring by @jbrockmendel (gh pr #492)
+- Moved many examples from the "examples" documentation into their appropriate
+  module documentation pages. Fixed by @Tomasz-Kluczkowski and @jakec-github
+  (gh pr #558, #561)
+- Fixed documentation so that the parser.isoparse documentation displays.
+  Fixed by @alexchamberlain (gh issue #545, gh pr #560)
+- Refactored build and release sections and added setup instructions to
+  CONTRIBUTING. Reported and fixed by @kynan (gh pr #562)
+- Cleaned up various dead links in the documentation. (gh pr #602, #608, #618)
+
+Version 2.6.1
+=============
+- Updated zoneinfo file to 2017b. (gh pr #395)
+- Added Python 3.6 to CI testing (gh pr #365)
+- Removed duplicate test name that was preventing a test from being run.
+  Reported and fixed by @jdufresne (gh pr #371)
+- Fixed testing of folds and gaps, particularly on Windows (gh pr #392)
+- Fixed deprecated escape characters in regular expressions. Reported by
+  @nascheme and @thierryba (gh issue #361), fixed by @thierryba (gh pr #358)
+- Many PEP8 style violations and other code smells were fixed by @jdufresne
+  (gh prs #358, #363, #364, #366, #367, #368, #372, #374, #379, #380, #398)
+- Improved performance of tzutc and tzoffset objects. (gh pr #391)
+- Fixed issue with several time zone classes around DST transitions in any
+  zones with +0 standard offset (e.g. Europe/London) (gh issue #321, pr #390)
+- Fixed issue with fuzzy parsing where tokens similar to AM/PM that are in the
+  end skipped were dropped in the fuzzy_with_tokens list. Reported and fixed
+  by @jbrockmendel (gh pr #332).
+- Fixed issue with parsing dates of the form X m YY. Reported by @jbrockmendel.
+  (gh issue #333, pr #393)
+- Added support for parser weekdays with less than 3 characters. Reported by
+  @arcadefoam (gh issue #343), fixed by @jonemo (gh pr #382)
+- Fixed issue with the addition and subtraction of certain relativedeltas.
+  Reported and fixed by @kootenpv (gh issue #346, pr #347)
+- Fixed issue where the COUNT parameter of rrules was ignored if 0. Fixed by
+  @mshenfield (gh pr #330), reported by @vaultah (gh issue #329).
+- Updated documentation to include the new tz methods. (gh pr #324)
+- Update documentation to reflect that the parser can raise TypeError, reported
+  and fixed by @tomchuk (gh issue #336, pr #337)
+- Fixed an incorrect year in a parser doctest. Fixed by @xlotlu (gh pr #357)
+- Moved version information into _version.py and set up the versions more
+  granularly.
+
+Version 2.6.0
+=============
+- Added PEP-495-compatible methods to address ambiguous and imaginary dates in
+  time zones in a backwards-compatible way. Ambiguous dates and times can now
+  be safely represented by all dateutil time zones. Many thanks to Alexander
+  Belopolski (@abalkin) and Tim Peters @tim-one for their inputs on how to
+  address this. Original issues reported by Yupeng and @zed (lP: 1390262,
+  gh issues #57, #112, #249, #284, #286, prs #127, #225, #248, #264, #302).
+- Added new methods for working with ambiguous and imaginary dates to the tz
+  module. datetime_ambiguous() determines if a datetime is ambiguous for a given
+  zone and datetime_exists() determines if a datetime exists in a given zone.
+  This works for all fold-aware datetimes, not just those provided by dateutil.
+  (gh issue #253, gh pr #302)
+- Fixed an issue where dst() in Portugal in 1996 was returning the wrong value
+  in tz.tzfile objects. Reported by @abalkin (gh issue #128, pr #225)
+- Fixed an issue where zoneinfo.ZoneInfoFile errors were not being properly
+  deep-copied. (gh issue #226, pr #225)
+- Refactored tzwin and tzrange as a subclass of a common class, tzrangebase, as
+  there was substantial overlapping functionality. As part of this change,
+  tzrange and tzstr now expose a transitions() function, which returns the
+  DST on and off transitions for a given year. (gh issue #260, pr #302)
+- Deprecated zoneinfo.gettz() due to confusion with tz.gettz(), in favor of
+  get() method of zoneinfo.ZoneInfoFile objects. (gh issue #11, pr #310)
+- For non-character, non-stream arguments, parser.parse now raises TypeError
+  instead of AttributeError. (gh issues #171, #269, pr #247)
+- Fixed an issue where tzfile objects were not properly handling dst() and
+  tzname() when attached to datetime.time objects. Reported by @ovacephaloid.
+  (gh issue #292, pr #309)
+- /usr/share/lib/zoneinfo was added to TZPATHS for compatibility with Solaris
+  systems. Reported by @dhduvall (gh issue #276, pr #307)
+- tzoffset and tzrange objects now accept either a number of seconds or a
+  datetime.timedelta() object wherever previously only a number of seconds was
+  allowed. (gh pr #264, #277)
+- datetime.timedelta objects can now be added to relativedelta objects. Reported
+  and added by Alec Nikolas Reiter (@justanr) (gh issue #282, pr #283
+- Refactored relativedelta.weekday and rrule.weekday into a common base class
+  to reduce code duplication. (gh issue #140, pr #311)
+- An issue where the WKST parameter was improperly rendering in str(rrule) was
+  reported and fixed by Daniel LePage (@dplepage). (gh issue #262, pr #263)
+- A replace() method has been added to rrule objects by @jendas1, which creates
+  new rrule with modified attributes, analogous to datetime.replace (gh pr #167)
+- Made some significant performance improvements to rrule objects in Python 2.x
+  (gh pr #245)
+- All classes defining equality functions now return NotImplemented when
+  compared to unsupported classes, rather than raising TypeError, to allow other
+  classes to provide fallback support. (gh pr #236)
+- Several classes have been marked as explicitly unhashable to maintain
+  identical behavior between Python 2 and 3. Submitted by Roy Williams
+  (@rowillia) (gh pr #296)
+- Trailing whitespace in easter.py has been removed. Submitted by @OmgImAlexis
+  (gh pr #299)
+- Windows-only batch files in build scripts had line endings switched to CRLF.
+  (gh pr #237)
+- @adamchainz updated the documentation links to reflect that the canonical
+  location for readthedocs links is now at .io, not .org. (gh pr #272)
+- Made some changes to the CI and codecov to test against newer versions of
+  Python and pypy, and to adjust the code coverage requirements. For the moment,
+  full pypy3 compatibility is not supported until a new release is available,
+  due to upstream bugs in the old version affecting PEP-495 support.
+  (gh prs #265, #266, #304, #308)
+- The full PGP signing key fingerprint was added to the README.md in favor of
+  the previously used long-id. Reported by @valholl (gh issue #287, pr #304)
+- Updated zoneinfo to 2016i. (gh issue #298, gh pr #306)
+
+
+Version 2.5.3
+=============
+- Updated zoneinfo to 2016d
+- Fixed parser bug where unambiguous datetimes fail to parse when dayfirst is
+  set to true. (gh issue #233, pr #234)
+- Bug in zoneinfo file on platforms such as Google App Engine which do not
+  do not allow importing of subprocess.check_call was reported and fixed by
+  @savraj (gh issue #239, gh pr #240)
+- Fixed incorrect version in documentation (gh issue #235, pr #243)
+
+Version 2.5.2
+=============
+- Updated zoneinfo to 2016c
+- Fixed parser bug where yearfirst and dayfirst parameters were not being
+  respected when no separator was present. (gh issue #81 and #217, pr #229)
+
+Version 2.5.1
+=============
+- Updated zoneinfo to 2016b
+- Changed MANIFEST.in to explicitly include test suite in source distributions,
+  with help from @koobs (gh issue #193, pr #194, #201, #221)
+- Explicitly set all line-endings to LF, except for the NEWS file, on a
+  per-repository basis (gh pr #218)
+- Fixed an issue with improper caching behavior in rruleset objects (gh issue
+  #104, pr #207)
+- Changed to an explicit error when rrulestr strings contain a missing BYDAY
+  (gh issue #162, pr #211)
+- tzfile now correctly handles files containing leapcnt (although the leapcnt
+  information is not actually used). Contributed by @hjoukl (gh issue #146, pr
+  #147)
+- Fixed recursive import issue with tz module (gh pr #204)
+- Added compatibility between tzwin objects and datetime.time objects (gh issue
+  #216, gh pr #219)
+- Refactored monolithic test suite by module (gh issue #61, pr #200 and #206)
+- Improved test coverage in the relativedelta module (gh pr #215)
+- Adjusted documentation to reflect possibly counter-intuitive properties of
+  RFC-5545-compliant rrules, and other documentation improvements in the rrule
+  module (gh issue #105, gh issue #149 - pointer to the solution by @phep,
+  pr #213).
+
+
+Version 2.5.0
+=============
+- Updated zoneinfo to 2016a
+- zoneinfo_metadata file version increased to 2.0 - the updated updatezinfo.py
+  script will work with older zoneinfo_metadata.json files, but new metadata
+  files will not work with older updatezinfo.py versions. Additionally, we have
+  started hosting our own mirror of the Olson databases on a github pages
+  site (https://dateutil.github.io/tzdata/) (gh pr #183)
+- dateutil zoneinfo tarballs now contain the full zoneinfo_metadata file used
+  to generate them. (gh issue #27, gh pr #85)
+- relativedelta can now be safely subclassed without derived objects reverting
+  to base relativedelta objects as a result of arithmetic operations.
+  (lp:1010199, gh issue #44, pr #49)
+- relativedelta 'weeks' parameter can now be set and retrieved as a property of
+  relativedelta instances. (lp: 727525, gh issue #45, pr #49)
+- relativedelta now explicitly supports fractional relative weeks, days, hours,
+  minutes and seconds. Fractional values in absolute parameters (year, day, etc)
+  are now deprecated. (gh issue #40, pr #190)
+- relativedelta objects previously did not use microseconds to determine of two
+  relativedelta objects were equal. This oversight has been corrected.
+  Contributed by @elprans (gh pr #113)
+- rrule now has an xafter() method for retrieving multiple recurrences after a
+  specified date. (gh pr #38)
+- str(rrule) now returns an RFC2445-compliant rrule string, contributed by
+  @schinckel and @armicron (lp:1406305, gh issue #47, prs #50, #62 and #160)
+- rrule performance under certain conditions has been significantly improved
+  thanks to a patch contributed by @dekoza, based on an article by Brian Beck
+  (@exogen) (gh pr #136)
+- The use of both the 'until' and 'count' parameters is now deprecated as
+  inconsistent with RFC2445 (gh pr #62, #185)
+- Parsing an empty string will now raise a ValueError, rather than returning the
+  datetime passed to the 'default' parameter. (gh issue #78, pr #187)
+- tzwinlocal objects now have a meaningful repr() and str() implementation
+  (gh issue #148, prs #184 and #186)
+- Added equality logic for tzwin and tzwinlocal objects. (gh issue #151,
+  pr #180, #184)
+- Added some flexibility in subclassing timelex, and switched the default
+  behavior over to using string methods rather than comparing against a fixed
+  list. (gh pr #122, #139)
+- An issue causing tzstr() to crash on Python 2.x was fixed. (lp: 1331576,
+  gh issue #51, pr #55)
+- An issue with string encoding causing exceptions under certain circumstances
+  when tzname() is called was fixed. (gh issue #60, #74, pr #75)
+- Parser issue where calling parse() on dates with no day specified when the
+  day of the month in the default datetime (which is "today" if unspecified) is
+  greater than the number of days in the parsed month was fixed (this issue
+  tended to crop up between the 29th and 31st of the month, for obvious reasons)
+  (canonical gh issue #25, pr #30, #191)
+- Fixed parser issue causing fuzzy_with_tokens to raise an unexpected exception
+  in certain circumstances. Contributed by @MichaelAquilina (gh pr #91)
+- Fixed parser issue where years > 100 AD were incorrectly parsed. Contributed
+  by @Bachmann1234 (gh pr #130)
+- Fixed parser issue where commas were not a valid separator between seconds
+  and microseconds, preventing parsing of ISO 8601 dates. Contributed by
+  @ryanss (gh issue #28, pr #106)
+- Fixed issue with tzwin encoding in locales with non-Latin alphabets
+  (gh issue #92, pr #98)
+- Fixed an issue where tzwin was not being properly imported on Windows.
+  Contributed by @labrys. (gh pr #134)
+- Fixed a problem causing issues importing zoneinfo in certain circumstances.
+  Issue and solution contributed by @alexxv (gh issue #97, pr #99)
+- Fixed an issue where dateutil timezones were not compatible with basic time
+  objects. One of many, many timezone related issues contributed and tested by
+  @labrys. (gh issue #132, pr #181)
+- Fixed issue where tzwinlocal had an invalid utcoffset. (gh issue #135,
+  pr #141, #142)
+- Fixed issue with tzwin and tzwinlocal where DST transitions were incorrectly
+  parsed from the registry. (gh issue #143, pr #178)
+- updatezinfo.py no longer suppresses certain OSErrors. Contributed by @bjamesv
+  (gh pr #164)
+- An issue that arose when timezone locale changes during runtime has been
+  fixed by @carlosxl and @mjschultz (gh issue #100, prs #107, #109)
+- Python 3.5 was added to the supported platforms in the metadata (@tacaswell
+  gh pr #159) and the test suites (@moreati gh pr #117).
+- An issue with tox failing without unittest2 installed in Python 2.6 was fixed
+  by @moreati (gh pr #115)
+- Several deprecated functions were replaced in the tests by @moreati
+  (gh pr #116)
+- Improved the logic in Travis and Appveyor to alleviate issues where builds
+  were failing due to connection issues when downloading the IANA timezone
+  files. In addition to adding our own mirror for the files (gh pr #183), the
+  download is now retried a number of times (with a delay) (gh pr #177)
+- Many failing doctests were fixed by @moreati. (gh pr #120)
+- Many fixes to the documentation (gh pr #103, gh pr #87 from @radarhere,
+  gh pr #154 from @gpoesia, gh pr #156 from @awsum, gh pr #168 from @ja8zyjits)
+- Added a code coverage tool to the CI to help improve the library. (gh pr #182)
+- We now have a mailing list - dateutil@python.org, graciously hosted by
+  Python.org.
+
+
+Version 2.4.2
+=============
+- Updated zoneinfo to 2015b.
+- Fixed issue with parsing of tzstr on Python 2.7.x; tzstr will now be decoded
+  if not a unicode type. gh #51 (lp:1331576), gh pr #55.
+- Fix a parser issue where AM and PM tokens were showing up in fuzzy date
+  stamps, triggering inappropriate errors. gh #56 (lp: 1428895), gh pr #63.
+- Missing function "setcachesize" removed from zoneinfo __all__ list by @ryanss,
+  fixing an issue with wildcard imports of dateutil.zoneinfo. (gh pr #66).
+- (PyPI only) Fix an issue with source distributions not including the test
+  suite.
+
+
+Version 2.4.1
+=============
+
+- Added explicit check for valid hours if AM/PM is specified in parser.
+  (gh pr #22, issue #21)
+- Fix bug in rrule introduced in 2.4.0 where byweekday parameter was not
+  handled properly. (gh pr #35, issue #34)
+- Fix error where parser allowed some invalid dates, overwriting existing hours
+  with the last 2-digit number in the string. (gh pr #32, issue #31)
+- Fix and add test for Python 2.x compatibility with boolean checking of
+  relativedelta objects. Implemented by @nimasmi (gh pr #43) and Cédric Krier
+  (lp: 1035038)
+- Replaced parse() calls with explicit datetime objects in unit tests unrelated
+  to parser. (gh pr #36)
+- Changed private _byxxx from sets to sorted tuples and fixed one currently
+  unreachable bug in _construct_byset. (gh pr #54)
+- Additional documentation for parser (gh pr #29, #33, #41) and rrule.
+- Formatting fixes to documentation of rrule and README.rst.
+- Updated zoneinfo to 2015a.
+
+Version 2.4.0
+=============
+
+- Fix an issue with relativedelta and freezegun (lp:1374022)
+- Fix tzinfo in windows for timezones without dst (lp:1010050, gh #2)
+- Ignore missing timezones in windows like in POSIX
+- Fix minimal version requirement for six (gh #6)
+- Many rrule changes and fixes by @pganssle (gh pull requests #13 #14 #17),
+    including defusing some infinite loops (gh #4)
+
+Version 2.3
+===========
+
+- Cleanup directory structure, moved test.py to dateutil/tests/test.py
+
+- Changed many aspects of dealing with the zone info file. Instead of a cache,
+  all the zones are loaded to memory, but symbolic links are loaded only once,
+  so not much memory is used.
+
+- The package is now zip-safe, and universal-wheelable, thanks to changes in
+  the handling of the zoneinfo file.
+
+- Fixed tzwin silently not imported on windows python2
+
+- New maintainer, together with new hosting: GitHub, Travis, Read-The-Docs
+
+Version 2.2
+===========
+
+- Updated zoneinfo to 2013h
+
+- fuzzy_with_tokens parse addon from Christopher Corley
+
+- Bug with LANG=C fixed by Mike Gilbert
+
+Version 2.1
+===========
+
+- New maintainer
+
+- Dateutil now works on Python 2.6, 2.7 and 3.2 from same codebase (with six)
+
+- #704047: Ismael Carnales' patch for a new time format
+
+- Small bug fixes, thanks for reporters!
+
+
+Version 2.0
+===========
+
+- Ported to Python 3, by Brian Jones.  If you need dateutil for Python 2.X,
+  please continue using the 1.X series.
+
+- There's no such thing as a "PSF License".  This source code is now
+  made available under the Simplified BSD license.  See LICENSE for
+  details.
+
+Version 1.5
+===========
+
+- As reported by Mathieu Bridon, rrules were matching the bysecond rules
+  incorrectly against byminute in some circumstances when the SECONDLY
+  frequency was in use, due to a copy & paste bug.  The problem has been
+  unittested and corrected.
+
+- Adam Ryan reported a problem in the relativedelta implementation which
+  affected the yearday parameter in the month of January specifically.
+  This has been unittested and fixed.
+
+- Updated timezone information.
+
+
+Version 1.4.1
+=============
+
+- Updated timezone information.
+
+
+Version 1.4
+===========
+
+- Fixed another parser precision problem on conversion of decimal seconds
+  to microseconds, as reported by Erik Brown.  Now these issues are gone
+  for real since it's not using floating point arithmetic anymore.
+
+- Fixed case where tzrange.utcoffset and tzrange.dst() might fail due
+  to a date being used where a datetime was expected (reported and fixed
+  by Lennart Regebro).
+
+- Prevent tzstr from introducing daylight timings in strings that didn't
+  specify them (reported by Lennart Regebro).
+
+- Calls like gettz("GMT+3") and gettz("UTC-2") will now return the
+  expected values, instead of the TZ variable behavior.
+
+- Fixed DST signal handling in zoneinfo files.  Reported by
+  Nicholas F. Fabry and John-Mark Gurney.
+
+
+Version 1.3
+===========
+
+- Fixed precision problem on conversion of decimal seconds to
+  microseconds, as reported by Skip Montanaro.
+
+- Fixed bug in constructor of parser, and converted parser classes to
+  new-style classes.  Original report and patch by Michael Elsdörfer.
+
+- Initialize tzid and comps in tz.py, to prevent the code from ever
+  raising a NameError (even with broken files).  Johan Dahlin suggested
+  the fix after a pyflakes run.
+
+- Version is now published in dateutil.__version__, as requested
+  by Darren Dale.
+
+- All code is compatible with new-style division.
+
+
+Version 1.2
+===========
+
+- Now tzfile will round timezones to full-minutes if necessary,
+  since Python's datetime doesn't support sub-minute offsets.
+  Thanks to Ilpo Nyyssönen for reporting the issue.
+
+- Removed bare string exceptions, as reported and fixed by
+  Wilfredo Sánchez Vega.
+
+- Fix bug in leap count parsing (reported and fixed by Eugene Oden).
+
+
+Version 1.1
+===========
+
+- Fixed rrule byyearday handling. Abramo Bagnara pointed out that
+  RFC2445 allows negative numbers.
+
+- Fixed --prefix handling in setup.py (by Sidnei da Silva).
+
+- Now tz.gettz() returns a tzlocal instance when not given any
+  arguments and no other timezone information is found.
+
+- Updating timezone information to version 2005q.
+
+
+Version 1.0
+===========
+
+- Fixed parsing of XXhXXm formatted time after day/month/year
+  has been parsed.
+
+- Added patch by Jeffrey Harris optimizing rrule.__contains__.
+
+
+Version 0.9
+===========
+
+- Fixed pickling of timezone types, as reported by
+  Andreas Köhler.
+
+- Implemented internal timezone information with binary
+  timezone files. datautil.tz.gettz() function will now
+  try to use the system timezone files, and fallback to
+  the internal versions. It's also possible to ask for
+  the internal versions directly by using
+  dateutil.zoneinfo.gettz().
+
+- New tzwin timezone type, allowing access to Windows
+  internal timezones (contributed by Jeffrey Harris).
+
+- Fixed parsing of unicode date strings.
+
+- Accept parserinfo instances as the parser constructor
+  parameter, besides parserinfo (sub)classes.
+
+- Changed weekday to spell the not-set n value as None
+  instead of 0.
+
+- Fixed other reported bugs.
+
+
+Version 0.5
+===========
+
+- Removed ``FREQ_`` prefix from rrule frequency constants
+  WARNING: this breaks compatibility with previous versions.
+
+- Fixed rrule.between() for cases where "after" is achieved
+  before even starting, as reported by Andreas Köhler.
+
+- Fixed two digit zero-year parsing (such as 31-Dec-00), as
+  reported by Jim Abramson, and included test case for this.
+
+- Sort exdate and rdate before iterating over them, so that
+  it's not necessary to sort them before adding to the rruleset,
+  as reported by Nicholas Piper.
diff --git a/resources/lib/libraries/dateutil/README.rst b/resources/lib/libraries/dateutil/README.rst
new file mode 100644
index 00000000..7a37552e
--- /dev/null
+++ b/resources/lib/libraries/dateutil/README.rst
@@ -0,0 +1,158 @@
+dateutil - powerful extensions to datetime
+==========================================
+
+|pypi| |support| |licence| 
+
+|gitter| |readthedocs|
+
+|travis| |appveyor| |coverage|
+
+.. |pypi| image:: https://img.shields.io/pypi/v/python-dateutil.svg?style=flat-square
+    :target: https://pypi.org/project/python-dateutil/
+    :alt: pypi version
+
+.. |support| image:: https://img.shields.io/pypi/pyversions/python-dateutil.svg?style=flat-square
+    :target: https://pypi.org/project/python-dateutil/
+    :alt: supported Python version
+
+.. |travis| image:: https://img.shields.io/travis/dateutil/dateutil/master.svg?style=flat-square&label=Travis%20Build
+    :target: https://travis-ci.org/dateutil/dateutil
+    :alt: travis build status
+
+.. |appveyor| image:: https://img.shields.io/appveyor/ci/dateutil/dateutil/master.svg?style=flat-square&logo=appveyor
+    :target: https://ci.appveyor.com/project/dateutil/dateutil
+    :alt: appveyor build status
+
+.. |coverage| image:: https://codecov.io/github/dateutil/dateutil/coverage.svg?branch=master
+    :target: https://codecov.io/github/dateutil/dateutil?branch=master
+    :alt: Code coverage
+
+.. |gitter| image:: https://badges.gitter.im/dateutil/dateutil.svg
+   :alt: Join the chat at https://gitter.im/dateutil/dateutil
+   :target: https://gitter.im/dateutil/dateutil
+
+.. |licence| image:: https://img.shields.io/pypi/l/python-dateutil.svg?style=flat-square
+    :target: https://pypi.org/project/python-dateutil/
+    :alt: licence
+
+.. |readthedocs| image:: https://img.shields.io/readthedocs/dateutil/latest.svg?style=flat-square&label=Read%20the%20Docs
+   :alt: Read the documentation at https://dateutil.readthedocs.io/en/latest/
+   :target: https://dateutil.readthedocs.io/en/latest/
+
+The `dateutil` module provides powerful extensions to
+the standard `datetime` module, available in Python.
+
+
+Download
+========
+dateutil is available on PyPI
+https://pypi.org/project/python-dateutil/
+
+The documentation is hosted at:
+https://dateutil.readthedocs.io/en/stable/
+
+Code
+====
+The code and issue tracker are hosted on Github:
+https://github.com/dateutil/dateutil/
+
+Features
+========
+
+* Computing of relative deltas (next month, next year,
+  next monday, last week of month, etc);
+* Computing of relative deltas between two given
+  date and/or datetime objects;
+* Computing of dates based on very flexible recurrence rules,
+  using a superset of the `iCalendar <https://www.ietf.org/rfc/rfc2445.txt>`_
+  specification. Parsing of RFC strings is supported as well.
+* Generic parsing of dates in almost any string format;
+* Timezone (tzinfo) implementations for tzfile(5) format
+  files (/etc/localtime, /usr/share/zoneinfo, etc), TZ
+  environment string (in all known formats), iCalendar
+  format files, given ranges (with help from relative deltas),
+  local machine timezone, fixed offset timezone, UTC timezone,
+  and Windows registry-based time zones.
+* Internal up-to-date world timezone information based on
+  Olson's database.
+* Computing of Easter Sunday dates for any given year,
+  using Western, Orthodox or Julian algorithms;
+* A comprehensive test suite.
+
+Quick example
+=============
+Here's a snapshot, just to give an idea about the power of the
+package. For more examples, look at the documentation.
+
+Suppose you want to know how much time is left, in
+years/months/days/etc, before the next easter happening on a
+year with a Friday 13th in August, and you want to get today's
+date out of the "date" unix system command. Here is the code:
+
+.. doctest:: readmeexample
+
+    >>> from dateutil.relativedelta import *
+    >>> from dateutil.easter import *
+    >>> from dateutil.rrule import *
+    >>> from dateutil.parser import *
+    >>> from datetime import *
+    >>> now = parse("Sat Oct 11 17:13:46 UTC 2003")
+    >>> today = now.date()
+    >>> year = rrule(YEARLY,dtstart=now,bymonth=8,bymonthday=13,byweekday=FR)[0].year
+    >>> rdelta = relativedelta(easter(year), today)
+    >>> print("Today is: %s" % today)
+    Today is: 2003-10-11
+    >>> print("Year with next Aug 13th on a Friday is: %s" % year)
+    Year with next Aug 13th on a Friday is: 2004
+    >>> print("How far is the Easter of that year: %s" % rdelta)
+    How far is the Easter of that year: relativedelta(months=+6)
+    >>> print("And the Easter of that year is: %s" % (today+rdelta))
+    And the Easter of that year is: 2004-04-11
+
+Being exactly 6 months ahead was **really** a coincidence :)
+
+Contributing
+============
+
+We welcome many types of contributions - bug reports, pull requests (code, infrastructure or documentation fixes). For more information about how to contribute to the project, see the ``CONTRIBUTING.md`` file in the repository.
+
+
+Author
+======
+The dateutil module was written by Gustavo Niemeyer <gustavo@niemeyer.net>
+in 2003.
+
+It is maintained by:
+
+* Gustavo Niemeyer <gustavo@niemeyer.net> 2003-2011
+* Tomi Pieviläinen <tomi.pievilainen@iki.fi> 2012-2014
+* Yaron de Leeuw <me@jarondl.net> 2014-2016
+* Paul Ganssle <paul@ganssle.io> 2015-
+
+Starting with version 2.4.1, all source and binary distributions will be signed
+by a PGP key that has, at the very least, been signed by the key which made the
+previous release. A table of release signing keys can be found below:
+
+===========  ============================
+Releases     Signing key fingerprint
+===========  ============================
+2.4.1-       `6B49 ACBA DCF6 BD1C A206 67AB CD54 FCE3 D964 BEFB`_ (|pgp_mirror|_)
+===========  ============================
+
+
+Contact
+=======
+Our mailing list is available at `dateutil@python.org <https://mail.python.org/mailman/listinfo/dateutil>`_. As it is hosted by the PSF, it is subject to the `PSF code of
+conduct <https://www.python.org/psf/codeofconduct/>`_.
+
+License
+=======
+
+All contributions after December 1, 2017 released under dual license - either `Apache 2.0 License <https://www.apache.org/licenses/LICENSE-2.0>`_ or the `BSD 3-Clause License <https://opensource.org/licenses/BSD-3-Clause>`_. Contributions before December 1, 2017 - except those those explicitly relicensed - are released only under the BSD 3-Clause License.
+
+
+.. _6B49 ACBA DCF6 BD1C A206 67AB CD54 FCE3 D964 BEFB:
+   https://pgp.mit.edu/pks/lookup?op=vindex&search=0xCD54FCE3D964BEFB
+
+.. |pgp_mirror| replace:: mirror
+.. _pgp_mirror: https://sks-keyservers.net/pks/lookup?op=vindex&search=0xCD54FCE3D964BEFB
diff --git a/resources/lib/libraries/dateutil/__init__.py b/resources/lib/libraries/dateutil/__init__.py
new file mode 100644
index 00000000..0defb82e
--- /dev/null
+++ b/resources/lib/libraries/dateutil/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+try:
+    from ._version import version as __version__
+except ImportError:
+    __version__ = 'unknown'
+
+__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz',
+           'utils', 'zoneinfo']
diff --git a/resources/lib/libraries/dateutil/_common.py b/resources/lib/libraries/dateutil/_common.py
new file mode 100644
index 00000000..4eb2659b
--- /dev/null
+++ b/resources/lib/libraries/dateutil/_common.py
@@ -0,0 +1,43 @@
+"""
+Common code used in multiple modules.
+"""
+
+
+class weekday(object):
+    __slots__ = ["weekday", "n"]
+
+    def __init__(self, weekday, n=None):
+        self.weekday = weekday
+        self.n = n
+
+    def __call__(self, n):
+        if n == self.n:
+            return self
+        else:
+            return self.__class__(self.weekday, n)
+
+    def __eq__(self, other):
+        try:
+            if self.weekday != other.weekday or self.n != other.n:
+                return False
+        except AttributeError:
+            return False
+        return True
+
+    def __hash__(self):
+        return hash((
+          self.weekday,
+          self.n,
+        ))
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
+        if not self.n:
+            return s
+        else:
+            return "%s(%+d)" % (s, self.n)
+
+# vim:ts=4:sw=4:et
diff --git a/resources/lib/libraries/dateutil/easter.py b/resources/lib/libraries/dateutil/easter.py
new file mode 100644
index 00000000..53b7c789
--- /dev/null
+++ b/resources/lib/libraries/dateutil/easter.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers a generic easter computing method for any given year, using
+Western, Orthodox or Julian algorithms.
+"""
+
+import datetime
+
+__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
+
+EASTER_JULIAN = 1
+EASTER_ORTHODOX = 2
+EASTER_WESTERN = 3
+
+
+def easter(year, method=EASTER_WESTERN):
+    """
+    This method was ported from the work done by GM Arts,
+    on top of the algorithm by Claus Tondering, which was
+    based in part on the algorithm of Ouding (1940), as
+    quoted in "Explanatory Supplement to the Astronomical
+    Almanac", P.  Kenneth Seidelmann, editor.
+
+    This algorithm implements three different easter
+    calculation methods:
+
+    1 - Original calculation in Julian calendar, valid in
+        dates after 326 AD
+    2 - Original method, with date converted to Gregorian
+        calendar, valid in years 1583 to 4099
+    3 - Revised method, in Gregorian calendar, valid in
+        years 1583 to 4099 as well
+
+    These methods are represented by the constants:
+
+    * ``EASTER_JULIAN   = 1``
+    * ``EASTER_ORTHODOX = 2``
+    * ``EASTER_WESTERN  = 3``
+
+    The default method is method 3.
+
+    More about the algorithm may be found at:
+
+    `GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_
+
+    and
+
+    `The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_
+
+    """
+
+    if not (1 <= method <= 3):
+        raise ValueError("invalid method")
+
+    # g - Golden year - 1
+    # c - Century
+    # h - (23 - Epact) mod 30
+    # i - Number of days from March 21 to Paschal Full Moon
+    # j - Weekday for PFM (0=Sunday, etc)
+    # p - Number of days from March 21 to Sunday on or before PFM
+    #     (-6 to 28 methods 1 & 3, to 56 for method 2)
+    # e - Extra days to add for method 2 (converting Julian
+    #     date to Gregorian date)
+
+    y = year
+    g = y % 19
+    e = 0
+    if method < 3:
+        # Old method
+        i = (19*g + 15) % 30
+        j = (y + y//4 + i) % 7
+        if method == 2:
+            # Extra dates to convert Julian to Gregorian date
+            e = 10
+            if y > 1600:
+                e = e + y//100 - 16 - (y//100 - 16)//4
+    else:
+        # New method
+        c = y//100
+        h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30
+        i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
+        j = (y + y//4 + i + 2 - c + c//4) % 7
+
+    # p can be from -6 to 56 corresponding to dates 22 March to 23 May
+    # (later dates apply to method 2, although 23 May never actually occurs)
+    p = i - j + e
+    d = 1 + (p + 27 + (p + 6)//40) % 31
+    m = 3 + (p + 26)//30
+    return datetime.date(int(y), int(m), int(d))
diff --git a/resources/lib/libraries/dateutil/parser/__init__.py b/resources/lib/libraries/dateutil/parser/__init__.py
new file mode 100644
index 00000000..216762c0
--- /dev/null
+++ b/resources/lib/libraries/dateutil/parser/__init__.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+from ._parser import parse, parser, parserinfo
+from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
+from ._parser import UnknownTimezoneWarning
+
+from ._parser import __doc__
+
+from .isoparser import isoparser, isoparse
+
+__all__ = ['parse', 'parser', 'parserinfo',
+           'isoparse', 'isoparser',
+           'UnknownTimezoneWarning']
+
+
+###
+# Deprecate portions of the private interface so that downstream code that
+# is improperly relying on it is given *some* notice.
+
+
+def __deprecated_private_func(f):
+    from functools import wraps
+    import warnings
+
+    msg = ('{name} is a private function and may break without warning, '
+           'it will be moved and or renamed in future versions.')
+    msg = msg.format(name=f.__name__)
+
+    @wraps(f)
+    def deprecated_func(*args, **kwargs):
+        warnings.warn(msg, DeprecationWarning)
+        return f(*args, **kwargs)
+
+    return deprecated_func
+
+def __deprecate_private_class(c):
+    import warnings
+
+    msg = ('{name} is a private class and may break without warning, '
+           'it will be moved and or renamed in future versions.')
+    msg = msg.format(name=c.__name__)
+
+    class private_class(c):
+        __doc__ = c.__doc__
+
+        def __init__(self, *args, **kwargs):
+            warnings.warn(msg, DeprecationWarning)
+            super(private_class, self).__init__(*args, **kwargs)
+
+    private_class.__name__ = c.__name__
+
+    return private_class
+
+
+from ._parser import _timelex, _resultbase
+from ._parser import _tzparser, _parsetz
+
+_timelex = __deprecate_private_class(_timelex)
+_tzparser = __deprecate_private_class(_tzparser)
+_resultbase = __deprecate_private_class(_resultbase)
+_parsetz = __deprecated_private_func(_parsetz)
diff --git a/resources/lib/libraries/dateutil/parser/_parser.py b/resources/lib/libraries/dateutil/parser/_parser.py
new file mode 100644
index 00000000..e8a522c9
--- /dev/null
+++ b/resources/lib/libraries/dateutil/parser/_parser.py
@@ -0,0 +1,1578 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers a generic date/time string parser which is able to parse
+most known formats to represent a date and/or time.
+
+This module attempts to be forgiving with regards to unlikely input formats,
+returning a datetime object even for dates which are ambiguous. If an element
+of a date/time stamp is omitted, the following rules are applied:
+
+- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour
+  on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is
+  specified.
+- If a time zone is omitted, a timezone-naive datetime is returned.
+
+If any other elements are missing, they are taken from the
+:class:`datetime.datetime` object passed to the parameter ``default``. If this
+results in a day number exceeding the valid number of days per month, the
+value falls back to the end of the month.
+
+Additional resources about date/time string formats can be found below:
+
+- `A summary of the international standard date and time notation
+  <http://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_
+- `W3C Date and Time Formats <http://www.w3.org/TR/NOTE-datetime>`_
+- `Time Formats (Planetary Rings Node) <https://pds-rings.seti.org:443/tools/time_formats.html>`_
+- `CPAN ParseDate module
+  <http://search.cpan.org/~muir/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_
+- `Java SimpleDateFormat Class
+  <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_
+"""
+from __future__ import unicode_literals
+
+import datetime
+import re
+import string
+import time
+import warnings
+
+from calendar import monthrange
+from io import StringIO
+
+from .. import six
+from ..six import binary_type, integer_types, text_type
+
+from decimal import Decimal
+
+from warnings import warn
+
+from .. import relativedelta
+from .. import tz
+
+__all__ = ["parse", "parserinfo"]
+
+
+# TODO: pandas.core.tools.datetimes imports this explicitly.  Might be worth
+# making public and/or figuring out if there is something we can
+# take off their plate.
+class _timelex(object):
+    # Fractional seconds are sometimes split by a comma
+    _split_decimal = re.compile("([.,])")
+
+    def __init__(self, instream):
+        if six.PY2:
+            # In Python 2, we can't duck type properly because unicode has
+            # a 'decode' function, and we'd be double-decoding
+            if isinstance(instream, (binary_type, bytearray)):
+                instream = instream.decode()
+        else:
+            if getattr(instream, 'decode', None) is not None:
+                instream = instream.decode()
+
+        if isinstance(instream, text_type):
+            instream = StringIO(instream)
+        elif getattr(instream, 'read', None) is None:
+            raise TypeError('Parser must be a string or character stream, not '
+                            '{itype}'.format(itype=instream.__class__.__name__))
+
+        self.instream = instream
+        self.charstack = []
+        self.tokenstack = []
+        self.eof = False
+
+    def get_token(self):
+        """
+        This function breaks the time string into lexical units (tokens), which
+        can be parsed by the parser. Lexical units are demarcated by changes in
+        the character set, so any continuous string of letters is considered
+        one unit, any continuous string of numbers is considered one unit.
+
+        The main complication arises from the fact that dots ('.') can be used
+        both as separators (e.g. "Sep.20.2009") or decimal points (e.g.
+        "4:30:21.447"). As such, it is necessary to read the full context of
+        any dot-separated strings before breaking it into tokens; as such, this
+        function maintains a "token stack", for when the ambiguous context
+        demands that multiple tokens be parsed at once.
+        """
+        if self.tokenstack:
+            return self.tokenstack.pop(0)
+
+        seenletters = False
+        token = None
+        state = None
+
+        while not self.eof:
+            # We only realize that we've reached the end of a token when we
+            # find a character that's not part of the current token - since
+            # that character may be part of the next token, it's stored in the
+            # charstack.
+            if self.charstack:
+                nextchar = self.charstack.pop(0)
+            else:
+                nextchar = self.instream.read(1)
+                while nextchar == '\x00':
+                    nextchar = self.instream.read(1)
+
+            if not nextchar:
+                self.eof = True
+                break
+            elif not state:
+                # First character of the token - determines if we're starting
+                # to parse a word, a number or something else.
+                token = nextchar
+                if self.isword(nextchar):
+                    state = 'a'
+                elif self.isnum(nextchar):
+                    state = '0'
+                elif self.isspace(nextchar):
+                    token = ' '
+                    break  # emit token
+                else:
+                    break  # emit token
+            elif state == 'a':
+                # If we've already started reading a word, we keep reading
+                # letters until we find something that's not part of a word.
+                seenletters = True
+                if self.isword(nextchar):
+                    token += nextchar
+                elif nextchar == '.':
+                    token += nextchar
+                    state = 'a.'
+                else:
+                    self.charstack.append(nextchar)
+                    break  # emit token
+            elif state == '0':
+                # If we've already started reading a number, we keep reading
+                # numbers until we find something that doesn't fit.
+                if self.isnum(nextchar):
+                    token += nextchar
+                elif nextchar == '.' or (nextchar == ',' and len(token) >= 2):
+                    token += nextchar
+                    state = '0.'
+                else:
+                    self.charstack.append(nextchar)
+                    break  # emit token
+            elif state == 'a.':
+                # If we've seen some letters and a dot separator, continue
+                # parsing, and the tokens will be broken up later.
+                seenletters = True
+                if nextchar == '.' or self.isword(nextchar):
+                    token += nextchar
+                elif self.isnum(nextchar) and token[-1] == '.':
+                    token += nextchar
+                    state = '0.'
+                else:
+                    self.charstack.append(nextchar)
+                    break  # emit token
+            elif state == '0.':
+                # If we've seen at least one dot separator, keep going, we'll
+                # break up the tokens later.
+                if nextchar == '.' or self.isnum(nextchar):
+                    token += nextchar
+                elif self.isword(nextchar) and token[-1] == '.':
+                    token += nextchar
+                    state = 'a.'
+                else:
+                    self.charstack.append(nextchar)
+                    break  # emit token
+
+        if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
+                                       token[-1] in '.,')):
+            l = self._split_decimal.split(token)
+            token = l[0]
+            for tok in l[1:]:
+                if tok:
+                    self.tokenstack.append(tok)
+
+        if state == '0.' and token.count('.') == 0:
+            token = token.replace(',', '.')
+
+        return token
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        token = self.get_token()
+        if token is None:
+            raise StopIteration
+
+        return token
+
+    def next(self):
+        return self.__next__()  # Python 2.x support
+
+    @classmethod
+    def split(cls, s):
+        return list(cls(s))
+
+    @classmethod
+    def isword(cls, nextchar):
+        """ Whether or not the next character is part of a word """
+        return nextchar.isalpha()
+
+    @classmethod
+    def isnum(cls, nextchar):
+        """ Whether the next character is part of a number """
+        return nextchar.isdigit()
+
+    @classmethod
+    def isspace(cls, nextchar):
+        """ Whether the next character is whitespace """
+        return nextchar.isspace()
+
+
+class _resultbase(object):
+
+    def __init__(self):
+        for attr in self.__slots__:
+            setattr(self, attr, None)
+
+    def _repr(self, classname):
+        l = []
+        for attr in self.__slots__:
+            value = getattr(self, attr)
+            if value is not None:
+                l.append("%s=%s" % (attr, repr(value)))
+        return "%s(%s)" % (classname, ", ".join(l))
+
+    def __len__(self):
+        return (sum(getattr(self, attr) is not None
+                    for attr in self.__slots__))
+
+    def __repr__(self):
+        return self._repr(self.__class__.__name__)
+
+
+class parserinfo(object):
+    """
+    Class which handles what inputs are accepted. Subclass this to customize
+    the language and acceptable values for each parameter.
+
+    :param dayfirst:
+        Whether to interpret the first value in an ambiguous 3-integer date
+        (e.g. 01/05/09) as the day (``True``) or month (``False``). If
+        ``yearfirst`` is set to ``True``, this distinguishes between YDM
+        and YMD. Default is ``False``.
+
+    :param yearfirst:
+        Whether to interpret the first value in an ambiguous 3-integer date
+        (e.g. 01/05/09) as the year. If ``True``, the first number is taken
+        to be the year, otherwise the last number is taken to be the year.
+        Default is ``False``.
+    """
+
+    # m from a.m/p.m, t from ISO T separator
+    JUMP = [" ", ".", ",", ";", "-", "/", "'",
+            "at", "on", "and", "ad", "m", "t", "of",
+            "st", "nd", "rd", "th"]
+
+    WEEKDAYS = [("Mon", "Monday"),
+                ("Tue", "Tuesday"),     # TODO: "Tues"
+                ("Wed", "Wednesday"),
+                ("Thu", "Thursday"),    # TODO: "Thurs"
+                ("Fri", "Friday"),
+                ("Sat", "Saturday"),
+                ("Sun", "Sunday")]
+    MONTHS = [("Jan", "January"),
+              ("Feb", "February"),      # TODO: "Febr"
+              ("Mar", "March"),
+              ("Apr", "April"),
+              ("May", "May"),
+              ("Jun", "June"),
+              ("Jul", "July"),
+              ("Aug", "August"),
+              ("Sep", "Sept", "September"),
+              ("Oct", "October"),
+              ("Nov", "November"),
+              ("Dec", "December")]
+    HMS = [("h", "hour", "hours"),
+           ("m", "minute", "minutes"),
+           ("s", "second", "seconds")]
+    AMPM = [("am", "a"),
+            ("pm", "p")]
+    UTCZONE = ["UTC", "GMT", "Z"]
+    PERTAIN = ["of"]
+    TZOFFSET = {}
+    # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate",
+    #              "Anno Domini", "Year of Our Lord"]
+
+    def __init__(self, dayfirst=False, yearfirst=False):
+        self._jump = self._convert(self.JUMP)
+        self._weekdays = self._convert(self.WEEKDAYS)
+        self._months = self._convert(self.MONTHS)
+        self._hms = self._convert(self.HMS)
+        self._ampm = self._convert(self.AMPM)
+        self._utczone = self._convert(self.UTCZONE)
+        self._pertain = self._convert(self.PERTAIN)
+
+        self.dayfirst = dayfirst
+        self.yearfirst = yearfirst
+
+        self._year = time.localtime().tm_year
+        self._century = self._year // 100 * 100
+
+    def _convert(self, lst):
+        dct = {}
+        for i, v in enumerate(lst):
+            if isinstance(v, tuple):
+                for v in v:
+                    dct[v.lower()] = i
+            else:
+                dct[v.lower()] = i
+        return dct
+
+    def jump(self, name):
+        return name.lower() in self._jump
+
+    def weekday(self, name):
+        try:
+            return self._weekdays[name.lower()]
+        except KeyError:
+            pass
+        return None
+
+    def month(self, name):
+        try:
+            return self._months[name.lower()] + 1
+        except KeyError:
+            pass
+        return None
+
+    def hms(self, name):
+        try:
+            return self._hms[name.lower()]
+        except KeyError:
+            return None
+
+    def ampm(self, name):
+        try:
+            return self._ampm[name.lower()]
+        except KeyError:
+            return None
+
+    def pertain(self, name):
+        return name.lower() in self._pertain
+
+    def utczone(self, name):
+        return name.lower() in self._utczone
+
+    def tzoffset(self, name):
+        if name in self._utczone:
+            return 0
+
+        return self.TZOFFSET.get(name)
+
+    def convertyear(self, year, century_specified=False):
+        """
+        Converts two-digit years to year within [-50, 49]
+        range of self._year (current local time)
+        """
+
+        # Function contract is that the year is always positive
+        assert year >= 0
+
+        if year < 100 and not century_specified:
+            # assume current century to start
+            year += self._century
+
+            if year >= self._year + 50:  # if too far in future
+                year -= 100
+            elif year < self._year - 50:  # if too far in past
+                year += 100
+
+        return year
+
+    def validate(self, res):
+        # move to info
+        if res.year is not None:
+            res.year = self.convertyear(res.year, res.century_specified)
+
+        if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
+            res.tzname = "UTC"
+            res.tzoffset = 0
+        elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
+            res.tzoffset = 0
+        return True
+
+
+class _ymd(list):
+    def __init__(self, *args, **kwargs):
+        super(self.__class__, self).__init__(*args, **kwargs)
+        self.century_specified = False
+        self.dstridx = None
+        self.mstridx = None
+        self.ystridx = None
+
+    @property
+    def has_year(self):
+        return self.ystridx is not None
+
+    @property
+    def has_month(self):
+        return self.mstridx is not None
+
+    @property
+    def has_day(self):
+        return self.dstridx is not None
+
+    def could_be_day(self, value):
+        if self.has_day:
+            return False
+        elif not self.has_month:
+            return 1 <= value <= 31
+        elif not self.has_year:
+            # Be permissive, assume leapyear
+            month = self[self.mstridx]
+            return 1 <= value <= monthrange(2000, month)[1]
+        else:
+            month = self[self.mstridx]
+            year = self[self.ystridx]
+            return 1 <= value <= monthrange(year, month)[1]
+
+    def append(self, val, label=None):
+        if hasattr(val, '__len__'):
+            if val.isdigit() and len(val) > 2:
+                self.century_specified = True
+                if label not in [None, 'Y']:  # pragma: no cover
+                    raise ValueError(label)
+                label = 'Y'
+        elif val > 100:
+            self.century_specified = True
+            if label not in [None, 'Y']:  # pragma: no cover
+                raise ValueError(label)
+            label = 'Y'
+
+        super(self.__class__, self).append(int(val))
+
+        if label == 'M':
+            if self.has_month:
+                raise ValueError('Month is already set')
+            self.mstridx = len(self) - 1
+        elif label == 'D':
+            if self.has_day:
+                raise ValueError('Day is already set')
+            self.dstridx = len(self) - 1
+        elif label == 'Y':
+            if self.has_year:
+                raise ValueError('Year is already set')
+            self.ystridx = len(self) - 1
+
+    def _resolve_from_stridxs(self, strids):
+        """
+        Try to resolve the identities of year/month/day elements using
+        ystridx, mstridx, and dstridx, if enough of these are specified.
+        """
+        if len(self) == 3 and len(strids) == 2:
+            # we can back out the remaining stridx value
+            missing = [x for x in range(3) if x not in strids.values()]
+            key = [x for x in ['y', 'm', 'd'] if x not in strids]
+            assert len(missing) == len(key) == 1
+            key = key[0]
+            val = missing[0]
+            strids[key] = val
+
+        assert len(self) == len(strids)  # otherwise this should not be called
+        out = {key: self[strids[key]] for key in strids}
+        return (out.get('y'), out.get('m'), out.get('d'))
+
+    def resolve_ymd(self, yearfirst, dayfirst):
+        len_ymd = len(self)
+        year, month, day = (None, None, None)
+
+        strids = (('y', self.ystridx),
+                  ('m', self.mstridx),
+                  ('d', self.dstridx))
+
+        strids = {key: val for key, val in strids if val is not None}
+        if (len(self) == len(strids) > 0 or
+                (len(self) == 3 and len(strids) == 2)):
+            return self._resolve_from_stridxs(strids)
+
+        mstridx = self.mstridx
+
+        if len_ymd > 3:
+            raise ValueError("More than three YMD values")
+        elif len_ymd == 1 or (mstridx is not None and len_ymd == 2):
+            # One member, or two members with a month string
+            if mstridx is not None:
+                month = self[mstridx]
+                # since mstridx is 0 or 1, self[mstridx-1] always
+                # looks up the other element
+                other = self[mstridx - 1]
+            else:
+                other = self[0]
+
+            if len_ymd > 1 or mstridx is None:
+                if other > 31:
+                    year = other
+                else:
+                    day = other
+
+        elif len_ymd == 2:
+            # Two members with numbers
+            if self[0] > 31:
+                # 99-01
+                year, month = self
+            elif self[1] > 31:
+                # 01-99
+                month, year = self
+            elif dayfirst and self[1] <= 12:
+                # 13-01
+                day, month = self
+            else:
+                # 01-13
+                month, day = self
+
+        elif len_ymd == 3:
+            # Three members
+            if mstridx == 0:
+                if self[1] > 31:
+                    # Apr-2003-25
+                    month, year, day = self
+                else:
+                    month, day, year = self
+            elif mstridx == 1:
+                if self[0] > 31 or (yearfirst and self[2] <= 31):
+                    # 99-Jan-01
+                    year, month, day = self
+                else:
+                    # 01-Jan-01
+                    # Give precendence to day-first, since
+                    # two-digit years is usually hand-written.
+                    day, month, year = self
+
+            elif mstridx == 2:
+                # WTF!?
+                if self[1] > 31:
+                    # 01-99-Jan
+                    day, year, month = self
+                else:
+                    # 99-01-Jan
+                    year, day, month = self
+
+            else:
+                if (self[0] > 31 or
+                    self.ystridx == 0 or
+                        (yearfirst and self[1] <= 12 and self[2] <= 31)):
+                    # 99-01-01
+                    if dayfirst and self[2] <= 12:
+                        year, day, month = self
+                    else:
+                        year, month, day = self
+                elif self[0] > 12 or (dayfirst and self[1] <= 12):
+                    # 13-01-01
+                    day, month, year = self
+                else:
+                    # 01-13-01
+                    month, day, year = self
+
+        return year, month, day
+
+
+class parser(object):
+    def __init__(self, info=None):
+        self.info = info or parserinfo()
+
+    def parse(self, timestr, default=None,
+              ignoretz=False, tzinfos=None, **kwargs):
+        """
+        Parse the date/time string into a :class:`datetime.datetime` object.
+
+        :param timestr:
+            Any date/time string using the supported formats.
+
+        :param default:
+            The default datetime object, if this is a datetime object and not
+            ``None``, elements specified in ``timestr`` replace elements in the
+            default object.
+
+        :param ignoretz:
+            If set ``True``, time zones in parsed strings are ignored and a
+            naive :class:`datetime.datetime` object is returned.
+
+        :param tzinfos:
+            Additional time zone names / aliases which may be present in the
+            string. This argument maps time zone names (and optionally offsets
+            from those time zones) to time zones. This parameter can be a
+            dictionary with timezone aliases mapping time zone names to time
+            zones or a function taking two parameters (``tzname`` and
+            ``tzoffset``) and returning a time zone.
+
+            The timezones to which the names are mapped can be an integer
+            offset from UTC in seconds or a :class:`tzinfo` object.
+
+            .. doctest::
+               :options: +NORMALIZE_WHITESPACE
+
+                >>> from dateutil.parser import parse
+                >>> from dateutil.tz import gettz
+                >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")}
+                >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
+                datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200))
+                >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
+                datetime.datetime(2012, 1, 19, 17, 21,
+                                  tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
+
+            This parameter is ignored if ``ignoretz`` is set.
+
+        :param \\*\\*kwargs:
+            Keyword arguments as passed to ``_parse()``.
+
+        :return:
+            Returns a :class:`datetime.datetime` object or, if the
+            ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
+            first element being a :class:`datetime.datetime` object, the second
+            a tuple containing the fuzzy tokens.
+
+        :raises ValueError:
+            Raised for invalid or unknown string format, if the provided
+            :class:`tzinfo` is not in a valid format, or if an invalid date
+            would be created.
+
+        :raises TypeError:
+            Raised for non-string or character stream input.
+
+        :raises OverflowError:
+            Raised if the parsed date exceeds the largest valid C integer on
+            your system.
+        """
+
+        if default is None:
+            default = datetime.datetime.now().replace(hour=0, minute=0,
+                                                      second=0, microsecond=0)
+
+        res, skipped_tokens = self._parse(timestr, **kwargs)
+
+        if res is None:
+            raise ValueError("Unknown string format:", timestr)
+
+        if len(res) == 0:
+            raise ValueError("String does not contain a date:", timestr)
+
+        ret = self._build_naive(res, default)
+
+        if not ignoretz:
+            ret = self._build_tzaware(ret, res, tzinfos)
+
+        if kwargs.get('fuzzy_with_tokens', False):
+            return ret, skipped_tokens
+        else:
+            return ret
+
+    class _result(_resultbase):
+        __slots__ = ["year", "month", "day", "weekday",
+                     "hour", "minute", "second", "microsecond",
+                     "tzname", "tzoffset", "ampm","any_unused_tokens"]
+
+    def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False,
+               fuzzy_with_tokens=False):
+        """
+        Private method which performs the heavy lifting of parsing, called from
+        ``parse()``, which passes on its ``kwargs`` to this function.
+
+        :param timestr:
+            The string to parse.
+
+        :param dayfirst:
+            Whether to interpret the first value in an ambiguous 3-integer date
+            (e.g. 01/05/09) as the day (``True``) or month (``False``). If
+            ``yearfirst`` is set to ``True``, this distinguishes between YDM
+            and YMD. If set to ``None``, this value is retrieved from the
+            current :class:`parserinfo` object (which itself defaults to
+            ``False``).
+
+        :param yearfirst:
+            Whether to interpret the first value in an ambiguous 3-integer date
+            (e.g. 01/05/09) as the year. If ``True``, the first number is taken
+            to be the year, otherwise the last number is taken to be the year.
+            If this is set to ``None``, the value is retrieved from the current
+            :class:`parserinfo` object (which itself defaults to ``False``).
+
+        :param fuzzy:
+            Whether to allow fuzzy parsing, allowing for string like "Today is
+            January 1, 2047 at 8:21:00AM".
+
+        :param fuzzy_with_tokens:
+            If ``True``, ``fuzzy`` is automatically set to True, and the parser
+            will return a tuple where the first element is the parsed
+            :class:`datetime.datetime` datetimestamp and the second element is
+            a tuple containing the portions of the string which were ignored:
+
+            .. doctest::
+
+                >>> from dateutil.parser import parse
+                >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
+                (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
+
+        """
+        if fuzzy_with_tokens:
+            fuzzy = True
+
+        info = self.info
+
+        if dayfirst is None:
+            dayfirst = info.dayfirst
+
+        if yearfirst is None:
+            yearfirst = info.yearfirst
+
+        res = self._result()
+        l = _timelex.split(timestr)         # Splits the timestr into tokens
+
+        skipped_idxs = []
+
+        # year/month/day list
+        ymd = _ymd()
+
+        len_l = len(l)
+        i = 0
+        try:
+            while i < len_l:
+
+                # Check if it's a number
+                value_repr = l[i]
+                try:
+                    value = float(value_repr)
+                except ValueError:
+                    value = None
+
+                if value is not None:
+                    # Numeric token
+                    i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy)
+
+                # Check weekday
+                elif info.weekday(l[i]) is not None:
+                    value = info.weekday(l[i])
+                    res.weekday = value
+
+                # Check month name
+                elif info.month(l[i]) is not None:
+                    value = info.month(l[i])
+                    ymd.append(value, 'M')
+
+                    if i + 1 < len_l:
+                        if l[i + 1] in ('-', '/'):
+                            # Jan-01[-99]
+                            sep = l[i + 1]
+                            ymd.append(l[i + 2])
+
+                            if i + 3 < len_l and l[i + 3] == sep:
+                                # Jan-01-99
+                                ymd.append(l[i + 4])
+                                i += 2
+
+                            i += 2
+
+                        elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and
+                              info.pertain(l[i + 2])):
+                            # Jan of 01
+                            # In this case, 01 is clearly year
+                            if l[i + 4].isdigit():
+                                # Convert it here to become unambiguous
+                                value = int(l[i + 4])
+                                year = str(info.convertyear(value))
+                                ymd.append(year, 'Y')
+                            else:
+                                # Wrong guess
+                                pass
+                                # TODO: not hit in tests
+                            i += 4
+
+                # Check am/pm
+                elif info.ampm(l[i]) is not None:
+                    value = info.ampm(l[i])
+                    val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy)
+
+                    if val_is_ampm:
+                        res.hour = self._adjust_ampm(res.hour, value)
+                        res.ampm = value
+
+                    elif fuzzy:
+                        skipped_idxs.append(i)
+
+                # Check for a timezone name
+                elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]):
+                    res.tzname = l[i]
+                    res.tzoffset = info.tzoffset(res.tzname)
+
+                    # Check for something like GMT+3, or BRST+3. Notice
+                    # that it doesn't mean "I am 3 hours after GMT", but
+                    # "my time +3 is GMT". If found, we reverse the
+                    # logic so that timezone parsing code will get it
+                    # right.
+                    if i + 1 < len_l and l[i + 1] in ('+', '-'):
+                        l[i + 1] = ('+', '-')[l[i + 1] == '+']
+                        res.tzoffset = None
+                        if info.utczone(res.tzname):
+                            # With something like GMT+3, the timezone
+                            # is *not* GMT.
+                            res.tzname = None
+
+                # Check for a numbered timezone
+                elif res.hour is not None and l[i] in ('+', '-'):
+                    signal = (-1, 1)[l[i] == '+']
+                    len_li = len(l[i + 1])
+
+                    # TODO: check that l[i + 1] is integer?
+                    if len_li == 4:
+                        # -0300
+                        hour_offset = int(l[i + 1][:2])
+                        min_offset = int(l[i + 1][2:])
+                    elif i + 2 < len_l and l[i + 2] == ':':
+                        # -03:00
+                        hour_offset = int(l[i + 1])
+                        min_offset = int(l[i + 3])  # TODO: Check that l[i+3] is minute-like?
+                        i += 2
+                    elif len_li <= 2:
+                        # -[0]3
+                        hour_offset = int(l[i + 1][:2])
+                        min_offset = 0
+                    else:
+                        raise ValueError(timestr)
+
+                    res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60)
+
+                    # Look for a timezone name between parenthesis
+                    if (i + 5 < len_l and
+                            info.jump(l[i + 2]) and l[i + 3] == '(' and
+                            l[i + 5] == ')' and
+                            3 <= len(l[i + 4]) and
+                            self._could_be_tzname(res.hour, res.tzname,
+                                                  None, l[i + 4])):
+                        # -0300 (BRST)
+                        res.tzname = l[i + 4]
+                        i += 4
+
+                    i += 1
+
+                # Check jumps
+                elif not (info.jump(l[i]) or fuzzy):
+                    raise ValueError(timestr)
+
+                else:
+                    skipped_idxs.append(i)
+                i += 1
+
+            # Process year/month/day
+            year, month, day = ymd.resolve_ymd(yearfirst, dayfirst)
+
+            res.century_specified = ymd.century_specified
+            res.year = year
+            res.month = month
+            res.day = day
+
+        except (IndexError, ValueError):
+            return None, None
+
+        if not info.validate(res):
+            return None, None
+
+        if fuzzy_with_tokens:
+            skipped_tokens = self._recombine_skipped(l, skipped_idxs)
+            return res, tuple(skipped_tokens)
+        else:
+            return res, None
+
+    def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy):
+        # Token is a number
+        value_repr = tokens[idx]
+        try:
+            value = self._to_decimal(value_repr)
+        except Exception as e:
+            six.raise_from(ValueError('Unknown numeric token'), e)
+
+        len_li = len(value_repr)
+
+        len_l = len(tokens)
+
+        if (len(ymd) == 3 and len_li in (2, 4) and
+            res.hour is None and
+            (idx + 1 >= len_l or
+             (tokens[idx + 1] != ':' and
+              info.hms(tokens[idx + 1]) is None))):
+            # 19990101T23[59]
+            s = tokens[idx]
+            res.hour = int(s[:2])
+
+            if len_li == 4:
+                res.minute = int(s[2:])
+
+        elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6):
+            # YYMMDD or HHMMSS[.ss]
+            s = tokens[idx]
+
+            if not ymd and '.' not in tokens[idx]:
+                ymd.append(s[:2])
+                ymd.append(s[2:4])
+                ymd.append(s[4:])
+            else:
+                # 19990101T235959[.59]
+
+                # TODO: Check if res attributes already set.
+                res.hour = int(s[:2])
+                res.minute = int(s[2:4])
+                res.second, res.microsecond = self._parsems(s[4:])
+
+        elif len_li in (8, 12, 14):
+            # YYYYMMDD
+            s = tokens[idx]
+            ymd.append(s[:4], 'Y')
+            ymd.append(s[4:6])
+            ymd.append(s[6:8])
+
+            if len_li > 8:
+                res.hour = int(s[8:10])
+                res.minute = int(s[10:12])
+
+                if len_li > 12:
+                    res.second = int(s[12:])
+
+        elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None:
+            # HH[ ]h or MM[ ]m or SS[.ss][ ]s
+            hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True)
+            (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx)
+            if hms is not None:
+                # TODO: checking that hour/minute/second are not
+                # already set?
+                self._assign_hms(res, value_repr, hms)
+
+        elif idx + 2 < len_l and tokens[idx + 1] == ':':
+            # HH:MM[:SS[.ss]]
+            res.hour = int(value)
+            value = self._to_decimal(tokens[idx + 2])  # TODO: try/except for this?
+            (res.minute, res.second) = self._parse_min_sec(value)
+
+            if idx + 4 < len_l and tokens[idx + 3] == ':':
+                res.second, res.microsecond = self._parsems(tokens[idx + 4])
+
+                idx += 2
+
+            idx += 2
+
+        elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'):
+            sep = tokens[idx + 1]
+            ymd.append(value_repr)
+
+            if idx + 2 < len_l and not info.jump(tokens[idx + 2]):
+                if tokens[idx + 2].isdigit():
+                    # 01-01[-01]
+                    ymd.append(tokens[idx + 2])
+                else:
+                    # 01-Jan[-01]
+                    value = info.month(tokens[idx + 2])
+
+                    if value is not None:
+                        ymd.append(value, 'M')
+                    else:
+                        raise ValueError()
+
+                if idx + 3 < len_l and tokens[idx + 3] == sep:
+                    # We have three members
+                    value = info.month(tokens[idx + 4])
+
+                    if value is not None:
+                        ymd.append(value, 'M')
+                    else:
+                        ymd.append(tokens[idx + 4])
+                    idx += 2
+
+                idx += 1
+            idx += 1
+
+        elif idx + 1 >= len_l or info.jump(tokens[idx + 1]):
+            if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None:
+                # 12 am
+                hour = int(value)
+                res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2]))
+                idx += 1
+            else:
+                # Year, month or day
+                ymd.append(value)
+            idx += 1
+
+        elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24):
+            # 12am
+            hour = int(value)
+            res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1]))
+            idx += 1
+
+        elif ymd.could_be_day(value):
+            ymd.append(value)
+
+        elif not fuzzy:
+            raise ValueError()
+
+        return idx
+
+    def _find_hms_idx(self, idx, tokens, info, allow_jump):
+        len_l = len(tokens)
+
+        if idx+1 < len_l and info.hms(tokens[idx+1]) is not None:
+            # There is an "h", "m", or "s" label following this token.  We take
+            # assign the upcoming label to the current token.
+            # e.g. the "12" in 12h"
+            hms_idx = idx + 1
+
+        elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and
+              info.hms(tokens[idx+2]) is not None):
+            # There is a space and then an "h", "m", or "s" label.
+            # e.g. the "12" in "12 h"
+            hms_idx = idx + 2
+
+        elif idx > 0 and info.hms(tokens[idx-1]) is not None:
+            # There is a "h", "m", or "s" preceeding this token.  Since neither
+            # of the previous cases was hit, there is no label following this
+            # token, so we use the previous label.
+            # e.g. the "04" in "12h04"
+            hms_idx = idx-1
+
+        elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and
+              info.hms(tokens[idx-2]) is not None):
+            # If we are looking at the final token, we allow for a
+            # backward-looking check to skip over a space.
+            # TODO: Are we sure this is the right condition here?
+            hms_idx = idx - 2
+
+        else:
+            hms_idx = None
+
+        return hms_idx
+
+    def _assign_hms(self, res, value_repr, hms):
+        # See GH issue #427, fixing float rounding
+        value = self._to_decimal(value_repr)
+
+        if hms == 0:
+            # Hour
+            res.hour = int(value)
+            if value % 1:
+                res.minute = int(60*(value % 1))
+
+        elif hms == 1:
+            (res.minute, res.second) = self._parse_min_sec(value)
+
+        elif hms == 2:
+            (res.second, res.microsecond) = self._parsems(value_repr)
+
+    def _could_be_tzname(self, hour, tzname, tzoffset, token):
+        return (hour is not None and
+                tzname is None and
+                tzoffset is None and
+                len(token) <= 5 and
+                all(x in string.ascii_uppercase for x in token))
+
+    def _ampm_valid(self, hour, ampm, fuzzy):
+        """
+        For fuzzy parsing, 'a' or 'am' (both valid English words)
+        may erroneously trigger the AM/PM flag. Deal with that
+        here.
+        """
+        val_is_ampm = True
+
+        # If there's already an AM/PM flag, this one isn't one.
+        if fuzzy and ampm is not None:
+            val_is_ampm = False
+
+        # If AM/PM is found and hour is not, raise a ValueError
+        if hour is None:
+            if fuzzy:
+                val_is_ampm = False
+            else:
+                raise ValueError('No hour specified with AM or PM flag.')
+        elif not 0 <= hour <= 12:
+            # If AM/PM is found, it's a 12 hour clock, so raise
+            # an error for invalid range
+            if fuzzy:
+                val_is_ampm = False
+            else:
+                raise ValueError('Invalid hour specified for 12-hour clock.')
+
+        return val_is_ampm
+
+    def _adjust_ampm(self, hour, ampm):
+        if hour < 12 and ampm == 1:
+            hour += 12
+        elif hour == 12 and ampm == 0:
+            hour = 0
+        return hour
+
+    def _parse_min_sec(self, value):
+        # TODO: Every usage of this function sets res.second to the return
+        # value. Are there any cases where second will be returned as None and
+        # we *dont* want to set res.second = None?
+        minute = int(value)
+        second = None
+
+        sec_remainder = value % 1
+        if sec_remainder:
+            second = int(60 * sec_remainder)
+        return (minute, second)
+
+    def _parsems(self, value):
+        """Parse a I[.F] seconds value into (seconds, microseconds)."""
+        if "." not in value:
+            return int(value), 0
+        else:
+            i, f = value.split(".")
+            return int(i), int(f.ljust(6, "0")[:6])
+
+    def _parse_hms(self, idx, tokens, info, hms_idx):
+        # TODO: Is this going to admit a lot of false-positives for when we
+        # just happen to have digits and "h", "m" or "s" characters in non-date
+        # text?  I guess hex hashes won't have that problem, but there's plenty
+        # of random junk out there.
+        if hms_idx is None:
+            hms = None
+            new_idx = idx
+        elif hms_idx > idx:
+            hms = info.hms(tokens[hms_idx])
+            new_idx = hms_idx
+        else:
+            # Looking backwards, increment one.
+            hms = info.hms(tokens[hms_idx]) + 1
+            new_idx = idx
+
+        return (new_idx, hms)
+
+    def _recombine_skipped(self, tokens, skipped_idxs):
+        """
+        >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"]
+        >>> skipped_idxs = [0, 1, 2, 5]
+        >>> _recombine_skipped(tokens, skipped_idxs)
+        ["foo bar", "baz"]
+        """
+        skipped_tokens = []
+        for i, idx in enumerate(sorted(skipped_idxs)):
+            if i > 0 and idx - 1 == skipped_idxs[i - 1]:
+                skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx]
+            else:
+                skipped_tokens.append(tokens[idx])
+
+        return skipped_tokens
+
+    def _build_tzinfo(self, tzinfos, tzname, tzoffset):
+        if callable(tzinfos):
+            tzdata = tzinfos(tzname, tzoffset)
+        else:
+            tzdata = tzinfos.get(tzname)
+        # handle case where tzinfo is paased an options that returns None
+        # eg tzinfos = {'BRST' : None}
+        if isinstance(tzdata, datetime.tzinfo) or tzdata is None:
+            tzinfo = tzdata
+        elif isinstance(tzdata, text_type):
+            tzinfo = tz.tzstr(tzdata)
+        elif isinstance(tzdata, integer_types):
+            tzinfo = tz.tzoffset(tzname, tzdata)
+        return tzinfo
+
+    def _build_tzaware(self, naive, res, tzinfos):
+        if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)):
+            tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset)
+            aware = naive.replace(tzinfo=tzinfo)
+            aware = self._assign_tzname(aware, res.tzname)
+
+        elif res.tzname and res.tzname in time.tzname:
+            aware = naive.replace(tzinfo=tz.tzlocal())
+
+            # Handle ambiguous local datetime
+            aware = self._assign_tzname(aware, res.tzname)
+
+            # This is mostly relevant for winter GMT zones parsed in the UK
+            if (aware.tzname() != res.tzname and
+                    res.tzname in self.info.UTCZONE):
+                aware = aware.replace(tzinfo=tz.tzutc())
+
+        elif res.tzoffset == 0:
+            aware = naive.replace(tzinfo=tz.tzutc())
+
+        elif res.tzoffset:
+            aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
+
+        elif not res.tzname and not res.tzoffset:
+            # i.e. no timezone information was found.
+            aware = naive
+
+        elif res.tzname:
+            # tz-like string was parsed but we don't know what to do
+            # with it
+            warnings.warn("tzname {tzname} identified but not understood.  "
+                          "Pass `tzinfos` argument in order to correctly "
+                          "return a timezone-aware datetime.  In a future "
+                          "version, this will raise an "
+                          "exception.".format(tzname=res.tzname),
+                          category=UnknownTimezoneWarning)
+            aware = naive
+
+        return aware
+
+    def _build_naive(self, res, default):
+        repl = {}
+        for attr in ("year", "month", "day", "hour",
+                     "minute", "second", "microsecond"):
+            value = getattr(res, attr)
+            if value is not None:
+                repl[attr] = value
+
+        if 'day' not in repl:
+            # If the default day exceeds the last day of the month, fall back
+            # to the end of the month.
+            cyear = default.year if res.year is None else res.year
+            cmonth = default.month if res.month is None else res.month
+            cday = default.day if res.day is None else res.day
+
+            if cday > monthrange(cyear, cmonth)[1]:
+                repl['day'] = monthrange(cyear, cmonth)[1]
+
+        naive = default.replace(**repl)
+
+        if res.weekday is not None and not res.day:
+            naive = naive + relativedelta.relativedelta(weekday=res.weekday)
+
+        return naive
+
+    def _assign_tzname(self, dt, tzname):
+        if dt.tzname() != tzname:
+            new_dt = tz.enfold(dt, fold=1)
+            if new_dt.tzname() == tzname:
+                return new_dt
+
+        return dt
+
+    def _to_decimal(self, val):
+        try:
+            decimal_value = Decimal(val)
+            # See GH 662, edge case, infinite value should not be converted via `_to_decimal`
+            if not decimal_value.is_finite():
+                raise ValueError("Converted decimal value is infinite or NaN")
+        except Exception as e:
+            msg = "Could not convert %s to decimal" % val
+            six.raise_from(ValueError(msg), e)
+        else:
+            return decimal_value
+
+
+DEFAULTPARSER = parser()
+
+
+def parse(timestr, parserinfo=None, **kwargs):
+    """
+
+    Parse a string in one of the supported formats, using the
+    ``parserinfo`` parameters.
+
+    :param timestr:
+        A string containing a date/time stamp.
+
+    :param parserinfo:
+        A :class:`parserinfo` object containing parameters for the parser.
+        If ``None``, the default arguments to the :class:`parserinfo`
+        constructor are used.
+
+    The ``**kwargs`` parameter takes the following keyword arguments:
+
+    :param default:
+        The default datetime object, if this is a datetime object and not
+        ``None``, elements specified in ``timestr`` replace elements in the
+        default object.
+
+    :param ignoretz:
+        If set ``True``, time zones in parsed strings are ignored and a naive
+        :class:`datetime` object is returned.
+
+    :param tzinfos:
+        Additional time zone names / aliases which may be present in the
+        string. This argument maps time zone names (and optionally offsets
+        from those time zones) to time zones. This parameter can be a
+        dictionary with timezone aliases mapping time zone names to time
+        zones or a function taking two parameters (``tzname`` and
+        ``tzoffset``) and returning a time zone.
+
+        The timezones to which the names are mapped can be an integer
+        offset from UTC in seconds or a :class:`tzinfo` object.
+
+        .. doctest::
+           :options: +NORMALIZE_WHITESPACE
+
+            >>> from dateutil.parser import parse
+            >>> from dateutil.tz import gettz
+            >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")}
+            >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
+            datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200))
+            >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
+            datetime.datetime(2012, 1, 19, 17, 21,
+                              tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
+
+        This parameter is ignored if ``ignoretz`` is set.
+
+    :param dayfirst:
+        Whether to interpret the first value in an ambiguous 3-integer date
+        (e.g. 01/05/09) as the day (``True``) or month (``False``). If
+        ``yearfirst`` is set to ``True``, this distinguishes between YDM and
+        YMD. If set to ``None``, this value is retrieved from the current
+        :class:`parserinfo` object (which itself defaults to ``False``).
+
+    :param yearfirst:
+        Whether to interpret the first value in an ambiguous 3-integer date
+        (e.g. 01/05/09) as the year. If ``True``, the first number is taken to
+        be the year, otherwise the last number is taken to be the year. If
+        this is set to ``None``, the value is retrieved from the current
+        :class:`parserinfo` object (which itself defaults to ``False``).
+
+    :param fuzzy:
+        Whether to allow fuzzy parsing, allowing for string like "Today is
+        January 1, 2047 at 8:21:00AM".
+
+    :param fuzzy_with_tokens:
+        If ``True``, ``fuzzy`` is automatically set to True, and the parser
+        will return a tuple where the first element is the parsed
+        :class:`datetime.datetime` datetimestamp and the second element is
+        a tuple containing the portions of the string which were ignored:
+
+        .. doctest::
+
+            >>> from dateutil.parser import parse
+            >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
+            (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
+
+    :return:
+        Returns a :class:`datetime.datetime` object or, if the
+        ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
+        first element being a :class:`datetime.datetime` object, the second
+        a tuple containing the fuzzy tokens.
+
+    :raises ValueError:
+        Raised for invalid or unknown string format, if the provided
+        :class:`tzinfo` is not in a valid format, or if an invalid date
+        would be created.
+
+    :raises OverflowError:
+        Raised if the parsed date exceeds the largest valid C integer on
+        your system.
+    """
+    if parserinfo:
+        return parser(parserinfo).parse(timestr, **kwargs)
+    else:
+        return DEFAULTPARSER.parse(timestr, **kwargs)
+
+
+class _tzparser(object):
+
+    class _result(_resultbase):
+
+        __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
+                     "start", "end"]
+
+        class _attr(_resultbase):
+            __slots__ = ["month", "week", "weekday",
+                         "yday", "jyday", "day", "time"]
+
+        def __repr__(self):
+            return self._repr("")
+
+        def __init__(self):
+            _resultbase.__init__(self)
+            self.start = self._attr()
+            self.end = self._attr()
+
+    def parse(self, tzstr):
+        res = self._result()
+        l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x]
+        used_idxs = list()
+        try:
+
+            len_l = len(l)
+
+            i = 0
+            while i < len_l:
+                # BRST+3[BRDT[+2]]
+                j = i
+                while j < len_l and not [x for x in l[j]
+                                         if x in "0123456789:,-+"]:
+                    j += 1
+                if j != i:
+                    if not res.stdabbr:
+                        offattr = "stdoffset"
+                        res.stdabbr = "".join(l[i:j])
+                    else:
+                        offattr = "dstoffset"
+                        res.dstabbr = "".join(l[i:j])
+
+                    for ii in range(j):
+                        used_idxs.append(ii)
+                    i = j
+                    if (i < len_l and (l[i] in ('+', '-') or l[i][0] in
+                                       "0123456789")):
+                        if l[i] in ('+', '-'):
+                            # Yes, that's right.  See the TZ variable
+                            # documentation.
+                            signal = (1, -1)[l[i] == '+']
+                            used_idxs.append(i)
+                            i += 1
+                        else:
+                            signal = -1
+                        len_li = len(l[i])
+                        if len_li == 4:
+                            # -0300
+                            setattr(res, offattr, (int(l[i][:2]) * 3600 +
+                                                   int(l[i][2:]) * 60) * signal)
+                        elif i + 1 < len_l and l[i + 1] == ':':
+                            # -03:00
+                            setattr(res, offattr,
+                                    (int(l[i]) * 3600 +
+                                     int(l[i + 2]) * 60) * signal)
+                            used_idxs.append(i)
+                            i += 2
+                        elif len_li <= 2:
+                            # -[0]3
+                            setattr(res, offattr,
+                                    int(l[i][:2]) * 3600 * signal)
+                        else:
+                            return None
+                        used_idxs.append(i)
+                        i += 1
+                    if res.dstabbr:
+                        break
+                else:
+                    break
+
+
+            if i < len_l:
+                for j in range(i, len_l):
+                    if l[j] == ';':
+                        l[j] = ','
+
+                assert l[i] == ','
+
+                i += 1
+
+            if i >= len_l:
+                pass
+            elif (8 <= l.count(',') <= 9 and
+                  not [y for x in l[i:] if x != ','
+                       for y in x if y not in "0123456789+-"]):
+                # GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
+                for x in (res.start, res.end):
+                    x.month = int(l[i])
+                    used_idxs.append(i)
+                    i += 2
+                    if l[i] == '-':
+                        value = int(l[i + 1]) * -1
+                        used_idxs.append(i)
+                        i += 1
+                    else:
+                        value = int(l[i])
+                    used_idxs.append(i)
+                    i += 2
+                    if value:
+                        x.week = value
+                        x.weekday = (int(l[i]) - 1) % 7
+                    else:
+                        x.day = int(l[i])
+                    used_idxs.append(i)
+                    i += 2
+                    x.time = int(l[i])
+                    used_idxs.append(i)
+                    i += 2
+                if i < len_l:
+                    if l[i] in ('-', '+'):
+                        signal = (-1, 1)[l[i] == "+"]
+                        used_idxs.append(i)
+                        i += 1
+                    else:
+                        signal = 1
+                    used_idxs.append(i)
+                    res.dstoffset = (res.stdoffset + int(l[i]) * signal)
+
+                # This was a made-up format that is not in normal use
+                warn(('Parsed time zone "%s"' % tzstr) +
+                     'is in a non-standard dateutil-specific format, which ' +
+                     'is now deprecated; support for parsing this format ' +
+                     'will be removed in future versions. It is recommended ' +
+                     'that you switch to a standard format like the GNU ' +
+                     'TZ variable format.', tz.DeprecatedTzFormatWarning)
+            elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
+                  not [y for x in l[i:] if x not in (',', '/', 'J', 'M',
+                                                     '.', '-', ':')
+                       for y in x if y not in "0123456789"]):
+                for x in (res.start, res.end):
+                    if l[i] == 'J':
+                        # non-leap year day (1 based)
+                        used_idxs.append(i)
+                        i += 1
+                        x.jyday = int(l[i])
+                    elif l[i] == 'M':
+                        # month[-.]week[-.]weekday
+                        used_idxs.append(i)
+                        i += 1
+                        x.month = int(l[i])
+                        used_idxs.append(i)
+                        i += 1
+                        assert l[i] in ('-', '.')
+                        used_idxs.append(i)
+                        i += 1
+                        x.week = int(l[i])
+                        if x.week == 5:
+                            x.week = -1
+                        used_idxs.append(i)
+                        i += 1
+                        assert l[i] in ('-', '.')
+                        used_idxs.append(i)
+                        i += 1
+                        x.weekday = (int(l[i]) - 1) % 7
+                    else:
+                        # year day (zero based)
+                        x.yday = int(l[i]) + 1
+
+                    used_idxs.append(i)
+                    i += 1
+
+                    if i < len_l and l[i] == '/':
+                        used_idxs.append(i)
+                        i += 1
+                        # start time
+                        len_li = len(l[i])
+                        if len_li == 4:
+                            # -0300
+                            x.time = (int(l[i][:2]) * 3600 +
+                                      int(l[i][2:]) * 60)
+                        elif i + 1 < len_l and l[i + 1] == ':':
+                            # -03:00
+                            x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60
+                            used_idxs.append(i)
+                            i += 2
+                            if i + 1 < len_l and l[i + 1] == ':':
+                                used_idxs.append(i)
+                                i += 2
+                                x.time += int(l[i])
+                        elif len_li <= 2:
+                            # -[0]3
+                            x.time = (int(l[i][:2]) * 3600)
+                        else:
+                            return None
+                        used_idxs.append(i)
+                        i += 1
+
+                    assert i == len_l or l[i] == ','
+
+                    i += 1
+
+                assert i >= len_l
+
+        except (IndexError, ValueError, AssertionError):
+            return None
+
+        unused_idxs = set(range(len_l)).difference(used_idxs)
+        res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"})
+        return res
+
+
+DEFAULTTZPARSER = _tzparser()
+
+
+def _parsetz(tzstr):
+    return DEFAULTTZPARSER.parse(tzstr)
+
+class UnknownTimezoneWarning(RuntimeWarning):
+    """Raised when the parser finds a timezone it cannot parse into a tzinfo"""
+# vim:ts=4:sw=4:et
diff --git a/resources/lib/libraries/dateutil/parser/isoparser.py b/resources/lib/libraries/dateutil/parser/isoparser.py
new file mode 100644
index 00000000..b63ef712
--- /dev/null
+++ b/resources/lib/libraries/dateutil/parser/isoparser.py
@@ -0,0 +1,406 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers a parser for ISO-8601 strings
+
+It is intended to support all valid date, time and datetime formats per the
+ISO-8601 specification.
+
+..versionadded:: 2.7.0
+"""
+from datetime import datetime, timedelta, time, date
+import calendar
+from .. import tz
+
+from functools import wraps
+
+import re
+from .. import six
+
+__all__ = ["isoparse", "isoparser"]
+
+
+def _takes_ascii(f):
+    @wraps(f)
+    def func(self, str_in, *args, **kwargs):
+        # If it's a stream, read the whole thing
+        str_in = getattr(str_in, 'read', lambda: str_in)()
+
+        # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
+        if isinstance(str_in, six.text_type):
+            # ASCII is the same in UTF-8
+            try:
+                str_in = str_in.encode('ascii')
+            except UnicodeEncodeError as e:
+                msg = 'ISO-8601 strings should contain only ASCII characters'
+                six.raise_from(ValueError(msg), e)
+
+        return f(self, str_in, *args, **kwargs)
+
+    return func
+
+
+class isoparser(object):
+    def __init__(self, sep=None):
+        """
+        :param sep:
+            A single character that separates date and time portions. If
+            ``None``, the parser will accept any single character.
+            For strict ISO-8601 adherence, pass ``'T'``.
+        """
+        if sep is not None:
+            if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'):
+                raise ValueError('Separator must be a single, non-numeric ' +
+                                 'ASCII character')
+
+            sep = sep.encode('ascii')
+
+        self._sep = sep
+
+    @_takes_ascii
+    def isoparse(self, dt_str):
+        """
+        Parse an ISO-8601 datetime string into a :class:`datetime.datetime`.
+
+        An ISO-8601 datetime string consists of a date portion, followed
+        optionally by a time portion - the date and time portions are separated
+        by a single character separator, which is ``T`` in the official
+        standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be
+        combined with a time portion.
+
+        Supported date formats are:
+
+        Common:
+
+        - ``YYYY``
+        - ``YYYY-MM`` or ``YYYYMM``
+        - ``YYYY-MM-DD`` or ``YYYYMMDD``
+
+        Uncommon:
+
+        - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0)
+        - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day
+
+        The ISO week and day numbering follows the same logic as
+        :func:`datetime.date.isocalendar`.
+
+        Supported time formats are:
+
+        - ``hh``
+        - ``hh:mm`` or ``hhmm``
+        - ``hh:mm:ss`` or ``hhmmss``
+        - ``hh:mm:ss.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits)
+
+        Midnight is a special case for `hh`, as the standard supports both
+        00:00 and 24:00 as a representation.
+
+        .. caution::
+
+            Support for fractional components other than seconds is part of the
+            ISO-8601 standard, but is not currently implemented in this parser.
+
+        Supported time zone offset formats are:
+
+        - `Z` (UTC)
+        - `±HH:MM`
+        - `±HHMM`
+        - `±HH`
+
+        Offsets will be represented as :class:`dateutil.tz.tzoffset` objects,
+        with the exception of UTC, which will be represented as
+        :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such
+        as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`.
+
+        :param dt_str:
+            A string or stream containing only an ISO-8601 datetime string
+
+        :return:
+            Returns a :class:`datetime.datetime` representing the string.
+            Unspecified components default to their lowest value.
+
+        .. warning::
+
+            As of version 2.7.0, the strictness of the parser should not be
+            considered a stable part of the contract. Any valid ISO-8601 string
+            that parses correctly with the default settings will continue to
+            parse correctly in future versions, but invalid strings that
+            currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
+            guaranteed to continue failing in future versions if they encode
+            a valid date.
+
+        .. versionadded:: 2.7.0
+        """
+        components, pos = self._parse_isodate(dt_str)
+
+        if len(dt_str) > pos:
+            if self._sep is None or dt_str[pos:pos + 1] == self._sep:
+                components += self._parse_isotime(dt_str[pos + 1:])
+            else:
+                raise ValueError('String contains unknown ISO components')
+
+        return datetime(*components)
+
+    @_takes_ascii
+    def parse_isodate(self, datestr):
+        """
+        Parse the date portion of an ISO string.
+
+        :param datestr:
+            The string portion of an ISO string, without a separator
+
+        :return:
+            Returns a :class:`datetime.date` object
+        """
+        components, pos = self._parse_isodate(datestr)
+        if pos < len(datestr):
+            raise ValueError('String contains unknown ISO ' +
+                             'components: {}'.format(datestr))
+        return date(*components)
+
+    @_takes_ascii
+    def parse_isotime(self, timestr):
+        """
+        Parse the time portion of an ISO string.
+
+        :param timestr:
+            The time portion of an ISO string, without a separator
+
+        :return:
+            Returns a :class:`datetime.time` object
+        """
+        return time(*self._parse_isotime(timestr))
+
+    @_takes_ascii
+    def parse_tzstr(self, tzstr, zero_as_utc=True):
+        """
+        Parse a valid ISO time zone string.
+
+        See :func:`isoparser.isoparse` for details on supported formats.
+
+        :param tzstr:
+            A string representing an ISO time zone offset
+
+        :param zero_as_utc:
+            Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones
+
+        :return:
+            Returns :class:`dateutil.tz.tzoffset` for offsets and
+            :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
+            specified) offsets equivalent to UTC.
+        """
+        return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
+
+    # Constants
+    _MICROSECOND_END_REGEX = re.compile(b'[-+Z]+')
+    _DATE_SEP = b'-'
+    _TIME_SEP = b':'
+    _MICRO_SEP = b'.'
+
+    def _parse_isodate(self, dt_str):
+        try:
+            return self._parse_isodate_common(dt_str)
+        except ValueError:
+            return self._parse_isodate_uncommon(dt_str)
+
+    def _parse_isodate_common(self, dt_str):
+        len_str = len(dt_str)
+        components = [1, 1, 1]
+
+        if len_str < 4:
+            raise ValueError('ISO string too short')
+
+        # Year
+        components[0] = int(dt_str[0:4])
+        pos = 4
+        if pos >= len_str:
+            return components, pos
+
+        has_sep = dt_str[pos:pos + 1] == self._DATE_SEP
+        if has_sep:
+            pos += 1
+
+        # Month
+        if len_str - pos < 2:
+            raise ValueError('Invalid common month')
+
+        components[1] = int(dt_str[pos:pos + 2])
+        pos += 2
+
+        if pos >= len_str:
+            if has_sep:
+                return components, pos
+            else:
+                raise ValueError('Invalid ISO format')
+
+        if has_sep:
+            if dt_str[pos:pos + 1] != self._DATE_SEP:
+                raise ValueError('Invalid separator in ISO string')
+            pos += 1
+
+        # Day
+        if len_str - pos < 2:
+            raise ValueError('Invalid common day')
+        components[2] = int(dt_str[pos:pos + 2])
+        return components, pos + 2
+
+    def _parse_isodate_uncommon(self, dt_str):
+        if len(dt_str) < 4:
+            raise ValueError('ISO string too short')
+
+        # All ISO formats start with the year
+        year = int(dt_str[0:4])
+
+        has_sep = dt_str[4:5] == self._DATE_SEP
+
+        pos = 4 + has_sep       # Skip '-' if it's there
+        if dt_str[pos:pos + 1] == b'W':
+            # YYYY-?Www-?D?
+            pos += 1
+            weekno = int(dt_str[pos:pos + 2])
+            pos += 2
+
+            dayno = 1
+            if len(dt_str) > pos:
+                if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep:
+                    raise ValueError('Inconsistent use of dash separator')
+
+                pos += has_sep
+
+                dayno = int(dt_str[pos:pos + 1])
+                pos += 1
+
+            base_date = self._calculate_weekdate(year, weekno, dayno)
+        else:
+            # YYYYDDD or YYYY-DDD
+            if len(dt_str) - pos < 3:
+                raise ValueError('Invalid ordinal day')
+
+            ordinal_day = int(dt_str[pos:pos + 3])
+            pos += 3
+
+            if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
+                raise ValueError('Invalid ordinal day' +
+                                 ' {} for year {}'.format(ordinal_day, year))
+
+            base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1)
+
+        components = [base_date.year, base_date.month, base_date.day]
+        return components, pos
+
+    def _calculate_weekdate(self, year, week, day):
+        """
+        Calculate the day of corresponding to the ISO year-week-day calendar.
+
+        This function is effectively the inverse of
+        :func:`datetime.date.isocalendar`.
+
+        :param year:
+            The year in the ISO calendar
+
+        :param week:
+            The week in the ISO calendar - range is [1, 53]
+
+        :param day:
+            The day in the ISO calendar - range is [1 (MON), 7 (SUN)]
+
+        :return:
+            Returns a :class:`datetime.date`
+        """
+        if not 0 < week < 54:
+            raise ValueError('Invalid week: {}'.format(week))
+
+        if not 0 < day < 8:     # Range is 1-7
+            raise ValueError('Invalid weekday: {}'.format(day))
+
+        # Get week 1 for the specific year:
+        jan_4 = date(year, 1, 4)   # Week 1 always has January 4th in it
+        week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1)
+
+        # Now add the specific number of weeks and days to get what we want
+        week_offset = (week - 1) * 7 + (day - 1)
+        return week_1 + timedelta(days=week_offset)
+
+    def _parse_isotime(self, timestr):
+        len_str = len(timestr)
+        components = [0, 0, 0, 0, None]
+        pos = 0
+        comp = -1
+
+        if len(timestr) < 2:
+            raise ValueError('ISO time too short')
+
+        has_sep = len_str >= 3 and timestr[2:3] == self._TIME_SEP
+
+        while pos < len_str and comp < 5:
+            comp += 1
+
+            if timestr[pos:pos + 1] in b'-+Z':
+                # Detect time zone boundary
+                components[-1] = self._parse_tzstr(timestr[pos:])
+                pos = len_str
+                break
+
+            if comp < 3:
+                # Hour, minute, second
+                components[comp] = int(timestr[pos:pos + 2])
+                pos += 2
+                if (has_sep and pos < len_str and
+                        timestr[pos:pos + 1] == self._TIME_SEP):
+                    pos += 1
+
+            if comp == 3:
+                # Microsecond
+                if timestr[pos:pos + 1] != self._MICRO_SEP:
+                    continue
+
+                pos += 1
+                us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6],
+                                                           1)[0]
+
+                components[comp] = int(us_str) * 10**(6 - len(us_str))
+                pos += len(us_str)
+
+        if pos < len_str:
+            raise ValueError('Unused components in ISO string')
+
+        if components[0] == 24:
+            # Standard supports 00:00 and 24:00 as representations of midnight
+            if any(component != 0 for component in components[1:4]):
+                raise ValueError('Hour may only be 24 at 24:00:00.000')
+            components[0] = 0
+
+        return components
+
+    def _parse_tzstr(self, tzstr, zero_as_utc=True):
+        if tzstr == b'Z':
+            return tz.tzutc()
+
+        if len(tzstr) not in {3, 5, 6}:
+            raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters')
+
+        if tzstr[0:1] == b'-':
+            mult = -1
+        elif tzstr[0:1] == b'+':
+            mult = 1
+        else:
+            raise ValueError('Time zone offset requires sign')
+
+        hours = int(tzstr[1:3])
+        if len(tzstr) == 3:
+            minutes = 0
+        else:
+            minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):])
+
+        if zero_as_utc and hours == 0 and minutes == 0:
+            return tz.tzutc()
+        else:
+            if minutes > 59:
+                raise ValueError('Invalid minutes in time zone offset')
+
+            if hours > 23:
+                raise ValueError('Invalid hours in time zone offset')
+
+            return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60)
+
+
+DEFAULT_ISOPARSER = isoparser()
+isoparse = DEFAULT_ISOPARSER.isoparse
diff --git a/resources/lib/libraries/dateutil/relativedelta.py b/resources/lib/libraries/dateutil/relativedelta.py
new file mode 100644
index 00000000..1e0d6165
--- /dev/null
+++ b/resources/lib/libraries/dateutil/relativedelta.py
@@ -0,0 +1,590 @@
+# -*- coding: utf-8 -*-
+import datetime
+import calendar
+
+import operator
+from math import copysign
+
+from six import integer_types
+from warnings import warn
+
+from ._common import weekday
+
+MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
+
+__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
+
+
+class relativedelta(object):
+    """
+    The relativedelta type is based on the specification of the excellent
+    work done by M.-A. Lemburg in his
+    `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
+    However, notice that this type does *NOT* implement the same algorithm as
+    his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
+
+    There are two different ways to build a relativedelta instance. The
+    first one is passing it two date/datetime classes::
+
+        relativedelta(datetime1, datetime2)
+
+    The second one is passing it any number of the following keyword arguments::
+
+        relativedelta(arg1=x,arg2=y,arg3=z...)
+
+        year, month, day, hour, minute, second, microsecond:
+            Absolute information (argument is singular); adding or subtracting a
+            relativedelta with absolute information does not perform an arithmetic
+            operation, but rather REPLACES the corresponding value in the
+            original datetime with the value(s) in relativedelta.
+
+        years, months, weeks, days, hours, minutes, seconds, microseconds:
+            Relative information, may be negative (argument is plural); adding
+            or subtracting a relativedelta with relative information performs
+            the corresponding aritmetic operation on the original datetime value
+            with the information in the relativedelta.
+
+        weekday: 
+            One of the weekday instances (MO, TU, etc). These
+            instances may receive a parameter N, specifying the Nth
+            weekday, which could be positive or negative (like MO(+1)
+            or MO(-2). Not specifying it is the same as specifying
+            +1. You can also use an integer, where 0=MO. Notice that
+            if the calculated date is already Monday, for example,
+            using MO(1) or MO(-1) won't change the day.
+
+        leapdays:
+            Will add given days to the date found, if year is a leap
+            year, and the date found is post 28 of february.
+
+        yearday, nlyearday:
+            Set the yearday or the non-leap year day (jump leap days).
+            These are converted to day/month/leapdays information.
+
+    There are relative and absolute forms of the keyword
+    arguments. The plural is relative, and the singular is
+    absolute. For each argument in the order below, the absolute form
+    is applied first (by setting each attribute to that value) and
+    then the relative form (by adding the value to the attribute).
+
+    The order of attributes considered when this relativedelta is
+    added to a datetime is:
+
+    1. Year
+    2. Month
+    3. Day
+    4. Hours
+    5. Minutes
+    6. Seconds
+    7. Microseconds
+
+    Finally, weekday is applied, using the rule described above.
+
+    For example
+
+    >>> dt = datetime(2018, 4, 9, 13, 37, 0)
+    >>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
+    datetime(2018, 4, 2, 14, 37, 0)
+
+    First, the day is set to 1 (the first of the month), then 25 hours
+    are added, to get to the 2nd day and 14th hour, finally the
+    weekday is applied, but since the 2nd is already a Monday there is
+    no effect.
+
+    """
+
+    def __init__(self, dt1=None, dt2=None,
+                 years=0, months=0, days=0, leapdays=0, weeks=0,
+                 hours=0, minutes=0, seconds=0, microseconds=0,
+                 year=None, month=None, day=None, weekday=None,
+                 yearday=None, nlyearday=None,
+                 hour=None, minute=None, second=None, microsecond=None):
+
+        if dt1 and dt2:
+            # datetime is a subclass of date. So both must be date
+            if not (isinstance(dt1, datetime.date) and
+                    isinstance(dt2, datetime.date)):
+                raise TypeError("relativedelta only diffs datetime/date")
+
+            # We allow two dates, or two datetimes, so we coerce them to be
+            # of the same type
+            if (isinstance(dt1, datetime.datetime) !=
+                    isinstance(dt2, datetime.datetime)):
+                if not isinstance(dt1, datetime.datetime):
+                    dt1 = datetime.datetime.fromordinal(dt1.toordinal())
+                elif not isinstance(dt2, datetime.datetime):
+                    dt2 = datetime.datetime.fromordinal(dt2.toordinal())
+
+            self.years = 0
+            self.months = 0
+            self.days = 0
+            self.leapdays = 0
+            self.hours = 0
+            self.minutes = 0
+            self.seconds = 0
+            self.microseconds = 0
+            self.year = None
+            self.month = None
+            self.day = None
+            self.weekday = None
+            self.hour = None
+            self.minute = None
+            self.second = None
+            self.microsecond = None
+            self._has_time = 0
+
+            # Get year / month delta between the two
+            months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
+            self._set_months(months)
+
+            # Remove the year/month delta so the timedelta is just well-defined
+            # time units (seconds, days and microseconds)
+            dtm = self.__radd__(dt2)
+
+            # If we've overshot our target, make an adjustment
+            if dt1 < dt2:
+                compare = operator.gt
+                increment = 1
+            else:
+                compare = operator.lt
+                increment = -1
+
+            while compare(dt1, dtm):
+                months += increment
+                self._set_months(months)
+                dtm = self.__radd__(dt2)
+
+            # Get the timedelta between the "months-adjusted" date and dt1
+            delta = dt1 - dtm
+            self.seconds = delta.seconds + delta.days * 86400
+            self.microseconds = delta.microseconds
+        else:
+            # Check for non-integer values in integer-only quantities
+            if any(x is not None and x != int(x) for x in (years, months)):
+                raise ValueError("Non-integer years and months are "
+                                 "ambiguous and not currently supported.")
+
+            # Relative information
+            self.years = int(years)
+            self.months = int(months)
+            self.days = days + weeks * 7
+            self.leapdays = leapdays
+            self.hours = hours
+            self.minutes = minutes
+            self.seconds = seconds
+            self.microseconds = microseconds
+
+            # Absolute information
+            self.year = year
+            self.month = month
+            self.day = day
+            self.hour = hour
+            self.minute = minute
+            self.second = second
+            self.microsecond = microsecond
+
+            if any(x is not None and int(x) != x
+                   for x in (year, month, day, hour,
+                             minute, second, microsecond)):
+                # For now we'll deprecate floats - later it'll be an error.
+                warn("Non-integer value passed as absolute information. " +
+                     "This is not a well-defined condition and will raise " +
+                     "errors in future versions.", DeprecationWarning)
+
+            if isinstance(weekday, integer_types):
+                self.weekday = weekdays[weekday]
+            else:
+                self.weekday = weekday
+
+            yday = 0
+            if nlyearday:
+                yday = nlyearday
+            elif yearday:
+                yday = yearday
+                if yearday > 59:
+                    self.leapdays = -1
+            if yday:
+                ydayidx = [31, 59, 90, 120, 151, 181, 212,
+                           243, 273, 304, 334, 366]
+                for idx, ydays in enumerate(ydayidx):
+                    if yday <= ydays:
+                        self.month = idx+1
+                        if idx == 0:
+                            self.day = yday
+                        else:
+                            self.day = yday-ydayidx[idx-1]
+                        break
+                else:
+                    raise ValueError("invalid year day (%d)" % yday)
+
+        self._fix()
+
+    def _fix(self):
+        if abs(self.microseconds) > 999999:
+            s = _sign(self.microseconds)
+            div, mod = divmod(self.microseconds * s, 1000000)
+            self.microseconds = mod * s
+            self.seconds += div * s
+        if abs(self.seconds) > 59:
+            s = _sign(self.seconds)
+            div, mod = divmod(self.seconds * s, 60)
+            self.seconds = mod * s
+            self.minutes += div * s
+        if abs(self.minutes) > 59:
+            s = _sign(self.minutes)
+            div, mod = divmod(self.minutes * s, 60)
+            self.minutes = mod * s
+            self.hours += div * s
+        if abs(self.hours) > 23:
+            s = _sign(self.hours)
+            div, mod = divmod(self.hours * s, 24)
+            self.hours = mod * s
+            self.days += div * s
+        if abs(self.months) > 11:
+            s = _sign(self.months)
+            div, mod = divmod(self.months * s, 12)
+            self.months = mod * s
+            self.years += div * s
+        if (self.hours or self.minutes or self.seconds or self.microseconds
+                or self.hour is not None or self.minute is not None or
+                self.second is not None or self.microsecond is not None):
+            self._has_time = 1
+        else:
+            self._has_time = 0
+
+    @property
+    def weeks(self):
+        return int(self.days / 7.0)
+
+    @weeks.setter
+    def weeks(self, value):
+        self.days = self.days - (self.weeks * 7) + value * 7
+
+    def _set_months(self, months):
+        self.months = months
+        if abs(self.months) > 11:
+            s = _sign(self.months)
+            div, mod = divmod(self.months * s, 12)
+            self.months = mod * s
+            self.years = div * s
+        else:
+            self.years = 0
+
+    def normalized(self):
+        """
+        Return a version of this object represented entirely using integer
+        values for the relative attributes.
+
+        >>> relativedelta(days=1.5, hours=2).normalized()
+        relativedelta(days=1, hours=14)
+
+        :return:
+            Returns a :class:`dateutil.relativedelta.relativedelta` object.
+        """
+        # Cascade remainders down (rounding each to roughly nearest microsecond)
+        days = int(self.days)
+
+        hours_f = round(self.hours + 24 * (self.days - days), 11)
+        hours = int(hours_f)
+
+        minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
+        minutes = int(minutes_f)
+
+        seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
+        seconds = int(seconds_f)
+
+        microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
+
+        # Constructor carries overflow back up with call to _fix()
+        return self.__class__(years=self.years, months=self.months,
+                              days=days, hours=hours, minutes=minutes,
+                              seconds=seconds, microseconds=microseconds,
+                              leapdays=self.leapdays, year=self.year,
+                              month=self.month, day=self.day,
+                              weekday=self.weekday, hour=self.hour,
+                              minute=self.minute, second=self.second,
+                              microsecond=self.microsecond)
+
+    def __add__(self, other):
+        if isinstance(other, relativedelta):
+            return self.__class__(years=other.years + self.years,
+                                 months=other.months + self.months,
+                                 days=other.days + self.days,
+                                 hours=other.hours + self.hours,
+                                 minutes=other.minutes + self.minutes,
+                                 seconds=other.seconds + self.seconds,
+                                 microseconds=(other.microseconds +
+                                               self.microseconds),
+                                 leapdays=other.leapdays or self.leapdays,
+                                 year=(other.year if other.year is not None
+                                       else self.year),
+                                 month=(other.month if other.month is not None
+                                        else self.month),
+                                 day=(other.day if other.day is not None
+                                      else self.day),
+                                 weekday=(other.weekday if other.weekday is not None
+                                          else self.weekday),
+                                 hour=(other.hour if other.hour is not None
+                                       else self.hour),
+                                 minute=(other.minute if other.minute is not None
+                                         else self.minute),
+                                 second=(other.second if other.second is not None
+                                         else self.second),
+                                 microsecond=(other.microsecond if other.microsecond
+                                              is not None else
+                                              self.microsecond))
+        if isinstance(other, datetime.timedelta):
+            return self.__class__(years=self.years,
+                                  months=self.months,
+                                  days=self.days + other.days,
+                                  hours=self.hours,
+                                  minutes=self.minutes,
+                                  seconds=self.seconds + other.seconds,
+                                  microseconds=self.microseconds + other.microseconds,
+                                  leapdays=self.leapdays,
+                                  year=self.year,
+                                  month=self.month,
+                                  day=self.day,
+                                  weekday=self.weekday,
+                                  hour=self.hour,
+                                  minute=self.minute,
+                                  second=self.second,
+                                  microsecond=self.microsecond)
+        if not isinstance(other, datetime.date):
+            return NotImplemented
+        elif self._has_time and not isinstance(other, datetime.datetime):
+            other = datetime.datetime.fromordinal(other.toordinal())
+        year = (self.year or other.year)+self.years
+        month = self.month or other.month
+        if self.months:
+            assert 1 <= abs(self.months) <= 12
+            month += self.months
+            if month > 12:
+                year += 1
+                month -= 12
+            elif month < 1:
+                year -= 1
+                month += 12
+        day = min(calendar.monthrange(year, month)[1],
+                  self.day or other.day)
+        repl = {"year": year, "month": month, "day": day}
+        for attr in ["hour", "minute", "second", "microsecond"]:
+            value = getattr(self, attr)
+            if value is not None:
+                repl[attr] = value
+        days = self.days
+        if self.leapdays and month > 2 and calendar.isleap(year):
+            days += self.leapdays
+        ret = (other.replace(**repl)
+               + datetime.timedelta(days=days,
+                                    hours=self.hours,
+                                    minutes=self.minutes,
+                                    seconds=self.seconds,
+                                    microseconds=self.microseconds))
+        if self.weekday:
+            weekday, nth = self.weekday.weekday, self.weekday.n or 1
+            jumpdays = (abs(nth) - 1) * 7
+            if nth > 0:
+                jumpdays += (7 - ret.weekday() + weekday) % 7
+            else:
+                jumpdays += (ret.weekday() - weekday) % 7
+                jumpdays *= -1
+            ret += datetime.timedelta(days=jumpdays)
+        return ret
+
+    def __radd__(self, other):
+        return self.__add__(other)
+
+    def __rsub__(self, other):
+        return self.__neg__().__radd__(other)
+
+    def __sub__(self, other):
+        if not isinstance(other, relativedelta):
+            return NotImplemented   # In case the other object defines __rsub__
+        return self.__class__(years=self.years - other.years,
+                             months=self.months - other.months,
+                             days=self.days - other.days,
+                             hours=self.hours - other.hours,
+                             minutes=self.minutes - other.minutes,
+                             seconds=self.seconds - other.seconds,
+                             microseconds=self.microseconds - other.microseconds,
+                             leapdays=self.leapdays or other.leapdays,
+                             year=(self.year if self.year is not None
+                                   else other.year),
+                             month=(self.month if self.month is not None else
+                                    other.month),
+                             day=(self.day if self.day is not None else
+                                  other.day),
+                             weekday=(self.weekday if self.weekday is not None else
+                                      other.weekday),
+                             hour=(self.hour if self.hour is not None else
+                                   other.hour),
+                             minute=(self.minute if self.minute is not None else
+                                     other.minute),
+                             second=(self.second if self.second is not None else
+                                     other.second),
+                             microsecond=(self.microsecond if self.microsecond
+                                          is not None else
+                                          other.microsecond))
+
+    def __abs__(self):
+        return self.__class__(years=abs(self.years),
+                              months=abs(self.months),
+                              days=abs(self.days),
+                              hours=abs(self.hours),
+                              minutes=abs(self.minutes),
+                              seconds=abs(self.seconds),
+                              microseconds=abs(self.microseconds),
+                              leapdays=self.leapdays,
+                              year=self.year,
+                              month=self.month,
+                              day=self.day,
+                              weekday=self.weekday,
+                              hour=self.hour,
+                              minute=self.minute,
+                              second=self.second,
+                              microsecond=self.microsecond)
+
+    def __neg__(self):
+        return self.__class__(years=-self.years,
+                             months=-self.months,
+                             days=-self.days,
+                             hours=-self.hours,
+                             minutes=-self.minutes,
+                             seconds=-self.seconds,
+                             microseconds=-self.microseconds,
+                             leapdays=self.leapdays,
+                             year=self.year,
+                             month=self.month,
+                             day=self.day,
+                             weekday=self.weekday,
+                             hour=self.hour,
+                             minute=self.minute,
+                             second=self.second,
+                             microsecond=self.microsecond)
+
+    def __bool__(self):
+        return not (not self.years and
+                    not self.months and
+                    not self.days and
+                    not self.hours and
+                    not self.minutes and
+                    not self.seconds and
+                    not self.microseconds and
+                    not self.leapdays and
+                    self.year is None and
+                    self.month is None and
+                    self.day is None and
+                    self.weekday is None and
+                    self.hour is None and
+                    self.minute is None and
+                    self.second is None and
+                    self.microsecond is None)
+    # Compatibility with Python 2.x
+    __nonzero__ = __bool__
+
+    def __mul__(self, other):
+        try:
+            f = float(other)
+        except TypeError:
+            return NotImplemented
+
+        return self.__class__(years=int(self.years * f),
+                             months=int(self.months * f),
+                             days=int(self.days * f),
+                             hours=int(self.hours * f),
+                             minutes=int(self.minutes * f),
+                             seconds=int(self.seconds * f),
+                             microseconds=int(self.microseconds * f),
+                             leapdays=self.leapdays,
+                             year=self.year,
+                             month=self.month,
+                             day=self.day,
+                             weekday=self.weekday,
+                             hour=self.hour,
+                             minute=self.minute,
+                             second=self.second,
+                             microsecond=self.microsecond)
+
+    __rmul__ = __mul__
+
+    def __eq__(self, other):
+        if not isinstance(other, relativedelta):
+            return NotImplemented
+        if self.weekday or other.weekday:
+            if not self.weekday or not other.weekday:
+                return False
+            if self.weekday.weekday != other.weekday.weekday:
+                return False
+            n1, n2 = self.weekday.n, other.weekday.n
+            if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
+                return False
+        return (self.years == other.years and
+                self.months == other.months and
+                self.days == other.days and
+                self.hours == other.hours and
+                self.minutes == other.minutes and
+                self.seconds == other.seconds and
+                self.microseconds == other.microseconds and
+                self.leapdays == other.leapdays and
+                self.year == other.year and
+                self.month == other.month and
+                self.day == other.day and
+                self.hour == other.hour and
+                self.minute == other.minute and
+                self.second == other.second and
+                self.microsecond == other.microsecond)
+
+    def __hash__(self):
+        return hash((
+            self.weekday,
+            self.years,
+            self.months,
+            self.days,
+            self.hours,
+            self.minutes,
+            self.seconds,
+            self.microseconds,
+            self.leapdays,
+            self.year,
+            self.month,
+            self.day,
+            self.hour,
+            self.minute,
+            self.second,
+            self.microsecond,
+        ))
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __div__(self, other):
+        try:
+            reciprocal = 1 / float(other)
+        except TypeError:
+            return NotImplemented
+
+        return self.__mul__(reciprocal)
+
+    __truediv__ = __div__
+
+    def __repr__(self):
+        l = []
+        for attr in ["years", "months", "days", "leapdays",
+                     "hours", "minutes", "seconds", "microseconds"]:
+            value = getattr(self, attr)
+            if value:
+                l.append("{attr}={value:+g}".format(attr=attr, value=value))
+        for attr in ["year", "month", "day", "weekday",
+                     "hour", "minute", "second", "microsecond"]:
+            value = getattr(self, attr)
+            if value is not None:
+                l.append("{attr}={value}".format(attr=attr, value=repr(value)))
+        return "{classname}({attrs})".format(classname=self.__class__.__name__,
+                                             attrs=", ".join(l))
+
+
+def _sign(x):
+    return int(copysign(1, x))
+
+# vim:ts=4:sw=4:et
diff --git a/resources/lib/libraries/dateutil/rrule.py b/resources/lib/libraries/dateutil/rrule.py
new file mode 100644
index 00000000..8e9c2af1
--- /dev/null
+++ b/resources/lib/libraries/dateutil/rrule.py
@@ -0,0 +1,1672 @@
+# -*- coding: utf-8 -*-
+"""
+The rrule module offers a small, complete, and very fast, implementation of
+the recurrence rules documented in the
+`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_,
+including support for caching of results.
+"""
+import itertools
+import datetime
+import calendar
+import re
+import sys
+
+try:
+    from math import gcd
+except ImportError:
+    from fractions import gcd
+
+from six import advance_iterator, integer_types
+from six.moves import _thread, range
+import heapq
+
+from ._common import weekday as weekdaybase
+from .tz import tzutc, tzlocal
+
+# For warning about deprecation of until and count
+from warnings import warn
+
+__all__ = ["rrule", "rruleset", "rrulestr",
+           "YEARLY", "MONTHLY", "WEEKLY", "DAILY",
+           "HOURLY", "MINUTELY", "SECONDLY",
+           "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
+
+# Every mask is 7 days longer to handle cross-year weekly periods.
+M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 +
+                 [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)
+M365MASK = list(M366MASK)
+M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32))
+MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
+MDAY365MASK = list(MDAY366MASK)
+M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0))
+NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
+NMDAY365MASK = list(NMDAY366MASK)
+M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366)
+M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
+WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55
+del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]
+MDAY365MASK = tuple(MDAY365MASK)
+M365MASK = tuple(M365MASK)
+
+FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY']
+
+(YEARLY,
+ MONTHLY,
+ WEEKLY,
+ DAILY,
+ HOURLY,
+ MINUTELY,
+ SECONDLY) = list(range(7))
+
+# Imported on demand.
+easter = None
+parser = None
+
+
+class weekday(weekdaybase):
+    """
+    This version of weekday does not allow n = 0.
+    """
+    def __init__(self, wkday, n=None):
+        if n == 0:
+            raise ValueError("Can't create weekday with n==0")
+
+        super(weekday, self).__init__(wkday, n)
+
+
+MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
+
+
+def _invalidates_cache(f):
+    """
+    Decorator for rruleset methods which may invalidate the
+    cached length.
+    """
+    def inner_func(self, *args, **kwargs):
+        rv = f(self, *args, **kwargs)
+        self._invalidate_cache()
+        return rv
+
+    return inner_func
+
+
+class rrulebase(object):
+    def __init__(self, cache=False):
+        if cache:
+            self._cache = []
+            self._cache_lock = _thread.allocate_lock()
+            self._invalidate_cache()
+        else:
+            self._cache = None
+            self._cache_complete = False
+            self._len = None
+
+    def __iter__(self):
+        if self._cache_complete:
+            return iter(self._cache)
+        elif self._cache is None:
+            return self._iter()
+        else:
+            return self._iter_cached()
+
+    def _invalidate_cache(self):
+        if self._cache is not None:
+            self._cache = []
+            self._cache_complete = False
+            self._cache_gen = self._iter()
+
+            if self._cache_lock.locked():
+                self._cache_lock.release()
+
+        self._len = None
+
+    def _iter_cached(self):
+        i = 0
+        gen = self._cache_gen
+        cache = self._cache
+        acquire = self._cache_lock.acquire
+        release = self._cache_lock.release
+        while gen:
+            if i == len(cache):
+                acquire()
+                if self._cache_complete:
+                    break
+                try:
+                    for j in range(10):
+                        cache.append(advance_iterator(gen))
+                except StopIteration:
+                    self._cache_gen = gen = None
+                    self._cache_complete = True
+                    break
+                release()
+            yield cache[i]
+            i += 1
+        while i < self._len:
+            yield cache[i]
+            i += 1
+
+    def __getitem__(self, item):
+        if self._cache_complete:
+            return self._cache[item]
+        elif isinstance(item, slice):
+            if item.step and item.step < 0:
+                return list(iter(self))[item]
+            else:
+                return list(itertools.islice(self,
+                                             item.start or 0,
+                                             item.stop or sys.maxsize,
+                                             item.step or 1))
+        elif item >= 0:
+            gen = iter(self)
+            try:
+                for i in range(item+1):
+                    res = advance_iterator(gen)
+            except StopIteration:
+                raise IndexError
+            return res
+        else:
+            return list(iter(self))[item]
+
+    def __contains__(self, item):
+        if self._cache_complete:
+            return item in self._cache
+        else:
+            for i in self:
+                if i == item:
+                    return True
+                elif i > item:
+                    return False
+        return False
+
+    # __len__() introduces a large performance penality.
+    def count(self):
+        """ Returns the number of recurrences in this set. It will have go
+            trough the whole recurrence, if this hasn't been done before. """
+        if self._len is None:
+            for x in self:
+                pass
+        return self._len
+
+    def before(self, dt, inc=False):
+        """ Returns the last recurrence before the given datetime instance. The
+            inc keyword defines what happens if dt is an occurrence. With
+            inc=True, if dt itself is an occurrence, it will be returned. """
+        if self._cache_complete:
+            gen = self._cache
+        else:
+            gen = self
+        last = None
+        if inc:
+            for i in gen:
+                if i > dt:
+                    break
+                last = i
+        else:
+            for i in gen:
+                if i >= dt:
+                    break
+                last = i
+        return last
+
+    def after(self, dt, inc=False):
+        """ Returns the first recurrence after the given datetime instance. The
+            inc keyword defines what happens if dt is an occurrence. With
+            inc=True, if dt itself is an occurrence, it will be returned.  """
+        if self._cache_complete:
+            gen = self._cache
+        else:
+            gen = self
+        if inc:
+            for i in gen:
+                if i >= dt:
+                    return i
+        else:
+            for i in gen:
+                if i > dt:
+                    return i
+        return None
+
+    def xafter(self, dt, count=None, inc=False):
+        """
+        Generator which yields up to `count` recurrences after the given
+        datetime instance, equivalent to `after`.
+
+        :param dt:
+            The datetime at which to start generating recurrences.
+
+        :param count:
+            The maximum number of recurrences to generate. If `None` (default),
+            dates are generated until the recurrence rule is exhausted.
+
+        :param inc:
+            If `dt` is an instance of the rule and `inc` is `True`, it is
+            included in the output.
+
+        :yields: Yields a sequence of `datetime` objects.
+        """
+
+        if self._cache_complete:
+            gen = self._cache
+        else:
+            gen = self
+
+        # Select the comparison function
+        if inc:
+            comp = lambda dc, dtc: dc >= dtc
+        else:
+            comp = lambda dc, dtc: dc > dtc
+
+        # Generate dates
+        n = 0
+        for d in gen:
+            if comp(d, dt):
+                if count is not None:
+                    n += 1
+                    if n > count:
+                        break
+
+                yield d
+
+    def between(self, after, before, inc=False, count=1):
+        """ Returns all the occurrences of the rrule between after and before.
+        The inc keyword defines what happens if after and/or before are
+        themselves occurrences. With inc=True, they will be included in the
+        list, if they are found in the recurrence set. """
+        if self._cache_complete:
+            gen = self._cache
+        else:
+            gen = self
+        started = False
+        l = []
+        if inc:
+            for i in gen:
+                if i > before:
+                    break
+                elif not started:
+                    if i >= after:
+                        started = True
+                        l.append(i)
+                else:
+                    l.append(i)
+        else:
+            for i in gen:
+                if i >= before:
+                    break
+                elif not started:
+                    if i > after:
+                        started = True
+                        l.append(i)
+                else:
+                    l.append(i)
+        return l
+
+
+class rrule(rrulebase):
+    """
+    That's the base of the rrule operation. It accepts all the keywords
+    defined in the RFC as its constructor parameters (except byday,
+    which was renamed to byweekday) and more. The constructor prototype is::
+
+            rrule(freq)
+
+    Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
+    or SECONDLY.
+
+    .. note::
+        Per RFC section 3.3.10, recurrence instances falling on invalid dates
+        and times are ignored rather than coerced:
+
+            Recurrence rules may generate recurrence instances with an invalid
+            date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM
+            on a day where the local time is moved forward by an hour at 1:00
+            AM).  Such recurrence instances MUST be ignored and MUST NOT be
+            counted as part of the recurrence set.
+
+        This can lead to possibly surprising behavior when, for example, the
+        start date occurs at the end of the month:
+
+        >>> from dateutil.rrule import rrule, MONTHLY
+        >>> from datetime import datetime
+        >>> start_date = datetime(2014, 12, 31)
+        >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date))
+        ... # doctest: +NORMALIZE_WHITESPACE
+        [datetime.datetime(2014, 12, 31, 0, 0),
+         datetime.datetime(2015, 1, 31, 0, 0),
+         datetime.datetime(2015, 3, 31, 0, 0),
+         datetime.datetime(2015, 5, 31, 0, 0)]
+
+    Additionally, it supports the following keyword arguments:
+
+    :param dtstart:
+        The recurrence start. Besides being the base for the recurrence,
+        missing parameters in the final recurrence instances will also be
+        extracted from this date. If not given, datetime.now() will be used
+        instead.
+    :param interval:
+        The interval between each freq iteration. For example, when using
+        YEARLY, an interval of 2 means once every two years, but with HOURLY,
+        it means once every two hours. The default interval is 1.
+    :param wkst:
+        The week start day. Must be one of the MO, TU, WE constants, or an
+        integer, specifying the first day of the week. This will affect
+        recurrences based on weekly periods. The default week start is got
+        from calendar.firstweekday(), and may be modified by
+        calendar.setfirstweekday().
+    :param count:
+        How many occurrences will be generated.
+
+        .. note::
+            As of version 2.5.0, the use of the ``until`` keyword together
+            with the ``count`` keyword is deprecated per RFC-5545 Sec. 3.3.10.
+    :param until:
+        If given, this must be a datetime instance, that will specify the
+        limit of the recurrence. The last recurrence in the rule is the greatest
+        datetime that is less than or equal to the value specified in the
+        ``until`` parameter.
+
+        .. note::
+            As of version 2.5.0, the use of the ``until`` keyword together
+            with the ``count`` keyword is deprecated per RFC-5545 Sec. 3.3.10.
+    :param bysetpos:
+        If given, it must be either an integer, or a sequence of integers,
+        positive or negative. Each given integer will specify an occurrence
+        number, corresponding to the nth occurrence of the rule inside the
+        frequency period. For example, a bysetpos of -1 if combined with a
+        MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will
+        result in the last work day of every month.
+    :param bymonth:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the months to apply the recurrence to.
+    :param bymonthday:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the month days to apply the recurrence to.
+    :param byyearday:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the year days to apply the recurrence to.
+    :param byeaster:
+        If given, it must be either an integer, or a sequence of integers,
+        positive or negative. Each integer will define an offset from the
+        Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
+        Sunday itself. This is an extension to the RFC specification.
+    :param byweekno:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the week numbers to apply the recurrence to. Week numbers
+        have the meaning described in ISO8601, that is, the first week of
+        the year is that containing at least four days of the new year.
+    :param byweekday:
+        If given, it must be either an integer (0 == MO), a sequence of
+        integers, one of the weekday constants (MO, TU, etc), or a sequence
+        of these constants. When given, these variables will define the
+        weekdays where the recurrence will be applied. It's also possible to
+        use an argument n for the weekday instances, which will mean the nth
+        occurrence of this weekday in the period. For example, with MONTHLY,
+        or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the
+        first friday of the month where the recurrence happens. Notice that in
+        the RFC documentation, this is specified as BYDAY, but was renamed to
+        avoid the ambiguity of that keyword.
+    :param byhour:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the hours to apply the recurrence to.
+    :param byminute:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the minutes to apply the recurrence to.
+    :param bysecond:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the seconds to apply the recurrence to.
+    :param cache:
+        If given, it must be a boolean value specifying to enable or disable
+        caching of results. If you will use the same rrule instance multiple
+        times, enabling caching will improve the performance considerably.
+     """
+    def __init__(self, freq, dtstart=None,
+                 interval=1, wkst=None, count=None, until=None, bysetpos=None,
+                 bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
+                 byweekno=None, byweekday=None,
+                 byhour=None, byminute=None, bysecond=None,
+                 cache=False):
+        super(rrule, self).__init__(cache)
+        global easter
+        if not dtstart:
+            if until and until.tzinfo:
+                dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0)
+            else:           
+                dtstart = datetime.datetime.now().replace(microsecond=0)
+        elif not isinstance(dtstart, datetime.datetime):
+            dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
+        else:
+            dtstart = dtstart.replace(microsecond=0)
+        self._dtstart = dtstart
+        self._tzinfo = dtstart.tzinfo
+        self._freq = freq
+        self._interval = interval
+        self._count = count
+
+        # Cache the original byxxx rules, if they are provided, as the _byxxx
+        # attributes do not necessarily map to the inputs, and this can be
+        # a problem in generating the strings. Only store things if they've
+        # been supplied (the string retrieval will just use .get())
+        self._original_rule = {}
+
+        if until and not isinstance(until, datetime.datetime):
+            until = datetime.datetime.fromordinal(until.toordinal())
+        self._until = until
+
+        if self._dtstart and self._until:
+            if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None):
+                # According to RFC5545 Section 3.3.10:
+                # https://tools.ietf.org/html/rfc5545#section-3.3.10
+                #
+                # > If the "DTSTART" property is specified as a date with UTC
+                # > time or a date with local time and time zone reference,
+                # > then the UNTIL rule part MUST be specified as a date with
+                # > UTC time.
+                raise ValueError(
+                    'RRULE UNTIL values must be specified in UTC when DTSTART '
+                    'is timezone-aware'
+                )
+
+        if count is not None and until:
+            warn("Using both 'count' and 'until' is inconsistent with RFC 5545"
+                 " and has been deprecated in dateutil. Future versions will "
+                 "raise an error.", DeprecationWarning)
+
+        if wkst is None:
+            self._wkst = calendar.firstweekday()
+        elif isinstance(wkst, integer_types):
+            self._wkst = wkst
+        else:
+            self._wkst = wkst.weekday
+
+        if bysetpos is None:
+            self._bysetpos = None
+        elif isinstance(bysetpos, integer_types):
+            if bysetpos == 0 or not (-366 <= bysetpos <= 366):
+                raise ValueError("bysetpos must be between 1 and 366, "
+                                 "or between -366 and -1")
+            self._bysetpos = (bysetpos,)
+        else:
+            self._bysetpos = tuple(bysetpos)
+            for pos in self._bysetpos:
+                if pos == 0 or not (-366 <= pos <= 366):
+                    raise ValueError("bysetpos must be between 1 and 366, "
+                                     "or between -366 and -1")
+
+        if self._bysetpos:
+            self._original_rule['bysetpos'] = self._bysetpos
+
+        if (byweekno is None and byyearday is None and bymonthday is None and
+                byweekday is None and byeaster is None):
+            if freq == YEARLY:
+                if bymonth is None:
+                    bymonth = dtstart.month
+                    self._original_rule['bymonth'] = None
+                bymonthday = dtstart.day
+                self._original_rule['bymonthday'] = None
+            elif freq == MONTHLY:
+                bymonthday = dtstart.day
+                self._original_rule['bymonthday'] = None
+            elif freq == WEEKLY:
+                byweekday = dtstart.weekday()
+                self._original_rule['byweekday'] = None
+
+        # bymonth
+        if bymonth is None:
+            self._bymonth = None
+        else:
+            if isinstance(bymonth, integer_types):
+                bymonth = (bymonth,)
+
+            self._bymonth = tuple(sorted(set(bymonth)))
+
+            if 'bymonth' not in self._original_rule:
+                self._original_rule['bymonth'] = self._bymonth
+
+        # byyearday
+        if byyearday is None:
+            self._byyearday = None
+        else:
+            if isinstance(byyearday, integer_types):
+                byyearday = (byyearday,)
+
+            self._byyearday = tuple(sorted(set(byyearday)))
+            self._original_rule['byyearday'] = self._byyearday
+
+        # byeaster
+        if byeaster is not None:
+            if not easter:
+                from dateutil import easter
+            if isinstance(byeaster, integer_types):
+                self._byeaster = (byeaster,)
+            else:
+                self._byeaster = tuple(sorted(byeaster))
+
+            self._original_rule['byeaster'] = self._byeaster
+        else:
+            self._byeaster = None
+
+        # bymonthday
+        if bymonthday is None:
+            self._bymonthday = ()
+            self._bynmonthday = ()
+        else:
+            if isinstance(bymonthday, integer_types):
+                bymonthday = (bymonthday,)
+
+            bymonthday = set(bymonthday)            # Ensure it's unique
+
+            self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0))
+            self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0))
+
+            # Storing positive numbers first, then negative numbers
+            if 'bymonthday' not in self._original_rule:
+                self._original_rule['bymonthday'] = tuple(
+                    itertools.chain(self._bymonthday, self._bynmonthday))
+
+        # byweekno
+        if byweekno is None:
+            self._byweekno = None
+        else:
+            if isinstance(byweekno, integer_types):
+                byweekno = (byweekno,)
+
+            self._byweekno = tuple(sorted(set(byweekno)))
+
+            self._original_rule['byweekno'] = self._byweekno
+
+        # byweekday / bynweekday
+        if byweekday is None:
+            self._byweekday = None
+            self._bynweekday = None
+        else:
+            # If it's one of the valid non-sequence types, convert to a
+            # single-element sequence before the iterator that builds the
+            # byweekday set.
+            if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"):
+                byweekday = (byweekday,)
+
+            self._byweekday = set()
+            self._bynweekday = set()
+            for wday in byweekday:
+                if isinstance(wday, integer_types):
+                    self._byweekday.add(wday)
+                elif not wday.n or freq > MONTHLY:
+                    self._byweekday.add(wday.weekday)
+                else:
+                    self._bynweekday.add((wday.weekday, wday.n))
+
+            if not self._byweekday:
+                self._byweekday = None
+            elif not self._bynweekday:
+                self._bynweekday = None
+
+            if self._byweekday is not None:
+                self._byweekday = tuple(sorted(self._byweekday))
+                orig_byweekday = [weekday(x) for x in self._byweekday]
+            else:
+                orig_byweekday = ()
+
+            if self._bynweekday is not None:
+                self._bynweekday = tuple(sorted(self._bynweekday))
+                orig_bynweekday = [weekday(*x) for x in self._bynweekday]
+            else:
+                orig_bynweekday = ()
+
+            if 'byweekday' not in self._original_rule:
+                self._original_rule['byweekday'] = tuple(itertools.chain(
+                    orig_byweekday, orig_bynweekday))
+
+        # byhour
+        if byhour is None:
+            if freq < HOURLY:
+                self._byhour = {dtstart.hour}
+            else:
+                self._byhour = None
+        else:
+            if isinstance(byhour, integer_types):
+                byhour = (byhour,)
+
+            if freq == HOURLY:
+                self._byhour = self.__construct_byset(start=dtstart.hour,
+                                                      byxxx=byhour,
+                                                      base=24)
+            else:
+                self._byhour = set(byhour)
+
+            self._byhour = tuple(sorted(self._byhour))
+            self._original_rule['byhour'] = self._byhour
+
+        # byminute
+        if byminute is None:
+            if freq < MINUTELY:
+                self._byminute = {dtstart.minute}
+            else:
+                self._byminute = None
+        else:
+            if isinstance(byminute, integer_types):
+                byminute = (byminute,)
+
+            if freq == MINUTELY:
+                self._byminute = self.__construct_byset(start=dtstart.minute,
+                                                        byxxx=byminute,
+                                                        base=60)
+            else:
+                self._byminute = set(byminute)
+
+            self._byminute = tuple(sorted(self._byminute))
+            self._original_rule['byminute'] = self._byminute
+
+        # bysecond
+        if bysecond is None:
+            if freq < SECONDLY:
+                self._bysecond = ((dtstart.second,))
+            else:
+                self._bysecond = None
+        else:
+            if isinstance(bysecond, integer_types):
+                bysecond = (bysecond,)
+
+            self._bysecond = set(bysecond)
+
+            if freq == SECONDLY:
+                self._bysecond = self.__construct_byset(start=dtstart.second,
+                                                        byxxx=bysecond,
+                                                        base=60)
+            else:
+                self._bysecond = set(bysecond)
+
+            self._bysecond = tuple(sorted(self._bysecond))
+            self._original_rule['bysecond'] = self._bysecond
+
+        if self._freq >= HOURLY:
+            self._timeset = None
+        else:
+            self._timeset = []
+            for hour in self._byhour:
+                for minute in self._byminute:
+                    for second in self._bysecond:
+                        self._timeset.append(
+                            datetime.time(hour, minute, second,
+                                          tzinfo=self._tzinfo))
+            self._timeset.sort()
+            self._timeset = tuple(self._timeset)
+
+    def __str__(self):
+        """
+        Output a string that would generate this RRULE if passed to rrulestr.
+        This is mostly compatible with RFC5545, except for the
+        dateutil-specific extension BYEASTER.
+        """
+
+        output = []
+        h, m, s = [None] * 3
+        if self._dtstart:
+            output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S'))
+            h, m, s = self._dtstart.timetuple()[3:6]
+
+        parts = ['FREQ=' + FREQNAMES[self._freq]]
+        if self._interval != 1:
+            parts.append('INTERVAL=' + str(self._interval))
+
+        if self._wkst:
+            parts.append('WKST=' + repr(weekday(self._wkst))[0:2])
+
+        if self._count is not None:
+            parts.append('COUNT=' + str(self._count))
+
+        if self._until:
+            parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S'))
+
+        if self._original_rule.get('byweekday') is not None:
+            # The str() method on weekday objects doesn't generate
+            # RFC5545-compliant strings, so we should modify that.
+            original_rule = dict(self._original_rule)
+            wday_strings = []
+            for wday in original_rule['byweekday']:
+                if wday.n:
+                    wday_strings.append('{n:+d}{wday}'.format(
+                        n=wday.n,
+                        wday=repr(wday)[0:2]))
+                else:
+                    wday_strings.append(repr(wday))
+
+            original_rule['byweekday'] = wday_strings
+        else:
+            original_rule = self._original_rule
+
+        partfmt = '{name}={vals}'
+        for name, key in [('BYSETPOS', 'bysetpos'),
+                          ('BYMONTH', 'bymonth'),
+                          ('BYMONTHDAY', 'bymonthday'),
+                          ('BYYEARDAY', 'byyearday'),
+                          ('BYWEEKNO', 'byweekno'),
+                          ('BYDAY', 'byweekday'),
+                          ('BYHOUR', 'byhour'),
+                          ('BYMINUTE', 'byminute'),
+                          ('BYSECOND', 'bysecond'),
+                          ('BYEASTER', 'byeaster')]:
+            value = original_rule.get(key)
+            if value:
+                parts.append(partfmt.format(name=name, vals=(','.join(str(v)
+                                                             for v in value))))
+
+        output.append('RRULE:' + ';'.join(parts))
+        return '\n'.join(output)
+
+    def replace(self, **kwargs):
+        """Return new rrule with same attributes except for those attributes given new
+           values by whichever keyword arguments are specified."""
+        new_kwargs = {"interval": self._interval,
+                      "count": self._count,
+                      "dtstart": self._dtstart,
+                      "freq": self._freq,
+                      "until": self._until,
+                      "wkst": self._wkst,
+                      "cache": False if self._cache is None else True }
+        new_kwargs.update(self._original_rule)
+        new_kwargs.update(kwargs)
+        return rrule(**new_kwargs)
+
+    def _iter(self):
+        year, month, day, hour, minute, second, weekday, yearday, _ = \
+            self._dtstart.timetuple()
+
+        # Some local variables to speed things up a bit
+        freq = self._freq
+        interval = self._interval
+        wkst = self._wkst
+        until = self._until
+        bymonth = self._bymonth
+        byweekno = self._byweekno
+        byyearday = self._byyearday
+        byweekday = self._byweekday
+        byeaster = self._byeaster
+        bymonthday = self._bymonthday
+        bynmonthday = self._bynmonthday
+        bysetpos = self._bysetpos
+        byhour = self._byhour
+        byminute = self._byminute
+        bysecond = self._bysecond
+
+        ii = _iterinfo(self)
+        ii.rebuild(year, month)
+
+        getdayset = {YEARLY: ii.ydayset,
+                     MONTHLY: ii.mdayset,
+                     WEEKLY: ii.wdayset,
+                     DAILY: ii.ddayset,
+                     HOURLY: ii.ddayset,
+                     MINUTELY: ii.ddayset,
+                     SECONDLY: ii.ddayset}[freq]
+
+        if freq < HOURLY:
+            timeset = self._timeset
+        else:
+            gettimeset = {HOURLY: ii.htimeset,
+                          MINUTELY: ii.mtimeset,
+                          SECONDLY: ii.stimeset}[freq]
+            if ((freq >= HOURLY and
+                 self._byhour and hour not in self._byhour) or
+                (freq >= MINUTELY and
+                 self._byminute and minute not in self._byminute) or
+                (freq >= SECONDLY and
+                 self._bysecond and second not in self._bysecond)):
+                timeset = ()
+            else:
+                timeset = gettimeset(hour, minute, second)
+
+        total = 0
+        count = self._count
+        while True:
+            # Get dayset with the right frequency
+            dayset, start, end = getdayset(year, month, day)
+
+            # Do the "hard" work ;-)
+            filtered = False
+            for i in dayset[start:end]:
+                if ((bymonth and ii.mmask[i] not in bymonth) or
+                    (byweekno and not ii.wnomask[i]) or
+                    (byweekday and ii.wdaymask[i] not in byweekday) or
+                    (ii.nwdaymask and not ii.nwdaymask[i]) or
+                    (byeaster and not ii.eastermask[i]) or
+                    ((bymonthday or bynmonthday) and
+                     ii.mdaymask[i] not in bymonthday and
+                     ii.nmdaymask[i] not in bynmonthday) or
+                    (byyearday and
+                     ((i < ii.yearlen and i+1 not in byyearday and
+                       -ii.yearlen+i not in byyearday) or
+                      (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and
+                       -ii.nextyearlen+i-ii.yearlen not in byyearday)))):
+                    dayset[i] = None
+                    filtered = True
+
+            # Output results
+            if bysetpos and timeset:
+                poslist = []
+                for pos in bysetpos:
+                    if pos < 0:
+                        daypos, timepos = divmod(pos, len(timeset))
+                    else:
+                        daypos, timepos = divmod(pos-1, len(timeset))
+                    try:
+                        i = [x for x in dayset[start:end]
+                             if x is not None][daypos]
+                        time = timeset[timepos]
+                    except IndexError:
+                        pass
+                    else:
+                        date = datetime.date.fromordinal(ii.yearordinal+i)
+                        res = datetime.datetime.combine(date, time)
+                        if res not in poslist:
+                            poslist.append(res)
+                poslist.sort()
+                for res in poslist:
+                    if until and res > until:
+                        self._len = total
+                        return
+                    elif res >= self._dtstart:
+                        if count is not None:
+                            count -= 1
+                            if count < 0:
+                                self._len = total
+                                return
+                        total += 1
+                        yield res
+            else:
+                for i in dayset[start:end]:
+                    if i is not None:
+                        date = datetime.date.fromordinal(ii.yearordinal + i)
+                        for time in timeset:
+                            res = datetime.datetime.combine(date, time)
+                            if until and res > until:
+                                self._len = total
+                                return
+                            elif res >= self._dtstart:
+                                if count is not None:
+                                    count -= 1
+                                    if count < 0:
+                                        self._len = total
+                                        return
+
+                                total += 1
+                                yield res
+
+            # Handle frequency and interval
+            fixday = False
+            if freq == YEARLY:
+                year += interval
+                if year > datetime.MAXYEAR:
+                    self._len = total
+                    return
+                ii.rebuild(year, month)
+            elif freq == MONTHLY:
+                month += interval
+                if month > 12:
+                    div, mod = divmod(month, 12)
+                    month = mod
+                    year += div
+                    if month == 0:
+                        month = 12
+                        year -= 1
+                    if year > datetime.MAXYEAR:
+                        self._len = total
+                        return
+                ii.rebuild(year, month)
+            elif freq == WEEKLY:
+                if wkst > weekday:
+                    day += -(weekday+1+(6-wkst))+self._interval*7
+                else:
+                    day += -(weekday-wkst)+self._interval*7
+                weekday = wkst
+                fixday = True
+            elif freq == DAILY:
+                day += interval
+                fixday = True
+            elif freq == HOURLY:
+                if filtered:
+                    # Jump to one iteration before next day
+                    hour += ((23-hour)//interval)*interval
+
+                if byhour:
+                    ndays, hour = self.__mod_distance(value=hour,
+                                                      byxxx=self._byhour,
+                                                      base=24)
+                else:
+                    ndays, hour = divmod(hour+interval, 24)
+
+                if ndays:
+                    day += ndays
+                    fixday = True
+
+                timeset = gettimeset(hour, minute, second)
+            elif freq == MINUTELY:
+                if filtered:
+                    # Jump to one iteration before next day
+                    minute += ((1439-(hour*60+minute))//interval)*interval
+
+                valid = False
+                rep_rate = (24*60)
+                for j in range(rep_rate // gcd(interval, rep_rate)):
+                    if byminute:
+                        nhours, minute = \
+                            self.__mod_distance(value=minute,
+                                                byxxx=self._byminute,
+                                                base=60)
+                    else:
+                        nhours, minute = divmod(minute+interval, 60)
+
+                    div, hour = divmod(hour+nhours, 24)
+                    if div:
+                        day += div
+                        fixday = True
+                        filtered = False
+
+                    if not byhour or hour in byhour:
+                        valid = True
+                        break
+
+                if not valid:
+                    raise ValueError('Invalid combination of interval and ' +
+                                     'byhour resulting in empty rule.')
+
+                timeset = gettimeset(hour, minute, second)
+            elif freq == SECONDLY:
+                if filtered:
+                    # Jump to one iteration before next day
+                    second += (((86399 - (hour * 3600 + minute * 60 + second))
+                                // interval) * interval)
+
+                rep_rate = (24 * 3600)
+                valid = False
+                for j in range(0, rep_rate // gcd(interval, rep_rate)):
+                    if bysecond:
+                        nminutes, second = \
+                            self.__mod_distance(value=second,
+                                                byxxx=self._bysecond,
+                                                base=60)
+                    else:
+                        nminutes, second = divmod(second+interval, 60)
+
+                    div, minute = divmod(minute+nminutes, 60)
+                    if div:
+                        hour += div
+                        div, hour = divmod(hour, 24)
+                        if div:
+                            day += div
+                            fixday = True
+
+                    if ((not byhour or hour in byhour) and
+                            (not byminute or minute in byminute) and
+                            (not bysecond or second in bysecond)):
+                        valid = True
+                        break
+
+                if not valid:
+                    raise ValueError('Invalid combination of interval, ' +
+                                     'byhour and byminute resulting in empty' +
+                                     ' rule.')
+
+                timeset = gettimeset(hour, minute, second)
+
+            if fixday and day > 28:
+                daysinmonth = calendar.monthrange(year, month)[1]
+                if day > daysinmonth:
+                    while day > daysinmonth:
+                        day -= daysinmonth
+                        month += 1
+                        if month == 13:
+                            month = 1
+                            year += 1
+                            if year > datetime.MAXYEAR:
+                                self._len = total
+                                return
+                        daysinmonth = calendar.monthrange(year, month)[1]
+                    ii.rebuild(year, month)
+
+    def __construct_byset(self, start, byxxx, base):
+        """
+        If a `BYXXX` sequence is passed to the constructor at the same level as
+        `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some
+        specifications which cannot be reached given some starting conditions.
+
+        This occurs whenever the interval is not coprime with the base of a
+        given unit and the difference between the starting position and the
+        ending position is not coprime with the greatest common denominator
+        between the interval and the base. For example, with a FREQ of hourly
+        starting at 17:00 and an interval of 4, the only valid values for
+        BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not
+        coprime.
+
+        :param start:
+            Specifies the starting position.
+        :param byxxx:
+            An iterable containing the list of allowed values.
+        :param base:
+            The largest allowable value for the specified frequency (e.g.
+            24 hours, 60 minutes).
+
+        This does not preserve the type of the iterable, returning a set, since
+        the values should be unique and the order is irrelevant, this will
+        speed up later lookups.
+
+        In the event of an empty set, raises a :exception:`ValueError`, as this
+        results in an empty rrule.
+        """
+
+        cset = set()
+
+        # Support a single byxxx value.
+        if isinstance(byxxx, integer_types):
+            byxxx = (byxxx, )
+
+        for num in byxxx:
+            i_gcd = gcd(self._interval, base)
+            # Use divmod rather than % because we need to wrap negative nums.
+            if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0:
+                cset.add(num)
+
+        if len(cset) == 0:
+            raise ValueError("Invalid rrule byxxx generates an empty set.")
+
+        return cset
+
+    def __mod_distance(self, value, byxxx, base):
+        """
+        Calculates the next value in a sequence where the `FREQ` parameter is
+        specified along with a `BYXXX` parameter at the same "level"
+        (e.g. `HOURLY` specified with `BYHOUR`).
+
+        :param value:
+            The old value of the component.
+        :param byxxx:
+            The `BYXXX` set, which should have been generated by
+            `rrule._construct_byset`, or something else which checks that a
+            valid rule is present.
+        :param base:
+            The largest allowable value for the specified frequency (e.g.
+            24 hours, 60 minutes).
+
+        If a valid value is not found after `base` iterations (the maximum
+        number before the sequence would start to repeat), this raises a
+        :exception:`ValueError`, as no valid values were found.
+
+        This returns a tuple of `divmod(n*interval, base)`, where `n` is the
+        smallest number of `interval` repetitions until the next specified
+        value in `byxxx` is found.
+        """
+        accumulator = 0
+        for ii in range(1, base + 1):
+            # Using divmod() over % to account for negative intervals
+            div, value = divmod(value + self._interval, base)
+            accumulator += div
+            if value in byxxx:
+                return (accumulator, value)
+
+
+class _iterinfo(object):
+    __slots__ = ["rrule", "lastyear", "lastmonth",
+                 "yearlen", "nextyearlen", "yearordinal", "yearweekday",
+                 "mmask", "mrange", "mdaymask", "nmdaymask",
+                 "wdaymask", "wnomask", "nwdaymask", "eastermask"]
+
+    def __init__(self, rrule):
+        for attr in self.__slots__:
+            setattr(self, attr, None)
+        self.rrule = rrule
+
+    def rebuild(self, year, month):
+        # Every mask is 7 days longer to handle cross-year weekly periods.
+        rr = self.rrule
+        if year != self.lastyear:
+            self.yearlen = 365 + calendar.isleap(year)
+            self.nextyearlen = 365 + calendar.isleap(year + 1)
+            firstyday = datetime.date(year, 1, 1)
+            self.yearordinal = firstyday.toordinal()
+            self.yearweekday = firstyday.weekday()
+
+            wday = datetime.date(year, 1, 1).weekday()
+            if self.yearlen == 365:
+                self.mmask = M365MASK
+                self.mdaymask = MDAY365MASK
+                self.nmdaymask = NMDAY365MASK
+                self.wdaymask = WDAYMASK[wday:]
+                self.mrange = M365RANGE
+            else:
+                self.mmask = M366MASK
+                self.mdaymask = MDAY366MASK
+                self.nmdaymask = NMDAY366MASK
+                self.wdaymask = WDAYMASK[wday:]
+                self.mrange = M366RANGE
+
+            if not rr._byweekno:
+                self.wnomask = None
+            else:
+                self.wnomask = [0]*(self.yearlen+7)
+                # no1wkst = firstwkst = self.wdaymask.index(rr._wkst)
+                no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7
+                if no1wkst >= 4:
+                    no1wkst = 0
+                    # Number of days in the year, plus the days we got
+                    # from last year.
+                    wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7
+                else:
+                    # Number of days in the year, minus the days we
+                    # left in last year.
+                    wyearlen = self.yearlen-no1wkst
+                div, mod = divmod(wyearlen, 7)
+                numweeks = div+mod//4
+                for n in rr._byweekno:
+                    if n < 0:
+                        n += numweeks+1
+                    if not (0 < n <= numweeks):
+                        continue
+                    if n > 1:
+                        i = no1wkst+(n-1)*7
+                        if no1wkst != firstwkst:
+                            i -= 7-firstwkst
+                    else:
+                        i = no1wkst
+                    for j in range(7):
+                        self.wnomask[i] = 1
+                        i += 1
+                        if self.wdaymask[i] == rr._wkst:
+                            break
+                if 1 in rr._byweekno:
+                    # Check week number 1 of next year as well
+                    # TODO: Check -numweeks for next year.
+                    i = no1wkst+numweeks*7
+                    if no1wkst != firstwkst:
+                        i -= 7-firstwkst
+                    if i < self.yearlen:
+                        # If week starts in next year, we
+                        # don't care about it.
+                        for j in range(7):
+                            self.wnomask[i] = 1
+                            i += 1
+                            if self.wdaymask[i] == rr._wkst:
+                                break
+                if no1wkst:
+                    # Check last week number of last year as
+                    # well. If no1wkst is 0, either the year
+                    # started on week start, or week number 1
+                    # got days from last year, so there are no
+                    # days from last year's last week number in
+                    # this year.
+                    if -1 not in rr._byweekno:
+                        lyearweekday = datetime.date(year-1, 1, 1).weekday()
+                        lno1wkst = (7-lyearweekday+rr._wkst) % 7
+                        lyearlen = 365+calendar.isleap(year-1)
+                        if lno1wkst >= 4:
+                            lno1wkst = 0
+                            lnumweeks = 52+(lyearlen +
+                                            (lyearweekday-rr._wkst) % 7) % 7//4
+                        else:
+                            lnumweeks = 52+(self.yearlen-no1wkst) % 7//4
+                    else:
+                        lnumweeks = -1
+                    if lnumweeks in rr._byweekno:
+                        for i in range(no1wkst):
+                            self.wnomask[i] = 1
+
+        if (rr._bynweekday and (month != self.lastmonth or
+                                year != self.lastyear)):
+            ranges = []
+            if rr._freq == YEARLY:
+                if rr._bymonth:
+                    for month in rr._bymonth:
+                        ranges.append(self.mrange[month-1:month+1])
+                else:
+                    ranges = [(0, self.yearlen)]
+            elif rr._freq == MONTHLY:
+                ranges = [self.mrange[month-1:month+1]]
+            if ranges:
+                # Weekly frequency won't get here, so we may not
+                # care about cross-year weekly periods.
+                self.nwdaymask = [0]*self.yearlen
+                for first, last in ranges:
+                    last -= 1
+                    for wday, n in rr._bynweekday:
+                        if n < 0:
+                            i = last+(n+1)*7
+                            i -= (self.wdaymask[i]-wday) % 7
+                        else:
+                            i = first+(n-1)*7
+                            i += (7-self.wdaymask[i]+wday) % 7
+                        if first <= i <= last:
+                            self.nwdaymask[i] = 1
+
+        if rr._byeaster:
+            self.eastermask = [0]*(self.yearlen+7)
+            eyday = easter.easter(year).toordinal()-self.yearordinal
+            for offset in rr._byeaster:
+                self.eastermask[eyday+offset] = 1
+
+        self.lastyear = year
+        self.lastmonth = month
+
+    def ydayset(self, year, month, day):
+        return list(range(self.yearlen)), 0, self.yearlen
+
+    def mdayset(self, year, month, day):
+        dset = [None]*self.yearlen
+        start, end = self.mrange[month-1:month+1]
+        for i in range(start, end):
+            dset[i] = i
+        return dset, start, end
+
+    def wdayset(self, year, month, day):
+        # We need to handle cross-year weeks here.
+        dset = [None]*(self.yearlen+7)
+        i = datetime.date(year, month, day).toordinal()-self.yearordinal
+        start = i
+        for j in range(7):
+            dset[i] = i
+            i += 1
+            # if (not (0 <= i < self.yearlen) or
+            #    self.wdaymask[i] == self.rrule._wkst):
+            # This will cross the year boundary, if necessary.
+            if self.wdaymask[i] == self.rrule._wkst:
+                break
+        return dset, start, i
+
+    def ddayset(self, year, month, day):
+        dset = [None] * self.yearlen
+        i = datetime.date(year, month, day).toordinal() - self.yearordinal
+        dset[i] = i
+        return dset, i, i + 1
+
+    def htimeset(self, hour, minute, second):
+        tset = []
+        rr = self.rrule
+        for minute in rr._byminute:
+            for second in rr._bysecond:
+                tset.append(datetime.time(hour, minute, second,
+                                          tzinfo=rr._tzinfo))
+        tset.sort()
+        return tset
+
+    def mtimeset(self, hour, minute, second):
+        tset = []
+        rr = self.rrule
+        for second in rr._bysecond:
+            tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
+        tset.sort()
+        return tset
+
+    def stimeset(self, hour, minute, second):
+        return (datetime.time(hour, minute, second,
+                tzinfo=self.rrule._tzinfo),)
+
+
+class rruleset(rrulebase):
+    """ The rruleset type allows more complex recurrence setups, mixing
+    multiple rules, dates, exclusion rules, and exclusion dates. The type
+    constructor takes the following keyword arguments:
+
+    :param cache: If True, caching of results will be enabled, improving
+                  performance of multiple queries considerably. """
+
+    class _genitem(object):
+        def __init__(self, genlist, gen):
+            try:
+                self.dt = advance_iterator(gen)
+                genlist.append(self)
+            except StopIteration:
+                pass
+            self.genlist = genlist
+            self.gen = gen
+
+        def __next__(self):
+            try:
+                self.dt = advance_iterator(self.gen)
+            except StopIteration:
+                if self.genlist[0] is self:
+                    heapq.heappop(self.genlist)
+                else:
+                    self.genlist.remove(self)
+                    heapq.heapify(self.genlist)
+
+        next = __next__
+
+        def __lt__(self, other):
+            return self.dt < other.dt
+
+        def __gt__(self, other):
+            return self.dt > other.dt
+
+        def __eq__(self, other):
+            return self.dt == other.dt
+
+        def __ne__(self, other):
+            return self.dt != other.dt
+
+    def __init__(self, cache=False):
+        super(rruleset, self).__init__(cache)
+        self._rrule = []
+        self._rdate = []
+        self._exrule = []
+        self._exdate = []
+
+    @_invalidates_cache
+    def rrule(self, rrule):
+        """ Include the given :py:class:`rrule` instance in the recurrence set
+            generation. """
+        self._rrule.append(rrule)
+
+    @_invalidates_cache
+    def rdate(self, rdate):
+        """ Include the given :py:class:`datetime` instance in the recurrence
+            set generation. """
+        self._rdate.append(rdate)
+
+    @_invalidates_cache
+    def exrule(self, exrule):
+        """ Include the given rrule instance in the recurrence set exclusion
+            list. Dates which are part of the given recurrence rules will not
+            be generated, even if some inclusive rrule or rdate matches them.
+        """
+        self._exrule.append(exrule)
+
+    @_invalidates_cache
+    def exdate(self, exdate):
+        """ Include the given datetime instance in the recurrence set
+            exclusion list. Dates included that way will not be generated,
+            even if some inclusive rrule or rdate matches them. """
+        self._exdate.append(exdate)
+
+    def _iter(self):
+        rlist = []
+        self._rdate.sort()
+        self._genitem(rlist, iter(self._rdate))
+        for gen in [iter(x) for x in self._rrule]:
+            self._genitem(rlist, gen)
+        exlist = []
+        self._exdate.sort()
+        self._genitem(exlist, iter(self._exdate))
+        for gen in [iter(x) for x in self._exrule]:
+            self._genitem(exlist, gen)
+        lastdt = None
+        total = 0
+        heapq.heapify(rlist)
+        heapq.heapify(exlist)
+        while rlist:
+            ritem = rlist[0]
+            if not lastdt or lastdt != ritem.dt:
+                while exlist and exlist[0] < ritem:
+                    exitem = exlist[0]
+                    advance_iterator(exitem)
+                    if exlist and exlist[0] is exitem:
+                        heapq.heapreplace(exlist, exitem)
+                if not exlist or ritem != exlist[0]:
+                    total += 1
+                    yield ritem.dt
+                lastdt = ritem.dt
+            advance_iterator(ritem)
+            if rlist and rlist[0] is ritem:
+                heapq.heapreplace(rlist, ritem)
+        self._len = total
+
+
+class _rrulestr(object):
+
+    _freq_map = {"YEARLY": YEARLY,
+                 "MONTHLY": MONTHLY,
+                 "WEEKLY": WEEKLY,
+                 "DAILY": DAILY,
+                 "HOURLY": HOURLY,
+                 "MINUTELY": MINUTELY,
+                 "SECONDLY": SECONDLY}
+
+    _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3,
+                    "FR": 4, "SA": 5, "SU": 6}
+
+    def _handle_int(self, rrkwargs, name, value, **kwargs):
+        rrkwargs[name.lower()] = int(value)
+
+    def _handle_int_list(self, rrkwargs, name, value, **kwargs):
+        rrkwargs[name.lower()] = [int(x) for x in value.split(',')]
+
+    _handle_INTERVAL = _handle_int
+    _handle_COUNT = _handle_int
+    _handle_BYSETPOS = _handle_int_list
+    _handle_BYMONTH = _handle_int_list
+    _handle_BYMONTHDAY = _handle_int_list
+    _handle_BYYEARDAY = _handle_int_list
+    _handle_BYEASTER = _handle_int_list
+    _handle_BYWEEKNO = _handle_int_list
+    _handle_BYHOUR = _handle_int_list
+    _handle_BYMINUTE = _handle_int_list
+    _handle_BYSECOND = _handle_int_list
+
+    def _handle_FREQ(self, rrkwargs, name, value, **kwargs):
+        rrkwargs["freq"] = self._freq_map[value]
+
+    def _handle_UNTIL(self, rrkwargs, name, value, **kwargs):
+        global parser
+        if not parser:
+            from dateutil import parser
+        try:
+            rrkwargs["until"] = parser.parse(value,
+                                             ignoretz=kwargs.get("ignoretz"),
+                                             tzinfos=kwargs.get("tzinfos"))
+        except ValueError:
+            raise ValueError("invalid until date")
+
+    def _handle_WKST(self, rrkwargs, name, value, **kwargs):
+        rrkwargs["wkst"] = self._weekday_map[value]
+
+    def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs):
+        """
+        Two ways to specify this: +1MO or MO(+1)
+        """
+        l = []
+        for wday in value.split(','):
+            if '(' in wday:
+                # If it's of the form TH(+1), etc.
+                splt = wday.split('(')
+                w = splt[0]
+                n = int(splt[1][:-1])
+            elif len(wday):
+                # If it's of the form +1MO
+                for i in range(len(wday)):
+                    if wday[i] not in '+-0123456789':
+                        break
+                n = wday[:i] or None
+                w = wday[i:]
+                if n:
+                    n = int(n)
+            else:
+                raise ValueError("Invalid (empty) BYDAY specification.")
+
+            l.append(weekdays[self._weekday_map[w]](n))
+        rrkwargs["byweekday"] = l
+
+    _handle_BYDAY = _handle_BYWEEKDAY
+
+    def _parse_rfc_rrule(self, line,
+                         dtstart=None,
+                         cache=False,
+                         ignoretz=False,
+                         tzinfos=None):
+        if line.find(':') != -1:
+            name, value = line.split(':')
+            if name != "RRULE":
+                raise ValueError("unknown parameter name")
+        else:
+            value = line
+        rrkwargs = {}
+        for pair in value.split(';'):
+            name, value = pair.split('=')
+            name = name.upper()
+            value = value.upper()
+            try:
+                getattr(self, "_handle_"+name)(rrkwargs, name, value,
+                                               ignoretz=ignoretz,
+                                               tzinfos=tzinfos)
+            except AttributeError:
+                raise ValueError("unknown parameter '%s'" % name)
+            except (KeyError, ValueError):
+                raise ValueError("invalid '%s': %s" % (name, value))
+        return rrule(dtstart=dtstart, cache=cache, **rrkwargs)
+
+    def _parse_rfc(self, s,
+                   dtstart=None,
+                   cache=False,
+                   unfold=False,
+                   forceset=False,
+                   compatible=False,
+                   ignoretz=False,
+                   tzids=None,
+                   tzinfos=None):
+        global parser
+        if compatible:
+            forceset = True
+            unfold = True
+
+        TZID_NAMES = dict(map(
+            lambda x: (x.upper(), x),
+            re.findall('TZID=(?P<name>[^:]+):', s)
+        ))
+        s = s.upper()
+        if not s.strip():
+            raise ValueError("empty string")
+        if unfold:
+            lines = s.splitlines()
+            i = 0
+            while i < len(lines):
+                line = lines[i].rstrip()
+                if not line:
+                    del lines[i]
+                elif i > 0 and line[0] == " ":
+                    lines[i-1] += line[1:]
+                    del lines[i]
+                else:
+                    i += 1
+        else:
+            lines = s.split()
+        if (not forceset and len(lines) == 1 and (s.find(':') == -1 or
+                                                  s.startswith('RRULE:'))):
+            return self._parse_rfc_rrule(lines[0], cache=cache,
+                                         dtstart=dtstart, ignoretz=ignoretz,
+                                         tzinfos=tzinfos)
+        else:
+            rrulevals = []
+            rdatevals = []
+            exrulevals = []
+            exdatevals = []
+            for line in lines:
+                if not line:
+                    continue
+                if line.find(':') == -1:
+                    name = "RRULE"
+                    value = line
+                else:
+                    name, value = line.split(':', 1)
+                parms = name.split(';')
+                if not parms:
+                    raise ValueError("empty property name")
+                name = parms[0]
+                parms = parms[1:]
+                if name == "RRULE":
+                    for parm in parms:
+                        raise ValueError("unsupported RRULE parm: "+parm)
+                    rrulevals.append(value)
+                elif name == "RDATE":
+                    for parm in parms:
+                        if parm != "VALUE=DATE-TIME":
+                            raise ValueError("unsupported RDATE parm: "+parm)
+                    rdatevals.append(value)
+                elif name == "EXRULE":
+                    for parm in parms:
+                        raise ValueError("unsupported EXRULE parm: "+parm)
+                    exrulevals.append(value)
+                elif name == "EXDATE":
+                    for parm in parms:
+                        if parm != "VALUE=DATE-TIME":
+                            raise ValueError("unsupported EXDATE parm: "+parm)
+                    exdatevals.append(value)
+                elif name == "DTSTART":
+                    # RFC 5445 3.8.2.4: The VALUE parameter is optional, but
+                    # may be found only once.
+                    value_found = False
+                    TZID = None
+                    valid_values = {"VALUE=DATE-TIME", "VALUE=DATE"}
+                    for parm in parms:
+                        if parm.startswith("TZID="):
+                            try:
+                                tzkey = TZID_NAMES[parm.split('TZID=')[-1]]
+                            except KeyError:
+                                continue
+                            if tzids is None:
+                                from . import tz
+                                tzlookup = tz.gettz
+                            elif callable(tzids):
+                                tzlookup = tzids
+                            else:
+                                tzlookup = getattr(tzids, 'get', None)
+                                if tzlookup is None:
+                                    msg = ('tzids must be a callable, ' +
+                                           'mapping, or None, ' +
+                                           'not %s' % tzids)
+                                    raise ValueError(msg)
+
+                            TZID = tzlookup(tzkey)
+                            continue
+                        if parm not in valid_values:
+                            raise ValueError("unsupported DTSTART parm: "+parm)
+                        else:
+                            if value_found:
+                                msg = ("Duplicate value parameter found in " +
+                                       "DTSTART: " + parm)
+                                raise ValueError(msg)
+                            value_found = True
+                    if not parser:
+                        from dateutil import parser
+                    dtstart = parser.parse(value, ignoretz=ignoretz,
+                                           tzinfos=tzinfos)
+                    if TZID is not None:
+                        if dtstart.tzinfo is None:
+                            dtstart = dtstart.replace(tzinfo=TZID)
+                        else:
+                            raise ValueError('DTSTART specifies multiple timezones')
+                else:
+                    raise ValueError("unsupported property: "+name)
+            if (forceset or len(rrulevals) > 1 or rdatevals
+                    or exrulevals or exdatevals):
+                if not parser and (rdatevals or exdatevals):
+                    from dateutil import parser
+                rset = rruleset(cache=cache)
+                for value in rrulevals:
+                    rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,
+                                                     ignoretz=ignoretz,
+                                                     tzinfos=tzinfos))
+                for value in rdatevals:
+                    for datestr in value.split(','):
+                        rset.rdate(parser.parse(datestr,
+                                                ignoretz=ignoretz,
+                                                tzinfos=tzinfos))
+                for value in exrulevals:
+                    rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,
+                                                      ignoretz=ignoretz,
+                                                      tzinfos=tzinfos))
+                for value in exdatevals:
+                    for datestr in value.split(','):
+                        rset.exdate(parser.parse(datestr,
+                                                 ignoretz=ignoretz,
+                                                 tzinfos=tzinfos))
+                if compatible and dtstart:
+                    rset.rdate(dtstart)
+                return rset
+            else:
+                return self._parse_rfc_rrule(rrulevals[0],
+                                             dtstart=dtstart,
+                                             cache=cache,
+                                             ignoretz=ignoretz,
+                                             tzinfos=tzinfos)
+
+    def __call__(self, s, **kwargs):
+        return self._parse_rfc(s, **kwargs)
+
+
+rrulestr = _rrulestr()
+
+# vim:ts=4:sw=4:et
diff --git a/resources/lib/libraries/dateutil/six.py b/resources/lib/libraries/dateutil/six.py
new file mode 100644
index 00000000..6bf4fd38
--- /dev/null
+++ b/resources/lib/libraries/dateutil/six.py
@@ -0,0 +1,891 @@
+# Copyright (c) 2010-2017 Benjamin Peterson
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""Utilities for writing code that runs on Python 2 and 3"""
+
+from __future__ import absolute_import
+
+import functools
+import itertools
+import operator
+import sys
+import types
+
+__author__ = "Benjamin Peterson <benjamin@python.org>"
+__version__ = "1.11.0"
+
+
+# Useful for very coarse version differentiation.
+PY2 = sys.version_info[0] == 2
+PY3 = sys.version_info[0] == 3
+PY34 = sys.version_info[0:2] >= (3, 4)
+
+if PY3:
+    string_types = str,
+    integer_types = int,
+    class_types = type,
+    text_type = str
+    binary_type = bytes
+
+    MAXSIZE = sys.maxsize
+else:
+    string_types = basestring,
+    integer_types = (int, long)
+    class_types = (type, types.ClassType)
+    text_type = unicode
+    binary_type = str
+
+    if sys.platform.startswith("java"):
+        # Jython always uses 32 bits.
+        MAXSIZE = int((1 << 31) - 1)
+    else:
+        # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+        class X(object):
+
+            def __len__(self):
+                return 1 << 31
+        try:
+            len(X())
+        except OverflowError:
+            # 32-bit
+            MAXSIZE = int((1 << 31) - 1)
+        else:
+            # 64-bit
+            MAXSIZE = int((1 << 63) - 1)
+        del X
+
+
+def _add_doc(func, doc):
+    """Add documentation to a function."""
+    func.__doc__ = doc
+
+
+def _import_module(name):
+    """Import module, returning the module after the last dot."""
+    __import__(name)
+    return sys.modules[name]
+
+
+class _LazyDescr(object):
+
+    def __init__(self, name):
+        self.name = name
+
+    def __get__(self, obj, tp):
+        result = self._resolve()
+        setattr(obj, self.name, result)  # Invokes __set__.
+        try:
+            # This is a bit ugly, but it avoids running this again by
+            # removing this descriptor.
+            delattr(obj.__class__, self.name)
+        except AttributeError:
+            pass
+        return result
+
+
+class MovedModule(_LazyDescr):
+
+    def __init__(self, name, old, new=None):
+        super(MovedModule, self).__init__(name)
+        if PY3:
+            if new is None:
+                new = name
+            self.mod = new
+        else:
+            self.mod = old
+
+    def _resolve(self):
+        return _import_module(self.mod)
+
+    def __getattr__(self, attr):
+        _module = self._resolve()
+        value = getattr(_module, attr)
+        setattr(self, attr, value)
+        return value
+
+
+class _LazyModule(types.ModuleType):
+
+    def __init__(self, name):
+        super(_LazyModule, self).__init__(name)
+        self.__doc__ = self.__class__.__doc__
+
+    def __dir__(self):
+        attrs = ["__doc__", "__name__"]
+        attrs += [attr.name for attr in self._moved_attributes]
+        return attrs
+
+    # Subclasses should override this
+    _moved_attributes = []
+
+
+class MovedAttribute(_LazyDescr):
+
+    def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+        super(MovedAttribute, self).__init__(name)
+        if PY3:
+            if new_mod is None:
+                new_mod = name
+            self.mod = new_mod
+            if new_attr is None:
+                if old_attr is None:
+                    new_attr = name
+                else:
+                    new_attr = old_attr
+            self.attr = new_attr
+        else:
+            self.mod = old_mod
+            if old_attr is None:
+                old_attr = name
+            self.attr = old_attr
+
+    def _resolve(self):
+        module = _import_module(self.mod)
+        return getattr(module, self.attr)
+
+
+class _SixMetaPathImporter(object):
+
+    """
+    A meta path importer to import six.moves and its submodules.
+
+    This class implements a PEP302 finder and loader. It should be compatible
+    with Python 2.5 and all existing versions of Python3
+    """
+
+    def __init__(self, six_module_name):
+        self.name = six_module_name
+        self.known_modules = {}
+
+    def _add_module(self, mod, *fullnames):
+        for fullname in fullnames:
+            self.known_modules[self.name + "." + fullname] = mod
+
+    def _get_module(self, fullname):
+        return self.known_modules[self.name + "." + fullname]
+
+    def find_module(self, fullname, path=None):
+        if fullname in self.known_modules:
+            return self
+        return None
+
+    def __get_module(self, fullname):
+        try:
+            return self.known_modules[fullname]
+        except KeyError:
+            raise ImportError("This loader does not know module " + fullname)
+
+    def load_module(self, fullname):
+        try:
+            # in case of a reload
+            return sys.modules[fullname]
+        except KeyError:
+            pass
+        mod = self.__get_module(fullname)
+        if isinstance(mod, MovedModule):
+            mod = mod._resolve()
+        else:
+            mod.__loader__ = self
+        sys.modules[fullname] = mod
+        return mod
+
+    def is_package(self, fullname):
+        """
+        Return true, if the named module is a package.
+
+        We need this method to get correct spec objects with
+        Python 3.4 (see PEP451)
+        """
+        return hasattr(self.__get_module(fullname), "__path__")
+
+    def get_code(self, fullname):
+        """Return None
+
+        Required, if is_package is implemented"""
+        self.__get_module(fullname)  # eventually raises ImportError
+        return None
+    get_source = get_code  # same as get_code
+
+_importer = _SixMetaPathImporter(__name__)
+
+
+class _MovedItems(_LazyModule):
+
+    """Lazy loading of moved objects"""
+    __path__ = []  # mark as package
+
+
+_moved_attributes = [
+    MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+    MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+    MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+    MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+    MovedAttribute("intern", "__builtin__", "sys"),
+    MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+    MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+    MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+    MovedAttribute("getoutput", "commands", "subprocess"),
+    MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+    MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+    MovedAttribute("reduce", "__builtin__", "functools"),
+    MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+    MovedAttribute("StringIO", "StringIO", "io"),
+    MovedAttribute("UserDict", "UserDict", "collections"),
+    MovedAttribute("UserList", "UserList", "collections"),
+    MovedAttribute("UserString", "UserString", "collections"),
+    MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+    MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+    MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+    MovedModule("builtins", "__builtin__"),
+    MovedModule("configparser", "ConfigParser"),
+    MovedModule("copyreg", "copy_reg"),
+    MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+    MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+    MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+    MovedModule("http_cookies", "Cookie", "http.cookies"),
+    MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+    MovedModule("html_parser", "HTMLParser", "html.parser"),
+    MovedModule("http_client", "httplib", "http.client"),
+    MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+    MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
+    MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+    MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+    MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+    MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+    MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+    MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+    MovedModule("cPickle", "cPickle", "pickle"),
+    MovedModule("queue", "Queue"),
+    MovedModule("reprlib", "repr"),
+    MovedModule("socketserver", "SocketServer"),
+    MovedModule("_thread", "thread", "_thread"),
+    MovedModule("tkinter", "Tkinter"),
+    MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+    MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+    MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+    MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+    MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+    MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+    MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+    MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+    MovedModule("tkinter_colorchooser", "tkColorChooser",
+                "tkinter.colorchooser"),
+    MovedModule("tkinter_commondialog", "tkCommonDialog",
+                "tkinter.commondialog"),
+    MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+    MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+    MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+    MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+                "tkinter.simpledialog"),
+    MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+    MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+    MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+    MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+    MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+    MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+]
+# Add windows specific modules.
+if sys.platform == "win32":
+    _moved_attributes += [
+        MovedModule("winreg", "_winreg"),
+    ]
+
+for attr in _moved_attributes:
+    setattr(_MovedItems, attr.name, attr)
+    if isinstance(attr, MovedModule):
+        _importer._add_module(attr, "moves." + attr.name)
+del attr
+
+_MovedItems._moved_attributes = _moved_attributes
+
+moves = _MovedItems(__name__ + ".moves")
+_importer._add_module(moves, "moves")
+
+
+class Module_six_moves_urllib_parse(_LazyModule):
+
+    """Lazy loading of moved objects in six.moves.urllib_parse"""
+
+
+_urllib_parse_moved_attributes = [
+    MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+    MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+    MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+    MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+    MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+    MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+    MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+    MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+    MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+    MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+    MovedAttribute("quote", "urllib", "urllib.parse"),
+    MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+    MovedAttribute("unquote", "urllib", "urllib.parse"),
+    MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+    MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
+    MovedAttribute("urlencode", "urllib", "urllib.parse"),
+    MovedAttribute("splitquery", "urllib", "urllib.parse"),
+    MovedAttribute("splittag", "urllib", "urllib.parse"),
+    MovedAttribute("splituser", "urllib", "urllib.parse"),
+    MovedAttribute("splitvalue", "urllib", "urllib.parse"),
+    MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+    MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+    MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+    MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+    MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+]
+for attr in _urllib_parse_moved_attributes:
+    setattr(Module_six_moves_urllib_parse, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+                      "moves.urllib_parse", "moves.urllib.parse")
+
+
+class Module_six_moves_urllib_error(_LazyModule):
+
+    """Lazy loading of moved objects in six.moves.urllib_error"""
+
+
+_urllib_error_moved_attributes = [
+    MovedAttribute("URLError", "urllib2", "urllib.error"),
+    MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+    MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+]
+for attr in _urllib_error_moved_attributes:
+    setattr(Module_six_moves_urllib_error, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+                      "moves.urllib_error", "moves.urllib.error")
+
+
+class Module_six_moves_urllib_request(_LazyModule):
+
+    """Lazy loading of moved objects in six.moves.urllib_request"""
+
+
+_urllib_request_moved_attributes = [
+    MovedAttribute("urlopen", "urllib2", "urllib.request"),
+    MovedAttribute("install_opener", "urllib2", "urllib.request"),
+    MovedAttribute("build_opener", "urllib2", "urllib.request"),
+    MovedAttribute("pathname2url", "urllib", "urllib.request"),
+    MovedAttribute("url2pathname", "urllib", "urllib.request"),
+    MovedAttribute("getproxies", "urllib", "urllib.request"),
+    MovedAttribute("Request", "urllib2", "urllib.request"),
+    MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+    MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+    MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+    MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+    MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+    MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+    MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+    MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+    MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+    MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+    MovedAttribute("URLopener", "urllib", "urllib.request"),
+    MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+    MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+    MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
+    MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
+]
+for attr in _urllib_request_moved_attributes:
+    setattr(Module_six_moves_urllib_request, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+                      "moves.urllib_request", "moves.urllib.request")
+
+
+class Module_six_moves_urllib_response(_LazyModule):
+
+    """Lazy loading of moved objects in six.moves.urllib_response"""
+
+
+_urllib_response_moved_attributes = [
+    MovedAttribute("addbase", "urllib", "urllib.response"),
+    MovedAttribute("addclosehook", "urllib", "urllib.response"),
+    MovedAttribute("addinfo", "urllib", "urllib.response"),
+    MovedAttribute("addinfourl", "urllib", "urllib.response"),
+]
+for attr in _urllib_response_moved_attributes:
+    setattr(Module_six_moves_urllib_response, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+                      "moves.urllib_response", "moves.urllib.response")
+
+
+class Module_six_moves_urllib_robotparser(_LazyModule):
+
+    """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+
+
+_urllib_robotparser_moved_attributes = [
+    MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+]
+for attr in _urllib_robotparser_moved_attributes:
+    setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+                      "moves.urllib_robotparser", "moves.urllib.robotparser")
+
+
+class Module_six_moves_urllib(types.ModuleType):
+
+    """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+    __path__ = []  # mark as package
+    parse = _importer._get_module("moves.urllib_parse")
+    error = _importer._get_module("moves.urllib_error")
+    request = _importer._get_module("moves.urllib_request")
+    response = _importer._get_module("moves.urllib_response")
+    robotparser = _importer._get_module("moves.urllib_robotparser")
+
+    def __dir__(self):
+        return ['parse', 'error', 'request', 'response', 'robotparser']
+
+_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+                      "moves.urllib")
+
+
+def add_move(move):
+    """Add an item to six.moves."""
+    setattr(_MovedItems, move.name, move)
+
+
+def remove_move(name):
+    """Remove item from six.moves."""
+    try:
+        delattr(_MovedItems, name)
+    except AttributeError:
+        try:
+            del moves.__dict__[name]
+        except KeyError:
+            raise AttributeError("no such move, %r" % (name,))
+
+
+if PY3:
+    _meth_func = "__func__"
+    _meth_self = "__self__"
+
+    _func_closure = "__closure__"
+    _func_code = "__code__"
+    _func_defaults = "__defaults__"
+    _func_globals = "__globals__"
+else:
+    _meth_func = "im_func"
+    _meth_self = "im_self"
+
+    _func_closure = "func_closure"
+    _func_code = "func_code"
+    _func_defaults = "func_defaults"
+    _func_globals = "func_globals"
+
+
+try:
+    advance_iterator = next
+except NameError:
+    def advance_iterator(it):
+        return it.next()
+next = advance_iterator
+
+
+try:
+    callable = callable
+except NameError:
+    def callable(obj):
+        return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+
+
+if PY3:
+    def get_unbound_function(unbound):
+        return unbound
+
+    create_bound_method = types.MethodType
+
+    def create_unbound_method(func, cls):
+        return func
+
+    Iterator = object
+else:
+    def get_unbound_function(unbound):
+        return unbound.im_func
+
+    def create_bound_method(func, obj):
+        return types.MethodType(func, obj, obj.__class__)
+
+    def create_unbound_method(func, cls):
+        return types.MethodType(func, None, cls)
+
+    class Iterator(object):
+
+        def next(self):
+            return type(self).__next__(self)
+
+    callable = callable
+_add_doc(get_unbound_function,
+         """Get the function out of a possibly unbound function""")
+
+
+get_method_function = operator.attrgetter(_meth_func)
+get_method_self = operator.attrgetter(_meth_self)
+get_function_closure = operator.attrgetter(_func_closure)
+get_function_code = operator.attrgetter(_func_code)
+get_function_defaults = operator.attrgetter(_func_defaults)
+get_function_globals = operator.attrgetter(_func_globals)
+
+
+if PY3:
+    def iterkeys(d, **kw):
+        return iter(d.keys(**kw))
+
+    def itervalues(d, **kw):
+        return iter(d.values(**kw))
+
+    def iteritems(d, **kw):
+        return iter(d.items(**kw))
+
+    def iterlists(d, **kw):
+        return iter(d.lists(**kw))
+
+    viewkeys = operator.methodcaller("keys")
+
+    viewvalues = operator.methodcaller("values")
+
+    viewitems = operator.methodcaller("items")
+else:
+    def iterkeys(d, **kw):
+        return d.iterkeys(**kw)
+
+    def itervalues(d, **kw):
+        return d.itervalues(**kw)
+
+    def iteritems(d, **kw):
+        return d.iteritems(**kw)
+
+    def iterlists(d, **kw):
+        return d.iterlists(**kw)
+
+    viewkeys = operator.methodcaller("viewkeys")
+
+    viewvalues = operator.methodcaller("viewvalues")
+
+    viewitems = operator.methodcaller("viewitems")
+
+_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+_add_doc(iteritems,
+         "Return an iterator over the (key, value) pairs of a dictionary.")
+_add_doc(iterlists,
+         "Return an iterator over the (key, [values]) pairs of a dictionary.")
+
+
+if PY3:
+    def b(s):
+        return s.encode("latin-1")
+
+    def u(s):
+        return s
+    unichr = chr
+    import struct
+    int2byte = struct.Struct(">B").pack
+    del struct
+    byte2int = operator.itemgetter(0)
+    indexbytes = operator.getitem
+    iterbytes = iter
+    import io
+    StringIO = io.StringIO
+    BytesIO = io.BytesIO
+    _assertCountEqual = "assertCountEqual"
+    if sys.version_info[1] <= 1:
+        _assertRaisesRegex = "assertRaisesRegexp"
+        _assertRegex = "assertRegexpMatches"
+    else:
+        _assertRaisesRegex = "assertRaisesRegex"
+        _assertRegex = "assertRegex"
+else:
+    def b(s):
+        return s
+    # Workaround for standalone backslash
+
+    def u(s):
+        return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+    unichr = unichr
+    int2byte = chr
+
+    def byte2int(bs):
+        return ord(bs[0])
+
+    def indexbytes(buf, i):
+        return ord(buf[i])
+    iterbytes = functools.partial(itertools.imap, ord)
+    import StringIO
+    StringIO = BytesIO = StringIO.StringIO
+    _assertCountEqual = "assertItemsEqual"
+    _assertRaisesRegex = "assertRaisesRegexp"
+    _assertRegex = "assertRegexpMatches"
+_add_doc(b, """Byte literal""")
+_add_doc(u, """Text literal""")
+
+
+def assertCountEqual(self, *args, **kwargs):
+    return getattr(self, _assertCountEqual)(*args, **kwargs)
+
+
+def assertRaisesRegex(self, *args, **kwargs):
+    return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+
+
+def assertRegex(self, *args, **kwargs):
+    return getattr(self, _assertRegex)(*args, **kwargs)
+
+
+if PY3:
+    exec_ = getattr(moves.builtins, "exec")
+
+    def reraise(tp, value, tb=None):
+        try:
+            if value is None:
+                value = tp()
+            if value.__traceback__ is not tb:
+                raise value.with_traceback(tb)
+            raise value
+        finally:
+            value = None
+            tb = None
+
+else:
+    def exec_(_code_, _globs_=None, _locs_=None):
+        """Execute code in a namespace."""
+        if _globs_ is None:
+            frame = sys._getframe(1)
+            _globs_ = frame.f_globals
+            if _locs_ is None:
+                _locs_ = frame.f_locals
+            del frame
+        elif _locs_ is None:
+            _locs_ = _globs_
+        exec("""exec _code_ in _globs_, _locs_""")
+
+    exec_("""def reraise(tp, value, tb=None):
+    try:
+        raise tp, value, tb
+    finally:
+        tb = None
+""")
+
+
+if sys.version_info[:2] == (3, 2):
+    exec_("""def raise_from(value, from_value):
+    try:
+        if from_value is None:
+            raise value
+        raise value from from_value
+    finally:
+        value = None
+""")
+elif sys.version_info[:2] > (3, 2):
+    exec_("""def raise_from(value, from_value):
+    try:
+        raise value from from_value
+    finally:
+        value = None
+""")
+else:
+    def raise_from(value, from_value):
+        raise value
+
+
+print_ = getattr(moves.builtins, "print", None)
+if print_ is None:
+    def print_(*args, **kwargs):
+        """The new-style print function for Python 2.4 and 2.5."""
+        fp = kwargs.pop("file", sys.stdout)
+        if fp is None:
+            return
+
+        def write(data):
+            if not isinstance(data, basestring):
+                data = str(data)
+            # If the file has an encoding, encode unicode with it.
+            if (isinstance(fp, file) and
+                    isinstance(data, unicode) and
+                    fp.encoding is not None):
+                errors = getattr(fp, "errors", None)
+                if errors is None:
+                    errors = "strict"
+                data = data.encode(fp.encoding, errors)
+            fp.write(data)
+        want_unicode = False
+        sep = kwargs.pop("sep", None)
+        if sep is not None:
+            if isinstance(sep, unicode):
+                want_unicode = True
+            elif not isinstance(sep, str):
+                raise TypeError("sep must be None or a string")
+        end = kwargs.pop("end", None)
+        if end is not None:
+            if isinstance(end, unicode):
+                want_unicode = True
+            elif not isinstance(end, str):
+                raise TypeError("end must be None or a string")
+        if kwargs:
+            raise TypeError("invalid keyword arguments to print()")
+        if not want_unicode:
+            for arg in args:
+                if isinstance(arg, unicode):
+                    want_unicode = True
+                    break
+        if want_unicode:
+            newline = unicode("\n")
+            space = unicode(" ")
+        else:
+            newline = "\n"
+            space = " "
+        if sep is None:
+            sep = space
+        if end is None:
+            end = newline
+        for i, arg in enumerate(args):
+            if i:
+                write(sep)
+            write(arg)
+        write(end)
+if sys.version_info[:2] < (3, 3):
+    _print = print_
+
+    def print_(*args, **kwargs):
+        fp = kwargs.get("file", sys.stdout)
+        flush = kwargs.pop("flush", False)
+        _print(*args, **kwargs)
+        if flush and fp is not None:
+            fp.flush()
+
+_add_doc(reraise, """Reraise an exception.""")
+
+if sys.version_info[0:2] < (3, 4):
+    def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+              updated=functools.WRAPPER_UPDATES):
+        def wrapper(f):
+            f = functools.wraps(wrapped, assigned, updated)(f)
+            f.__wrapped__ = wrapped
+            return f
+        return wrapper
+else:
+    wraps = functools.wraps
+
+
+def with_metaclass(meta, *bases):
+    """Create a base class with a metaclass."""
+    # This requires a bit of explanation: the basic idea is to make a dummy
+    # metaclass for one level of class instantiation that replaces itself with
+    # the actual metaclass.
+    class metaclass(type):
+
+        def __new__(cls, name, this_bases, d):
+            return meta(name, bases, d)
+
+        @classmethod
+        def __prepare__(cls, name, this_bases):
+            return meta.__prepare__(name, bases)
+    return type.__new__(metaclass, 'temporary_class', (), {})
+
+
+def add_metaclass(metaclass):
+    """Class decorator for creating a class with a metaclass."""
+    def wrapper(cls):
+        orig_vars = cls.__dict__.copy()
+        slots = orig_vars.get('__slots__')
+        if slots is not None:
+            if isinstance(slots, str):
+                slots = [slots]
+            for slots_var in slots:
+                orig_vars.pop(slots_var)
+        orig_vars.pop('__dict__', None)
+        orig_vars.pop('__weakref__', None)
+        return metaclass(cls.__name__, cls.__bases__, orig_vars)
+    return wrapper
+
+
+def python_2_unicode_compatible(klass):
+    """
+    A decorator that defines __unicode__ and __str__ methods under Python 2.
+    Under Python 3 it does nothing.
+
+    To support Python 2 and 3 with a single code base, define a __str__ method
+    returning text and apply this decorator to the class.
+    """
+    if PY2:
+        if '__str__' not in klass.__dict__:
+            raise ValueError("@python_2_unicode_compatible cannot be applied "
+                             "to %s because it doesn't define __str__()." %
+                             klass.__name__)
+        klass.__unicode__ = klass.__str__
+        klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+    return klass
+
+
+# Complete the moves implementation.
+# This code is at the end of this module to speed up module loading.
+# Turn this module into a package.
+__path__ = []  # required for PEP 302 and PEP 451
+__package__ = __name__  # see PEP 366 @ReservedAssignment
+if globals().get("__spec__") is not None:
+    __spec__.submodule_search_locations = []  # PEP 451 @UndefinedVariable
+# Remove other six meta path importers, since they cause problems. This can
+# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+# this for some reason.)
+if sys.meta_path:
+    for i, importer in enumerate(sys.meta_path):
+        # Here's some real nastiness: Another "instance" of the six module might
+        # be floating around. Therefore, we can't use isinstance() to check for
+        # the six meta path importer, since the other six instance will have
+        # inserted an importer with different class.
+        if (type(importer).__name__ == "_SixMetaPathImporter" and
+                importer.name == __name__):
+            del sys.meta_path[i]
+            break
+    del i, importer
+# Finally, add the importer to the meta path import hook.
+sys.meta_path.append(_importer)
diff --git a/resources/lib/libraries/dateutil/test/__init__.py b/resources/lib/libraries/dateutil/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/resources/lib/libraries/dateutil/test/_common.py b/resources/lib/libraries/dateutil/test/_common.py
new file mode 100644
index 00000000..264dfbda
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/_common.py
@@ -0,0 +1,275 @@
+from __future__ import unicode_literals
+import os
+import time
+import subprocess
+import warnings
+import tempfile
+import pickle
+
+
+class WarningTestMixin(object):
+    # Based on https://stackoverflow.com/a/12935176/467366
+    class _AssertWarnsContext(warnings.catch_warnings):
+        def __init__(self, expected_warnings, parent, **kwargs):
+            super(WarningTestMixin._AssertWarnsContext, self).__init__(**kwargs)
+
+            self.parent = parent
+            try:
+                self.expected_warnings = list(expected_warnings)
+            except TypeError:
+                self.expected_warnings = [expected_warnings]
+
+            self._warning_log = []
+
+        def __enter__(self, *args, **kwargs):
+            rv = super(WarningTestMixin._AssertWarnsContext, self).__enter__(*args, **kwargs)
+
+            if self._showwarning is not self._module.showwarning:
+                super_showwarning = self._module.showwarning
+            else:
+                super_showwarning = None
+
+            def showwarning(*args, **kwargs):
+                if super_showwarning is not None:
+                    super_showwarning(*args, **kwargs)
+
+                self._warning_log.append(warnings.WarningMessage(*args, **kwargs))
+
+            self._module.showwarning = showwarning
+            return rv
+
+        def __exit__(self, *args, **kwargs):
+            super(WarningTestMixin._AssertWarnsContext, self).__exit__(self, *args, **kwargs)
+
+            self.parent.assertTrue(any(issubclass(item.category, warning)
+                                       for warning in self.expected_warnings
+                                       for item in self._warning_log))
+
+    def assertWarns(self, warning, callable=None, *args, **kwargs):
+        warnings.simplefilter('always')
+        context = self.__class__._AssertWarnsContext(warning, self)
+        if callable is None:
+            return context
+        else:
+            with context:
+                callable(*args, **kwargs)
+
+
+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():
+            raise ValueError(self.tz_change_disallowed_message())
+
+        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/resources/lib/libraries/dateutil/test/property/test_isoparse_prop.py b/resources/lib/libraries/dateutil/test/property/test_isoparse_prop.py
new file mode 100644
index 00000000..c6a4b82a
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/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.tzutc()] +
+    [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/resources/lib/libraries/dateutil/test/property/test_parser_prop.py b/resources/lib/libraries/dateutil/test/property/test_parser_prop.py
new file mode 100644
index 00000000..fdfd171e
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/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/resources/lib/libraries/dateutil/test/test_easter.py b/resources/lib/libraries/dateutil/test/test_easter.py
new file mode 100644
index 00000000..eeb094ee
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_easter.py
@@ -0,0 +1,95 @@
+from dateutil.easter import easter
+from dateutil.easter import EASTER_WESTERN, EASTER_ORTHODOX, EASTER_JULIAN
+
+from datetime import date
+import unittest
+
+# 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)
+]
+
+
+class EasterTest(unittest.TestCase):
+    def testEasterWestern(self):
+        for easter_date in western_easter_dates:
+            self.assertEqual(easter_date,
+                             easter(easter_date.year, EASTER_WESTERN))
+
+    def testEasterOrthodox(self):
+        for easter_date in orthodox_easter_dates:
+            self.assertEqual(easter_date,
+                             easter(easter_date.year, EASTER_ORTHODOX))
+
+    def testEasterJulian(self):
+        for easter_date in julian_easter_dates:
+            self.assertEqual(easter_date,
+                             easter(easter_date.year, EASTER_JULIAN))
+
+    def testEasterBadMethod(self):
+        # Invalid methods raise ValueError
+        with self.assertRaises(ValueError):
+            easter(1975, 4)
diff --git a/resources/lib/libraries/dateutil/test/test_import_star.py b/resources/lib/libraries/dateutil/test/test_import_star.py
new file mode 100644
index 00000000..8e66f38a
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_import_star.py
@@ -0,0 +1,33 @@
+"""Test for the "import *" functionality.
+
+As imort * can be only done at module level, it has been added in a separate file
+"""
+import unittest
+
+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')
+
+class ImportStarTest(unittest.TestCase):
+    """ Test that `from dateutil import *` adds the modules in __all__ locally"""
+
+    def testImportedModules(self):
+        import dateutil.easter
+        import dateutil.parser
+        import dateutil.relativedelta
+        import dateutil.rrule
+        import dateutil.tz
+        import dateutil.utils
+        import dateutil.zoneinfo
+
+        self.assertEquals(dateutil.easter, new_locals.pop("easter"))
+        self.assertEquals(dateutil.parser, new_locals.pop("parser"))
+        self.assertEquals(dateutil.relativedelta, new_locals.pop("relativedelta"))
+        self.assertEquals(dateutil.rrule, new_locals.pop("rrule"))
+        self.assertEquals(dateutil.tz, new_locals.pop("tz"))
+        self.assertEquals(dateutil.utils, new_locals.pop("utils"))
+        self.assertEquals(dateutil.zoneinfo, new_locals.pop("zoneinfo"))
+
+        self.assertFalse(new_locals)
diff --git a/resources/lib/libraries/dateutil/test/test_imports.py b/resources/lib/libraries/dateutil/test/test_imports.py
new file mode 100644
index 00000000..2a19b62a
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_imports.py
@@ -0,0 +1,166 @@
+import sys
+import unittest
+
+class ImportVersionTest(unittest.TestCase):
+    """ Test that dateutil.__version__ can be imported"""
+
+    def testImportVersionStr(self):
+        from dateutil import __version__
+
+    def testImportRoot(self):
+        import dateutil
+
+        self.assertTrue(hasattr(dateutil, '__version__'))
+
+
+class ImportEasterTest(unittest.TestCase):
+    """ Test that dateutil.easter-related imports work properly """
+
+    def testEasterDirect(self):
+        import dateutil.easter
+
+    def testEasterFrom(self):
+        from dateutil import easter
+
+    def testEasterStar(self):
+        from dateutil.easter import easter
+
+
+class ImportParserTest(unittest.TestCase):
+    """ Test that dateutil.parser-related imports work properly """
+    def testParserDirect(self):
+        import dateutil.parser
+
+    def testParserFrom(self):
+        from dateutil import parser
+
+    def testParserAll(self):
+        # 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):
+            self.assertIsNot(var, None)
+
+
+class ImportRelativeDeltaTest(unittest.TestCase):
+    """ Test that dateutil.relativedelta-related imports work properly """
+    def testRelativeDeltaDirect(self):
+        import dateutil.relativedelta
+
+    def testRelativeDeltaFrom(self):
+        from dateutil import relativedelta
+
+    def testRelativeDeltaAll(self):
+        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):
+            self.assertIsNot(var, None)
+
+        # In the public interface but not in all
+        from dateutil.relativedelta import weekday
+        self.assertIsNot(weekday, None)
+
+
+class ImportRRuleTest(unittest.TestCase):
+    """ Test that dateutil.rrule related imports work properly """
+    def testRRuleDirect(self):
+        import dateutil.rrule
+
+    def testRRuleFrom(self):
+        from dateutil import rrule
+
+    def testRRuleAll(self):
+        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:
+            self.assertIsNot(var, None)
+
+        # In the public interface but not in all
+        from dateutil.rrule import weekday
+        self.assertIsNot(weekday, None)
+
+
+class ImportTZTest(unittest.TestCase):
+    """ Test that dateutil.tz related imports work properly """
+    def testTzDirect(self):
+        import dateutil.tz
+
+    def testTzFrom(self):
+        from dateutil import tz
+
+    def testTzAll(self):
+        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:
+            self.assertIsNot(lvars[var], None)
+
+@unittest.skipUnless(sys.platform.startswith('win'), "Requires Windows")
+class ImportTZWinTest(unittest.TestCase):
+    """ Test that dateutil.tzwin related imports work properly """
+    def testTzwinDirect(self):
+        import dateutil.tzwin
+
+    def testTzwinFrom(self):
+        from dateutil import tzwin
+
+    def testTzwinStar(self):
+        from dateutil.tzwin import tzwin
+        from dateutil.tzwin import tzwinlocal
+
+        tzwin_all = [tzwin, tzwinlocal]
+
+        for var in tzwin_all:
+            self.assertIsNot(var, None)
+
+
+class ImportZoneInfoTest(unittest.TestCase):
+    def testZoneinfoDirect(self):
+        import dateutil.zoneinfo
+
+    def testZoneinfoFrom(self):
+        from dateutil import zoneinfo
+
+    def testZoneinfoStar(self):
+        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:
+            self.assertIsNot(var, None)
diff --git a/resources/lib/libraries/dateutil/test/test_internals.py b/resources/lib/libraries/dateutil/test/test_internals.py
new file mode 100644
index 00000000..a64c5148
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_internals.py
@@ -0,0 +1,95 @@
+# -*- 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 unittest
+import sys
+
+import pytest
+
+from dateutil.parser._parser import _ymd
+from dateutil import tz
+
+IS_PY32 = sys.version_info[0:2] == (3, 2)
+
+
+class TestYMD(unittest.TestCase):
+
+    # @pytest.mark.smoke
+    def test_could_be_day(self):
+        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 leapyear
+        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 pytest.warns(None) as recorder:
+        _tzparser()
+        assert len(recorder) == 0
+
+    with pytest.warns(None) as recorder:
+        _timelex('2014-03-03')
+
+        assert len(recorder) == 0
+
+    with pytest.warns(None) as recorder:
+        _parsetz('+05:00')
+        assert len(recorder) == 0
+
+
+@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/resources/lib/libraries/dateutil/test/test_isoparser.py b/resources/lib/libraries/dateutil/test/test_isoparser.py
new file mode 100644
index 00000000..28c1bf76
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_isoparser.py
@@ -0,0 +1,482 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from datetime import datetime, timedelta, date, time
+import itertools as it
+
+from dateutil.tz import tz
+from dateutil.parser import isoparser, isoparse
+
+import pytest
+import six
+
+UTC = tz.tzutc()
+
+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((tz.tzutc(), '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'):
+            raise ValueError('Time format has no microseconds!')
+
+        if microsecond_precision != 6:
+            dtstr = dtstr[:-(6 - microsecond_precision)]
+        elif microsecond_precision > 6:
+            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 + '.%f' for x in HMS_FMTS))
+@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)
+
+@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-11T24',
+    '2014-04-11T00:00',
+    '2014-04-11T24:00',
+    '2014-04-11T00:00:00',
+    '2014-04-11T24:00:00',
+    '2014-04-11T00:00:00.000',
+    '2014-04-11T24:00:00.000',
+    '2014-04-11T00:00:00.000000',
+    '2014-04-11T24: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,
+                                           tz.tzutc())),
+    (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
+    ('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', [
+    ('C', 'T'),
+    ('T', 'C')
+])
+def test_iso_raises_sep(sep_act, valid_sep):
+    isostr = '2012-04-25' + sep_act + '01:25:00'
+
+
+@pytest.mark.xfail()
+@pytest.mark.parametrize('isostr,exception', [
+    ('20120425T01:2000', ValueError),           # Inconsistent time separators
+])
+def test_iso_raises_failing(isostr, exception):
+    # These are test cases where the current implementation is too lenient
+    # and need to be fixed
+    with pytest.raises(exception):
+        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(six.PY3, 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 == tz.tzutc()
+    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 six.PY3:
+        # 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, six.binary_type) 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
+])
+def test_isodate_raises(isostr, exception):
+    with pytest.raises(exception):
+        isoparser().parse_isodate(isostr)
+
+
+###
+# 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(time_val, six.text_type) and as_bytes:
+        tstr = tstr.encode('ascii')
+    elif isinstance(time_val, six.binary_type) and not as_bytes:
+        tstr = tstr.decode('ascii')
+
+    iparser = isoparser()
+
+    assert iparser.parse_isotime(tstr) == time_val
+
+@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
+    ('14:30:15.3684000309', ValueError),        # Too much us precision
+    ('25', ValueError),                         # Invalid hours
+    ('25:15', ValueError),                      # Invalid hours
+    ('14:60', ValueError),                      # Invalid minutes
+    ('14:59:61', ValueError),                   # Invalid seconds
+    ('14:30:15.3446830500', 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
+])
+def test_isotime_raises(isostr, exception):
+    iparser = isoparser()
+    with pytest.raises(exception):
+        iparser.parse_isotime(isostr)
+
+
+@pytest.mark.xfail()
+@pytest.mark.parametrize('isostr,exception', [
+    ('14:3015', ValueError),                    # Inconsistent separator use
+    ('201202', ValueError)                      # Invalid ISO format
+])
+def test_isotime_raises_xfail(isostr, exception):
+    iparser = isoparser()
+    with pytest.raises(exception):
+        iparser.parse_isotime(isostr)
diff --git a/resources/lib/libraries/dateutil/test/test_parser.py b/resources/lib/libraries/dateutil/test/test_parser.py
new file mode 100644
index 00000000..f8c20720
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_parser.py
@@ -0,0 +1,1114 @@
+# -*- 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 UnknownTimezoneWarning
+
+from ._common import TZEnvContext
+
+from six import assertRaisesRegex, PY3
+from six.moves import StringIO
+
+import pytest
+
+# Platform info
+IS_WIN = sys.platform.startswith('win')
+
+try:
+    datetime.now().strftime('%-d')
+    PLATFORM_HAS_DASH_D = True
+except ValueError:
+    PLATFORM_HAS_DASH_D = False
+
+
+class TestFormat(unittest.TestCase):
+
+    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)
+            self.assertEqual(res, actual)
+
+
+class ParserTest(unittest.TestCase):
+
+    def setUp(self):
+        self.tzinfos = {"BRST": -10800}
+        self.brsttz = tzoffset("BRST", -10800)
+        self.default = datetime(2003, 9, 25)
+
+        # Parser should be able to handle bytestring and unicode
+        self.uni_str = '2014-05-01 08:00:00'
+        self.str_str = self.uni_str.encode()
+
+    def testEmptyString(self):
+        with self.assertRaises(ValueError):
+            parse('')
+
+    def testNone(self):
+        with self.assertRaises(TypeError):
+            parse(None)
+
+    def testInvalidType(self):
+        with self.assertRaises(TypeError):
+            parse(13)
+
+    def testDuckTyping(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'))
+
+        self.assertEqual(parse(dstr), datetime(2014, 1, 19))
+
+    def testParseStream(self):
+        dstr = StringIO('2014 January 19')
+
+        self.assertEqual(parse(dstr), datetime(2014, 1, 19))
+
+    def testParseStr(self):
+        self.assertEqual(parse(self.str_str),
+                         parse(self.uni_str))
+
+    def testParseBytes(self):
+        self.assertEqual(parse(b'2014 January 19'), datetime(2014, 1, 19))
+
+    def testParseBytearray(self):
+        # GH #417
+        self.assertEqual(parse(bytearray(b'2014 January 19')),
+                         datetime(2014, 1, 19))
+
+    def testParserParseStr(self):
+        from dateutil.parser import parser
+
+        self.assertEqual(parser().parse(self.str_str),
+                         parser().parse(self.uni_str))
+
+    def testParseUnicodeWords(self):
+
+        class rus_parserinfo(parserinfo):
+            MONTHS = [("янв", "Январь"),
+                      ("фев", "Февраль"),
+                      ("мар", "Март"),
+                      ("апр", "Апрель"),
+                      ("май", "Май"),
+                      ("июн", "Июнь"),
+                      ("июл", "Июль"),
+                      ("авг", "Август"),
+                      ("сен", "Сентябрь"),
+                      ("окт", "Октябрь"),
+                      ("ноя", "Ноябрь"),
+                      ("дек", "Декабрь")]
+
+        self.assertEqual(parse('10 Сентябрь 2015 10:20',
+                               parserinfo=rus_parserinfo()),
+                         datetime(2015, 9, 10, 10, 20))
+
+    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'
+
+        self.assertEqual(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 testDateCommandFormatUnicode(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 not PY3:
+            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 testDateCommandFormatIgnoreTz(self):
+        self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003",
+                               ignoretz=True),
+                         datetime(2003, 9, 25, 10, 36, 28))
+
+    def testDateCommandFormatStrip1(self):
+        self.assertEqual(parse("Thu Sep 25 10:36:28 2003"),
+                         datetime(2003, 9, 25, 10, 36, 28))
+
+    def testDateCommandFormatStrip2(self):
+        self.assertEqual(parse("Thu Sep 25 10:36:28", default=self.default),
+                         datetime(2003, 9, 25, 10, 36, 28))
+
+    def testDateCommandFormatStrip3(self):
+        self.assertEqual(parse("Thu Sep 10:36:28", default=self.default),
+                         datetime(2003, 9, 25, 10, 36, 28))
+
+    def testDateCommandFormatStrip4(self):
+        self.assertEqual(parse("Thu 10:36:28", default=self.default),
+                         datetime(2003, 9, 25, 10, 36, 28))
+
+    def testDateCommandFormatStrip5(self):
+        self.assertEqual(parse("Sep 10:36:28", default=self.default),
+                         datetime(2003, 9, 25, 10, 36, 28))
+
+    def testDateCommandFormatStrip6(self):
+        self.assertEqual(parse("10:36:28", default=self.default),
+                         datetime(2003, 9, 25, 10, 36, 28))
+
+    def testDateCommandFormatStrip7(self):
+        self.assertEqual(parse("10:36", default=self.default),
+                         datetime(2003, 9, 25, 10, 36))
+
+    def testDateCommandFormatStrip8(self):
+        self.assertEqual(parse("Thu Sep 25 2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateCommandFormatStrip10(self):
+        self.assertEqual(parse("Sep 2003", default=self.default),
+                         datetime(2003, 9, 25))
+
+    def testDateCommandFormatStrip11(self):
+        self.assertEqual(parse("Sep", default=self.default),
+                         datetime(2003, 9, 25))
+
+    def testDateCommandFormatStrip12(self):
+        self.assertEqual(parse("2003", default=self.default),
+                         datetime(2003, 9, 25))
+
+    def testDateRCommandFormat(self):
+        self.assertEqual(parse("Thu, 25 Sep 2003 10:49:41 -0300"),
+                         datetime(2003, 9, 25, 10, 49, 41,
+                                  tzinfo=self.brsttz))
+
+    def testISOFormat(self):
+        self.assertEqual(parse("2003-09-25T10:49:41.5-03:00"),
+                         datetime(2003, 9, 25, 10, 49, 41, 500000,
+                                  tzinfo=self.brsttz))
+
+    def testISOFormatStrip1(self):
+        self.assertEqual(parse("2003-09-25T10:49:41-03:00"),
+                         datetime(2003, 9, 25, 10, 49, 41,
+                                  tzinfo=self.brsttz))
+
+    def testISOFormatStrip2(self):
+        self.assertEqual(parse("2003-09-25T10:49:41"),
+                         datetime(2003, 9, 25, 10, 49, 41))
+
+    def testISOFormatStrip3(self):
+        self.assertEqual(parse("2003-09-25T10:49"),
+                         datetime(2003, 9, 25, 10, 49))
+
+    def testISOFormatStrip4(self):
+        self.assertEqual(parse("2003-09-25T10"),
+                         datetime(2003, 9, 25, 10))
+
+    def testISOFormatStrip5(self):
+        self.assertEqual(parse("2003-09-25"),
+                         datetime(2003, 9, 25))
+
+    def testISOStrippedFormat(self):
+        self.assertEqual(parse("20030925T104941.5-0300"),
+                         datetime(2003, 9, 25, 10, 49, 41, 500000,
+                                  tzinfo=self.brsttz))
+
+    def testISOStrippedFormatStrip1(self):
+        self.assertEqual(parse("20030925T104941-0300"),
+                         datetime(2003, 9, 25, 10, 49, 41,
+                                  tzinfo=self.brsttz))
+
+    def testISOStrippedFormatStrip2(self):
+        self.assertEqual(parse("20030925T104941"),
+                         datetime(2003, 9, 25, 10, 49, 41))
+
+    def testISOStrippedFormatStrip3(self):
+        self.assertEqual(parse("20030925T1049"),
+                         datetime(2003, 9, 25, 10, 49, 0))
+
+    def testISOStrippedFormatStrip4(self):
+        self.assertEqual(parse("20030925T10"),
+                         datetime(2003, 9, 25, 10))
+
+    def testISOStrippedFormatStrip5(self):
+        self.assertEqual(parse("20030925"),
+                         datetime(2003, 9, 25))
+
+    def testPythonLoggerFormat(self):
+        self.assertEqual(parse("2003-09-25 10:49:41,502"),
+                         datetime(2003, 9, 25, 10, 49, 41, 502000))
+
+    def testNoSeparator1(self):
+        self.assertEqual(parse("199709020908"),
+                         datetime(1997, 9, 2, 9, 8))
+
+    def testNoSeparator2(self):
+        self.assertEqual(parse("19970902090807"),
+                         datetime(1997, 9, 2, 9, 8, 7))
+
+    def testDateWithDash1(self):
+        self.assertEqual(parse("2003-09-25"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDash6(self):
+        self.assertEqual(parse("09-25-2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDash7(self):
+        self.assertEqual(parse("25-09-2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDash8(self):
+        self.assertEqual(parse("10-09-2003", dayfirst=True),
+                         datetime(2003, 9, 10))
+
+    def testDateWithDash9(self):
+        self.assertEqual(parse("10-09-2003"),
+                         datetime(2003, 10, 9))
+
+    def testDateWithDash10(self):
+        self.assertEqual(parse("10-09-03"),
+                         datetime(2003, 10, 9))
+
+    def testDateWithDash11(self):
+        self.assertEqual(parse("10-09-03", yearfirst=True),
+                         datetime(2010, 9, 3))
+
+    def testDateWithDot1(self):
+        self.assertEqual(parse("2003.09.25"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDot6(self):
+        self.assertEqual(parse("09.25.2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDot7(self):
+        self.assertEqual(parse("25.09.2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDot8(self):
+        self.assertEqual(parse("10.09.2003", dayfirst=True),
+                         datetime(2003, 9, 10))
+
+    def testDateWithDot9(self):
+        self.assertEqual(parse("10.09.2003"),
+                         datetime(2003, 10, 9))
+
+    def testDateWithDot10(self):
+        self.assertEqual(parse("10.09.03"),
+                         datetime(2003, 10, 9))
+
+    def testDateWithDot11(self):
+        self.assertEqual(parse("10.09.03", yearfirst=True),
+                         datetime(2010, 9, 3))
+
+    def testDateWithSlash1(self):
+        self.assertEqual(parse("2003/09/25"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSlash6(self):
+        self.assertEqual(parse("09/25/2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSlash7(self):
+        self.assertEqual(parse("25/09/2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSlash8(self):
+        self.assertEqual(parse("10/09/2003", dayfirst=True),
+                         datetime(2003, 9, 10))
+
+    def testDateWithSlash9(self):
+        self.assertEqual(parse("10/09/2003"),
+                         datetime(2003, 10, 9))
+
+    def testDateWithSlash10(self):
+        self.assertEqual(parse("10/09/03"),
+                         datetime(2003, 10, 9))
+
+    def testDateWithSlash11(self):
+        self.assertEqual(parse("10/09/03", yearfirst=True),
+                         datetime(2010, 9, 3))
+
+    def testDateWithSpace1(self):
+        self.assertEqual(parse("2003 09 25"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSpace6(self):
+        self.assertEqual(parse("09 25 2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSpace7(self):
+        self.assertEqual(parse("25 09 2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSpace8(self):
+        self.assertEqual(parse("10 09 2003", dayfirst=True),
+                         datetime(2003, 9, 10))
+
+    def testDateWithSpace9(self):
+        self.assertEqual(parse("10 09 2003"),
+                         datetime(2003, 10, 9))
+
+    def testDateWithSpace10(self):
+        self.assertEqual(parse("10 09 03"),
+                         datetime(2003, 10, 9))
+
+    def testDateWithSpace11(self):
+        self.assertEqual(parse("10 09 03", yearfirst=True),
+                         datetime(2010, 9, 3))
+
+    def testDateWithSpace12(self):
+        self.assertEqual(parse("25 09 03"),
+                         datetime(2003, 9, 25))
+
+    def testStrangelyOrderedDate1(self):
+        self.assertEqual(parse("03 25 Sep"),
+                         datetime(2003, 9, 25))
+
+    def testStrangelyOrderedDate3(self):
+        self.assertEqual(parse("25 03 Sep"),
+                         datetime(2025, 9, 3))
+
+    def testHourWithLetters(self):
+        self.assertEqual(parse("10h36m28.5s", default=self.default),
+                         datetime(2003, 9, 25, 10, 36, 28, 500000))
+
+    def testHourWithLettersStrip1(self):
+        self.assertEqual(parse("10h36m28s", default=self.default),
+                         datetime(2003, 9, 25, 10, 36, 28))
+
+    def testHourWithLettersStrip2(self):
+        self.assertEqual(parse("10h36m", default=self.default),
+                         datetime(2003, 9, 25, 10, 36))
+
+    def testHourWithLettersStrip3(self):
+        self.assertEqual(parse("10h", default=self.default),
+                         datetime(2003, 9, 25, 10))
+
+    def testHourWithLettersStrip4(self):
+        self.assertEqual(parse("10 h 36", default=self.default),
+                         datetime(2003, 9, 25, 10, 36))
+
+    def testHourWithLetterStrip5(self):
+        self.assertEqual(parse("10 h 36.5", default=self.default),
+                         datetime(2003, 9, 25, 10, 36, 30))
+
+    def testMinuteWithLettersSpaces1(self):
+        self.assertEqual(parse("36 m 5", default=self.default),
+                         datetime(2003, 9, 25, 0, 36, 5))
+
+    def testMinuteWithLettersSpaces2(self):
+        self.assertEqual(parse("36 m 5 s", default=self.default),
+                         datetime(2003, 9, 25, 0, 36, 5))
+
+    def testMinuteWithLettersSpaces3(self):
+        self.assertEqual(parse("36 m 05", default=self.default),
+                         datetime(2003, 9, 25, 0, 36, 5))
+
+    def testMinuteWithLettersSpaces4(self):
+        self.assertEqual(parse("36 m 05 s", default=self.default),
+                         datetime(2003, 9, 25, 0, 36, 5))
+
+    def testAMPMNoHour(self):
+        with self.assertRaises(ValueError):
+            parse("AM")
+
+        with self.assertRaises(ValueError):
+            parse("Jan 20, 2015 PM")
+
+    def testHourAmPm1(self):
+        self.assertEqual(parse("10h am", default=self.default),
+                         datetime(2003, 9, 25, 10))
+
+    def testHourAmPm2(self):
+        self.assertEqual(parse("10h pm", default=self.default),
+                         datetime(2003, 9, 25, 22))
+
+    def testHourAmPm3(self):
+        self.assertEqual(parse("10am", default=self.default),
+                         datetime(2003, 9, 25, 10))
+
+    def testHourAmPm4(self):
+        self.assertEqual(parse("10pm", default=self.default),
+                         datetime(2003, 9, 25, 22))
+
+    def testHourAmPm5(self):
+        self.assertEqual(parse("10:00 am", default=self.default),
+                         datetime(2003, 9, 25, 10))
+
+    def testHourAmPm6(self):
+        self.assertEqual(parse("10:00 pm", default=self.default),
+                         datetime(2003, 9, 25, 22))
+
+    def testHourAmPm7(self):
+        self.assertEqual(parse("10:00am", default=self.default),
+                         datetime(2003, 9, 25, 10))
+
+    def testHourAmPm8(self):
+        self.assertEqual(parse("10:00pm", default=self.default),
+                         datetime(2003, 9, 25, 22))
+
+    def testHourAmPm9(self):
+        self.assertEqual(parse("10:00a.m", default=self.default),
+                         datetime(2003, 9, 25, 10))
+
+    def testHourAmPm10(self):
+        self.assertEqual(parse("10:00p.m", default=self.default),
+                         datetime(2003, 9, 25, 22))
+
+    def testHourAmPm11(self):
+        self.assertEqual(parse("10:00a.m.", default=self.default),
+                         datetime(2003, 9, 25, 10))
+
+    def testHourAmPm12(self):
+        self.assertEqual(parse("10:00p.m.", default=self.default),
+                         datetime(2003, 9, 25, 22))
+
+    def testAMPMRange(self):
+        with self.assertRaises(ValueError):
+            parse("13:44 AM")
+
+        with self.assertRaises(ValueError):
+            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 testWeekdayAlone(self):
+        self.assertEqual(parse("Wed", default=self.default),
+                         datetime(2003, 10, 1))
+
+    def testLongWeekday(self):
+        self.assertEqual(parse("Wednesday", default=self.default),
+                         datetime(2003, 10, 1))
+
+    def testLongMonth(self):
+        self.assertEqual(parse("October", default=self.default),
+                         datetime(2003, 10, 25))
+
+    def testZeroYear(self):
+        self.assertEqual(parse("31-Dec-00", default=self.default),
+                         datetime(2000, 12, 31))
+
+    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 testExtraSpace(self):
+        self.assertEqual(parse("  July   4 ,  1976   12:01:02   am  "),
+                         datetime(1976, 7, 4, 0, 1, 2))
+
+    def testRandomFormat1(self):
+        self.assertEqual(parse("Wed, July 10, '96"),
+                         datetime(1996, 7, 10, 0, 0))
+
+    def testRandomFormat2(self):
+        self.assertEqual(parse("1996.07.10 AD at 15:08:56 PDT",
+                               ignoretz=True),
+                         datetime(1996, 7, 10, 15, 8, 56))
+
+    def testRandomFormat3(self):
+        self.assertEqual(parse("1996.July.10 AD 12:08 PM"),
+                         datetime(1996, 7, 10, 12, 8))
+
+    def testRandomFormat4(self):
+        self.assertEqual(parse("Tuesday, April 12, 1952 AD 3:30:42pm PST",
+                               ignoretz=True),
+                         datetime(1952, 4, 12, 15, 30, 42))
+
+    def testRandomFormat5(self):
+        self.assertEqual(parse("November 5, 1994, 8:15:30 am EST",
+                               ignoretz=True),
+                         datetime(1994, 11, 5, 8, 15, 30))
+
+    def testRandomFormat6(self):
+        self.assertEqual(parse("1994-11-05T08:15:30-05:00",
+                               ignoretz=True),
+                         datetime(1994, 11, 5, 8, 15, 30))
+
+    def testRandomFormat7(self):
+        self.assertEqual(parse("1994-11-05T08:15:30Z",
+                               ignoretz=True),
+                         datetime(1994, 11, 5, 8, 15, 30))
+
+    def testRandomFormat8(self):
+        self.assertEqual(parse("July 4, 1976"), datetime(1976, 7, 4))
+
+    def testRandomFormat9(self):
+        self.assertEqual(parse("7 4 1976"), datetime(1976, 7, 4))
+
+    def testRandomFormat10(self):
+        self.assertEqual(parse("4 jul 1976"), datetime(1976, 7, 4))
+
+    def testRandomFormat11(self):
+        self.assertEqual(parse("7-4-76"), datetime(1976, 7, 4))
+
+    def testRandomFormat12(self):
+        self.assertEqual(parse("19760704"), datetime(1976, 7, 4))
+
+    def testRandomFormat13(self):
+        self.assertEqual(parse("0:01:02", default=self.default),
+                         datetime(2003, 9, 25, 0, 1, 2))
+
+    def testRandomFormat14(self):
+        self.assertEqual(parse("12h 01m02s am", default=self.default),
+                         datetime(2003, 9, 25, 0, 1, 2))
+
+    def testRandomFormat15(self):
+        self.assertEqual(parse("0:01:02 on July 4, 1976"),
+                         datetime(1976, 7, 4, 0, 1, 2))
+
+    def testRandomFormat16(self):
+        self.assertEqual(parse("0:01:02 on July 4, 1976"),
+                         datetime(1976, 7, 4, 0, 1, 2))
+
+    def testRandomFormat17(self):
+        self.assertEqual(parse("1976-07-04T00:01:02Z", ignoretz=True),
+                         datetime(1976, 7, 4, 0, 1, 2))
+
+    def testRandomFormat18(self):
+        self.assertEqual(parse("July 4, 1976 12:01:02 am"),
+                         datetime(1976, 7, 4, 0, 1, 2))
+
+    def testRandomFormat19(self):
+        self.assertEqual(parse("Mon Jan  2 04:24:27 1995"),
+                         datetime(1995, 1, 2, 4, 24, 27))
+
+    def testRandomFormat20(self):
+        self.assertEqual(parse("Tue Apr 4 00:22:12 PDT 1995", ignoretz=True),
+                         datetime(1995, 4, 4, 0, 22, 12))
+
+    def testRandomFormat21(self):
+        self.assertEqual(parse("04.04.95 00:22"),
+                         datetime(1995, 4, 4, 0, 22))
+
+    def testRandomFormat22(self):
+        self.assertEqual(parse("Jan 1 1999 11:23:34.578"),
+                         datetime(1999, 1, 1, 11, 23, 34, 578000))
+
+    def testRandomFormat23(self):
+        self.assertEqual(parse("950404 122212"),
+                         datetime(1995, 4, 4, 12, 22, 12))
+
+    def testRandomFormat24(self):
+        self.assertEqual(parse("0:00 PM, PST", default=self.default,
+                               ignoretz=True),
+                         datetime(2003, 9, 25, 12, 0))
+
+    def testRandomFormat25(self):
+        self.assertEqual(parse("12:08 PM", default=self.default),
+                         datetime(2003, 9, 25, 12, 8))
+
+    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 testRandomFormat27(self):
+        self.assertEqual(parse("3rd of May 2001"), datetime(2001, 5, 3))
+
+    def testRandomFormat28(self):
+        self.assertEqual(parse("5th of March 2001"), datetime(2001, 3, 5))
+
+    def testRandomFormat29(self):
+        self.assertEqual(parse("1st of May 2003"), datetime(2003, 5, 1))
+
+    def testRandomFormat30(self):
+        self.assertEqual(parse("01h02m03", default=self.default),
+                         datetime(2003, 9, 25, 1, 2, 3))
+
+    def testRandomFormat31(self):
+        self.assertEqual(parse("01h02", default=self.default),
+                         datetime(2003, 9, 25, 1, 2))
+
+    def testRandomFormat32(self):
+        self.assertEqual(parse("01h02s", default=self.default),
+                         datetime(2003, 9, 25, 1, 0, 2))
+
+    def testRandomFormat33(self):
+        self.assertEqual(parse("01m02", default=self.default),
+                         datetime(2003, 9, 25, 0, 1, 2))
+
+    def testRandomFormat34(self):
+        self.assertEqual(parse("01m02h", default=self.default),
+                         datetime(2003, 9, 25, 2, 1))
+
+    def testRandomFormat35(self):
+        self.assertEqual(parse("2004 10 Apr 11h30m", default=self.default),
+                         datetime(2004, 4, 10, 11, 30))
+
+    def test_99_ad(self):
+        self.assertEqual(parse('0099-01-01T00:00:00'),
+                         datetime(99, 1, 1, 0, 0))
+
+    def test_31_ad(self):
+        self.assertEqual(parse('0031-01-01T00:00:00'),
+                         datetime(31, 1, 1, 0, 0))
+
+    def testInvalidDay(self):
+        with self.assertRaises(ValueError):
+            parse("Feb 30, 2007")
+
+    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 testTzinfoDictionaryCouldReturnNone(self):
+        self.assertEqual(parse('2017-02-03 12:40 BRST', tzinfos={"BRST": None}),
+                        datetime(2017, 2, 3, 12, 40))
+
+    def testTzinfosCallableCouldReturnNone(self):
+        self.assertEqual(parse('2017-02-03 12:40 BRST', tzinfos=lambda *args: None),
+                                    datetime(2017, 2, 3, 12, 40))
+
+    def testErrorType01(self):
+        self.assertRaises(ValueError,
+                          parse, 'shouldfail')
+
+    def testCorrectErrorOnFuzzyWithTokens(self):
+        assertRaisesRegex(self, ValueError, 'Unknown string format',
+                          parse, '04/04/32/423', fuzzy_with_tokens=True)
+        assertRaisesRegex(self, ValueError, 'Unknown string format',
+                          parse, '04/04/04 +32423', fuzzy_with_tokens=True)
+        assertRaisesRegex(self, ValueError, '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):
+            self.assertEqual(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):
+            self.assertEqual(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")
+        self.assertEqual(dt1.microsecond, 10000)
+        self.assertEqual(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)
+            self.assertEqual(parse(dt.isoformat()), dt)
+
+    def testHighPrecisionSeconds(self):
+        self.assertEqual(parse("20080227T21:26:01.123456789"),
+                          datetime(2008, 2, 27, 21, 26, 1, 123456))
+
+    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")
+        self.assertEqual(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)
+        self.assertEqual(res, datetime(2017, 7, 17, 6, 15))
+
+    def test_dBY(self):
+        # See GH360
+        dtstr = '13NOV2017'
+        res = parse(dtstr)
+        self.assertEqual(res, datetime(2017, 11, 13))
+
+    def test_hmBY(self):
+        # See GH#483
+        dtstr = '02:17NOV2017'
+        res = parse(dtstr, default=self.default)
+        self.assertEqual(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 self.assertRaises(ValueError):
+            parse(invalid)
+
+    def test_era_trailing_year(self):
+        dstr = 'AD2001'
+        res = parse(dstr)
+        assert res.year == 2001, res
+
+    def test_pre_12_year_same_month(self):
+        # See GH PR #293
+        dtstr = '0003-03-04'
+        assert parse(dtstr) == datetime(3, 3, 4)
+
+
+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(ValueError):
+            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 infering 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.skipif(IS_WIN, reason='Windows does not use TZ var')
+def test_parse_unambiguous_nonexistent_local():
+    # 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
+
+
+@pytest.mark.skipif(IS_WIN, reason='Windows does not use TZ var')
+def test_tzlocal_in_gmt():
+    # 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.tzutc())
+
+        assert dt == dt_exp
+
+
+@pytest.mark.skipif(IS_WIN, reason='Windows does not use TZ var')
+def test_tzlocal_parse_fold():
+    # 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, kuntil `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.tzutc()) == dt_exp.astimezone(tz.tzutc())
+
+
+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.tzutc()) == dt_exp.astimezone(tz.tzutc())
+
+
+@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-ValueError exception when
+    # constructed with an invalid value
+    with pytest.raises(ValueError):
+        parse(value)
+
+
+def test_BYd_corner_case():
+    # GH#687
+    res = parse('December.0031.30')
+    assert res == datetime(31, 12, 30)
diff --git a/resources/lib/libraries/dateutil/test/test_relativedelta.py b/resources/lib/libraries/dateutil/test/test_relativedelta.py
new file mode 100644
index 00000000..70cb543a
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_relativedelta.py
@@ -0,0 +1,678 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from ._common import WarningTestMixin, NotAValue
+
+import calendar
+from datetime import datetime, date, timedelta
+import unittest
+
+from dateutil.relativedelta import relativedelta, MO, TU, WE, FR, SU
+
+
+class RelativeDeltaTest(WarningTestMixin, 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 testNextWednesdayIsToday(self):
+        self.assertEqual(self.today+relativedelta(weekday=WE),
+                         date(2003, 9, 17))
+
+    def testNextWenesdayNotToday(self):
+        self.assertEqual(self.today+relativedelta(days=+1, weekday=WE),
+                         date(2003, 9, 24))
+
+    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 testRelativeDeltaFractionalAbsolutes(self):
+        # Fractional absolute values will soon be unsupported,
+        # check for the deprecation warning.
+        with self.assertWarns(DeprecationWarning):
+            relativedelta(year=2.86)
+
+        with self.assertWarns(DeprecationWarning):
+            relativedelta(month=1.29)
+
+        with self.assertWarns(DeprecationWarning):
+            relativedelta(day=0.44)
+
+        with self.assertWarns(DeprecationWarning):
+            relativedelta(hour=23.98)
+
+        with self.assertWarns(DeprecationWarning):
+            relativedelta(minute=45.21)
+
+        with self.assertWarns(DeprecationWarning):
+            relativedelta(second=13.2)
+
+        with self.assertWarns(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))
+
+        # Equvalent 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!")
+
+
+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/resources/lib/libraries/dateutil/test/test_rrule.py b/resources/lib/libraries/dateutil/test/test_rrule.py
new file mode 100644
index 00000000..cd08ce29
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_rrule.py
@@ -0,0 +1,4842 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from ._common import WarningTestMixin
+
+from datetime import datetime, date
+import unittest
+from six import PY3
+
+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(WarningTestMixin, 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 not PY3:  # There is 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 self.assertWarns(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 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 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 not PY3:  # There is 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/resources/lib/libraries/dateutil/test/test_tz.py b/resources/lib/libraries/dateutil/test/test_tz.py
new file mode 100644
index 00000000..54dfb1bd
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_tz.py
@@ -0,0 +1,2603 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from ._common import PicklableMixin
+from ._common import TZEnvContext, TZWinContext
+from ._common import WarningTestMixin
+from ._common import ComparesEqual
+
+from datetime import datetime, timedelta
+from datetime import time as dt_time
+from datetime import tzinfo
+from six import BytesIO, StringIO
+import unittest
+
+import sys
+import base64
+import copy
+
+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))
+
+
+###
+# 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.tzutc())  # AEST
+            t1_u = datetime(2012, 3, 31, 16, 30, tzinfo=tz.tzutc())  # 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.tzutc())  # AEST
+            t1_u = datetime(2012, 10, 6, 16, 30, tzinfo=tz.tzutc())  # 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.tzutc())
+                t1_u = datetime(2011, 11, 6, 6, 30, tzinfo=tz.tzutc())
+
+                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.tzutc())
+            t1_u = datetime(2011, 3, 13, 7, 30, tzinfo=tz.tzutc())
+
+            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.tzutc()
+
+            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.tzutc()
+            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.tzutc()
+
+            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.tzutc())  # 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.tzutc())
+            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.tzutc())
+            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.tzutc()
+            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.tzutc()
+
+            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.tzutc()
+        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.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.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")
+@unittest.skipUnless(TZEnvContext.tz_change_allowed(),
+                     TZEnvContext.tz_change_disallowed_message())
+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.tzutc()
+
+
+# TODO: Maybe a better hack than this?
+def mark_tzlocal_nix(f):
+    marks = [
+        pytest.mark.tzlocal,
+        pytest.mark.skipif(IS_WIN, reason='requires Unix'),
+        pytest.mark.skipif(not TZEnvContext.tz_change_allowed,
+                           reason=TZEnvContext.tz_change_disallowed_message())
+    ]
+
+    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)),
+    ('GMT', 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
+@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
+
+
+class ZoneInfoGettzTest(GettzTest, WarningTestMixin):
+    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 self.assertWarns(DeprecationWarning):
+            zoneinfo.gettz('US/Eastern')
+
+    def testZoneInfoMetadataDeprecated(self):
+        with self.assertWarns(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.tzutc())
+        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.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 testRoundNonFullMinutes(self):
+        # This timezone has an offset of 5992 seconds in 1900-01-01.
+        tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
+        self.assertEqual(str(datetime(1900, 1, 1, 0, 0, tzinfo=tzc)),
+                             "1900-01-01 00:00:00+01:40")
+
+    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.tzutc())
+        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")
+    @unittest.skipUnless(TZEnvContext.tz_change_allowed(),
+                         TZEnvContext.tz_change_disallowed_message())
+    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')
+
+
+@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.tzutc()
+        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")
+@unittest.skipUnless(TZWinContext.tz_change_allowed(),
+                     TZWinContext.tz_change_disallowed_message())
+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 EnfoldTest(unittest.TestCase):
+    def testEnterFoldDefault(self):
+        dt = tz.enfold(datetime(2020, 1, 19, 3, 32))
+
+        self.assertEqual(dt.fold, 1)
+
+    def testEnterFold(self):
+        dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
+
+        self.assertEqual(dt.fold, 1)
+
+    def testExitFold(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.
+        self.assertEqual(getattr(dt, 'fold', 0), 0)
+
+
+@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.tzutc()),
+    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
+
+
+@pytest.mark.tz_resolve_imaginary
+@pytest.mark.parametrize('tzi, dt, dt_exp', [
+    (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(),
+])
+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()
+
+
+@pytest.mark.xfail
+@pytest.mark.tz_resolve_imaginary
+def test_resolve_imaginary_monrovia():
+    # See GH #582 - When that is resolved, move this into test_resolve_imaginary
+    tzi = tz.gettz('Africa/Monrovia')
+    dt = datetime(1972, 1, 7, hour=0, minute=30, second=0, tzinfo=tzi)
+    dt_exp = datetime(1972, 1, 7, hour=1, minute=14, second=30, 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/resources/lib/libraries/dateutil/test/test_utils.py b/resources/lib/libraries/dateutil/test/test_utils.py
new file mode 100644
index 00000000..fcdec1a5
--- /dev/null
+++ b/resources/lib/libraries/dateutil/test/test_utils.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from datetime import timedelta, datetime
+
+import unittest
+
+from dateutil import tz
+from dateutil import utils
+from dateutil.utils import within_delta
+
+from freezegun import freeze_time
+
+UTC = tz.tzutc()
+NYC = tz.gettz("America/New_York")
+
+
+class UtilsTest(unittest.TestCase):
+    @freeze_time(datetime(2014, 12, 15, 1, 21, 33, 4003))
+    def testToday(self):
+        self.assertEqual(utils.today(), datetime(2014, 12, 15, 0, 0, 0))
+
+    @freeze_time(datetime(2014, 12, 15, 12), tz_offset=5)
+    def testTodayTzInfo(self):
+        self.assertEqual(utils.today(NYC),
+                         datetime(2014, 12, 15, 0, 0, 0, tzinfo=NYC))
+
+    @freeze_time(datetime(2014, 12, 15, 23), tz_offset=5)
+    def testTodayTzInfoDifferentDay(self):
+        self.assertEqual(utils.today(UTC),
+                         datetime(2014, 12, 16, 0, 0, 0, tzinfo=UTC))
+
+    def testDefaultTZInfoNaive(self):
+        dt = datetime(2014, 9, 14, 9, 30)
+        self.assertIs(utils.default_tzinfo(dt, NYC).tzinfo,
+                      NYC)
+
+    def testDefaultTZInfoAware(self):
+        dt = datetime(2014, 9, 14, 9, 30, tzinfo=UTC)
+        self.assertIs(utils.default_tzinfo(dt, NYC).tzinfo,
+                      UTC)
+
+    def testWithinDelta(self):
+        d1 = datetime(2016, 1, 1, 12, 14, 1, 9)
+        d2 = d1.replace(microsecond=15)
+
+        self.assertTrue(within_delta(d1, d2, timedelta(seconds=1)))
+        self.assertFalse(within_delta(d1, d2, timedelta(microseconds=1)))
+
+    def testWithinDeltaWithNegativeDelta(self):
+        d1 = datetime(2016, 1, 1)
+        d2 = datetime(2015, 12, 31)
+
+        self.assertTrue(within_delta(d2, d1, timedelta(days=-1)))
diff --git a/resources/lib/libraries/dateutil/tz/__init__.py b/resources/lib/libraries/dateutil/tz/__init__.py
new file mode 100644
index 00000000..5a2d9cd6
--- /dev/null
+++ b/resources/lib/libraries/dateutil/tz/__init__.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+from .tz import *
+from .tz import __doc__
+
+#: Convenience constant providing a :class:`tzutc()` instance
+#:
+#: .. versionadded:: 2.7.0
+UTC = tzutc()
+
+__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
+           "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
+           "enfold", "datetime_ambiguous", "datetime_exists",
+           "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"]
+
+
+class DeprecatedTzFormatWarning(Warning):
+    """Warning raised when time zones are parsed from deprecated formats."""
diff --git a/resources/lib/libraries/dateutil/tz/_common.py b/resources/lib/libraries/dateutil/tz/_common.py
new file mode 100644
index 00000000..ccabb7da
--- /dev/null
+++ b/resources/lib/libraries/dateutil/tz/_common.py
@@ -0,0 +1,415 @@
+from six import PY3
+
+from functools import wraps
+
+from datetime import datetime, timedelta, tzinfo
+
+
+ZERO = timedelta(0)
+
+__all__ = ['tzname_in_python2', 'enfold']
+
+
+def tzname_in_python2(namefunc):
+    """Change unicode output into bytestrings in Python 2
+
+    tzname() API changed in Python 3. It used to return bytes, but was changed
+    to unicode strings
+    """
+    def adjust_encoding(*args, **kwargs):
+        name = namefunc(*args, **kwargs)
+        if name is not None and not PY3:
+            name = name.encode()
+
+        return name
+
+    return adjust_encoding
+
+
+# The following is adapted from Alexander Belopolsky's tz library
+# https://github.com/abalkin/tz
+if hasattr(datetime, 'fold'):
+    # This is the pre-python 3.6 fold situation
+    def enfold(dt, fold=1):
+        """
+        Provides a unified interface for assigning the ``fold`` attribute to
+        datetimes both before and after the implementation of PEP-495.
+
+        :param fold:
+            The value for the ``fold`` attribute in the returned datetime. This
+            should be either 0 or 1.
+
+        :return:
+            Returns an object for which ``getattr(dt, 'fold', 0)`` returns
+            ``fold`` for all versions of Python. In versions prior to
+            Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
+            subclass of :py:class:`datetime.datetime` with the ``fold``
+            attribute added, if ``fold`` is 1.
+
+        .. versionadded:: 2.6.0
+        """
+        return dt.replace(fold=fold)
+
+else:
+    class _DatetimeWithFold(datetime):
+        """
+        This is a class designed to provide a PEP 495-compliant interface for
+        Python versions before 3.6. It is used only for dates in a fold, so
+        the ``fold`` attribute is fixed at ``1``.
+
+        .. versionadded:: 2.6.0
+        """
+        __slots__ = ()
+
+        def replace(self, *args, **kwargs):
+            """
+            Return a datetime with the same attributes, except for those
+            attributes given new values by whichever keyword arguments are
+            specified. Note that tzinfo=None can be specified to create a naive
+            datetime from an aware datetime with no conversion of date and time
+            data.
+
+            This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
+            return a ``datetime.datetime`` even if ``fold`` is unchanged.
+            """
+            argnames = (
+                'year', 'month', 'day', 'hour', 'minute', 'second',
+                'microsecond', 'tzinfo'
+            )
+
+            for arg, argname in zip(args, argnames):
+                if argname in kwargs:
+                    raise TypeError('Duplicate argument: {}'.format(argname))
+
+                kwargs[argname] = arg
+
+            for argname in argnames:
+                if argname not in kwargs:
+                    kwargs[argname] = getattr(self, argname)
+
+            dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
+
+            return dt_class(**kwargs)
+
+        @property
+        def fold(self):
+            return 1
+
+    def enfold(dt, fold=1):
+        """
+        Provides a unified interface for assigning the ``fold`` attribute to
+        datetimes both before and after the implementation of PEP-495.
+
+        :param fold:
+            The value for the ``fold`` attribute in the returned datetime. This
+            should be either 0 or 1.
+
+        :return:
+            Returns an object for which ``getattr(dt, 'fold', 0)`` returns
+            ``fold`` for all versions of Python. In versions prior to
+            Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
+            subclass of :py:class:`datetime.datetime` with the ``fold``
+            attribute added, if ``fold`` is 1.
+
+        .. versionadded:: 2.6.0
+        """
+        if getattr(dt, 'fold', 0) == fold:
+            return dt
+
+        args = dt.timetuple()[:6]
+        args += (dt.microsecond, dt.tzinfo)
+
+        if fold:
+            return _DatetimeWithFold(*args)
+        else:
+            return datetime(*args)
+
+
+def _validate_fromutc_inputs(f):
+    """
+    The CPython version of ``fromutc`` checks that the input is a ``datetime``
+    object and that ``self`` is attached as its ``tzinfo``.
+    """
+    @wraps(f)
+    def fromutc(self, dt):
+        if not isinstance(dt, datetime):
+            raise TypeError("fromutc() requires a datetime argument")
+        if dt.tzinfo is not self:
+            raise ValueError("dt.tzinfo is not self")
+
+        return f(self, dt)
+
+    return fromutc
+
+
+class _tzinfo(tzinfo):
+    """
+    Base class for all ``dateutil`` ``tzinfo`` objects.
+    """
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+
+        dt = dt.replace(tzinfo=self)
+
+        wall_0 = enfold(dt, fold=0)
+        wall_1 = enfold(dt, fold=1)
+
+        same_offset = wall_0.utcoffset() == wall_1.utcoffset()
+        same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
+
+        return same_dt and not same_offset
+
+    def _fold_status(self, dt_utc, dt_wall):
+        """
+        Determine the fold status of a "wall" datetime, given a representation
+        of the same datetime as a (naive) UTC datetime. This is calculated based
+        on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
+        datetimes, and that this offset is the actual number of hours separating
+        ``dt_utc`` and ``dt_wall``.
+
+        :param dt_utc:
+            Representation of the datetime as UTC
+
+        :param dt_wall:
+            Representation of the datetime as "wall time". This parameter must
+            either have a `fold` attribute or have a fold-naive
+            :class:`datetime.tzinfo` attached, otherwise the calculation may
+            fail.
+        """
+        if self.is_ambiguous(dt_wall):
+            delta_wall = dt_wall - dt_utc
+            _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
+        else:
+            _fold = 0
+
+        return _fold
+
+    def _fold(self, dt):
+        return getattr(dt, 'fold', 0)
+
+    def _fromutc(self, dt):
+        """
+        Given a timezone-aware datetime in a given timezone, calculates a
+        timezone-aware datetime in a new timezone.
+
+        Since this is the one time that we *know* we have an unambiguous
+        datetime object, we take this opportunity to determine whether the
+        datetime is ambiguous and in a "fold" state (e.g. if it's the first
+        occurence, chronologically, of the ambiguous datetime).
+
+        :param dt:
+            A timezone-aware :class:`datetime.datetime` object.
+        """
+
+        # Re-implement the algorithm from Python's datetime.py
+        dtoff = dt.utcoffset()
+        if dtoff is None:
+            raise ValueError("fromutc() requires a non-None utcoffset() "
+                             "result")
+
+        # The original datetime.py code assumes that `dst()` defaults to
+        # zero during ambiguous times. PEP 495 inverts this presumption, so
+        # for pre-PEP 495 versions of python, we need to tweak the algorithm.
+        dtdst = dt.dst()
+        if dtdst is None:
+            raise ValueError("fromutc() requires a non-None dst() result")
+        delta = dtoff - dtdst
+
+        dt += delta
+        # Set fold=1 so we can default to being in the fold for
+        # ambiguous dates.
+        dtdst = enfold(dt, fold=1).dst()
+        if dtdst is None:
+            raise ValueError("fromutc(): dt.dst gave inconsistent "
+                             "results; cannot convert")
+        return dt + dtdst
+
+    @_validate_fromutc_inputs
+    def fromutc(self, dt):
+        """
+        Given a timezone-aware datetime in a given timezone, calculates a
+        timezone-aware datetime in a new timezone.
+
+        Since this is the one time that we *know* we have an unambiguous
+        datetime object, we take this opportunity to determine whether the
+        datetime is ambiguous and in a "fold" state (e.g. if it's the first
+        occurance, chronologically, of the ambiguous datetime).
+
+        :param dt:
+            A timezone-aware :class:`datetime.datetime` object.
+        """
+        dt_wall = self._fromutc(dt)
+
+        # Calculate the fold status given the two datetimes.
+        _fold = self._fold_status(dt, dt_wall)
+
+        # Set the default fold value for ambiguous dates
+        return enfold(dt_wall, fold=_fold)
+
+
+class tzrangebase(_tzinfo):
+    """
+    This is an abstract base class for time zones represented by an annual
+    transition into and out of DST. Child classes should implement the following
+    methods:
+
+        * ``__init__(self, *args, **kwargs)``
+        * ``transitions(self, year)`` - this is expected to return a tuple of
+          datetimes representing the DST on and off transitions in standard
+          time.
+
+    A fully initialized ``tzrangebase`` subclass should also provide the
+    following attributes:
+        * ``hasdst``: Boolean whether or not the zone uses DST.
+        * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
+          representing the respective UTC offsets.
+        * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
+          abbreviations in DST and STD, respectively.
+        * ``_hasdst``: Whether or not the zone has DST.
+
+    .. versionadded:: 2.6.0
+    """
+    def __init__(self):
+        raise NotImplementedError('tzrangebase is an abstract base class')
+
+    def utcoffset(self, dt):
+        isdst = self._isdst(dt)
+
+        if isdst is None:
+            return None
+        elif isdst:
+            return self._dst_offset
+        else:
+            return self._std_offset
+
+    def dst(self, dt):
+        isdst = self._isdst(dt)
+
+        if isdst is None:
+            return None
+        elif isdst:
+            return self._dst_base_offset
+        else:
+            return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        if self._isdst(dt):
+            return self._dst_abbr
+        else:
+            return self._std_abbr
+
+    def fromutc(self, dt):
+        """ Given a datetime in UTC, return local time """
+        if not isinstance(dt, datetime):
+            raise TypeError("fromutc() requires a datetime argument")
+
+        if dt.tzinfo is not self:
+            raise ValueError("dt.tzinfo is not self")
+
+        # Get transitions - if there are none, fixed offset
+        transitions = self.transitions(dt.year)
+        if transitions is None:
+            return dt + self.utcoffset(dt)
+
+        # Get the transition times in UTC
+        dston, dstoff = transitions
+
+        dston -= self._std_offset
+        dstoff -= self._std_offset
+
+        utc_transitions = (dston, dstoff)
+        dt_utc = dt.replace(tzinfo=None)
+
+        isdst = self._naive_isdst(dt_utc, utc_transitions)
+
+        if isdst:
+            dt_wall = dt + self._dst_offset
+        else:
+            dt_wall = dt + self._std_offset
+
+        _fold = int(not isdst and self.is_ambiguous(dt_wall))
+
+        return enfold(dt_wall, fold=_fold)
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        if not self.hasdst:
+            return False
+
+        start, end = self.transitions(dt.year)
+
+        dt = dt.replace(tzinfo=None)
+        return (end <= dt < end + self._dst_base_offset)
+
+    def _isdst(self, dt):
+        if not self.hasdst:
+            return False
+        elif dt is None:
+            return None
+
+        transitions = self.transitions(dt.year)
+
+        if transitions is None:
+            return False
+
+        dt = dt.replace(tzinfo=None)
+
+        isdst = self._naive_isdst(dt, transitions)
+
+        # Handle ambiguous dates
+        if not isdst and self.is_ambiguous(dt):
+            return not self._fold(dt)
+        else:
+            return isdst
+
+    def _naive_isdst(self, dt, transitions):
+        dston, dstoff = transitions
+
+        dt = dt.replace(tzinfo=None)
+
+        if dston < dstoff:
+            isdst = dston <= dt < dstoff
+        else:
+            isdst = not dstoff <= dt < dston
+
+        return isdst
+
+    @property
+    def _dst_base_offset(self):
+        return self._dst_offset - self._std_offset
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s(...)" % self.__class__.__name__
+
+    __reduce__ = object.__reduce__
diff --git a/resources/lib/libraries/dateutil/tz/_factories.py b/resources/lib/libraries/dateutil/tz/_factories.py
new file mode 100644
index 00000000..de2e0c1d
--- /dev/null
+++ b/resources/lib/libraries/dateutil/tz/_factories.py
@@ -0,0 +1,49 @@
+from datetime import timedelta
+
+
+class _TzSingleton(type):
+    def __init__(cls, *args, **kwargs):
+        cls.__instance = None
+        super(_TzSingleton, cls).__init__(*args, **kwargs)
+
+    def __call__(cls):
+        if cls.__instance is None:
+            cls.__instance = super(_TzSingleton, cls).__call__()
+        return cls.__instance
+
+class _TzFactory(type):
+    def instance(cls, *args, **kwargs):
+        """Alternate constructor that returns a fresh instance"""
+        return type.__call__(cls, *args, **kwargs)
+
+
+class _TzOffsetFactory(_TzFactory):
+    def __init__(cls, *args, **kwargs):
+        cls.__instances = {}
+
+    def __call__(cls, name, offset):
+        if isinstance(offset, timedelta):
+            key = (name, offset.total_seconds())
+        else:
+            key = (name, offset)
+
+        instance = cls.__instances.get(key, None)
+        if instance is None:
+            instance = cls.__instances.setdefault(key,
+                                                  cls.instance(name, offset))
+        return instance
+
+
+class _TzStrFactory(_TzFactory):
+    def __init__(cls, *args, **kwargs):
+        cls.__instances = {}
+
+    def __call__(cls, s, posix_offset=False):
+        key = (s, posix_offset)
+        instance = cls.__instances.get(key, None)
+
+        if instance is None:
+            instance = cls.__instances.setdefault(key,
+                cls.instance(s, posix_offset))
+        return instance
+
diff --git a/resources/lib/libraries/dateutil/tz/tz.py b/resources/lib/libraries/dateutil/tz/tz.py
new file mode 100644
index 00000000..4c23242a
--- /dev/null
+++ b/resources/lib/libraries/dateutil/tz/tz.py
@@ -0,0 +1,1785 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers timezone implementations subclassing the abstract
+:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
+files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
+etc), TZ environment string (in all known formats), given ranges (with help
+from relative deltas), local machine timezone, fixed offset timezone, and UTC
+timezone.
+"""
+import datetime
+import struct
+import time
+import sys
+import os
+import bisect
+
+from .. import six
+from ..six import string_types
+from ..six.moves import _thread
+from ._common import tzname_in_python2, _tzinfo
+from ._common import tzrangebase, enfold
+from ._common import _validate_fromutc_inputs
+
+from ._factories import _TzSingleton, _TzOffsetFactory
+from ._factories import _TzStrFactory
+try:
+    from .win import tzwin, tzwinlocal
+except ImportError:
+    tzwin = tzwinlocal = None
+
+ZERO = datetime.timedelta(0)
+EPOCH = datetime.datetime.utcfromtimestamp(0)
+EPOCHORDINAL = EPOCH.toordinal()
+
+
+@six.add_metaclass(_TzSingleton)
+class tzutc(datetime.tzinfo):
+    """
+    This is a tzinfo object that represents the UTC time zone.
+
+    **Examples:**
+
+    .. doctest::
+
+        >>> from datetime import *
+        >>> from dateutil.tz import *
+
+        >>> datetime.now()
+        datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)
+
+        >>> datetime.now(tzutc())
+        datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())
+
+        >>> datetime.now(tzutc()).tzname()
+        'UTC'
+
+    .. versionchanged:: 2.7.0
+        ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will
+        always return the same object.
+
+        .. doctest::
+
+            >>> from dateutil.tz import tzutc, UTC
+            >>> tzutc() is tzutc()
+            True
+            >>> tzutc() is UTC
+            True
+    """
+    def utcoffset(self, dt):
+        return ZERO
+
+    def dst(self, dt):
+        return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return "UTC"
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        return False
+
+    @_validate_fromutc_inputs
+    def fromutc(self, dt):
+        """
+        Fast track version of fromutc() returns the original ``dt`` object for
+        any valid :py:class:`datetime.datetime` object.
+        """
+        return dt
+
+    def __eq__(self, other):
+        if not isinstance(other, (tzutc, tzoffset)):
+            return NotImplemented
+
+        return (isinstance(other, tzutc) or
+                (isinstance(other, tzoffset) and other._offset == ZERO))
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s()" % self.__class__.__name__
+
+    __reduce__ = object.__reduce__
+
+
+@six.add_metaclass(_TzOffsetFactory)
+class tzoffset(datetime.tzinfo):
+    """
+    A simple class for representing a fixed offset from UTC.
+
+    :param name:
+        The timezone name, to be returned when ``tzname()`` is called.
+    :param offset:
+        The time zone offset in seconds, or (since version 2.6.0, represented
+        as a :py:class:`datetime.timedelta` object).
+    """
+    def __init__(self, name, offset):
+        self._name = name
+
+        try:
+            # Allow a timedelta
+            offset = offset.total_seconds()
+        except (TypeError, AttributeError):
+            pass
+        self._offset = datetime.timedelta(seconds=offset)
+
+    def utcoffset(self, dt):
+        return self._offset
+
+    def dst(self, dt):
+        return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return self._name
+
+    @_validate_fromutc_inputs
+    def fromutc(self, dt):
+        return dt + self._offset
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        return False
+
+    def __eq__(self, other):
+        if not isinstance(other, tzoffset):
+            return NotImplemented
+
+        return self._offset == other._offset
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s(%s, %s)" % (self.__class__.__name__,
+                               repr(self._name),
+                               int(self._offset.total_seconds()))
+
+    __reduce__ = object.__reduce__
+
+
+class tzlocal(_tzinfo):
+    """
+    A :class:`tzinfo` subclass built around the ``time`` timezone functions.
+    """
+    def __init__(self):
+        super(tzlocal, self).__init__()
+
+        self._std_offset = datetime.timedelta(seconds=-time.timezone)
+        if time.daylight:
+            self._dst_offset = datetime.timedelta(seconds=-time.altzone)
+        else:
+            self._dst_offset = self._std_offset
+
+        self._dst_saved = self._dst_offset - self._std_offset
+        self._hasdst = bool(self._dst_saved)
+        self._tznames = tuple(time.tzname)
+
+    def utcoffset(self, dt):
+        if dt is None and self._hasdst:
+            return None
+
+        if self._isdst(dt):
+            return self._dst_offset
+        else:
+            return self._std_offset
+
+    def dst(self, dt):
+        if dt is None and self._hasdst:
+            return None
+
+        if self._isdst(dt):
+            return self._dst_offset - self._std_offset
+        else:
+            return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return self._tznames[self._isdst(dt)]
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        naive_dst = self._naive_is_dst(dt)
+        return (not naive_dst and
+                (naive_dst != self._naive_is_dst(dt - self._dst_saved)))
+
+    def _naive_is_dst(self, dt):
+        timestamp = _datetime_to_timestamp(dt)
+        return time.localtime(timestamp + time.timezone).tm_isdst
+
+    def _isdst(self, dt, fold_naive=True):
+        # We can't use mktime here. It is unstable when deciding if
+        # the hour near to a change is DST or not.
+        #
+        # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
+        #                         dt.minute, dt.second, dt.weekday(), 0, -1))
+        # return time.localtime(timestamp).tm_isdst
+        #
+        # The code above yields the following result:
+        #
+        # >>> import tz, datetime
+        # >>> t = tz.tzlocal()
+        # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
+        # 'BRDT'
+        # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
+        # 'BRST'
+        # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
+        # 'BRST'
+        # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
+        # 'BRDT'
+        # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
+        # 'BRDT'
+        #
+        # Here is a more stable implementation:
+        #
+        if not self._hasdst:
+            return False
+
+        # Check for ambiguous times:
+        dstval = self._naive_is_dst(dt)
+        fold = getattr(dt, 'fold', None)
+
+        if self.is_ambiguous(dt):
+            if fold is not None:
+                return not self._fold(dt)
+            else:
+                return True
+
+        return dstval
+
+    def __eq__(self, other):
+        if isinstance(other, tzlocal):
+            return (self._std_offset == other._std_offset and
+                    self._dst_offset == other._dst_offset)
+        elif isinstance(other, tzutc):
+            return (not self._hasdst and
+                    self._tznames[0] in {'UTC', 'GMT'} and
+                    self._std_offset == ZERO)
+        elif isinstance(other, tzoffset):
+            return (not self._hasdst and
+                    self._tznames[0] == other._name and
+                    self._std_offset == other._offset)
+        else:
+            return NotImplemented
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s()" % self.__class__.__name__
+
+    __reduce__ = object.__reduce__
+
+
+class _ttinfo(object):
+    __slots__ = ["offset", "delta", "isdst", "abbr",
+                 "isstd", "isgmt", "dstoffset"]
+
+    def __init__(self):
+        for attr in self.__slots__:
+            setattr(self, attr, None)
+
+    def __repr__(self):
+        l = []
+        for attr in self.__slots__:
+            value = getattr(self, attr)
+            if value is not None:
+                l.append("%s=%s" % (attr, repr(value)))
+        return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
+
+    def __eq__(self, other):
+        if not isinstance(other, _ttinfo):
+            return NotImplemented
+
+        return (self.offset == other.offset and
+                self.delta == other.delta and
+                self.isdst == other.isdst and
+                self.abbr == other.abbr and
+                self.isstd == other.isstd and
+                self.isgmt == other.isgmt and
+                self.dstoffset == other.dstoffset)
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __getstate__(self):
+        state = {}
+        for name in self.__slots__:
+            state[name] = getattr(self, name, None)
+        return state
+
+    def __setstate__(self, state):
+        for name in self.__slots__:
+            if name in state:
+                setattr(self, name, state[name])
+
+
+class _tzfile(object):
+    """
+    Lightweight class for holding the relevant transition and time zone
+    information read from binary tzfiles.
+    """
+    attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list',
+             'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first']
+
+    def __init__(self, **kwargs):
+        for attr in self.attrs:
+            setattr(self, attr, kwargs.get(attr, None))
+
+
+class tzfile(_tzinfo):
+    """
+    This is a ``tzinfo`` subclass thant allows one to use the ``tzfile(5)``
+    format timezone files to extract current and historical zone information.
+
+    :param fileobj:
+        This can be an opened file stream or a file name that the time zone
+        information can be read from.
+
+    :param filename:
+        This is an optional parameter specifying the source of the time zone
+        information in the event that ``fileobj`` is a file object. If omitted
+        and ``fileobj`` is a file stream, this parameter will be set either to
+        ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.
+
+    See `Sources for Time Zone and Daylight Saving Time Data
+    <https://data.iana.org/time-zones/tz-link.html>`_ for more information.
+    Time zone files can be compiled from the `IANA Time Zone database files
+    <https://www.iana.org/time-zones>`_ with the `zic time zone compiler
+    <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_
+
+    .. note::
+
+        Only construct a ``tzfile`` directly if you have a specific timezone
+        file on disk that you want to read into a Python ``tzinfo`` object.
+        If you want to get a ``tzfile`` representing a specific IANA zone,
+        (e.g. ``'America/New_York'``), you should call
+        :func:`dateutil.tz.gettz` with the zone identifier.
+
+
+    **Examples:**
+
+    Using the US Eastern time zone as an example, we can see that a ``tzfile``
+    provides time zone information for the standard Daylight Saving offsets:
+
+    .. testsetup:: tzfile
+
+        from dateutil.tz import gettz
+        from datetime import datetime
+
+    .. doctest:: tzfile
+
+        >>> NYC = gettz('America/New_York')
+        >>> NYC
+        tzfile('/usr/share/zoneinfo/America/New_York')
+
+        >>> print(datetime(2016, 1, 3, tzinfo=NYC))     # EST
+        2016-01-03 00:00:00-05:00
+
+        >>> print(datetime(2016, 7, 7, tzinfo=NYC))     # EDT
+        2016-07-07 00:00:00-04:00
+
+
+    The ``tzfile`` structure contains a fully history of the time zone,
+    so historical dates will also have the right offsets. For example, before
+    the adoption of the UTC standards, New York used local solar  mean time:
+
+    .. doctest:: tzfile
+
+       >>> print(datetime(1901, 4, 12, tzinfo=NYC))    # LMT
+       1901-04-12 00:00:00-04:56
+
+    And during World War II, New York was on "Eastern War Time", which was a
+    state of permanent daylight saving time:
+
+    .. doctest:: tzfile
+
+        >>> print(datetime(1944, 2, 7, tzinfo=NYC))    # EWT
+        1944-02-07 00:00:00-04:00
+
+    """
+
+    def __init__(self, fileobj, filename=None):
+        super(tzfile, self).__init__()
+
+        file_opened_here = False
+        if isinstance(fileobj, string_types):
+            self._filename = fileobj
+            fileobj = open(fileobj, 'rb')
+            file_opened_here = True
+        elif filename is not None:
+            self._filename = filename
+        elif hasattr(fileobj, "name"):
+            self._filename = fileobj.name
+        else:
+            self._filename = repr(fileobj)
+
+        if fileobj is not None:
+            if not file_opened_here:
+                fileobj = _ContextWrapper(fileobj)
+
+            with fileobj as file_stream:
+                tzobj = self._read_tzfile(file_stream)
+
+            self._set_tzdata(tzobj)
+
+    def _set_tzdata(self, tzobj):
+        """ Set the time zone data of this object from a _tzfile object """
+        # Copy the relevant attributes over as private attributes
+        for attr in _tzfile.attrs:
+            setattr(self, '_' + attr, getattr(tzobj, attr))
+
+    def _read_tzfile(self, fileobj):
+        out = _tzfile()
+
+        # From tzfile(5):
+        #
+        # The time zone information files used by tzset(3)
+        # begin with the magic characters "TZif" to identify
+        # them as time zone information files, followed by
+        # sixteen bytes reserved for future use, followed by
+        # six four-byte values of type long, written in a
+        # ``standard'' byte order (the high-order  byte
+        # of the value is written first).
+        if fileobj.read(4).decode() != "TZif":
+            raise ValueError("magic not found")
+
+        fileobj.read(16)
+
+        (
+            # The number of UTC/local indicators stored in the file.
+            ttisgmtcnt,
+
+            # The number of standard/wall indicators stored in the file.
+            ttisstdcnt,
+
+            # The number of leap seconds for which data is
+            # stored in the file.
+            leapcnt,
+
+            # The number of "transition times" for which data
+            # is stored in the file.
+            timecnt,
+
+            # The number of "local time types" for which data
+            # is stored in the file (must not be zero).
+            typecnt,
+
+            # The  number  of  characters  of "time zone
+            # abbreviation strings" stored in the file.
+            charcnt,
+
+        ) = struct.unpack(">6l", fileobj.read(24))
+
+        # The above header is followed by tzh_timecnt four-byte
+        # values  of  type long,  sorted  in ascending order.
+        # These values are written in ``standard'' byte order.
+        # Each is used as a transition time (as  returned  by
+        # time(2)) at which the rules for computing local time
+        # change.
+
+        if timecnt:
+            out.trans_list_utc = list(struct.unpack(">%dl" % timecnt,
+                                                    fileobj.read(timecnt*4)))
+        else:
+            out.trans_list_utc = []
+
+        # Next come tzh_timecnt one-byte values of type unsigned
+        # char; each one tells which of the different types of
+        # ``local time'' types described in the file is associated
+        # with the same-indexed transition time. These values
+        # serve as indices into an array of ttinfo structures that
+        # appears next in the file.
+
+        if timecnt:
+            out.trans_idx = struct.unpack(">%dB" % timecnt,
+                                          fileobj.read(timecnt))
+        else:
+            out.trans_idx = []
+
+        # Each ttinfo structure is written as a four-byte value
+        # for tt_gmtoff  of  type long,  in  a  standard  byte
+        # order, followed  by a one-byte value for tt_isdst
+        # and a one-byte  value  for  tt_abbrind.   In  each
+        # structure, tt_gmtoff  gives  the  number  of
+        # seconds to be added to UTC, tt_isdst tells whether
+        # tm_isdst should be set by  localtime(3),  and
+        # tt_abbrind serves  as an index into the array of
+        # time zone abbreviation characters that follow the
+        # ttinfo structure(s) in the file.
+
+        ttinfo = []
+
+        for i in range(typecnt):
+            ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
+
+        abbr = fileobj.read(charcnt).decode()
+
+        # Then there are tzh_leapcnt pairs of four-byte
+        # values, written in  standard byte  order;  the
+        # first  value  of  each pair gives the time (as
+        # returned by time(2)) at which a leap second
+        # occurs;  the  second  gives the  total  number of
+        # leap seconds to be applied after the given time.
+        # The pairs of values are sorted in ascending order
+        # by time.
+
+        # Not used, for now (but seek for correct file position)
+        if leapcnt:
+            fileobj.seek(leapcnt * 8, os.SEEK_CUR)
+
+        # Then there are tzh_ttisstdcnt standard/wall
+        # indicators, each stored as a one-byte value;
+        # they tell whether the transition times associated
+        # with local time types were specified as standard
+        # time or wall clock time, and are used when
+        # a time zone file is used in handling POSIX-style
+        # time zone environment variables.
+
+        if ttisstdcnt:
+            isstd = struct.unpack(">%db" % ttisstdcnt,
+                                  fileobj.read(ttisstdcnt))
+
+        # Finally, there are tzh_ttisgmtcnt UTC/local
+        # indicators, each stored as a one-byte value;
+        # they tell whether the transition times associated
+        # with local time types were specified as UTC or
+        # local time, and are used when a time zone file
+        # is used in handling POSIX-style time zone envi-
+        # ronment variables.
+
+        if ttisgmtcnt:
+            isgmt = struct.unpack(">%db" % ttisgmtcnt,
+                                  fileobj.read(ttisgmtcnt))
+
+        # Build ttinfo list
+        out.ttinfo_list = []
+        for i in range(typecnt):
+            gmtoff, isdst, abbrind = ttinfo[i]
+            # Round to full-minutes if that's not the case. Python's
+            # datetime doesn't accept sub-minute timezones. Check
+            # http://python.org/sf/1447945 for some information.
+            gmtoff = 60 * ((gmtoff + 30) // 60)
+            tti = _ttinfo()
+            tti.offset = gmtoff
+            tti.dstoffset = datetime.timedelta(0)
+            tti.delta = datetime.timedelta(seconds=gmtoff)
+            tti.isdst = isdst
+            tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
+            tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
+            tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
+            out.ttinfo_list.append(tti)
+
+        # Replace ttinfo indexes for ttinfo objects.
+        out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx]
+
+        # Set standard, dst, and before ttinfos. before will be
+        # used when a given time is before any transitions,
+        # and will be set to the first non-dst ttinfo, or to
+        # the first dst, if all of them are dst.
+        out.ttinfo_std = None
+        out.ttinfo_dst = None
+        out.ttinfo_before = None
+        if out.ttinfo_list:
+            if not out.trans_list_utc:
+                out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0]
+            else:
+                for i in range(timecnt-1, -1, -1):
+                    tti = out.trans_idx[i]
+                    if not out.ttinfo_std and not tti.isdst:
+                        out.ttinfo_std = tti
+                    elif not out.ttinfo_dst and tti.isdst:
+                        out.ttinfo_dst = tti
+
+                    if out.ttinfo_std and out.ttinfo_dst:
+                        break
+                else:
+                    if out.ttinfo_dst and not out.ttinfo_std:
+                        out.ttinfo_std = out.ttinfo_dst
+
+                for tti in out.ttinfo_list:
+                    if not tti.isdst:
+                        out.ttinfo_before = tti
+                        break
+                else:
+                    out.ttinfo_before = out.ttinfo_list[0]
+
+        # Now fix transition times to become relative to wall time.
+        #
+        # I'm not sure about this. In my tests, the tz source file
+        # is setup to wall time, and in the binary file isstd and
+        # isgmt are off, so it should be in wall time. OTOH, it's
+        # always in gmt time. Let me know if you have comments
+        # about this.
+        laststdoffset = None
+        out.trans_list = []
+        for i, tti in enumerate(out.trans_idx):
+            if not tti.isdst:
+                offset = tti.offset
+                laststdoffset = offset
+            else:
+                if laststdoffset is not None:
+                    # Store the DST offset as well and update it in the list
+                    tti.dstoffset = tti.offset - laststdoffset
+                    out.trans_idx[i] = tti
+
+                offset = laststdoffset or 0
+
+            out.trans_list.append(out.trans_list_utc[i] + offset)
+
+        # In case we missed any DST offsets on the way in for some reason, make
+        # a second pass over the list, looking for the /next/ DST offset.
+        laststdoffset = None
+        for i in reversed(range(len(out.trans_idx))):
+            tti = out.trans_idx[i]
+            if tti.isdst:
+                if not (tti.dstoffset or laststdoffset is None):
+                    tti.dstoffset = tti.offset - laststdoffset
+            else:
+                laststdoffset = tti.offset
+
+            if not isinstance(tti.dstoffset, datetime.timedelta):
+                tti.dstoffset = datetime.timedelta(seconds=tti.dstoffset)
+
+            out.trans_idx[i] = tti
+
+        out.trans_idx = tuple(out.trans_idx)
+        out.trans_list = tuple(out.trans_list)
+        out.trans_list_utc = tuple(out.trans_list_utc)
+
+        return out
+
+    def _find_last_transition(self, dt, in_utc=False):
+        # If there's no list, there are no transitions to find
+        if not self._trans_list:
+            return None
+
+        timestamp = _datetime_to_timestamp(dt)
+
+        # Find where the timestamp fits in the transition list - if the
+        # timestamp is a transition time, it's part of the "after" period.
+        trans_list = self._trans_list_utc if in_utc else self._trans_list
+        idx = bisect.bisect_right(trans_list, timestamp)
+
+        # We want to know when the previous transition was, so subtract off 1
+        return idx - 1
+
+    def _get_ttinfo(self, idx):
+        # For no list or after the last transition, default to _ttinfo_std
+        if idx is None or (idx + 1) >= len(self._trans_list):
+            return self._ttinfo_std
+
+        # If there is a list and the time is before it, return _ttinfo_before
+        if idx < 0:
+            return self._ttinfo_before
+
+        return self._trans_idx[idx]
+
+    def _find_ttinfo(self, dt):
+        idx = self._resolve_ambiguous_time(dt)
+
+        return self._get_ttinfo(idx)
+
+    def fromutc(self, dt):
+        """
+        The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.
+
+        :param dt:
+            A :py:class:`datetime.datetime` object.
+
+        :raises TypeError:
+            Raised if ``dt`` is not a :py:class:`datetime.datetime` object.
+
+        :raises ValueError:
+            Raised if this is called with a ``dt`` which does not have this
+            ``tzinfo`` attached.
+
+        :return:
+            Returns a :py:class:`datetime.datetime` object representing the
+            wall time in ``self``'s time zone.
+        """
+        # These isinstance checks are in datetime.tzinfo, so we'll preserve
+        # them, even if we don't care about duck typing.
+        if not isinstance(dt, datetime.datetime):
+            raise TypeError("fromutc() requires a datetime argument")
+
+        if dt.tzinfo is not self:
+            raise ValueError("dt.tzinfo is not self")
+
+        # First treat UTC as wall time and get the transition we're in.
+        idx = self._find_last_transition(dt, in_utc=True)
+        tti = self._get_ttinfo(idx)
+
+        dt_out = dt + datetime.timedelta(seconds=tti.offset)
+
+        fold = self.is_ambiguous(dt_out, idx=idx)
+
+        return enfold(dt_out, fold=int(fold))
+
+    def is_ambiguous(self, dt, idx=None):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        if idx is None:
+            idx = self._find_last_transition(dt)
+
+        # Calculate the difference in offsets from current to previous
+        timestamp = _datetime_to_timestamp(dt)
+        tti = self._get_ttinfo(idx)
+
+        if idx is None or idx <= 0:
+            return False
+
+        od = self._get_ttinfo(idx - 1).offset - tti.offset
+        tt = self._trans_list[idx]          # Transition time
+
+        return timestamp < tt + od
+
+    def _resolve_ambiguous_time(self, dt):
+        idx = self._find_last_transition(dt)
+
+        # If we have no transitions, return the index
+        _fold = self._fold(dt)
+        if idx is None or idx == 0:
+            return idx
+
+        # If it's ambiguous and we're in a fold, shift to a different index.
+        idx_offset = int(not _fold and self.is_ambiguous(dt, idx))
+
+        return idx - idx_offset
+
+    def utcoffset(self, dt):
+        if dt is None:
+            return None
+
+        if not self._ttinfo_std:
+            return ZERO
+
+        return self._find_ttinfo(dt).delta
+
+    def dst(self, dt):
+        if dt is None:
+            return None
+
+        if not self._ttinfo_dst:
+            return ZERO
+
+        tti = self._find_ttinfo(dt)
+
+        if not tti.isdst:
+            return ZERO
+
+        # The documentation says that utcoffset()-dst() must
+        # be constant for every dt.
+        return tti.dstoffset
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        if not self._ttinfo_std or dt is None:
+            return None
+        return self._find_ttinfo(dt).abbr
+
+    def __eq__(self, other):
+        if not isinstance(other, tzfile):
+            return NotImplemented
+        return (self._trans_list == other._trans_list and
+                self._trans_idx == other._trans_idx and
+                self._ttinfo_list == other._ttinfo_list)
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
+
+    def __reduce__(self):
+        return self.__reduce_ex__(None)
+
+    def __reduce_ex__(self, protocol):
+        return (self.__class__, (None, self._filename), self.__dict__)
+
+
+class tzrange(tzrangebase):
+    """
+    The ``tzrange`` object is a time zone specified by a set of offsets and
+    abbreviations, equivalent to the way the ``TZ`` variable can be specified
+    in POSIX-like systems, but using Python delta objects to specify DST
+    start, end and offsets.
+
+    :param stdabbr:
+        The abbreviation for standard time (e.g. ``'EST'``).
+
+    :param stdoffset:
+        An integer or :class:`datetime.timedelta` object or equivalent
+        specifying the base offset from UTC.
+
+        If unspecified, +00:00 is used.
+
+    :param dstabbr:
+        The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).
+
+        If specified, with no other DST information, DST is assumed to occur
+        and the default behavior or ``dstoffset``, ``start`` and ``end`` is
+        used. If unspecified and no other DST information is specified, it
+        is assumed that this zone has no DST.
+
+        If this is unspecified and other DST information is *is* specified,
+        DST occurs in the zone but the time zone abbreviation is left
+        unchanged.
+
+    :param dstoffset:
+        A an integer or :class:`datetime.timedelta` object or equivalent
+        specifying the UTC offset during DST. If unspecified and any other DST
+        information is specified, it is assumed to be the STD offset +1 hour.
+
+    :param start:
+        A :class:`relativedelta.relativedelta` object or equivalent specifying
+        the time and time of year that daylight savings time starts. To
+        specify, for example, that DST starts at 2AM on the 2nd Sunday in
+        March, pass:
+
+            ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
+
+        If unspecified and any other DST information is specified, the default
+        value is 2 AM on the first Sunday in April.
+
+    :param end:
+        A :class:`relativedelta.relativedelta` object or equivalent
+        representing the time and time of year that daylight savings time
+        ends, with the same specification method as in ``start``. One note is
+        that this should point to the first time in the *standard* zone, so if
+        a transition occurs at 2AM in the DST zone and the clocks are set back
+        1 hour to 1AM, set the ``hours`` parameter to +1.
+
+
+    **Examples:**
+
+    .. testsetup:: tzrange
+
+        from dateutil.tz import tzrange, tzstr
+
+    .. doctest:: tzrange
+
+        >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")
+        True
+
+        >>> from dateutil.relativedelta import *
+        >>> range1 = tzrange("EST", -18000, "EDT")
+        >>> range2 = 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)))
+        >>> tzstr('EST5EDT') == range1 == range2
+        True
+
+    """
+    def __init__(self, stdabbr, stdoffset=None,
+                 dstabbr=None, dstoffset=None,
+                 start=None, end=None):
+
+        global relativedelta
+        from dateutil import relativedelta
+
+        self._std_abbr = stdabbr
+        self._dst_abbr = dstabbr
+
+        try:
+            stdoffset = stdoffset.total_seconds()
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            dstoffset = dstoffset.total_seconds()
+        except (TypeError, AttributeError):
+            pass
+
+        if stdoffset is not None:
+            self._std_offset = datetime.timedelta(seconds=stdoffset)
+        else:
+            self._std_offset = ZERO
+
+        if dstoffset is not None:
+            self._dst_offset = datetime.timedelta(seconds=dstoffset)
+        elif dstabbr and stdoffset is not None:
+            self._dst_offset = self._std_offset + datetime.timedelta(hours=+1)
+        else:
+            self._dst_offset = ZERO
+
+        if dstabbr and start is None:
+            self._start_delta = relativedelta.relativedelta(
+                hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
+        else:
+            self._start_delta = start
+
+        if dstabbr and end is None:
+            self._end_delta = relativedelta.relativedelta(
+                hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
+        else:
+            self._end_delta = end
+
+        self._dst_base_offset_ = self._dst_offset - self._std_offset
+        self.hasdst = bool(self._start_delta)
+
+    def transitions(self, year):
+        """
+        For a given year, get the DST on and off transition times, expressed
+        always on the standard time side. For zones with no transitions, this
+        function returns ``None``.
+
+        :param year:
+            The year whose transitions you would like to query.
+
+        :return:
+            Returns a :class:`tuple` of :class:`datetime.datetime` objects,
+            ``(dston, dstoff)`` for zones with an annual DST transition, or
+            ``None`` for fixed offset zones.
+        """
+        if not self.hasdst:
+            return None
+
+        base_year = datetime.datetime(year, 1, 1)
+
+        start = base_year + self._start_delta
+        end = base_year + self._end_delta
+
+        return (start, end)
+
+    def __eq__(self, other):
+        if not isinstance(other, tzrange):
+            return NotImplemented
+
+        return (self._std_abbr == other._std_abbr and
+                self._dst_abbr == other._dst_abbr and
+                self._std_offset == other._std_offset and
+                self._dst_offset == other._dst_offset and
+                self._start_delta == other._start_delta and
+                self._end_delta == other._end_delta)
+
+    @property
+    def _dst_base_offset(self):
+        return self._dst_base_offset_
+
+
+@six.add_metaclass(_TzStrFactory)
+class tzstr(tzrange):
+    """
+    ``tzstr`` objects are time zone objects specified by a time-zone string as
+    it would be passed to a ``TZ`` variable on POSIX-style systems (see
+    the `GNU C Library: TZ Variable`_ for more details).
+
+    There is one notable exception, which is that POSIX-style time zones use an
+    inverted offset format, so normally ``GMT+3`` would be parsed as an offset
+    3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an
+    offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX
+    behavior, pass a ``True`` value to ``posix_offset``.
+
+    The :class:`tzrange` object provides the same functionality, but is
+    specified using :class:`relativedelta.relativedelta` objects. rather than
+    strings.
+
+    :param s:
+        A time zone string in ``TZ`` variable format. This can be a
+        :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:
+        :class:`unicode`) or a stream emitting unicode characters
+        (e.g. :class:`StringIO`).
+
+    :param posix_offset:
+        Optional. If set to ``True``, interpret strings such as ``GMT+3`` or
+        ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the
+        POSIX standard.
+
+    .. caution::
+
+        Prior to version 2.7.0, this function also supported time zones
+        in the format:
+
+            * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``
+            * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``
+
+        This format is non-standard and has been deprecated; this function
+        will raise a :class:`DeprecatedTZFormatWarning` until
+        support is removed in a future version.
+
+    .. _`GNU C Library: TZ Variable`:
+        https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
+    """
+    def __init__(self, s, posix_offset=False):
+        global parser
+        from dateutil.parser import _parser as parser
+
+        self._s = s
+
+        res = parser._parsetz(s)
+        if res is None or res.any_unused_tokens:
+            raise ValueError("unknown string format")
+
+        # Here we break the compatibility with the TZ variable handling.
+        # GMT-3 actually *means* the timezone -3.
+        if res.stdabbr in ("GMT", "UTC") and not posix_offset:
+            res.stdoffset *= -1
+
+        # We must initialize it first, since _delta() needs
+        # _std_offset and _dst_offset set. Use False in start/end
+        # to avoid building it two times.
+        tzrange.__init__(self, res.stdabbr, res.stdoffset,
+                         res.dstabbr, res.dstoffset,
+                         start=False, end=False)
+
+        if not res.dstabbr:
+            self._start_delta = None
+            self._end_delta = None
+        else:
+            self._start_delta = self._delta(res.start)
+            if self._start_delta:
+                self._end_delta = self._delta(res.end, isend=1)
+
+        self.hasdst = bool(self._start_delta)
+
+    def _delta(self, x, isend=0):
+        from dateutil import relativedelta
+        kwargs = {}
+        if x.month is not None:
+            kwargs["month"] = x.month
+            if x.weekday is not None:
+                kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
+                if x.week > 0:
+                    kwargs["day"] = 1
+                else:
+                    kwargs["day"] = 31
+            elif x.day:
+                kwargs["day"] = x.day
+        elif x.yday is not None:
+            kwargs["yearday"] = x.yday
+        elif x.jyday is not None:
+            kwargs["nlyearday"] = x.jyday
+        if not kwargs:
+            # Default is to start on first sunday of april, and end
+            # on last sunday of october.
+            if not isend:
+                kwargs["month"] = 4
+                kwargs["day"] = 1
+                kwargs["weekday"] = relativedelta.SU(+1)
+            else:
+                kwargs["month"] = 10
+                kwargs["day"] = 31
+                kwargs["weekday"] = relativedelta.SU(-1)
+        if x.time is not None:
+            kwargs["seconds"] = x.time
+        else:
+            # Default is 2AM.
+            kwargs["seconds"] = 7200
+        if isend:
+            # Convert to standard time, to follow the documented way
+            # of working with the extra hour. See the documentation
+            # of the tzinfo class.
+            delta = self._dst_offset - self._std_offset
+            kwargs["seconds"] -= delta.seconds + delta.days * 86400
+        return relativedelta.relativedelta(**kwargs)
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, repr(self._s))
+
+
+class _tzicalvtzcomp(object):
+    def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
+                 tzname=None, rrule=None):
+        self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
+        self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
+        self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom
+        self.isdst = isdst
+        self.tzname = tzname
+        self.rrule = rrule
+
+
+class _tzicalvtz(_tzinfo):
+    def __init__(self, tzid, comps=[]):
+        super(_tzicalvtz, self).__init__()
+
+        self._tzid = tzid
+        self._comps = comps
+        self._cachedate = []
+        self._cachecomp = []
+        self._cache_lock = _thread.allocate_lock()
+
+    def _find_comp(self, dt):
+        if len(self._comps) == 1:
+            return self._comps[0]
+
+        dt = dt.replace(tzinfo=None)
+
+        try:
+            with self._cache_lock:
+                return self._cachecomp[self._cachedate.index(
+                    (dt, self._fold(dt)))]
+        except ValueError:
+            pass
+
+        lastcompdt = None
+        lastcomp = None
+
+        for comp in self._comps:
+            compdt = self._find_compdt(comp, dt)
+
+            if compdt and (not lastcompdt or lastcompdt < compdt):
+                lastcompdt = compdt
+                lastcomp = comp
+
+        if not lastcomp:
+            # RFC says nothing about what to do when a given
+            # time is before the first onset date. We'll look for the
+            # first standard component, or the first component, if
+            # none is found.
+            for comp in self._comps:
+                if not comp.isdst:
+                    lastcomp = comp
+                    break
+            else:
+                lastcomp = comp[0]
+
+        with self._cache_lock:
+            self._cachedate.insert(0, (dt, self._fold(dt)))
+            self._cachecomp.insert(0, lastcomp)
+
+            if len(self._cachedate) > 10:
+                self._cachedate.pop()
+                self._cachecomp.pop()
+
+        return lastcomp
+
+    def _find_compdt(self, comp, dt):
+        if comp.tzoffsetdiff < ZERO and self._fold(dt):
+            dt -= comp.tzoffsetdiff
+
+        compdt = comp.rrule.before(dt, inc=True)
+
+        return compdt
+
+    def utcoffset(self, dt):
+        if dt is None:
+            return None
+
+        return self._find_comp(dt).tzoffsetto
+
+    def dst(self, dt):
+        comp = self._find_comp(dt)
+        if comp.isdst:
+            return comp.tzoffsetdiff
+        else:
+            return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return self._find_comp(dt).tzname
+
+    def __repr__(self):
+        return "<tzicalvtz %s>" % repr(self._tzid)
+
+    __reduce__ = object.__reduce__
+
+
+class tzical(object):
+    """
+    This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure
+    as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects.
+
+    :param `fileobj`:
+        A file or stream in iCalendar format, which should be UTF-8 encoded
+        with CRLF endings.
+
+    .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545
+    """
+    def __init__(self, fileobj):
+        global rrule
+        from dateutil import rrule
+
+        if isinstance(fileobj, string_types):
+            self._s = fileobj
+            # ical should be encoded in UTF-8 with CRLF
+            fileobj = open(fileobj, 'r')
+        else:
+            self._s = getattr(fileobj, 'name', repr(fileobj))
+            fileobj = _ContextWrapper(fileobj)
+
+        self._vtz = {}
+
+        with fileobj as fobj:
+            self._parse_rfc(fobj.read())
+
+    def keys(self):
+        """
+        Retrieves the available time zones as a list.
+        """
+        return list(self._vtz.keys())
+
+    def get(self, tzid=None):
+        """
+        Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``.
+
+        :param tzid:
+            If there is exactly one time zone available, omitting ``tzid``
+            or passing :py:const:`None` value returns it. Otherwise a valid
+            key (which can be retrieved from :func:`keys`) is required.
+
+        :raises ValueError:
+            Raised if ``tzid`` is not specified but there are either more
+            or fewer than 1 zone defined.
+
+        :returns:
+            Returns either a :py:class:`datetime.tzinfo` object representing
+            the relevant time zone or :py:const:`None` if the ``tzid`` was
+            not found.
+        """
+        if tzid is None:
+            if len(self._vtz) == 0:
+                raise ValueError("no timezones defined")
+            elif len(self._vtz) > 1:
+                raise ValueError("more than one timezone available")
+            tzid = next(iter(self._vtz))
+
+        return self._vtz.get(tzid)
+
+    def _parse_offset(self, s):
+        s = s.strip()
+        if not s:
+            raise ValueError("empty offset")
+        if s[0] in ('+', '-'):
+            signal = (-1, +1)[s[0] == '+']
+            s = s[1:]
+        else:
+            signal = +1
+        if len(s) == 4:
+            return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal
+        elif len(s) == 6:
+            return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal
+        else:
+            raise ValueError("invalid offset: " + s)
+
+    def _parse_rfc(self, s):
+        lines = s.splitlines()
+        if not lines:
+            raise ValueError("empty string")
+
+        # Unfold
+        i = 0
+        while i < len(lines):
+            line = lines[i].rstrip()
+            if not line:
+                del lines[i]
+            elif i > 0 and line[0] == " ":
+                lines[i-1] += line[1:]
+                del lines[i]
+            else:
+                i += 1
+
+        tzid = None
+        comps = []
+        invtz = False
+        comptype = None
+        for line in lines:
+            if not line:
+                continue
+            name, value = line.split(':', 1)
+            parms = name.split(';')
+            if not parms:
+                raise ValueError("empty property name")
+            name = parms[0].upper()
+            parms = parms[1:]
+            if invtz:
+                if name == "BEGIN":
+                    if value in ("STANDARD", "DAYLIGHT"):
+                        # Process component
+                        pass
+                    else:
+                        raise ValueError("unknown component: "+value)
+                    comptype = value
+                    founddtstart = False
+                    tzoffsetfrom = None
+                    tzoffsetto = None
+                    rrulelines = []
+                    tzname = None
+                elif name == "END":
+                    if value == "VTIMEZONE":
+                        if comptype:
+                            raise ValueError("component not closed: "+comptype)
+                        if not tzid:
+                            raise ValueError("mandatory TZID not found")
+                        if not comps:
+                            raise ValueError(
+                                "at least one component is needed")
+                        # Process vtimezone
+                        self._vtz[tzid] = _tzicalvtz(tzid, comps)
+                        invtz = False
+                    elif value == comptype:
+                        if not founddtstart:
+                            raise ValueError("mandatory DTSTART not found")
+                        if tzoffsetfrom is None:
+                            raise ValueError(
+                                "mandatory TZOFFSETFROM not found")
+                        if tzoffsetto is None:
+                            raise ValueError(
+                                "mandatory TZOFFSETFROM not found")
+                        # Process component
+                        rr = None
+                        if rrulelines:
+                            rr = rrule.rrulestr("\n".join(rrulelines),
+                                                compatible=True,
+                                                ignoretz=True,
+                                                cache=True)
+                        comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
+                                              (comptype == "DAYLIGHT"),
+                                              tzname, rr)
+                        comps.append(comp)
+                        comptype = None
+                    else:
+                        raise ValueError("invalid component end: "+value)
+                elif comptype:
+                    if name == "DTSTART":
+                        # DTSTART in VTIMEZONE takes a subset of valid RRULE
+                        # values under RFC 5545.
+                        for parm in parms:
+                            if parm != 'VALUE=DATE-TIME':
+                                msg = ('Unsupported DTSTART param in ' +
+                                       'VTIMEZONE: ' + parm)
+                                raise ValueError(msg)
+                        rrulelines.append(line)
+                        founddtstart = True
+                    elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
+                        rrulelines.append(line)
+                    elif name == "TZOFFSETFROM":
+                        if parms:
+                            raise ValueError(
+                                "unsupported %s parm: %s " % (name, parms[0]))
+                        tzoffsetfrom = self._parse_offset(value)
+                    elif name == "TZOFFSETTO":
+                        if parms:
+                            raise ValueError(
+                                "unsupported TZOFFSETTO parm: "+parms[0])
+                        tzoffsetto = self._parse_offset(value)
+                    elif name == "TZNAME":
+                        if parms:
+                            raise ValueError(
+                                "unsupported TZNAME parm: "+parms[0])
+                        tzname = value
+                    elif name == "COMMENT":
+                        pass
+                    else:
+                        raise ValueError("unsupported property: "+name)
+                else:
+                    if name == "TZID":
+                        if parms:
+                            raise ValueError(
+                                "unsupported TZID parm: "+parms[0])
+                        tzid = value
+                    elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
+                        pass
+                    else:
+                        raise ValueError("unsupported property: "+name)
+            elif name == "BEGIN" and value == "VTIMEZONE":
+                tzid = None
+                comps = []
+                invtz = True
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, repr(self._s))
+
+
+if sys.platform != "win32":
+    TZFILES = ["/etc/localtime", "localtime"]
+    TZPATHS = ["/usr/share/zoneinfo",
+               "/usr/lib/zoneinfo",
+               "/usr/share/lib/zoneinfo",
+               "/etc/zoneinfo"]
+else:
+    TZFILES = []
+    TZPATHS = []
+
+
+def __get_gettz():
+    tzlocal_classes = (tzlocal,)
+    if tzwinlocal is not None:
+        tzlocal_classes += (tzwinlocal,)
+
+    class GettzFunc(object):
+        """
+        Retrieve a time zone object from a string representation
+
+        This function is intended to retrieve the :py:class:`tzinfo` subclass
+        that best represents the time zone that would be used if a POSIX
+        `TZ variable`_ were set to the same value.
+
+        If no argument or an empty string is passed to ``gettz``, local time
+        is returned:
+
+        .. code-block:: python3
+
+            >>> gettz()
+            tzfile('/etc/localtime')
+
+        This function is also the preferred way to map IANA tz database keys
+        to :class:`tzfile` objects:
+
+        .. code-block:: python3
+
+            >>> gettz('Pacific/Kiritimati')
+            tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
+
+        On Windows, the standard is extended to include the Windows-specific
+        zone names provided by the operating system:
+
+        .. code-block:: python3
+
+            >>> gettz('Egypt Standard Time')
+            tzwin('Egypt Standard Time')
+
+        Passing a GNU ``TZ`` style string time zone specification returns a
+        :class:`tzstr` object:
+
+        .. code-block:: python3
+
+            >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
+            tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
+
+        :param name:
+            A time zone name (IANA, or, on Windows, Windows keys), location of
+            a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone
+            specifier. An empty string, no argument or ``None`` is interpreted
+            as local time.
+
+        :return:
+            Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
+            subclasses.
+
+        .. versionchanged:: 2.7.0
+
+            After version 2.7.0, any two calls to ``gettz`` using the same
+            input strings will return the same object:
+
+            .. code-block:: python3
+
+                >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
+                True
+
+            In addition to improving performance, this ensures that
+            `"same zone" semantics`_ are used for datetimes in the same zone.
+
+
+        .. _`TZ variable`:
+            https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
+
+        .. _`"same zone" semantics`:
+            https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
+        """
+        def __init__(self):
+
+            self.__instances = {}
+            self._cache_lock = _thread.allocate_lock()
+
+        def __call__(self, name=None):
+            with self._cache_lock:
+                rv = self.__instances.get(name, None)
+
+                if rv is None:
+                    rv = self.nocache(name=name)
+                    if not (name is None or isinstance(rv, tzlocal_classes)):
+                        # tzlocal is slightly more complicated than the other
+                        # time zone providers because it depends on environment
+                        # at construction time, so don't cache that.
+                        self.__instances[name] = rv
+
+            return rv
+
+        def cache_clear(self):
+            with self._cache_lock:
+                self.__instances = {}
+
+        @staticmethod
+        def nocache(name=None):
+            """A non-cached version of gettz"""
+            tz = None
+            if not name:
+                try:
+                    name = os.environ["TZ"]
+                except KeyError:
+                    pass
+            if name is None or name == ":":
+                for filepath in TZFILES:
+                    if not os.path.isabs(filepath):
+                        filename = filepath
+                        for path in TZPATHS:
+                            filepath = os.path.join(path, filename)
+                            if os.path.isfile(filepath):
+                                break
+                        else:
+                            continue
+                    if os.path.isfile(filepath):
+                        try:
+                            tz = tzfile(filepath)
+                            break
+                        except (IOError, OSError, ValueError):
+                            pass
+                else:
+                    tz = tzlocal()
+            else:
+                if name.startswith(":"):
+                    name = name[1:]
+                if os.path.isabs(name):
+                    if os.path.isfile(name):
+                        tz = tzfile(name)
+                    else:
+                        tz = None
+                else:
+                    for path in TZPATHS:
+                        filepath = os.path.join(path, name)
+                        if not os.path.isfile(filepath):
+                            filepath = filepath.replace(' ', '_')
+                            if not os.path.isfile(filepath):
+                                continue
+                        try:
+                            tz = tzfile(filepath)
+                            break
+                        except (IOError, OSError, ValueError):
+                            pass
+                    else:
+                        tz = None
+                        if tzwin is not None:
+                            try:
+                                tz = tzwin(name)
+                            except WindowsError:
+                                tz = None
+
+                        if not tz:
+                            from dateutil.zoneinfo import get_zonefile_instance
+                            tz = get_zonefile_instance().get(name)
+
+                        if not tz:
+                            for c in name:
+                                # name is not a tzstr unless it has at least
+                                # one offset. For short values of "name", an
+                                # explicit for loop seems to be the fastest way
+                                # To determine if a string contains a digit
+                                if c in "0123456789":
+                                    try:
+                                        tz = tzstr(name)
+                                    except ValueError:
+                                        pass
+                                    break
+                            else:
+                                if name in ("GMT", "UTC"):
+                                    tz = tzutc()
+                                elif name in time.tzname:
+                                    tz = tzlocal()
+            return tz
+
+    return GettzFunc()
+
+
+gettz = __get_gettz()
+del __get_gettz
+
+
+def datetime_exists(dt, tz=None):
+    """
+    Given a datetime and a time zone, determine whether or not a given datetime
+    would fall in a gap.
+
+    :param dt:
+        A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
+        is provided.)
+
+    :param tz:
+        A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
+        ``None`` or not provided, the datetime's own time zone will be used.
+
+    :return:
+        Returns a boolean value whether or not the "wall time" exists in
+        ``tz``.
+
+    .. versionadded:: 2.7.0
+    """
+    if tz is None:
+        if dt.tzinfo is None:
+            raise ValueError('Datetime is naive and no time zone provided.')
+        tz = dt.tzinfo
+
+    dt = dt.replace(tzinfo=None)
+
+    # This is essentially a test of whether or not the datetime can survive
+    # a round trip to UTC.
+    dt_rt = dt.replace(tzinfo=tz).astimezone(tzutc()).astimezone(tz)
+    dt_rt = dt_rt.replace(tzinfo=None)
+
+    return dt == dt_rt
+
+
+def datetime_ambiguous(dt, tz=None):
+    """
+    Given a datetime and a time zone, determine whether or not a given datetime
+    is ambiguous (i.e if there are two times differentiated only by their DST
+    status).
+
+    :param dt:
+        A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
+        is provided.)
+
+    :param tz:
+        A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
+        ``None`` or not provided, the datetime's own time zone will be used.
+
+    :return:
+        Returns a boolean value whether or not the "wall time" is ambiguous in
+        ``tz``.
+
+    .. versionadded:: 2.6.0
+    """
+    if tz is None:
+        if dt.tzinfo is None:
+            raise ValueError('Datetime is naive and no time zone provided.')
+
+        tz = dt.tzinfo
+
+    # If a time zone defines its own "is_ambiguous" function, we'll use that.
+    is_ambiguous_fn = getattr(tz, 'is_ambiguous', None)
+    if is_ambiguous_fn is not None:
+        try:
+            return tz.is_ambiguous(dt)
+        except Exception:
+            pass
+
+    # If it doesn't come out and tell us it's ambiguous, we'll just check if
+    # the fold attribute has any effect on this particular date and time.
+    dt = dt.replace(tzinfo=tz)
+    wall_0 = enfold(dt, fold=0)
+    wall_1 = enfold(dt, fold=1)
+
+    same_offset = wall_0.utcoffset() == wall_1.utcoffset()
+    same_dst = wall_0.dst() == wall_1.dst()
+
+    return not (same_offset and same_dst)
+
+
+def resolve_imaginary(dt):
+    """
+    Given a datetime that may be imaginary, return an existing datetime.
+
+    This function assumes that an imaginary datetime represents what the
+    wall time would be in a zone had the offset transition not occurred, so
+    it will always fall forward by the transition's change in offset.
+
+    .. doctest::
+
+        >>> from dateutil import tz
+        >>> from datetime import datetime
+        >>> NYC = tz.gettz('America/New_York')
+        >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC)))
+        2017-03-12 03:30:00-04:00
+
+        >>> KIR = tz.gettz('Pacific/Kiritimati')
+        >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR)))
+        1995-01-02 12:30:00+14:00
+
+    As a note, :func:`datetime.astimezone` is guaranteed to produce a valid,
+    existing datetime, so a round-trip to and from UTC is sufficient to get
+    an extant datetime, however, this generally "falls back" to an earlier time
+    rather than falling forward to the STD side (though no guarantees are made
+    about this behavior).
+
+    :param dt:
+        A :class:`datetime.datetime` which may or may not exist.
+
+    :return:
+        Returns an existing :class:`datetime.datetime`. If ``dt`` was not
+        imaginary, the datetime returned is guaranteed to be the same object
+        passed to the function.
+
+    .. versionadded:: 2.7.0
+    """
+    if dt.tzinfo is not None and not datetime_exists(dt):
+
+        curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset()
+        old_offset = (dt - datetime.timedelta(hours=24)).utcoffset()
+
+        dt += curr_offset - old_offset
+
+    return dt
+
+
+def _datetime_to_timestamp(dt):
+    """
+    Convert a :class:`datetime.datetime` object to an epoch timestamp in
+    seconds since January 1, 1970, ignoring the time zone.
+    """
+    return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
+
+
+class _ContextWrapper(object):
+    """
+    Class for wrapping contexts so that they are passed through in a
+    with statement.
+    """
+    def __init__(self, context):
+        self.context = context
+
+    def __enter__(self):
+        return self.context
+
+    def __exit__(*args, **kwargs):
+        pass
+
+# vim:ts=4:sw=4:et
diff --git a/resources/lib/libraries/dateutil/tz/win.py b/resources/lib/libraries/dateutil/tz/win.py
new file mode 100644
index 00000000..def4353a
--- /dev/null
+++ b/resources/lib/libraries/dateutil/tz/win.py
@@ -0,0 +1,331 @@
+# This code was originally contributed by Jeffrey Harris.
+import datetime
+import struct
+
+from six.moves import winreg
+from six import text_type
+
+try:
+    import ctypes
+    from ctypes import wintypes
+except ValueError:
+    # ValueError is raised on non-Windows systems for some horrible reason.
+    raise ImportError("Running tzwin on non-Windows system")
+
+from ._common import tzrangebase
+
+__all__ = ["tzwin", "tzwinlocal", "tzres"]
+
+ONEWEEK = datetime.timedelta(7)
+
+TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
+TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
+TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
+
+
+def _settzkeyname():
+    handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+    try:
+        winreg.OpenKey(handle, TZKEYNAMENT).Close()
+        TZKEYNAME = TZKEYNAMENT
+    except WindowsError:
+        TZKEYNAME = TZKEYNAME9X
+    handle.Close()
+    return TZKEYNAME
+
+
+TZKEYNAME = _settzkeyname()
+
+
+class tzres(object):
+    """
+    Class for accessing `tzres.dll`, which contains timezone name related
+    resources.
+
+    .. versionadded:: 2.5.0
+    """
+    p_wchar = ctypes.POINTER(wintypes.WCHAR)        # Pointer to a wide char
+
+    def __init__(self, tzres_loc='tzres.dll'):
+        # Load the user32 DLL so we can load strings from tzres
+        user32 = ctypes.WinDLL('user32')
+
+        # Specify the LoadStringW function
+        user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
+                                       wintypes.UINT,
+                                       wintypes.LPWSTR,
+                                       ctypes.c_int)
+
+        self.LoadStringW = user32.LoadStringW
+        self._tzres = ctypes.WinDLL(tzres_loc)
+        self.tzres_loc = tzres_loc
+
+    def load_name(self, offset):
+        """
+        Load a timezone name from a DLL offset (integer).
+
+        >>> from dateutil.tzwin import tzres
+        >>> tzr = tzres()
+        >>> print(tzr.load_name(112))
+        'Eastern Standard Time'
+
+        :param offset:
+            A positive integer value referring to a string from the tzres dll.
+
+        ..note:
+            Offsets found in the registry are generally of the form
+            `@tzres.dll,-114`. The offset in this case if 114, not -114.
+
+        """
+        resource = self.p_wchar()
+        lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
+        nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0)
+        return resource[:nchar]
+
+    def name_from_string(self, tzname_str):
+        """
+        Parse strings as returned from the Windows registry into the time zone
+        name as defined in the registry.
+
+        >>> from dateutil.tzwin import tzres
+        >>> tzr = tzres()
+        >>> print(tzr.name_from_string('@tzres.dll,-251'))
+        'Dateline Daylight Time'
+        >>> print(tzr.name_from_string('Eastern Standard Time'))
+        'Eastern Standard Time'
+
+        :param tzname_str:
+            A timezone name string as returned from a Windows registry key.
+
+        :return:
+            Returns the localized timezone string from tzres.dll if the string
+            is of the form `@tzres.dll,-offset`, else returns the input string.
+        """
+        if not tzname_str.startswith('@'):
+            return tzname_str
+
+        name_splt = tzname_str.split(',-')
+        try:
+            offset = int(name_splt[1])
+        except:
+            raise ValueError("Malformed timezone string.")
+
+        return self.load_name(offset)
+
+
+class tzwinbase(tzrangebase):
+    """tzinfo class based on win32's timezones available in the registry."""
+    def __init__(self):
+        raise NotImplementedError('tzwinbase is an abstract base class')
+
+    def __eq__(self, other):
+        # Compare on all relevant dimensions, including name.
+        if not isinstance(other, tzwinbase):
+            return NotImplemented
+
+        return  (self._std_offset == other._std_offset and
+                 self._dst_offset == other._dst_offset and
+                 self._stddayofweek == other._stddayofweek and
+                 self._dstdayofweek == other._dstdayofweek and
+                 self._stdweeknumber == other._stdweeknumber and
+                 self._dstweeknumber == other._dstweeknumber and
+                 self._stdhour == other._stdhour and
+                 self._dsthour == other._dsthour and
+                 self._stdminute == other._stdminute and
+                 self._dstminute == other._dstminute and
+                 self._std_abbr == other._std_abbr and
+                 self._dst_abbr == other._dst_abbr)
+
+    @staticmethod
+    def list():
+        """Return a list of all time zones known to the system."""
+        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
+            with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
+                result = [winreg.EnumKey(tzkey, i)
+                          for i in range(winreg.QueryInfoKey(tzkey)[0])]
+        return result
+
+    def display(self):
+        return self._display
+
+    def transitions(self, year):
+        """
+        For a given year, get the DST on and off transition times, expressed
+        always on the standard time side. For zones with no transitions, this
+        function returns ``None``.
+
+        :param year:
+            The year whose transitions you would like to query.
+
+        :return:
+            Returns a :class:`tuple` of :class:`datetime.datetime` objects,
+            ``(dston, dstoff)`` for zones with an annual DST transition, or
+            ``None`` for fixed offset zones.
+        """
+
+        if not self.hasdst:
+            return None
+
+        dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
+                               self._dsthour, self._dstminute,
+                               self._dstweeknumber)
+
+        dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
+                                self._stdhour, self._stdminute,
+                                self._stdweeknumber)
+
+        # Ambiguous dates default to the STD side
+        dstoff -= self._dst_base_offset
+
+        return dston, dstoff
+
+    def _get_hasdst(self):
+        return self._dstmonth != 0
+
+    @property
+    def _dst_base_offset(self):
+        return self._dst_base_offset_
+
+
+class tzwin(tzwinbase):
+
+    def __init__(self, name):
+        self._name = name
+
+        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
+            tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
+            with winreg.OpenKey(handle, tzkeyname) as tzkey:
+                keydict = valuestodict(tzkey)
+
+        self._std_abbr = keydict["Std"]
+        self._dst_abbr = keydict["Dlt"]
+
+        self._display = keydict["Display"]
+
+        # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
+        tup = struct.unpack("=3l16h", keydict["TZI"])
+        stdoffset = -tup[0]-tup[1]          # Bias + StandardBias * -1
+        dstoffset = stdoffset-tup[2]        # + DaylightBias * -1
+        self._std_offset = datetime.timedelta(minutes=stdoffset)
+        self._dst_offset = datetime.timedelta(minutes=dstoffset)
+
+        # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
+        # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
+        (self._stdmonth,
+         self._stddayofweek,   # Sunday = 0
+         self._stdweeknumber,  # Last = 5
+         self._stdhour,
+         self._stdminute) = tup[4:9]
+
+        (self._dstmonth,
+         self._dstdayofweek,   # Sunday = 0
+         self._dstweeknumber,  # Last = 5
+         self._dsthour,
+         self._dstminute) = tup[12:17]
+
+        self._dst_base_offset_ = self._dst_offset - self._std_offset
+        self.hasdst = self._get_hasdst()
+
+    def __repr__(self):
+        return "tzwin(%s)" % repr(self._name)
+
+    def __reduce__(self):
+        return (self.__class__, (self._name,))
+
+
+class tzwinlocal(tzwinbase):
+    def __init__(self):
+        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
+            with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
+                keydict = valuestodict(tzlocalkey)
+
+            self._std_abbr = keydict["StandardName"]
+            self._dst_abbr = keydict["DaylightName"]
+
+            try:
+                tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME,
+                                                          sn=self._std_abbr)
+                with winreg.OpenKey(handle, tzkeyname) as tzkey:
+                    _keydict = valuestodict(tzkey)
+                    self._display = _keydict["Display"]
+            except OSError:
+                self._display = None
+
+        stdoffset = -keydict["Bias"]-keydict["StandardBias"]
+        dstoffset = stdoffset-keydict["DaylightBias"]
+
+        self._std_offset = datetime.timedelta(minutes=stdoffset)
+        self._dst_offset = datetime.timedelta(minutes=dstoffset)
+
+        # For reasons unclear, in this particular key, the day of week has been
+        # moved to the END of the SYSTEMTIME structure.
+        tup = struct.unpack("=8h", keydict["StandardStart"])
+
+        (self._stdmonth,
+         self._stdweeknumber,  # Last = 5
+         self._stdhour,
+         self._stdminute) = tup[1:5]
+
+        self._stddayofweek = tup[7]
+
+        tup = struct.unpack("=8h", keydict["DaylightStart"])
+
+        (self._dstmonth,
+         self._dstweeknumber,  # Last = 5
+         self._dsthour,
+         self._dstminute) = tup[1:5]
+
+        self._dstdayofweek = tup[7]
+
+        self._dst_base_offset_ = self._dst_offset - self._std_offset
+        self.hasdst = self._get_hasdst()
+
+    def __repr__(self):
+        return "tzwinlocal()"
+
+    def __str__(self):
+        # str will return the standard name, not the daylight name.
+        return "tzwinlocal(%s)" % repr(self._std_abbr)
+
+    def __reduce__(self):
+        return (self.__class__, ())
+
+
+def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
+    """ dayofweek == 0 means Sunday, whichweek 5 means last instance """
+    first = datetime.datetime(year, month, 1, hour, minute)
+
+    # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
+    # Because 7 % 7 = 0
+    weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
+    wd = weekdayone + ((whichweek - 1) * ONEWEEK)
+    if (wd.month != month):
+        wd -= ONEWEEK
+
+    return wd
+
+
+def valuestodict(key):
+    """Convert a registry key's values to a dictionary."""
+    dout = {}
+    size = winreg.QueryInfoKey(key)[1]
+    tz_res = None
+
+    for i in range(size):
+        key_name, value, dtype = winreg.EnumValue(key, i)
+        if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
+            # If it's a DWORD (32-bit integer), it's stored as unsigned - convert
+            # that to a proper signed integer
+            if value & (1 << 31):
+                value = value - (1 << 32)
+        elif dtype == winreg.REG_SZ:
+            # If it's a reference to the tzres DLL, load the actual string
+            if value.startswith('@tzres'):
+                tz_res = tz_res or tzres()
+                value = tz_res.name_from_string(value)
+
+            value = value.rstrip('\x00')    # Remove trailing nulls
+
+        dout[key_name] = value
+
+    return dout
diff --git a/resources/lib/libraries/dateutil/tzwin.py b/resources/lib/libraries/dateutil/tzwin.py
new file mode 100644
index 00000000..cebc673e
--- /dev/null
+++ b/resources/lib/libraries/dateutil/tzwin.py
@@ -0,0 +1,2 @@
+# tzwin has moved to dateutil.tz.win
+from .tz.win import *
diff --git a/resources/lib/libraries/dateutil/utils.py b/resources/lib/libraries/dateutil/utils.py
new file mode 100644
index 00000000..ebcce6aa
--- /dev/null
+++ b/resources/lib/libraries/dateutil/utils.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers general convenience and utility functions for dealing with
+datetimes.
+
+.. versionadded:: 2.7.0
+"""
+from __future__ import unicode_literals
+
+from datetime import datetime, time
+
+
+def today(tzinfo=None):
+    """
+    Returns a :py:class:`datetime` representing the current day at midnight
+
+    :param tzinfo:
+        The time zone to attach (also used to determine the current day).
+
+    :return:
+        A :py:class:`datetime.datetime` object representing the current day
+        at midnight.
+    """
+
+    dt = datetime.now(tzinfo)
+    return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))
+
+
+def default_tzinfo(dt, tzinfo):
+    """
+    Sets the the ``tzinfo`` parameter on naive datetimes only
+
+    This is useful for example when you are provided a datetime that may have
+    either an implicit or explicit time zone, such as when parsing a time zone
+    string.
+
+    .. doctest::
+
+        >>> from dateutil.tz import tzoffset
+        >>> from dateutil.parser import parse
+        >>> from dateutil.utils import default_tzinfo
+        >>> dflt_tz = tzoffset("EST", -18000)
+        >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz))
+        2014-01-01 12:30:00+00:00
+        >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz))
+        2014-01-01 12:30:00-05:00
+
+    :param dt:
+        The datetime on which to replace the time zone
+
+    :param tzinfo:
+        The :py:class:`datetime.tzinfo` subclass instance to assign to
+        ``dt`` if (and only if) it is naive.
+
+    :return:
+        Returns an aware :py:class:`datetime.datetime`.
+    """
+    if dt.tzinfo is not None:
+        return dt
+    else:
+        return dt.replace(tzinfo=tzinfo)
+
+
+def within_delta(dt1, dt2, delta):
+    """
+    Useful for comparing two datetimes that may a negilible difference
+    to be considered equal.
+    """
+    delta = abs(delta)
+    difference = dt1 - dt2
+    return -delta <= difference <= delta
diff --git a/resources/lib/libraries/dateutil/zoneinfo/__init__.py b/resources/lib/libraries/dateutil/zoneinfo/__init__.py
new file mode 100644
index 00000000..34f11ad6
--- /dev/null
+++ b/resources/lib/libraries/dateutil/zoneinfo/__init__.py
@@ -0,0 +1,167 @@
+# -*- coding: utf-8 -*-
+import warnings
+import json
+
+from tarfile import TarFile
+from pkgutil import get_data
+from io import BytesIO
+
+from dateutil.tz import tzfile as _tzfile
+
+__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]
+
+ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
+METADATA_FN = 'METADATA'
+
+
+class tzfile(_tzfile):
+    def __reduce__(self):
+        return (gettz, (self._filename,))
+
+
+def getzoneinfofile_stream():
+    try:
+        return BytesIO(get_data(__name__, ZONEFILENAME))
+    except IOError as e:  # TODO  switch to FileNotFoundError?
+        warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
+        return None
+
+
+class ZoneInfoFile(object):
+    def __init__(self, zonefile_stream=None):
+        if zonefile_stream is not None:
+            with TarFile.open(fileobj=zonefile_stream) as tf:
+                self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
+                              for zf in tf.getmembers()
+                              if zf.isfile() and zf.name != METADATA_FN}
+                # deal with links: They'll point to their parent object. Less
+                # waste of memory
+                links = {zl.name: self.zones[zl.linkname]
+                         for zl in tf.getmembers() if
+                         zl.islnk() or zl.issym()}
+                self.zones.update(links)
+                try:
+                    metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
+                    metadata_str = metadata_json.read().decode('UTF-8')
+                    self.metadata = json.loads(metadata_str)
+                except KeyError:
+                    # no metadata in tar file
+                    self.metadata = None
+        else:
+            self.zones = {}
+            self.metadata = None
+
+    def get(self, name, default=None):
+        """
+        Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
+        for retrieving zones from the zone dictionary.
+
+        :param name:
+            The name of the zone to retrieve. (Generally IANA zone names)
+
+        :param default:
+            The value to return in the event of a missing key.
+
+        .. versionadded:: 2.6.0
+
+        """
+        return self.zones.get(name, default)
+
+
+# The current API has gettz as a module function, although in fact it taps into
+# a stateful class. So as a workaround for now, without changing the API, we
+# will create a new "global" class instance the first time a user requests a
+# timezone. Ugly, but adheres to the api.
+#
+# TODO: Remove after deprecation period.
+_CLASS_ZONE_INSTANCE = []
+
+
+def get_zonefile_instance(new_instance=False):
+    """
+    This is a convenience function which provides a :class:`ZoneInfoFile`
+    instance using the data provided by the ``dateutil`` package. By default, it
+    caches a single instance of the ZoneInfoFile object and returns that.
+
+    :param new_instance:
+        If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
+        used as the cached instance for the next call. Otherwise, new instances
+        are created only as necessary.
+
+    :return:
+        Returns a :class:`ZoneInfoFile` object.
+
+    .. versionadded:: 2.6
+    """
+    if new_instance:
+        zif = None
+    else:
+        zif = getattr(get_zonefile_instance, '_cached_instance', None)
+
+    if zif is None:
+        zif = ZoneInfoFile(getzoneinfofile_stream())
+
+        get_zonefile_instance._cached_instance = zif
+
+    return zif
+
+
+def gettz(name):
+    """
+    This retrieves a time zone from the local zoneinfo tarball that is packaged
+    with dateutil.
+
+    :param name:
+        An IANA-style time zone name, as found in the zoneinfo file.
+
+    :return:
+        Returns a :class:`dateutil.tz.tzfile` time zone object.
+
+    .. warning::
+        It is generally inadvisable to use this function, and it is only
+        provided for API compatibility with earlier versions. This is *not*
+        equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
+        time zone based on the inputs, favoring system zoneinfo. This is ONLY
+        for accessing the dateutil-specific zoneinfo (which may be out of
+        date compared to the system zoneinfo).
+
+    .. deprecated:: 2.6
+        If you need to use a specific zoneinfofile over the system zoneinfo,
+        instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
+        :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
+
+        Use :func:`get_zonefile_instance` to retrieve an instance of the
+        dateutil-provided zoneinfo.
+    """
+    warnings.warn("zoneinfo.gettz() will be removed in future versions, "
+                  "to use the dateutil-provided zoneinfo files, instantiate a "
+                  "ZoneInfoFile object and use ZoneInfoFile.zones.get() "
+                  "instead. See the documentation for details.",
+                  DeprecationWarning)
+
+    if len(_CLASS_ZONE_INSTANCE) == 0:
+        _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
+    return _CLASS_ZONE_INSTANCE[0].zones.get(name)
+
+
+def gettz_db_metadata():
+    """ Get the zonefile metadata
+
+    See `zonefile_metadata`_
+
+    :returns:
+        A dictionary with the database metadata
+
+    .. deprecated:: 2.6
+        See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
+        query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
+    """
+    warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
+                  "versions, to use the dateutil-provided zoneinfo files, "
+                  "ZoneInfoFile object and query the 'metadata' attribute "
+                  "instead. See the documentation for details.",
+                  DeprecationWarning)
+
+    if len(_CLASS_ZONE_INSTANCE) == 0:
+        _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
+    return _CLASS_ZONE_INSTANCE[0].metadata
diff --git a/resources/lib/libraries/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz b/resources/lib/libraries/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..e86b54fe2884553b2b5e3c7a453b4046a919a800
GIT binary patch
literal 139130
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
E4~rF|;{X5v

literal 0
HcmV?d00001

diff --git a/resources/lib/libraries/dateutil/zoneinfo/rebuild.py b/resources/lib/libraries/dateutil/zoneinfo/rebuild.py
new file mode 100644
index 00000000..78f0d1a0
--- /dev/null
+++ b/resources/lib/libraries/dateutil/zoneinfo/rebuild.py
@@ -0,0 +1,53 @@
+import logging
+import os
+import tempfile
+import shutil
+import json
+from subprocess import check_call
+from tarfile import TarFile
+
+from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
+
+
+def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
+    """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
+
+    filename is the timezone tarball from ``ftp.iana.org/tz``.
+
+    """
+    tmpdir = tempfile.mkdtemp()
+    zonedir = os.path.join(tmpdir, "zoneinfo")
+    moduledir = os.path.dirname(__file__)
+    try:
+        with TarFile.open(filename) as tf:
+            for name in zonegroups:
+                tf.extract(name, tmpdir)
+            filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
+            try:
+                check_call(["zic", "-d", zonedir] + filepaths)
+            except OSError as e:
+                _print_on_nosuchfile(e)
+                raise
+        # write metadata file
+        with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
+            json.dump(metadata, f, indent=4, sort_keys=True)
+        target = os.path.join(moduledir, ZONEFILENAME)
+        with TarFile.open(target, "w:%s" % format) as tf:
+            for entry in os.listdir(zonedir):
+                entrypath = os.path.join(zonedir, entry)
+                tf.add(entrypath, entry)
+    finally:
+        shutil.rmtree(tmpdir)
+
+
+def _print_on_nosuchfile(e):
+    """Print helpful troubleshooting message
+
+    e is an exception raised by subprocess.check_call()
+
+    """
+    if e.errno == 2:
+        logging.error(
+            "Could not find zic. Perhaps you need to install "
+            "libc-bin or some other package that provides it, "
+            "or it's not in your PATH?")