Kaynağa Gözat

[desktop] Upgrade python-dateutil for pysaml2

Erick Tryzelaar 11 yıl önce
ebeveyn
işleme
52c1f39bf4

+ 31 - 0
desktop/core/ext-py/python-dateutil-2.4.2/LICENSE

@@ -0,0 +1,31 @@
+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      - Yaron de Leeuw <me@jarondl.net>
+
+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.

+ 1 - 0
desktop/core/ext-py/python-dateutil-2.4.2/MANIFEST.in

@@ -0,0 +1 @@
+include LICENSE NEWS zonefile_metadata.json updatezinfo.py

+ 232 - 0
desktop/core/ext-py/python-dateutil-2.4.2/NEWS

@@ -0,0 +1,232 @@
+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 "setcachsize" 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 [1]. 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.
+
+[1] http://www.twinsun.com/tz/tz-link.htm
+
+
+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.
+

+ 26 - 0
desktop/core/ext-py/python-dateutil-2.4.2/PKG-INFO

@@ -0,0 +1,26 @@
+Metadata-Version: 1.1
+Name: python-dateutil
+Version: 2.4.2
+Summary: Extensions to the standard Python datetime module
+Home-page: https://dateutil.readthedocs.org
+Author: Yaron de Leeuw
+Author-email: me@jarondl.net
+License: Simplified BSD
+Description: 
+        The dateutil module provides powerful extensions to the
+        datetime module available in the Python standard library.
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Topic :: Software Development :: Libraries
+Requires: six

+ 124 - 0
desktop/core/ext-py/python-dateutil-2.4.2/README.rst

@@ -0,0 +1,124 @@
+dateutil - powerful extensions to datetime
+==========================================
+
+.. image:: https://img.shields.io/travis/dateutil/dateutil/master.svg?style=flat-square
+    :target: https://travis-ci.org/dateutil/dateutil
+    :alt: travis build status
+
+.. image:: https://img.shields.io/appveyor/ci/dateutil/dateutil/master.svg?style=flat-square
+    :target: https://ci.appveyor.com/project/dateutil/dateutil
+    :alt: appveyor build status
+
+.. image:: https://img.shields.io/pypi/dd/python-dateutil.svg?style=flat-square
+    :target: https://pypi.python.org/pypi/python-dateutil/
+    :alt: pypi downloads per day
+
+.. image:: https://img.shields.io/pypi/v/python-dateutil.svg?style=flat-square
+    :target: https://pypi.python.org/pypi/python-dateutil/
+    :alt: pypi version
+
+
+
+The `dateutil` module provides powerful extensions to
+the standard `datetime` module, available in Python.
+
+
+Download
+========
+dateutil is available on PyPI
+https://pypi.python.org/pypi/python-dateutil/
+
+The documentation is hosted at:
+https://dateutil.readthedocs.org/
+
+Code
+====
+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;
+* More than 400 test cases.
+
+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 :)
+
+
+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-
+
+Building and releasing
+======================
+When you get the source, it does not contain the internal zoneinfo
+database. To get (and update) the database, run the updatezinfo.py script. Make sure
+that the zic command is in your path, and that you have network connectivity
+to get the latest timezone information from IANA. If you have downloaded
+the timezone data earlier, you can give the tarball as a parameter to
+updatezinfo.py.
+
+
+Testing
+=======
+dateutil has a comprehensive test suite, which can be run simply by running
+`python setup.py test [-q]` in the project root. Note that if you don't have the internal
+zoneinfo database, some tests will fail. Apart from that, all tests should pass.
+
+To easily test dateutil against all supported Python versions, you can use
+`tox <https://tox.readthedocs.org/en/latest/>`_.
+
+All github pull requests are automatically tested using travis.

+ 2 - 0
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/__init__.py

@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+__version__ = "2.4.2"

+ 89 - 0
desktop/core/ext-py/python-dateutil-2.4.2/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:
+
+    http://users.chariot.net.au/~gmarts/eastalg.htm
+
+    and
+
+    http://www.tondering.dk/claus/calendar.html
+
+    """
+
+    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))

+ 1205 - 0
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/parser.py

@@ -0,0 +1,1205 @@
+# -*- coding:iso-8859-1 -*-
+"""
+This module offers a generic date/time string parser which is able to parse
+most known formats to represent a date and/or time.
+
+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) <http://pds-rings.seti.org/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 string
+import time
+import collections
+from io import StringIO
+
+from six import text_type, binary_type, integer_types
+
+from . import relativedelta
+from . import tz
+
+__all__ = ["parse", "parserinfo"]
+
+
+class _timelex(object):
+
+    def __init__(self, instream):
+        if isinstance(instream, text_type):
+            instream = StringIO(instream)
+
+        self.instream = instream
+        self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
+                          'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
+                          'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
+                          'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
+        self.numchars = '0123456789'
+        self.whitespace = ' \t\r\n'
+        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
+        wordchars = self.wordchars
+        numchars = self.numchars
+        whitespace = self.whitespace
+
+        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 nextchar in wordchars:
+                    state = 'a'
+                elif nextchar in numchars:
+                    state = '0'
+                elif nextchar in whitespace:
+                    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 nextchar in wordchars:
+                    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 nextchar in numchars:
+                    token += nextchar
+                elif nextchar == '.':
+                    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 nextchar in wordchars:
+                    token += nextchar
+                elif nextchar in numchars 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 nextchar in numchars:
+                    token += nextchar
+                elif nextchar in wordchars 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] == '.')):
+            l = token.split('.')
+            token = l[0]
+            for tok in l[1:]:
+                self.tokenstack.append('.')
+                if tok:
+                    self.tokenstack.append(tok)
+
+        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
+
+    def split(cls, s):
+        return list(cls(s))
+    split = classmethod(split)
+
+
+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 __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"),
+                ("Wed", "Wednesday"),
+                ("Thu", "Thursday"),
+                ("Fri", "Friday"),
+                ("Sat", "Saturday"),
+                ("Sun", "Sunday")]
+    MONTHS = [("Jan", "January"),
+              ("Feb", "February"),
+              ("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 = {}
+
+    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):
+        if len(name) >= 3:
+            try:
+                return self._weekdays[name.lower()]
+            except KeyError:
+                pass
+        return None
+
+    def month(self, name):
+        if len(name) >= 3:
+            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):
+        if year < 100:
+            year += self._century
+            if abs(year-self._year) >= 50:
+                if year < self._year:
+                    year += 100
+                else:
+                    year -= 100
+        return year
+
+    def validate(self, res):
+        # move to info
+        if res.year is not None:
+            res.year = self.convertyear(res.year)
+
+        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 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 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:
+            Whether or not to ignore the time zone.
+
+        :param tzinfos:
+            A time zone, to be applied to the date, if `ignoretz` is `True`.
+            This can be either a subclass of `tzinfo`, a time zone string or an
+            integer offset.
+
+        :param **kwargs:
+            Keyword arguments as passed to `_parse()`.
+
+        :return:
+            Returns a `datetime.datetime` object or, if the `fuzzy_with_tokens`
+            option is `True`, returns a tuple, the first element being a
+            `datetime.datetime` object, the second a tuple containing the
+            fuzzy tokens.
+
+        :raises ValueError:
+            Raised for invalid or unknown string format, if the provided
+            `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.
+        """
+
+        default_specified = default is not None
+
+        if not default_specified:
+            default = datetime.datetime.now().replace(hour=0, minute=0,
+                                                      second=0, microsecond=0)
+
+        if kwargs.get('fuzzy_with_tokens', False):
+            res, skipped_tokens = self._parse(timestr, **kwargs)
+        else:
+            res = self._parse(timestr, **kwargs)
+
+        if res is None:
+            raise ValueError("Unknown string format")
+
+        repl = {}
+        for attr in ["year", "month", "day", "hour",
+                     "minute", "second", "microsecond"]:
+            value = getattr(res, attr)
+            if value is not None:
+                repl[attr] = value
+
+        ret = default.replace(**repl)
+
+        if res.weekday is not None and not res.day:
+            ret = ret+relativedelta.relativedelta(weekday=res.weekday)
+
+        if not ignoretz:
+            if (isinstance(tzinfos, collections.Callable) or
+                    tzinfos and res.tzname in tzinfos):
+
+                if isinstance(tzinfos, collections.Callable):
+                    tzdata = tzinfos(res.tzname, res.tzoffset)
+                else:
+                    tzdata = tzinfos.get(res.tzname)
+
+                if isinstance(tzdata, datetime.tzinfo):
+                    tzinfo = tzdata
+                elif isinstance(tzdata, text_type):
+                    tzinfo = tz.tzstr(tzdata)
+                elif isinstance(tzdata, integer_types):
+                    tzinfo = tz.tzoffset(res.tzname, tzdata)
+                else:
+                    raise ValueError("Offset must be tzinfo subclass, "
+                                     "tz string, or int offset.")
+                ret = ret.replace(tzinfo=tzinfo)
+            elif res.tzname and res.tzname in time.tzname:
+                ret = ret.replace(tzinfo=tz.tzlocal())
+            elif res.tzoffset == 0:
+                ret = ret.replace(tzinfo=tz.tzutc())
+            elif res.tzoffset:
+                ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
+
+        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"]
+
+    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
+            `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
+            `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
+            `datetime.datetime` datetimestamp and the second element is a tuple
+            containing the portions of the string which were ignored, e.g.
+            "Today is January 1, 2047 at 8:21:00AM" should return
+            `(datetime.datetime(2011, 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
+
+        # keep up with the last token skipped so we can recombine
+        # consecutively skipped tokens (-2 for when i begins at 0).
+        last_skipped_token_i = -2
+        skipped_tokens = list()
+
+        try:
+            # year/month/day list
+            ymd = []
+
+            # Index of the month string in ymd
+            mstridx = -1
+
+            len_l = len(l)
+            i = 0
+            while i < len_l:
+
+                # Check if it's a number
+                try:
+                    value_repr = l[i]
+                    value = float(value_repr)
+                except ValueError:
+                    value = None
+
+                if value is not None:
+                    # Token is a number
+                    len_li = len(l[i])
+                    i += 1
+
+                    if (len(ymd) == 3 and len_li in (2, 4)
+                        and res.hour is None and (i >= len_l or (l[i] != ':' and
+                                                  info.hms(l[i]) is None))):
+                        # 19990101T23[59]
+                        s = l[i-1]
+                        res.hour = int(s[:2])
+
+                        if len_li == 4:
+                            res.minute = int(s[2:])
+
+                    elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
+                        # YYMMDD or HHMMSS[.ss]
+                        s = l[i-1]
+
+                        if not ymd and l[i-1].find('.') == -1:
+                            ymd.append(info.convertyear(int(s[:2])))
+                            ymd.append(int(s[2:4]))
+                            ymd.append(int(s[4:]))
+                        else:
+                            # 19990101T235959[.59]
+                            res.hour = int(s[:2])
+                            res.minute = int(s[2:4])
+                            res.second, res.microsecond = _parsems(s[4:])
+
+                    elif len_li == 8:
+                        # YYYYMMDD
+                        s = l[i-1]
+                        ymd.append(int(s[:4]))
+                        ymd.append(int(s[4:6]))
+                        ymd.append(int(s[6:]))
+
+                    elif len_li in (12, 14):
+                        # YYYYMMDDhhmm[ss]
+                        s = l[i-1]
+                        ymd.append(int(s[:4]))
+                        ymd.append(int(s[4:6]))
+                        ymd.append(int(s[6:8]))
+                        res.hour = int(s[8:10])
+                        res.minute = int(s[10:12])
+
+                        if len_li == 14:
+                            res.second = int(s[12:])
+
+                    elif ((i < len_l and info.hms(l[i]) is not None) or
+                          (i+1 < len_l and l[i] == ' ' and
+                           info.hms(l[i+1]) is not None)):
+
+                        # HH[ ]h or MM[ ]m or SS[.ss][ ]s
+                        if l[i] == ' ':
+                            i += 1
+
+                        idx = info.hms(l[i])
+
+                        while True:
+                            if idx == 0:
+                                res.hour = int(value)
+
+                                if value % 1:
+                                    res.minute = int(60*(value % 1))
+
+                            elif idx == 1:
+                                res.minute = int(value)
+
+                                if value % 1:
+                                    res.second = int(60*(value % 1))
+
+                            elif idx == 2:
+                                res.second, res.microsecond = \
+                                    _parsems(value_repr)
+
+                            i += 1
+
+                            if i >= len_l or idx == 2:
+                                break
+
+                            # 12h00
+                            try:
+                                value_repr = l[i]
+                                value = float(value_repr)
+                            except ValueError:
+                                break
+                            else:
+                                i += 1
+                                idx += 1
+
+                                if i < len_l:
+                                    newidx = info.hms(l[i])
+
+                                    if newidx is not None:
+                                        idx = newidx
+
+                    elif (i == len_l and l[i-2] == ' ' and
+                          info.hms(l[i-3]) is not None):
+                        # X h MM or X m SS
+                        idx = info.hms(l[i-3]) + 1
+
+                        if idx == 1:
+                            res.minute = int(value)
+
+                            if value % 1:
+                                res.second = int(60*(value % 1))
+                            elif idx == 2:
+                                res.second, res.microsecond = \
+                                    _parsems(value_repr)
+                                i += 1
+
+                    elif i+1 < len_l and l[i] == ':':
+                        # HH:MM[:SS[.ss]]
+                        res.hour = int(value)
+                        i += 1
+                        value = float(l[i])
+                        res.minute = int(value)
+
+                        if value % 1:
+                            res.second = int(60*(value % 1))
+
+                        i += 1
+
+                        if i < len_l and l[i] == ':':
+                            res.second, res.microsecond = _parsems(l[i+1])
+                            i += 2
+
+                    elif i < len_l and l[i] in ('-', '/', '.'):
+                        sep = l[i]
+                        ymd.append(int(value))
+                        i += 1
+
+                        if i < len_l and not info.jump(l[i]):
+                            try:
+                                # 01-01[-01]
+                                ymd.append(int(l[i]))
+                            except ValueError:
+                                # 01-Jan[-01]
+                                value = info.month(l[i])
+
+                                if value is not None:
+                                    ymd.append(value)
+                                    assert mstridx == -1
+                                    mstridx = len(ymd)-1
+                                else:
+                                    return None
+
+                            i += 1
+
+                            if i < len_l and l[i] == sep:
+                                # We have three members
+                                i += 1
+                                value = info.month(l[i])
+
+                                if value is not None:
+                                    ymd.append(value)
+                                    mstridx = len(ymd)-1
+                                    assert mstridx == -1
+                                else:
+                                    ymd.append(int(l[i]))
+
+                                i += 1
+                    elif i >= len_l or info.jump(l[i]):
+                        if i+1 < len_l and info.ampm(l[i+1]) is not None:
+                            # 12 am
+                            res.hour = int(value)
+
+                            if res.hour < 12 and info.ampm(l[i+1]) == 1:
+                                res.hour += 12
+                            elif res.hour == 12 and info.ampm(l[i+1]) == 0:
+                                res.hour = 0
+
+                            i += 1
+                        else:
+                            # Year, month or day
+                            ymd.append(int(value))
+                        i += 1
+                    elif info.ampm(l[i]) is not None:
+
+                        # 12am
+                        res.hour = int(value)
+
+                        if res.hour < 12 and info.ampm(l[i]) == 1:
+                            res.hour += 12
+                        elif res.hour == 12 and info.ampm(l[i]) == 0:
+                            res.hour = 0
+                        i += 1
+
+                    elif not fuzzy:
+                        return None
+                    else:
+                        i += 1
+                    continue
+
+                # Check weekday
+                value = info.weekday(l[i])
+                if value is not None:
+                    res.weekday = value
+                    i += 1
+                    continue
+
+                # Check month name
+                value = info.month(l[i])
+                if value is not None:
+                    ymd.append(value)
+                    assert mstridx == -1
+                    mstridx = len(ymd)-1
+
+                    i += 1
+                    if i < len_l:
+                        if l[i] in ('-', '/'):
+                            # Jan-01[-99]
+                            sep = l[i]
+                            i += 1
+                            ymd.append(int(l[i]))
+                            i += 1
+
+                            if i < len_l and l[i] == sep:
+                                # Jan-01-99
+                                i += 1
+                                ymd.append(int(l[i]))
+                                i += 1
+
+                        elif (i+3 < len_l and l[i] == l[i+2] == ' '
+                              and info.pertain(l[i+1])):
+                            # Jan of 01
+                            # In this case, 01 is clearly year
+                            try:
+                                value = int(l[i+3])
+                            except ValueError:
+                                # Wrong guess
+                                pass
+                            else:
+                                # Convert it here to become unambiguous
+                                ymd.append(info.convertyear(value))
+                            i += 4
+                    continue
+
+                # Check am/pm
+                value = info.ampm(l[i])
+                if value is not None:
+                    # 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 res.ampm is not None:
+                        val_is_ampm = False
+
+                    # If AM/PM is found and hour is not, raise a ValueError
+                    if res.hour is None:
+                        if fuzzy:
+                            val_is_ampm = False
+                        else:
+                            raise ValueError('No hour specified with ' +
+                                             'AM or PM flag.')
+                    elif not 0 <= res.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.')
+
+                    if val_is_ampm:
+                        if value == 1 and res.hour < 12:
+                            res.hour += 12
+                        elif value == 0 and res.hour == 12:
+                            res.hour = 0
+
+                        res.ampm = value
+
+                    i += 1
+                    continue
+
+                # Check for a timezone name
+                if (res.hour is not None and len(l[i]) <= 5 and
+                        res.tzname is None and res.tzoffset is None and
+                        not [x for x in l[i] if x not in
+                             string.ascii_uppercase]):
+                    res.tzname = l[i]
+                    res.tzoffset = info.tzoffset(res.tzname)
+                    i += 1
+
+                    # 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 < len_l and l[i] in ('+', '-'):
+                        l[i] = ('+', '-')[l[i] == '+']
+                        res.tzoffset = None
+                        if info.utczone(res.tzname):
+                            # With something like GMT+3, the timezone
+                            # is *not* GMT.
+                            res.tzname = None
+
+                    continue
+
+                # Check for a numbered timezone
+                if res.hour is not None and l[i] in ('+', '-'):
+                    signal = (-1, 1)[l[i] == '+']
+                    i += 1
+                    len_li = len(l[i])
+
+                    if len_li == 4:
+                        # -0300
+                        res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
+                    elif i+1 < len_l and l[i+1] == ':':
+                        # -03:00
+                        res.tzoffset = int(l[i])*3600+int(l[i+2])*60
+                        i += 2
+                    elif len_li <= 2:
+                        # -[0]3
+                        res.tzoffset = int(l[i][:2])*3600
+                    else:
+                        return None
+                    i += 1
+
+                    res.tzoffset *= signal
+
+                    # Look for a timezone name between parenthesis
+                    if (i+3 < len_l and
+                        info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
+                        3 <= len(l[i+2]) <= 5 and
+                        not [x for x in l[i+2]
+                             if x not in string.ascii_uppercase]):
+                        # -0300 (BRST)
+                        res.tzname = l[i+2]
+                        i += 4
+                    continue
+
+                # Check jumps
+                if not (info.jump(l[i]) or fuzzy):
+                    return None
+
+                if last_skipped_token_i == i - 1:
+                    # recombine the tokens
+                    skipped_tokens[-1] += l[i]
+                else:
+                    # just append
+                    skipped_tokens.append(l[i])
+                last_skipped_token_i = i
+                i += 1
+
+            # Process year/month/day
+            len_ymd = len(ymd)
+            if len_ymd > 3:
+                # More than three members!?
+                return None
+            elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
+                # One member, or two members with a month string
+                if mstridx != -1:
+                    res.month = ymd[mstridx]
+                    del ymd[mstridx]
+
+                if len_ymd > 1 or mstridx == -1:
+                    if ymd[0] > 31:
+                        res.year = ymd[0]
+                    else:
+                        res.day = ymd[0]
+
+            elif len_ymd == 2:
+                # Two members with numbers
+                if ymd[0] > 31:
+                    # 99-01
+                    res.year, res.month = ymd
+                elif ymd[1] > 31:
+                    # 01-99
+                    res.month, res.year = ymd
+                elif dayfirst and ymd[1] <= 12:
+                    # 13-01
+                    res.day, res.month = ymd
+                else:
+                    # 01-13
+                    res.month, res.day = ymd
+
+            elif len_ymd == 3:
+                # Three members
+                if mstridx == 0:
+                    res.month, res.day, res.year = ymd
+                elif mstridx == 1:
+                    if ymd[0] > 31 or (yearfirst and ymd[2] <= 31):
+                        # 99-Jan-01
+                        res.year, res.month, res.day = ymd
+                    else:
+                        # 01-Jan-01
+                        # Give precendence to day-first, since
+                        # two-digit years is usually hand-written.
+                        res.day, res.month, res.year = ymd
+
+                elif mstridx == 2:
+                    # WTF!?
+                    if ymd[1] > 31:
+                        # 01-99-Jan
+                        res.day, res.year, res.month = ymd
+                    else:
+                        # 99-01-Jan
+                        res.year, res.day, res.month = ymd
+
+                else:
+                    if ymd[0] > 31 or \
+                       (yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
+                        # 99-01-01
+                        res.year, res.month, res.day = ymd
+                    elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12):
+                        # 13-01-01
+                        res.day, res.month, res.year = ymd
+                    else:
+                        # 01-13-01
+                        res.month, res.day, res.year = ymd
+
+        except (IndexError, ValueError, AssertionError):
+            return None
+
+        if not info.validate(res):
+            return None
+
+        if fuzzy_with_tokens:
+            return res, tuple(skipped_tokens)
+        else:
+            return res
+
+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 `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:
+        Whether or not to ignore the time zone (boolean).
+
+    :param tzinfos:
+        A time zone, to be applied to the date, if `ignoretz` is `True`.
+        This can be either a subclass of `tzinfo`, a time zone string or an
+        integer offset.
+
+    :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
+        `datetime.datetime` datetimestamp and the second element is a tuple
+        containing the portions of the string which were ignored, e.g.
+        "Today is January 1, 2047 at 8:21:00AM" should return
+        `(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))`
+    """
+    # Python 2.x support: datetimes return their string presentation as
+    # bytes in 2.x and unicode in 3.x, so it's reasonable to expect that
+    # the parser will get both kinds. Internally we use unicode only.
+    if isinstance(timestr, binary_type):
+        timestr = timestr.decode()
+
+    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):
+        # Python 2.x compatibility: tzstr should be converted to unicode before
+        # being passed to _timelex.
+        if isinstance(tzstr, binary_type):
+            tzstr = tzstr.decode()
+
+        res = self._result()
+        l = _timelex.split(tzstr)
+        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])
+                    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] == '+']
+                            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)
+                            i += 2
+                        elif len_li <= 2:
+                            # -[0]3
+                            setattr(res, offattr,
+                                    int(l[i][:2])*3600*signal)
+                        else:
+                            return None
+                        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])
+                    i += 2
+                    if l[i] == '-':
+                        value = int(l[i+1])*-1
+                        i += 1
+                    else:
+                        value = int(l[i])
+                    i += 2
+                    if value:
+                        x.week = value
+                        x.weekday = (int(l[i])-1) % 7
+                    else:
+                        x.day = int(l[i])
+                    i += 2
+                    x.time = int(l[i])
+                    i += 2
+                if i < len_l:
+                    if l[i] in ('-', '+'):
+                        signal = (-1, 1)[l[i] == "+"]
+                        i += 1
+                    else:
+                        signal = 1
+                    res.dstoffset = (res.stdoffset+int(l[i]))*signal
+            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)
+                        i += 1
+                        x.jyday = int(l[i])
+                    elif l[i] == 'M':
+                        # month[-.]week[-.]weekday
+                        i += 1
+                        x.month = int(l[i])
+                        i += 1
+                        assert l[i] in ('-', '.')
+                        i += 1
+                        x.week = int(l[i])
+                        if x.week == 5:
+                            x.week = -1
+                        i += 1
+                        assert l[i] in ('-', '.')
+                        i += 1
+                        x.weekday = (int(l[i])-1) % 7
+                    else:
+                        # year day (zero based)
+                        x.yday = int(l[i])+1
+
+                    i += 1
+
+                    if i < len_l and l[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
+                            i += 2
+                            if i+1 < len_l and l[i+1] == ':':
+                                i += 2
+                                x.time += int(l[i])
+                        elif len_li <= 2:
+                            # -[0]3
+                            x.time = (int(l[i][:2])*3600)
+                        else:
+                            return None
+                        i += 1
+
+                    assert i == len_l or l[i] == ','
+
+                    i += 1
+
+                assert i >= len_l
+
+        except (IndexError, ValueError, AssertionError):
+            return None
+
+        return res
+
+
+DEFAULTTZPARSER = _tzparser()
+
+
+def _parsetz(tzstr):
+    return DEFAULTTZPARSER.parse(tzstr)
+
+
+def _parsems(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])
+
+
+# vim:ts=4:sw=4:et

+ 450 - 0
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/relativedelta.py

@@ -0,0 +1,450 @@
+# -*- coding: utf-8 -*-
+import datetime
+import calendar
+
+from six import integer_types
+
+__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
+
+
+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 __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)
+
+MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
+
+
+class relativedelta(object):
+    """
+The relativedelta type is based on the specification of the excellent
+work done by M.-A. Lemburg in his
+`mx.DateTime <http://www.egenix.com/files/python/mxDateTime.html>`_ 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 aritmetic
+        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.
+
+    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.
+
+Here is the behavior of operations with relativedelta:
+
+1. Calculate the absolute year, using the 'year' argument, or the
+   original datetime year, if the argument is not present.
+
+2. Add the relative 'years' argument to the absolute year.
+
+3. Do steps 1 and 2 for month/months.
+
+4. Calculate the absolute day, using the 'day' argument, or the
+   original datetime day, if the argument is not present. Then,
+   subtract from the day until it fits in the year and month
+   found after their operations.
+
+5. Add the relative 'days' argument to the absolute day. Notice
+   that the 'weeks' argument is multiplied by 7 and added to
+   'days'.
+
+6. Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
+   microsecond/microseconds.
+
+7. If the 'weekday' argument is present, calculate the weekday,
+   with the given (wday, nth) tuple. wday is the index of the
+   weekday (0-6, 0=Mon), and nth is the number of weeks to add
+   forward or backward, depending on its signal. Notice that if
+   the calculated date is already Monday, for example, using
+   (0, 1) or (0, -1) won't change the day.
+    """
+
+    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
+
+            months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
+            self._set_months(months)
+            dtm = self.__radd__(dt2)
+            if dt1 < dt2:
+                while dt1 > dtm:
+                    months += 1
+                    self._set_months(months)
+                    dtm = self.__radd__(dt2)
+            else:
+                while dt1 < dtm:
+                    months -= 1
+                    self._set_months(months)
+                    dtm = self.__radd__(dt2)
+            delta = dt1 - dtm
+            self.seconds = delta.seconds+delta.days*86400
+            self.microseconds = delta.microseconds
+        else:
+            self.years = years
+            self.months = months
+            self.days = days+weeks*7
+            self.leapdays = leapdays
+            self.hours = hours
+            self.minutes = minutes
+            self.seconds = seconds
+            self.microseconds = microseconds
+            self.year = year
+            self.month = month
+            self.day = day
+            self.hour = hour
+            self.minute = minute
+            self.second = second
+            self.microsecond = microsecond
+
+            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 = self.microseconds//abs(self.microseconds)
+            div, mod = divmod(self.microseconds*s, 1000000)
+            self.microseconds = mod*s
+            self.seconds += div*s
+        if abs(self.seconds) > 59:
+            s = self.seconds//abs(self.seconds)
+            div, mod = divmod(self.seconds*s, 60)
+            self.seconds = mod*s
+            self.minutes += div*s
+        if abs(self.minutes) > 59:
+            s = self.minutes//abs(self.minutes)
+            div, mod = divmod(self.minutes*s, 60)
+            self.minutes = mod*s
+            self.hours += div*s
+        if abs(self.hours) > 23:
+            s = self.hours//abs(self.hours)
+            div, mod = divmod(self.hours*s, 24)
+            self.hours = mod*s
+            self.days += div*s
+        if abs(self.months) > 11:
+            s = self.months//abs(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
+
+    def _set_months(self, months):
+        self.months = months
+        if abs(self.months) > 11:
+            s = self.months//abs(self.months)
+            div, mod = divmod(self.months*s, 12)
+            self.months = mod*s
+            self.years = div*s
+        else:
+            self.years = 0
+
+    def __add__(self, other):
+        if isinstance(other, relativedelta):
+            return relativedelta(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 or self.year,
+                                 month=other.month or self.month,
+                                 day=other.day or self.day,
+                                 weekday=other.weekday or self.weekday,
+                                 hour=other.hour or self.hour,
+                                 minute=other.minute or self.minute,
+                                 second=other.second or self.second,
+                                 microsecond=(other.microsecond or
+                                              self.microsecond))
+        if not isinstance(other, datetime.date):
+            raise TypeError("unsupported type for add operation")
+        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):
+            raise TypeError("unsupported type for sub operation")
+        return relativedelta(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 or other.year,
+                             month=self.month or other.month,
+                             day=self.day or other.day,
+                             weekday=self.weekday or other.weekday,
+                             hour=self.hour or other.hour,
+                             minute=self.minute or other.minute,
+                             second=self.second or other.second,
+                             microsecond=self.microsecond or other.microsecond)
+
+    def __neg__(self):
+        return relativedelta(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):
+        f = float(other)
+        return relativedelta(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 False
+        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.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 __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __div__(self, other):
+        return self.__mul__(1/float(other))
+
+    __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("%s=%+d" % (attr, value))
+        for attr in ["year", "month", "day", "weekday",
+                     "hour", "minute", "second", "microsecond"]:
+            value = getattr(self, attr)
+            if value is not None:
+                l.append("%s=%s" % (attr, repr(value)))
+        return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
+
+# vim:ts=4:sw=4:et

+ 1375 - 0
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/rrule.py

@@ -0,0 +1,1375 @@
+# -*- coding: utf-8 -*-
+"""
+The rrule module offers a small, complete, and very fast, implementation of
+the recurrence rules documented in the
+`iCalendar RFC <http://www.ietf.org/rfc/rfc2445.txt>`_,
+including support for caching of results.
+"""
+import itertools
+import datetime
+import calendar
+import sys
+
+from fractions import gcd
+
+from six import advance_iterator, integer_types
+from six.moves import _thread
+
+__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)
+
+(YEARLY,
+ MONTHLY,
+ WEEKLY,
+ DAILY,
+ HOURLY,
+ MINUTELY,
+ SECONDLY) = list(range(7))
+
+# Imported on demand.
+easter = None
+parser = None
+
+
+class weekday(object):
+    __slots__ = ["weekday", "n"]
+
+    def __init__(self, weekday, n=None):
+        if n == 0:
+            raise ValueError("Can't create weekday with n == 0")
+        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 __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)
+
+MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
+
+
+class rrulebase(object):
+    def __init__(self, cache=False):
+        if cache:
+            self._cache = []
+            self._cache_lock = _thread.allocate_lock()
+            self._cache_gen = self._iter()
+            self._cache_complete = False
+        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 _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 between(self, after, before, inc=False):
+        """ 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.
+
+    Additionally, it supports the following keyword arguments:
+
+    :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.
+    :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.
+    :param until:
+        If given, this must be a datetime instance, that will specify the
+        limit of the recurrence. If a recurrence instance happens to be the
+        same as the datetime instance given in the until keyword, this will
+        be the last occurrence.
+    :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 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 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.
+     """
+    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:
+            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
+
+        if until and not isinstance(until, datetime.datetime):
+            until = datetime.datetime.fromordinal(until.toordinal())
+        self._until = until
+
+        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 (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
+                bymonthday = dtstart.day
+            elif freq == MONTHLY:
+                bymonthday = dtstart.day
+            elif freq == WEEKLY:
+                byweekday = dtstart.weekday()
+
+        # bymonth
+        if bymonth is None:
+            self._bymonth = None
+        else:
+            if isinstance(bymonth, integer_types):
+                bymonth = (bymonth,)
+
+            self._bymonth = tuple(sorted(set(bymonth)))
+
+        # byyearday
+        if byyearday is None:
+            self._byyearday = None
+        else:
+            if isinstance(byyearday, integer_types):
+                byyearday = (byyearday,)
+
+            self._byyearday = tuple(sorted(set(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))
+        else:
+            self._byeaster = None
+
+        # bymonthay
+        if bymonthday is None:
+            self._bymonthday = ()
+            self._bynmonthday = ()
+        else:
+            if isinstance(bymonthday, integer_types):
+                bymonthday = (bymonthday,)
+
+            self._bymonthday = tuple(sorted(set([x for x in bymonthday if x > 0])))
+            self._bynmonthday = tuple(sorted(set([x for x in bymonthday if x < 0])))
+
+        # byweekno
+        if byweekno is None:
+            self._byweekno = None
+        else:
+            if isinstance(byweekno, integer_types):
+                byweekno = (byweekno,)
+
+            self._byweekno = tuple(sorted(set(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))
+
+            if self._bynweekday is not None:
+                self._bynweekday = tuple(sorted(self._bynweekday))
+
+        # byhour
+        if byhour is None:
+            if freq < HOURLY:
+                self._byhour = set((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))
+
+        # byminute
+        if byminute is None:
+            if freq < MINUTELY:
+                self._byminute = set((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))
+
+        # 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))
+
+        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 _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:
+                        total += 1
+                        yield res
+                        if count:
+                            count -= 1
+                            if not count:
+                                self._len = total
+                                return
+            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:
+                                total += 1
+                                yield res
+                                if count:
+                                    count -= 1
+                                    if not count:
+                                        self._len = total
+                                        return
+
+            # 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:
+                self.genlist.remove(self)
+
+        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 = []
+
+    def rrule(self, rrule):
+        """ Include the given :py:class:`rrule` instance in the recurrence set
+            generation. """
+        self._rrule.append(rrule)
+
+    def rdate(self, rdate):
+        """ Include the given :py:class:`datetime` instance in the recurrence
+            set generation. """
+        self._rdate.append(rdate)
+
+    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)
+
+    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)
+        rlist.sort()
+        exlist = []
+        self._exdate.sort()
+        self._genitem(exlist, iter(self._exdate))
+        for gen in [iter(x) for x in self._exrule]:
+            self._genitem(exlist, gen)
+        exlist.sort()
+        lastdt = None
+        total = 0
+        while rlist:
+            ritem = rlist[0]
+            if not lastdt or lastdt != ritem.dt:
+                while exlist and exlist[0] < ritem:
+                    advance_iterator(exlist[0])
+                    exlist.sort()
+                if not exlist or ritem != exlist[0]:
+                    total += 1
+                    yield ritem.dt
+                lastdt = ritem.dt
+            advance_iterator(ritem)
+            rlist.sort()
+        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, **kwarsg):
+        l = []
+        for wday in value.split(','):
+            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)
+            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,
+                   tzinfos=None):
+        global parser
+        if compatible:
+            forceset = True
+            unfold = True
+        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 RDATE parm: "+parm)
+                    exdatevals.append(value)
+                elif name == "DTSTART":
+                    for parm in parms:
+                        raise ValueError("unsupported DTSTART parm: "+parm)
+                    if not parser:
+                        from dateutil import parser
+                    dtstart = parser.parse(value, ignoretz=ignoretz,
+                                           tzinfos=tzinfos)
+                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

+ 0 - 0
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/test/__init__.py


+ 4131 - 0
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/test/test.py

@@ -0,0 +1,4131 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import calendar
+import base64
+import sys
+
+from six import StringIO, BytesIO, PY3
+
+try:
+    # python2.6 unittest has no skipUnless. So we use unittest2.
+    # if you have python >= 2.7, you don't need unittest2, but it won't harm
+    import unittest2 as unittest
+except ImportError:
+    import unittest
+
+MISSING_TARBALL = ("This test fails if you don't have the dateutil "
+                   "timezone file installed. Please read the README")
+
+from datetime import *
+
+from dateutil.relativedelta import *
+from dateutil.parser import *
+from dateutil.easter import *
+from dateutil.rrule import *
+from dateutil.tz import *
+from dateutil import zoneinfo
+
+try:
+    from dateutil import tzwin
+except ImportError:
+    pass
+
+
+class RelativeDeltaTest(unittest.TestCase):
+    now = datetime(2003, 9, 17, 20, 54, 47, 282310)
+    today = date(2003, 9, 17)
+
+    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 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 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 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 testDivision(self):
+        self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=28) / 28,
+                         datetime(2000, 1, 2))
+
+    def testBoolean(self):
+        self.assertFalse(relativedelta(days=0))
+        self.assertTrue(relativedelta(days=1))
+
+
+class RRuleTest(unittest.TestCase):
+    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 testUntilNotMatching(self):
+        self.assertEqual(list(rrule(DAILY,
+                              count=3,
+                              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,
+                              count=3,
+                              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,
+                              count=3,
+                              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,
+                              count=3,
+                              dtstart=datetime(1997, 9, 2, 9, 0),
+                              until=datetime(1997, 9, 1, 9, 0))),
+                         [])
+
+    def testUntilWithDate(self):
+        self.assertEqual(list(rrule(DAILY,
+                              count=3,
+                              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 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 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 testSet(self):
+        set = rruleset()
+        set.rrule(rrule(YEARLY, count=2, byweekday=TU,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        set.rrule(rrule(YEARLY, count=1, byweekday=TH,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        self.assertEqual(list(set),
+                         [datetime(1997, 9, 2, 9, 0),
+                          datetime(1997, 9, 4, 9, 0),
+                          datetime(1997, 9, 9, 9, 0)])
+
+    def testSetDate(self):
+        set = rruleset()
+        set.rrule(rrule(YEARLY, count=1, byweekday=TU,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        set.rdate(datetime(1997, 9, 4, 9))
+        set.rdate(datetime(1997, 9, 9, 9))
+        self.assertEqual(list(set),
+                         [datetime(1997, 9, 2, 9, 0),
+                          datetime(1997, 9, 4, 9, 0),
+                          datetime(1997, 9, 9, 9, 0)])
+
+    def testSetExRule(self):
+        set = rruleset()
+        set.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH),
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        set.exrule(rrule(YEARLY, count=3, byweekday=TH,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        self.assertEqual(list(set),
+                         [datetime(1997, 9, 2, 9, 0),
+                          datetime(1997, 9, 9, 9, 0),
+                          datetime(1997, 9, 16, 9, 0)])
+
+    def testSetExDate(self):
+        set = rruleset()
+        set.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH),
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        set.exdate(datetime(1997, 9, 4, 9))
+        set.exdate(datetime(1997, 9, 11, 9))
+        set.exdate(datetime(1997, 9, 18, 9))
+        self.assertEqual(list(set),
+                         [datetime(1997, 9, 2, 9, 0),
+                          datetime(1997, 9, 9, 9, 0),
+                          datetime(1997, 9, 16, 9, 0)])
+
+    def testSetExDateRevOrder(self):
+        set = rruleset()
+        set.rrule(rrule(MONTHLY, count=5, bymonthday=10,
+                        dtstart=datetime(2004, 1, 1, 9, 0)))
+        set.exdate(datetime(2004, 4, 10, 9, 0))
+        set.exdate(datetime(2004, 2, 10, 9, 0))
+        self.assertEqual(list(set),
+                         [datetime(2004, 1, 10, 9, 0),
+                          datetime(2004, 3, 10, 9, 0),
+                          datetime(2004, 5, 10, 9, 0)])
+
+    def testSetDateAndExDate(self):
+        set = rruleset()
+        set.rdate(datetime(1997, 9, 2, 9))
+        set.rdate(datetime(1997, 9, 4, 9))
+        set.rdate(datetime(1997, 9, 9, 9))
+        set.rdate(datetime(1997, 9, 11, 9))
+        set.rdate(datetime(1997, 9, 16, 9))
+        set.rdate(datetime(1997, 9, 18, 9))
+        set.exdate(datetime(1997, 9, 4, 9))
+        set.exdate(datetime(1997, 9, 11, 9))
+        set.exdate(datetime(1997, 9, 18, 9))
+        self.assertEqual(list(set),
+                         [datetime(1997, 9, 2, 9, 0),
+                          datetime(1997, 9, 9, 9, 0),
+                          datetime(1997, 9, 16, 9, 0)])
+
+    def testSetDateAndExRule(self):
+        set = rruleset()
+        set.rdate(datetime(1997, 9, 2, 9))
+        set.rdate(datetime(1997, 9, 4, 9))
+        set.rdate(datetime(1997, 9, 9, 9))
+        set.rdate(datetime(1997, 9, 11, 9))
+        set.rdate(datetime(1997, 9, 16, 9))
+        set.rdate(datetime(1997, 9, 18, 9))
+        set.exrule(rrule(YEARLY, count=3, byweekday=TH,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        self.assertEqual(list(set),
+                         [datetime(1997, 9, 2, 9, 0),
+                          datetime(1997, 9, 9, 9, 0),
+                          datetime(1997, 9, 16, 9, 0)])
+
+    def testSetCount(self):
+        set = rruleset()
+        set.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH),
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        set.exrule(rrule(YEARLY, count=3, byweekday=TH,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        self.assertEqual(set.count(), 3)
+
+    def testSetCachePre(self):
+        set = rruleset()
+        set.rrule(rrule(YEARLY, count=2, byweekday=TU,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        set.rrule(rrule(YEARLY, count=1, byweekday=TH,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        self.assertEqual(list(set),
+                         [datetime(1997, 9, 2, 9, 0),
+                          datetime(1997, 9, 4, 9, 0),
+                          datetime(1997, 9, 9, 9, 0)])
+
+    def testSetCachePost(self):
+        set = rruleset(cache=True)
+        set.rrule(rrule(YEARLY, count=2, byweekday=TU,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        set.rrule(rrule(YEARLY, count=1, byweekday=TH,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        for x in set: pass
+        self.assertEqual(list(set),
+                         [datetime(1997, 9, 2, 9, 0),
+                          datetime(1997, 9, 4, 9, 0),
+                          datetime(1997, 9, 9, 9, 0)])
+
+    def testSetCachePostInternal(self):
+        set = rruleset(cache=True)
+        set.rrule(rrule(YEARLY, count=2, byweekday=TU,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        set.rrule(rrule(YEARLY, count=1, byweekday=TH,
+                        dtstart=datetime(1997, 9, 2, 9, 0)))
+        for x in set: pass
+        self.assertEqual(list(set._cache),
+                         [datetime(1997, 9, 2, 9, 0),
+                          datetime(1997, 9, 4, 9, 0),
+                          datetime(1997, 9, 9, 9, 0)])
+
+    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 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 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))
+
+
+class ParserTest(unittest.TestCase):
+
+    def setUp(self):
+        self.tzinfos = {"BRST": -10800}
+        self.brsttz = tzoffset("BRST", -10800)
+        self.default = datetime(2003, 9, 25)
+
+    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 testDateCommandFormatStrip9(self):
+        self.assertEqual(parse("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 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 testDateWithDash2(self):
+        self.assertEqual(parse("2003-Sep-25"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDash3(self):
+        self.assertEqual(parse("25-Sep-2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDash4(self):
+        self.assertEqual(parse("25-Sep-2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDash5(self):
+        self.assertEqual(parse("Sep-25-2003"),
+                         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 testDateWithDot2(self):
+        self.assertEqual(parse("2003.Sep.25"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDot3(self):
+        self.assertEqual(parse("25.Sep.2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDot4(self):
+        self.assertEqual(parse("25.Sep.2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithDot5(self):
+        self.assertEqual(parse("Sep.25.2003"),
+                         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 testDateWithSlash2(self):
+        self.assertEqual(parse("2003/Sep/25"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSlash3(self):
+        self.assertEqual(parse("25/Sep/2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSlash4(self):
+        self.assertEqual(parse("25/Sep/2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSlash5(self):
+        self.assertEqual(parse("Sep/25/2003"),
+                         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 testDateWithSpace2(self):
+        self.assertEqual(parse("2003 Sep 25"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSpace3(self):
+        self.assertEqual(parse("25 Sep 2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSpace4(self):
+        self.assertEqual(parse("25 Sep 2003"),
+                         datetime(2003, 9, 25))
+
+    def testDateWithSpace5(self):
+        self.assertEqual(parse("Sep 25 2003"),
+                         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 testStrangelyOrderedDate2(self):
+        self.assertEqual(parse("2003 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 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 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):
+        s = "Today is 25 of September of 2003, exactly " \
+            "at 10:49:41 with timezone -03:00."
+        self.assertEqual(parse(s, fuzzy_with_tokens=True),
+                         (datetime(2003, 9, 25, 10, 49, 41,
+                                   tzinfo=self.brsttz),
+                         ('Today is ', 'of ', ', exactly at ',
+                          ' with timezone ', '.')))
+
+    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 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):
+        self.assertEqual(parse("5:50 A.M. on June 13, 1990"),
+                         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 testErrorType01(self):
+        self.assertRaises(ValueError,
+                          parse,'shouldfail')
+
+    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))
+
+
+class EasterTest(unittest.TestCase):
+    easterlist = [
+                  # WESTERN            ORTHODOX
+                  (date(1990, 4, 15), date(1990, 4, 15)),
+                  (date(1991, 3, 31), date(1991, 4,  7)),
+                  (date(1992, 4, 19), date(1992, 4, 26)),
+                  (date(1993, 4, 11), date(1993, 4, 18)),
+                  (date(1994, 4,  3), date(1994, 5,  1)),
+                  (date(1995, 4, 16), date(1995, 4, 23)),
+                  (date(1996, 4,  7), date(1996, 4, 14)),
+                  (date(1997, 3, 30), date(1997, 4, 27)),
+                  (date(1998, 4, 12), date(1998, 4, 19)),
+                  (date(1999, 4,  4), date(1999, 4, 11)),
+
+                  (date(2000, 4, 23), date(2000, 4, 30)),
+                  (date(2001, 4, 15), date(2001, 4, 15)),
+                  (date(2002, 3, 31), date(2002, 5,  5)),
+                  (date(2003, 4, 20), date(2003, 4, 27)),
+                  (date(2004, 4, 11), date(2004, 4, 11)),
+                  (date(2005, 3, 27), date(2005, 5,  1)),
+                  (date(2006, 4, 16), date(2006, 4, 23)),
+                  (date(2007, 4,  8), date(2007, 4,  8)),
+                  (date(2008, 3, 23), date(2008, 4, 27)),
+                  (date(2009, 4, 12), date(2009, 4, 19)),
+
+                  (date(2010, 4,  4), date(2010, 4,  4)),
+                  (date(2011, 4, 24), date(2011, 4, 24)),
+                  (date(2012, 4,  8), date(2012, 4, 15)),
+                  (date(2013, 3, 31), date(2013, 5,  5)),
+                  (date(2014, 4, 20), date(2014, 4, 20)),
+                  (date(2015, 4,  5), date(2015, 4, 12)),
+                  (date(2016, 3, 27), date(2016, 5,  1)),
+                  (date(2017, 4, 16), date(2017, 4, 16)),
+                  (date(2018, 4,  1), date(2018, 4,  8)),
+                  (date(2019, 4, 21), date(2019, 4, 28)),
+
+                  (date(2020, 4, 12), date(2020, 4, 19)),
+                  (date(2021, 4,  4), date(2021, 5,  2)),
+                  (date(2022, 4, 17), date(2022, 4, 24)),
+                  (date(2023, 4,  9), date(2023, 4, 16)),
+                  (date(2024, 3, 31), date(2024, 5,  5)),
+                  (date(2025, 4, 20), date(2025, 4, 20)),
+                  (date(2026, 4,  5), date(2026, 4, 12)),
+                  (date(2027, 3, 28), date(2027, 5,  2)),
+                  (date(2028, 4, 16), date(2028, 4, 16)),
+                  (date(2029, 4,  1), date(2029, 4,  8)),
+
+                  (date(2030, 4, 21), date(2030, 4, 28)),
+                  (date(2031, 4, 13), date(2031, 4, 13)),
+                  (date(2032, 3, 28), date(2032, 5,  2)),
+                  (date(2033, 4, 17), date(2033, 4, 24)),
+                  (date(2034, 4,  9), date(2034, 4,  9)),
+                  (date(2035, 3, 25), date(2035, 4, 29)),
+                  (date(2036, 4, 13), date(2036, 4, 20)),
+                  (date(2037, 4,  5), date(2037, 4,  5)),
+                  (date(2038, 4, 25), date(2038, 4, 25)),
+                  (date(2039, 4, 10), date(2039, 4, 17)),
+
+                  (date(2040, 4,  1), date(2040, 5,  6)),
+                  (date(2041, 4, 21), date(2041, 4, 21)),
+                  (date(2042, 4,  6), date(2042, 4, 13)),
+                  (date(2043, 3, 29), date(2043, 5,  3)),
+                  (date(2044, 4, 17), date(2044, 4, 24)),
+                  (date(2045, 4,  9), date(2045, 4,  9)),
+                  (date(2046, 3, 25), date(2046, 4, 29)),
+                  (date(2047, 4, 14), date(2047, 4, 21)),
+                  (date(2048, 4,  5), date(2048, 4,  5)),
+                  (date(2049, 4, 18), date(2049, 4, 25)),
+
+                  (date(2050, 4, 10), date(2050, 4, 17)),
+                ]
+
+    def testEaster(self):
+        for western, orthodox in self.easterlist:
+            self.assertEqual(western,  easter(western.year,  EASTER_WESTERN))
+            self.assertEqual(orthodox, easter(orthodox.year, EASTER_ORTHODOX))
+
+class TZTest(unittest.TestCase):
+
+    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
+    """
+
+    def testStrStart1(self):
+        self.assertEqual(datetime(2003, 4, 6, 1, 59,
+                                  tzinfo=tzstr("EST5EDT")).tzname(), "EST")
+        self.assertEqual(datetime(2003, 4, 6, 2, 00,
+                                  tzinfo=tzstr("EST5EDT")).tzname(), "EDT")
+
+    def testStrEnd1(self):
+        self.assertEqual(datetime(2003, 10, 26, 0, 59,
+                                  tzinfo=tzstr("EST5EDT")).tzname(), "EDT")
+        self.assertEqual(datetime(2003, 10, 26, 1, 00,
+                                  tzinfo=tzstr("EST5EDT")).tzname(), "EST")
+
+    def testStrStart2(self):
+        s = "EST5EDT,4,0,6,7200,10,0,26,7200,3600"
+        self.assertEqual(datetime(2003, 4, 6, 1, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+        self.assertEqual(datetime(2003, 4, 6, 2, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+
+    def testStrEnd2(self):
+        s = "EST5EDT,4,0,6,7200,10,0,26,7200,3600"
+        self.assertEqual(datetime(2003, 10, 26, 0, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+        self.assertEqual(datetime(2003, 10, 26, 1, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+
+    def testStrStart3(self):
+        s = "EST5EDT,4,1,0,7200,10,-1,0,7200,3600"
+        self.assertEqual(datetime(2003, 4, 6, 1, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+        self.assertEqual(datetime(2003, 4, 6, 2, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+
+    def testStrEnd3(self):
+        s = "EST5EDT,4,1,0,7200,10,-1,0,7200,3600"
+        self.assertEqual(datetime(2003, 10, 26, 0, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+        self.assertEqual(datetime(2003, 10, 26, 1, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+
+    def testStrStart4(self):
+        s = "EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00"
+        self.assertEqual(datetime(2003, 4, 6, 1, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+        self.assertEqual(datetime(2003, 4, 6, 2, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+
+    def testStrEnd4(self):
+        s = "EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00"
+        self.assertEqual(datetime(2003, 10, 26, 0, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+        self.assertEqual(datetime(2003, 10, 26, 1, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+
+    def testStrStart5(self):
+        s = "EST5EDT4,95/02:00:00,298/02:00"
+        self.assertEqual(datetime(2003, 4, 6, 1, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+        self.assertEqual(datetime(2003, 4, 6, 2, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+
+    def testStrEnd5(self):
+        s = "EST5EDT4,95/02:00:00,298/02"
+        self.assertEqual(datetime(2003, 10, 26, 0, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+        self.assertEqual(datetime(2003, 10, 26, 1, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+
+    def testStrStart6(self):
+        s = "EST5EDT4,J96/02:00:00,J299/02:00"
+        self.assertEqual(datetime(2003, 4, 6, 1, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+        self.assertEqual(datetime(2003, 4, 6, 2, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+
+    def testStrEnd6(self):
+        s = "EST5EDT4,J96/02:00:00,J299/02"
+        self.assertEqual(datetime(2003, 10, 26, 0, 59,
+                                  tzinfo=tzstr(s)).tzname(), "EDT")
+        self.assertEqual(datetime(2003, 10, 26, 1, 00,
+                                  tzinfo=tzstr(s)).tzname(), "EST")
+
+    def testStrStr(self):
+        # Test that tzstr() won't throw an error if given a str instead
+        # of a unicode literal.
+        self.assertEqual(datetime(2003, 4, 6, 1, 59,
+                                  tzinfo=tzstr(str("EST5EDT"))).tzname(), "EST")
+        self.assertEqual(datetime(2003, 4, 6, 2, 00,
+                                  tzinfo=tzstr(str("EST5EDT"))).tzname(), "EDT")
+
+    def testStrCmp1(self):
+        self.assertEqual(tzstr("EST5EDT"),
+                         tzstr("EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00"))
+
+    def testStrCmp2(self):
+        self.assertEqual(tzstr("EST5EDT"),
+                         tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200,3600"))
+
+    def testRangeCmp1(self):
+        self.assertEqual(tzstr("EST5EDT"),
+                         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(tzstr("EST5EDT"),
+                         tzrange("EST", -18000, "EDT"))
+
+    def testFileStart1(self):
+        tz = tzfile(BytesIO(base64.decodestring(self.TZFILE_EST5EDT)))
+        self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST")
+        self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT")
+
+    def testFileEnd1(self):
+        tz = tzfile(BytesIO(base64.decodestring(self.TZFILE_EST5EDT)))
+        self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(),
+                         "EDT")
+        self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(),
+                         "EST")
+
+    def testZoneInfoFileStart1(self):
+        tz = zoneinfo.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):
+        tz = zoneinfo.gettz("EST5EDT")
+        self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(),
+                         "EDT", MISSING_TARBALL)
+        self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(),
+                         "EST")
+
+    def testZoneInfoOffsetSignal(self):
+        utc = zoneinfo.gettz("UTC")
+        nyc = zoneinfo.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 testICalStart1(self):
+        tz = tzical(StringIO(self.TZICAL_EST5EDT)).get()
+        self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST")
+        self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT")
+
+    def testICalEnd1(self):
+        tz = tzical(StringIO(self.TZICAL_EST5EDT)).get()
+        self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(), "EDT")
+        self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(), "EST")
+
+    def testRoundNonFullMinutes(self):
+        # This timezone has an offset of 5992 seconds in 1900-01-01.
+        tz = tzfile(BytesIO(base64.decodestring(self.EUROPE_HELSINKI)))
+        self.assertEqual(str(datetime(1900, 1, 1, 0, 0, tzinfo=tz)),
+                            "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.
+        tz = tzfile(BytesIO(base64.decodestring(self.NEW_YORK)))
+        self.assertEqual(datetime(2007, 3, 31, 20, 12).tzname(), None)
+
+    def testGettz(self):
+        # bug 892569
+        str(gettz('UTC'))
+
+    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=tzutc())
+        self.assertEqual(dt.astimezone(tz=gettz("GMT+2")),
+                          datetime(2007, 8, 6, 6, 10, tzinfo=tzstr("GMT+2")))
+
+    def testGMTHasNoDaylight(self):
+        # tzstr("GMT+2") improperly considered daylight saving time.
+        # Issue reported by Lennart Regebro.
+        dt = datetime(2007, 8, 6, 4, 10)
+        self.assertEqual(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=tzutc())
+        self.assertEqual(dt.astimezone(tz=tzstr("GMT+2")),
+                          datetime(2007, 8, 6, 6, 10, tzinfo=tzstr("GMT+2")))
+        self.assertEqual(dt.astimezone(tz=gettz("UTC-2")),
+                          datetime(2007, 8, 6, 2, 10, tzinfo=tzstr("UTC-2")))
+
+    @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
+    def testIsdstZoneWithNoDaylightSaving(self):
+        tz = tzwin.tzwin("UTC")
+        dt = parse("2013-03-06 19:08:15")
+        self.assertFalse(tz._isdst(dt))
+
+# vim:ts=4:sw=4

+ 986 - 0
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/tz.py

@@ -0,0 +1,986 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers timezone implementations subclassing the abstract
+:py:`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
+
+from six import string_types, PY3
+
+try:
+    from dateutil.tzwin import tzwin, tzwinlocal
+except ImportError:
+    tzwin = tzwinlocal = None
+
+relativedelta = None
+parser = None
+rrule = None
+
+__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
+           "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
+
+
+def tzname_in_python2(myfunc):
+    """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 inner_func(*args, **kwargs):
+        if PY3:
+            return myfunc(*args, **kwargs)
+        else:
+            return myfunc(*args, **kwargs).encode()
+    return inner_func
+
+ZERO = datetime.timedelta(0)
+EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
+
+
+class tzutc(datetime.tzinfo):
+
+    def utcoffset(self, dt):
+        return ZERO
+
+    def dst(self, dt):
+        return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return "UTC"
+
+    def __eq__(self, other):
+        return (isinstance(other, tzutc) or
+                (isinstance(other, tzoffset) and other._offset == ZERO))
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return "%s()" % self.__class__.__name__
+
+    __reduce__ = object.__reduce__
+
+
+class tzoffset(datetime.tzinfo):
+
+    def __init__(self, name, offset):
+        self._name = name
+        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
+
+    def __eq__(self, other):
+        return (isinstance(other, tzoffset) and
+                self._offset == other._offset)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return "%s(%s, %s)" % (self.__class__.__name__,
+                               repr(self._name),
+                               self._offset.days*86400+self._offset.seconds)
+
+    __reduce__ = object.__reduce__
+
+
+class tzlocal(datetime.tzinfo):
+
+    _std_offset = datetime.timedelta(seconds=-time.timezone)
+    if time.daylight:
+        _dst_offset = datetime.timedelta(seconds=-time.altzone)
+    else:
+        _dst_offset = _std_offset
+
+    def utcoffset(self, dt):
+        if self._isdst(dt):
+            return self._dst_offset
+        else:
+            return self._std_offset
+
+    def dst(self, dt):
+        if self._isdst(dt):
+            return self._dst_offset-self._std_offset
+        else:
+            return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return time.tzname[self._isdst(dt)]
+
+    def _isdst(self, dt):
+        # 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:
+        #
+        timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
+                     + dt.hour * 3600
+                     + dt.minute * 60
+                     + dt.second)
+        return time.localtime(timestamp+time.timezone).tm_isdst
+
+    def __eq__(self, other):
+        if not isinstance(other, tzlocal):
+            return False
+        return (self._std_offset == other._std_offset and
+                self._dst_offset == other._dst_offset)
+        return True
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return "%s()" % self.__class__.__name__
+
+    __reduce__ = object.__reduce__
+
+
+class _ttinfo(object):
+    __slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
+
+    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 False
+        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)
+
+    def __ne__(self, other):
+        return not self.__eq__(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(datetime.tzinfo):
+
+    # http://www.twinsun.com/tz/tz-link.htm
+    # ftp://ftp.iana.org/tz/tz*.tar.gz
+
+    def __init__(self, fileobj, filename=None):
+        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)
+
+        # 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).
+        try:
+            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:
+                self._trans_list = struct.unpack(">%dl" % timecnt,
+                                                 fileobj.read(timecnt*4))
+            else:
+                self._trans_list = []
+
+            # 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:
+                self._trans_idx = struct.unpack(">%dB" % timecnt,
+                                                fileobj.read(timecnt))
+            else:
+                self._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
+            # if leapcnt:
+            #    leap = struct.unpack(">%dl" % (leapcnt*2),
+            #                         fileobj.read(leapcnt*8))
+
+            # 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))
+
+            # ** Everything has been read **
+        finally:
+            if file_opened_here:
+                fileobj.close()
+
+        # Build ttinfo list
+        self._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 = (gmtoff+30)//60*60
+            tti = _ttinfo()
+            tti.offset = gmtoff
+            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)
+            self._ttinfo_list.append(tti)
+
+        # Replace ttinfo indexes for ttinfo objects.
+        trans_idx = []
+        for idx in self._trans_idx:
+            trans_idx.append(self._ttinfo_list[idx])
+        self._trans_idx = tuple(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.
+        self._ttinfo_std = None
+        self._ttinfo_dst = None
+        self._ttinfo_before = None
+        if self._ttinfo_list:
+            if not self._trans_list:
+                self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0]
+            else:
+                for i in range(timecnt-1, -1, -1):
+                    tti = self._trans_idx[i]
+                    if not self._ttinfo_std and not tti.isdst:
+                        self._ttinfo_std = tti
+                    elif not self._ttinfo_dst and tti.isdst:
+                        self._ttinfo_dst = tti
+                    if self._ttinfo_std and self._ttinfo_dst:
+                        break
+                else:
+                    if self._ttinfo_dst and not self._ttinfo_std:
+                        self._ttinfo_std = self._ttinfo_dst
+
+                for tti in self._ttinfo_list:
+                    if not tti.isdst:
+                        self._ttinfo_before = tti
+                        break
+                else:
+                    self._ttinfo_before = self._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 = 0
+        self._trans_list = list(self._trans_list)
+        for i in range(len(self._trans_list)):
+            tti = self._trans_idx[i]
+            if not tti.isdst:
+                # This is std time.
+                self._trans_list[i] += tti.offset
+                laststdoffset = tti.offset
+            else:
+                # This is dst time. Convert to std.
+                self._trans_list[i] += laststdoffset
+        self._trans_list = tuple(self._trans_list)
+
+    def _find_ttinfo(self, dt, laststd=0):
+        timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
+                     + dt.hour * 3600
+                     + dt.minute * 60
+                     + dt.second)
+        idx = 0
+        for trans in self._trans_list:
+            if timestamp < trans:
+                break
+            idx += 1
+        else:
+            return self._ttinfo_std
+        if idx == 0:
+            return self._ttinfo_before
+        if laststd:
+            while idx > 0:
+                tti = self._trans_idx[idx-1]
+                if not tti.isdst:
+                    return tti
+                idx -= 1
+            else:
+                return self._ttinfo_std
+        else:
+            return self._trans_idx[idx-1]
+
+    def utcoffset(self, dt):
+        if not self._ttinfo_std:
+            return ZERO
+        return self._find_ttinfo(dt).delta
+
+    def dst(self, dt):
+        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.delta-self._find_ttinfo(dt, laststd=1).delta
+
+        # An alternative for that would be:
+        #
+        # return self._ttinfo_dst.offset-self._ttinfo_std.offset
+        #
+        # However, this class stores historical changes in the
+        # dst offset, so I belive that this wouldn't be the right
+        # way to implement this.
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        if not self._ttinfo_std:
+            return None
+        return self._find_ttinfo(dt).abbr
+
+    def __eq__(self, other):
+        if not isinstance(other, tzfile):
+            return False
+        return (self._trans_list == other._trans_list and
+                self._trans_idx == other._trans_idx and
+                self._ttinfo_list == other._ttinfo_list)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
+
+    def __reduce__(self):
+        if not os.path.isfile(self._filename):
+            raise ValueError("Unpickable %s class" % self.__class__.__name__)
+        return (self.__class__, (self._filename,))
+
+
+class tzrange(datetime.tzinfo):
+    def __init__(self, stdabbr, stdoffset=None,
+                 dstabbr=None, dstoffset=None,
+                 start=None, end=None):
+        global relativedelta
+        if not relativedelta:
+            from dateutil import relativedelta
+        self._std_abbr = stdabbr
+        self._dst_abbr = dstabbr
+        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
+
+    def utcoffset(self, dt):
+        if self._isdst(dt):
+            return self._dst_offset
+        else:
+            return self._std_offset
+
+    def dst(self, dt):
+        if self._isdst(dt):
+            return self._dst_offset-self._std_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 _isdst(self, dt):
+        if not self._start_delta:
+            return False
+        year = datetime.datetime(dt.year, 1, 1)
+        start = year+self._start_delta
+        end = year+self._end_delta
+        dt = dt.replace(tzinfo=None)
+        if start < end:
+            return dt >= start and dt < end
+        else:
+            return dt >= start or dt < end
+
+    def __eq__(self, other):
+        if not isinstance(other, tzrange):
+            return False
+        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)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return "%s(...)" % self.__class__.__name__
+
+    __reduce__ = object.__reduce__
+
+
+class tzstr(tzrange):
+
+    def __init__(self, s):
+        global parser
+        if not parser:
+            from dateutil import parser
+        self._s = s
+
+        res = parser._parsetz(s)
+        if res is None:
+            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"):
+            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)
+
+    def _delta(self, x, isend=0):
+        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(datetime.tzinfo):
+    def __init__(self, tzid, comps=[]):
+        self._tzid = tzid
+        self._comps = comps
+        self._cachedate = []
+        self._cachecomp = []
+
+    def _find_comp(self, dt):
+        if len(self._comps) == 1:
+            return self._comps[0]
+        dt = dt.replace(tzinfo=None)
+        try:
+            return self._cachecomp[self._cachedate.index(dt)]
+        except ValueError:
+            pass
+        lastcomp = None
+        lastcompdt = None
+        for comp in self._comps:
+            if not comp.isdst:
+                # Handle the extra hour in DST -> STD
+                compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True)
+            else:
+                compdt = comp.rrule.before(dt, inc=True)
+            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]
+        self._cachedate.insert(0, dt)
+        self._cachecomp.insert(0, lastcomp)
+        if len(self._cachedate) > 10:
+            self._cachedate.pop()
+            self._cachecomp.pop()
+        return lastcomp
+
+    def utcoffset(self, dt):
+        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):
+    def __init__(self, fileobj):
+        global rrule
+        if not 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')
+        elif hasattr(fileobj, "name"):
+            self._s = fileobj.name
+        else:
+            self._s = repr(fileobj)
+
+        self._vtz = {}
+
+        self._parse_rfc(fileobj.read())
+
+    def keys(self):
+        return list(self._vtz.keys())
+
+    def get(self, tzid=None):
+        if tzid is None:
+            keys = list(self._vtz.keys())
+            if len(keys) == 0:
+                raise ValueError("no timezones defined")
+            elif len(keys) > 1:
+                raise ValueError("more than one timezone available")
+            tzid = keys[0]
+        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":
+                        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", "/etc/zoneinfo"]
+else:
+    TZFILES = []
+    TZPATHS = []
+
+
+def gettz(name=None):
+    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 gettz
+                    tz = gettz(name)
+                if not tz:
+                    for c in name:
+                        # name must have at least one offset to be a tzstr
+                        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
+
+# vim:ts=4:sw=4:et

+ 184 - 0
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/tzwin.py

@@ -0,0 +1,184 @@
+# This code was originally contributed by Jeffrey Harris.
+import datetime
+import struct
+
+from six.moves import winreg
+
+__all__ = ["tzwin", "tzwinlocal"]
+
+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 tzwinbase(datetime.tzinfo):
+    """tzinfo class based on win32's timezones available in the registry."""
+
+    def utcoffset(self, dt):
+        if self._isdst(dt):
+            return datetime.timedelta(minutes=self._dstoffset)
+        else:
+            return datetime.timedelta(minutes=self._stdoffset)
+
+    def dst(self, dt):
+        if self._isdst(dt):
+            minutes = self._dstoffset - self._stdoffset
+            return datetime.timedelta(minutes=minutes)
+        else:
+            return datetime.timedelta(0)
+
+    def tzname(self, dt):
+        if self._isdst(dt):
+            return self._dstname
+        else:
+            return self._stdname
+
+    def list():
+        """Return a list of all time zones known to the system."""
+        handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+        tzkey = winreg.OpenKey(handle, TZKEYNAME)
+        result = [winreg.EnumKey(tzkey, i)
+                  for i in range(winreg.QueryInfoKey(tzkey)[0])]
+        tzkey.Close()
+        handle.Close()
+        return result
+    list = staticmethod(list)
+
+    def display(self):
+        return self._display
+
+    def _isdst(self, dt):
+        if not self._dstmonth:
+            # dstmonth == 0 signals the zone has no daylight saving time
+            return False
+        dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
+                               self._dsthour, self._dstminute,
+                               self._dstweeknumber)
+        dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek,
+                                self._stdhour, self._stdminute,
+                                self._stdweeknumber)
+        if dston < dstoff:
+            return dston <= dt.replace(tzinfo=None) < dstoff
+        else:
+            return not dstoff <= dt.replace(tzinfo=None) < dston
+
+
+class tzwin(tzwinbase):
+
+    def __init__(self, name):
+        self._name = name
+
+        # multiple contexts only possible in 2.7 and 3.1, we still support 2.6
+        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
+            with winreg.OpenKey(handle,
+                                "%s\%s" % (TZKEYNAME, name)) as tzkey:
+                keydict = valuestodict(tzkey)
+
+        self._stdname = keydict["Std"].encode("iso-8859-1")
+        self._dstname = keydict["Dlt"].encode("iso-8859-1")
+
+        self._display = keydict["Display"]
+
+        # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
+        tup = struct.unpack("=3l16h", keydict["TZI"])
+        self._stdoffset = -tup[0]-tup[1]          # Bias + StandardBias * -1
+        self._dstoffset = self._stdoffset-tup[2]  # + DaylightBias * -1
+
+        # 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]
+
+    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._stdname = keydict["StandardName"].encode("iso-8859-1")
+            self._dstname = keydict["DaylightName"].encode("iso-8859-1")
+
+            try:
+                with winreg.OpenKey(
+                        handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey:
+                    _keydict = valuestodict(tzkey)
+                    self._display = _keydict["Display"]
+            except OSError:
+                self._display = None
+
+        self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
+        self._dstoffset = self._stdoffset-keydict["DaylightBias"]
+
+        # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
+        tup = struct.unpack("=8h", keydict["StandardStart"])
+
+        (self._stdmonth,
+         self._stddayofweek,   # Sunday = 0
+         self._stdweeknumber,  # Last = 5
+         self._stdhour,
+         self._stdminute) = tup[1:6]
+
+        tup = struct.unpack("=8h", keydict["DaylightStart"])
+
+        (self._dstmonth,
+         self._dstdayofweek,   # Sunday = 0
+         self._dstweeknumber,  # Last = 5
+         self._dsthour,
+         self._dstminute) = tup[1:6]
+
+    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)
+    weekdayone = first.replace(day=((dayofweek-first.isoweekday()) % 7+1))
+    for n in range(whichweek):
+        dt = weekdayone+(whichweek-n)*ONEWEEK
+        if dt.month == month:
+            return dt
+
+
+def valuestodict(key):
+    """Convert a registry key's values to a dictionary."""
+    dict = {}
+    size = winreg.QueryInfoKey(key)[1]
+    for i in range(size):
+        data = winreg.EnumValue(key, i)
+        dict[data[0]] = data[1]
+    return dict

+ 108 - 0
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/zoneinfo/__init__.py

@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+import logging
+import os
+import warnings
+import tempfile
+import shutil
+from subprocess import check_call
+from tarfile import TarFile
+from pkgutil import get_data
+from io import BytesIO
+from contextlib import closing
+
+from dateutil.tz import tzfile
+
+__all__ = ["gettz", "rebuild"]
+
+_ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
+
+# python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but
+# it's close enough for python2.6
+_tar_open = TarFile.open
+if not hasattr(TarFile, '__exit__'):
+    def _tar_open(*args, **kwargs):
+        return closing(TarFile.open(*args, **kwargs))
+
+
+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 _tar_open(fileobj=zonefile_stream, mode='r') as tf:
+                # dict comprehension does not work on python2.6
+                # TODO: get back to the nicer syntax when we ditch python2.6
+                # self.zones = {zf.name: tzfile(tf.extractfile(zf),
+                #               filename = zf.name)
+                #              for zf in tf.getmembers() if zf.isfile()}
+                self.zones = dict((zf.name, tzfile(tf.extractfile(zf),
+                                                   filename=zf.name))
+                                  for zf in tf.getmembers() if zf.isfile())
+                # 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()}
+                links = dict((zl.name, self.zones[zl.linkname])
+                             for zl in tf.getmembers() if
+                             zl.islnk() or zl.issym())
+                self.zones.update(links)
+        else:
+            self.zones = dict()
+
+
+# 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: deprecate this.
+_CLASS_ZONE_INSTANCE = list()
+
+
+def gettz(name):
+    if len(_CLASS_ZONE_INSTANCE) == 0:
+        _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
+    return _CLASS_ZONE_INSTANCE[0].zones.get(name)
+
+
+def rebuild(filename, tag=None, format="gz", zonegroups=[]):
+    """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 _tar_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:
+                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?")
+                    raise
+        target = os.path.join(moduledir, _ZONEFILENAME)
+        with _tar_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)

BIN
desktop/core/ext-py/python-dateutil-2.4.2/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz


+ 8 - 0
desktop/core/ext-py/python-dateutil-2.4.2/setup.cfg

@@ -0,0 +1,8 @@
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+

+ 51 - 0
desktop/core/ext-py/python-dateutil-2.4.2/setup.py

@@ -0,0 +1,51 @@
+#!/usr/bin/python
+from os.path import isfile
+import codecs
+import os
+import re
+
+from setuptools import setup
+
+
+if isfile("MANIFEST"):
+    os.unlink("MANIFEST")
+
+
+TOPDIR = os.path.dirname(__file__) or "."
+VERSION = re.search('__version__ = "([^"]+)"',
+                    codecs.open(TOPDIR + "/dateutil/__init__.py",
+                                encoding='utf-8').read()).group(1)
+
+
+setup(name="python-dateutil",
+      version=VERSION,
+      description="Extensions to the standard Python datetime module",
+      author="Yaron de Leeuw",
+      author_email="me@jarondl.net",
+      url="https://dateutil.readthedocs.org",
+      license="Simplified BSD",
+      long_description="""
+The dateutil module provides powerful extensions to the
+datetime module available in the Python standard library.
+""",
+      packages=["dateutil", "dateutil.zoneinfo"],
+      package_data={"dateutil.zoneinfo": ["dateutil-zoneinfo.tar.gz"]},
+      zip_safe=True,
+      requires=["six"],
+      install_requires=["six >=1.5"],  # XXX fix when packaging is sane again
+      classifiers=[
+          'Development Status :: 5 - Production/Stable',
+          'Intended Audience :: Developers',
+          'License :: OSI Approved :: BSD License',
+          'Programming Language :: Python',
+          'Programming Language :: Python :: 2',
+          'Programming Language :: Python :: 2.6',
+          'Programming Language :: Python :: 2.7',
+          'Programming Language :: Python :: 3',
+          'Programming Language :: Python :: 3.2',
+          'Programming Language :: Python :: 3.3',
+          'Programming Language :: Python :: 3.4',
+          'Topic :: Software Development :: Libraries',
+      ],
+      test_suite="dateutil.test.test"
+      )

+ 33 - 0
desktop/core/ext-py/python-dateutil-2.4.2/updatezinfo.py

@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+import os
+import hashlib
+import json
+import io
+
+from six.moves.urllib import request
+
+from dateutil.zoneinfo import rebuild
+
+METADATA_FILE = "zonefile_metadata.json"
+
+
+def main():
+    with io.open(METADATA_FILE, 'r') as f:
+        metadata = json.load(f)
+
+    if not os.path.isfile(metadata['tzdata_file']):
+        print("Downloading tz file from iana")
+        request.urlretrieve(os.path.join(metadata['releases_url'],
+                                         metadata['tzdata_file']),
+                            metadata['tzdata_file'])
+    with open(metadata['tzdata_file'], 'rb') as tzfile:
+        sha_hasher = hashlib.sha512()
+        sha_hasher.update(tzfile.read())
+        sha_512_file = sha_hasher.hexdigest()
+        assert metadata['tzdata_file_sha512'] == sha_512_file, "SHA failed for"
+    print("Updating timezone information...")
+    rebuild(metadata['tzdata_file'], zonegroups=metadata['zonegroups'])
+    print("Done.")
+
+if __name__ == "__main__":
+    main()

+ 21 - 0
desktop/core/ext-py/python-dateutil-2.4.2/zonefile_metadata.json

@@ -0,0 +1,21 @@
+{
+    "metadata_version" : 0.1,
+    "releases_url" : "ftp://ftp.iana.org/tz/releases/",
+    "tzdata_file" : "tzdata2015b.tar.gz",
+    "tzdata_file_sha512" : "767782b87e62a8f7a4dbcae595d16a54197c9e04ca974d7016d11f90ebaf2537b804d111f204af9052c68d4670afe0af0af9e5b150867a357fc199bb541368d0",
+    "zonegroups" : [
+        "africa",
+        "antarctica",
+        "asia",
+        "australasia",
+        "europe",
+        "northamerica",
+        "southamerica",
+        "pacificnew",
+        "etcetera",
+        "systemv",
+        "factory",
+        "backzone",
+        "backward"]
+}
+