diff options
author | dshmatkov <dshmatkov@yandex-team.ru> | 2022-02-10 16:48:27 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:48:27 +0300 |
commit | 3a1449f44e2361ebf1a230ca6ab2cbcb1fed4e74 (patch) | |
tree | ab124f0c5ee8a3a3a3475bf13b15cee6c20401f4 /contrib/python/dateutil | |
parent | 46a845266642908d219e388c0d88bf7f0e1950de (diff) | |
download | ydb-3a1449f44e2361ebf1a230ca6ab2cbcb1fed4e74.tar.gz |
Restoring authorship annotation for <dshmatkov@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/dateutil')
31 files changed, 12249 insertions, 12249 deletions
diff --git a/contrib/python/dateutil/README.rst b/contrib/python/dateutil/README.rst index 106023b324..8348478725 100644 --- a/contrib/python/dateutil/README.rst +++ b/contrib/python/dateutil/README.rst @@ -1,168 +1,168 @@ -dateutil - powerful extensions to datetime -========================================== - +dateutil - powerful extensions to datetime +========================================== + |pypi| |support| |licence| - -|gitter| |readthedocs| - + +|gitter| |readthedocs| + |travis| |appveyor| |pipelines| |coverage| - -.. |pypi| image:: https://img.shields.io/pypi/v/python-dateutil.svg?style=flat-square - :target: https://pypi.org/project/python-dateutil/ - :alt: pypi version - -.. |support| image:: https://img.shields.io/pypi/pyversions/python-dateutil.svg?style=flat-square - :target: https://pypi.org/project/python-dateutil/ - :alt: supported Python version - -.. |travis| image:: https://img.shields.io/travis/dateutil/dateutil/master.svg?style=flat-square&label=Travis%20Build - :target: https://travis-ci.org/dateutil/dateutil - :alt: travis build status - -.. |appveyor| image:: https://img.shields.io/appveyor/ci/dateutil/dateutil/master.svg?style=flat-square&logo=appveyor - :target: https://ci.appveyor.com/project/dateutil/dateutil - :alt: appveyor build status - + +.. |pypi| image:: https://img.shields.io/pypi/v/python-dateutil.svg?style=flat-square + :target: https://pypi.org/project/python-dateutil/ + :alt: pypi version + +.. |support| image:: https://img.shields.io/pypi/pyversions/python-dateutil.svg?style=flat-square + :target: https://pypi.org/project/python-dateutil/ + :alt: supported Python version + +.. |travis| image:: https://img.shields.io/travis/dateutil/dateutil/master.svg?style=flat-square&label=Travis%20Build + :target: https://travis-ci.org/dateutil/dateutil + :alt: travis build status + +.. |appveyor| image:: https://img.shields.io/appveyor/ci/dateutil/dateutil/master.svg?style=flat-square&logo=appveyor + :target: https://ci.appveyor.com/project/dateutil/dateutil + :alt: appveyor build status + .. |pipelines| image:: https://dev.azure.com/pythondateutilazure/dateutil/_apis/build/status/dateutil.dateutil?branchName=master :target: https://dev.azure.com/pythondateutilazure/dateutil/_build/latest?definitionId=1&branchName=master :alt: azure pipelines build status .. |coverage| image:: https://codecov.io/gh/dateutil/dateutil/branch/master/graphs/badge.svg?branch=master :target: https://codecov.io/gh/dateutil/dateutil?branch=master - :alt: Code coverage - -.. |gitter| image:: https://badges.gitter.im/dateutil/dateutil.svg - :alt: Join the chat at https://gitter.im/dateutil/dateutil - :target: https://gitter.im/dateutil/dateutil - -.. |licence| image:: https://img.shields.io/pypi/l/python-dateutil.svg?style=flat-square - :target: https://pypi.org/project/python-dateutil/ - :alt: licence - -.. |readthedocs| image:: https://img.shields.io/readthedocs/dateutil/latest.svg?style=flat-square&label=Read%20the%20Docs - :alt: Read the documentation at https://dateutil.readthedocs.io/en/latest/ - :target: https://dateutil.readthedocs.io/en/latest/ - -The `dateutil` module provides powerful extensions to -the standard `datetime` module, available in Python. - + :alt: Code coverage + +.. |gitter| image:: https://badges.gitter.im/dateutil/dateutil.svg + :alt: Join the chat at https://gitter.im/dateutil/dateutil + :target: https://gitter.im/dateutil/dateutil + +.. |licence| image:: https://img.shields.io/pypi/l/python-dateutil.svg?style=flat-square + :target: https://pypi.org/project/python-dateutil/ + :alt: licence + +.. |readthedocs| image:: https://img.shields.io/readthedocs/dateutil/latest.svg?style=flat-square&label=Read%20the%20Docs + :alt: Read the documentation at https://dateutil.readthedocs.io/en/latest/ + :target: https://dateutil.readthedocs.io/en/latest/ + +The `dateutil` module provides powerful extensions to +the standard `datetime` module, available in Python. + Installation ============ `dateutil` can be installed from PyPI using `pip` (note that the package name is different from the importable name):: - + pip install python-dateutil -Download -======== -dateutil is available on PyPI -https://pypi.org/project/python-dateutil/ - -The documentation is hosted at: -https://dateutil.readthedocs.io/en/stable/ - -Code -==== +Download +======== +dateutil is available on PyPI +https://pypi.org/project/python-dateutil/ + +The documentation is hosted at: +https://dateutil.readthedocs.io/en/stable/ + +Code +==== The code and issue tracker are hosted on GitHub: -https://github.com/dateutil/dateutil/ - -Features -======== - -* Computing of relative deltas (next month, next year, +https://github.com/dateutil/dateutil/ + +Features +======== + +* Computing of relative deltas (next month, next year, next Monday, last week of month, etc); -* Computing of relative deltas between two given - date and/or datetime objects; -* Computing of dates based on very flexible recurrence rules, - using a superset of the `iCalendar <https://www.ietf.org/rfc/rfc2445.txt>`_ - specification. Parsing of RFC strings is supported as well. -* Generic parsing of dates in almost any string format; -* Timezone (tzinfo) implementations for tzfile(5) format - files (/etc/localtime, /usr/share/zoneinfo, etc), TZ - environment string (in all known formats), iCalendar - format files, given ranges (with help from relative deltas), - local machine timezone, fixed offset timezone, UTC timezone, - and Windows registry-based time zones. -* Internal up-to-date world timezone information based on - Olson's database. -* Computing of Easter Sunday dates for any given year, - using Western, Orthodox or Julian algorithms; -* A comprehensive test suite. - -Quick example -============= -Here's a snapshot, just to give an idea about the power of the -package. For more examples, look at the documentation. - -Suppose you want to know how much time is left, in -years/months/days/etc, before the next easter happening on a -year with a Friday 13th in August, and you want to get today's -date out of the "date" unix system command. Here is the code: - -.. doctest:: readmeexample - - >>> from dateutil.relativedelta import * - >>> from dateutil.easter import * - >>> from dateutil.rrule import * - >>> from dateutil.parser import * - >>> from datetime import * - >>> now = parse("Sat Oct 11 17:13:46 UTC 2003") - >>> today = now.date() - >>> year = rrule(YEARLY,dtstart=now,bymonth=8,bymonthday=13,byweekday=FR)[0].year - >>> rdelta = relativedelta(easter(year), today) - >>> print("Today is: %s" % today) - Today is: 2003-10-11 - >>> print("Year with next Aug 13th on a Friday is: %s" % year) - Year with next Aug 13th on a Friday is: 2004 - >>> print("How far is the Easter of that year: %s" % rdelta) - How far is the Easter of that year: relativedelta(months=+6) - >>> print("And the Easter of that year is: %s" % (today+rdelta)) - And the Easter of that year is: 2004-04-11 - -Being exactly 6 months ahead was **really** a coincidence :) - -Contributing -============ - -We welcome many types of contributions - bug reports, pull requests (code, infrastructure or documentation fixes). For more information about how to contribute to the project, see the ``CONTRIBUTING.md`` file in the repository. - - -Author -====== -The dateutil module was written by Gustavo Niemeyer <gustavo@niemeyer.net> -in 2003. - -It is maintained by: - -* Gustavo Niemeyer <gustavo@niemeyer.net> 2003-2011 -* Tomi Pieviläinen <tomi.pievilainen@iki.fi> 2012-2014 -* Yaron de Leeuw <me@jarondl.net> 2014-2016 -* Paul Ganssle <paul@ganssle.io> 2015- - +* Computing of relative deltas between two given + date and/or datetime objects; +* Computing of dates based on very flexible recurrence rules, + using a superset of the `iCalendar <https://www.ietf.org/rfc/rfc2445.txt>`_ + specification. Parsing of RFC strings is supported as well. +* Generic parsing of dates in almost any string format; +* Timezone (tzinfo) implementations for tzfile(5) format + files (/etc/localtime, /usr/share/zoneinfo, etc), TZ + environment string (in all known formats), iCalendar + format files, given ranges (with help from relative deltas), + local machine timezone, fixed offset timezone, UTC timezone, + and Windows registry-based time zones. +* Internal up-to-date world timezone information based on + Olson's database. +* Computing of Easter Sunday dates for any given year, + using Western, Orthodox or Julian algorithms; +* A comprehensive test suite. + +Quick example +============= +Here's a snapshot, just to give an idea about the power of the +package. For more examples, look at the documentation. + +Suppose you want to know how much time is left, in +years/months/days/etc, before the next easter happening on a +year with a Friday 13th in August, and you want to get today's +date out of the "date" unix system command. Here is the code: + +.. doctest:: readmeexample + + >>> from dateutil.relativedelta import * + >>> from dateutil.easter import * + >>> from dateutil.rrule import * + >>> from dateutil.parser import * + >>> from datetime import * + >>> now = parse("Sat Oct 11 17:13:46 UTC 2003") + >>> today = now.date() + >>> year = rrule(YEARLY,dtstart=now,bymonth=8,bymonthday=13,byweekday=FR)[0].year + >>> rdelta = relativedelta(easter(year), today) + >>> print("Today is: %s" % today) + Today is: 2003-10-11 + >>> print("Year with next Aug 13th on a Friday is: %s" % year) + Year with next Aug 13th on a Friday is: 2004 + >>> print("How far is the Easter of that year: %s" % rdelta) + How far is the Easter of that year: relativedelta(months=+6) + >>> print("And the Easter of that year is: %s" % (today+rdelta)) + And the Easter of that year is: 2004-04-11 + +Being exactly 6 months ahead was **really** a coincidence :) + +Contributing +============ + +We welcome many types of contributions - bug reports, pull requests (code, infrastructure or documentation fixes). For more information about how to contribute to the project, see the ``CONTRIBUTING.md`` file in the repository. + + +Author +====== +The dateutil module was written by Gustavo Niemeyer <gustavo@niemeyer.net> +in 2003. + +It is maintained by: + +* Gustavo Niemeyer <gustavo@niemeyer.net> 2003-2011 +* Tomi Pieviläinen <tomi.pievilainen@iki.fi> 2012-2014 +* Yaron de Leeuw <me@jarondl.net> 2014-2016 +* Paul Ganssle <paul@ganssle.io> 2015- + Starting with version 2.4.1 and running until 2.8.2, all source and binary distributions will be signed by a PGP key that has, at the very least, been signed by the key which made the previous release. A table of release signing keys can be found below: - -=========== ============================ -Releases Signing key fingerprint -=========== ============================ + +=========== ============================ +Releases Signing key fingerprint +=========== ============================ 2.4.1-2.8.2 `6B49 ACBA DCF6 BD1C A206 67AB CD54 FCE3 D964 BEFB`_ -=========== ============================ - +=========== ============================ + New releases *may* have signed tags, but binary and source distributions uploaded to PyPI will no longer have GPG signatures attached. - -Contact -======= -Our mailing list is available at `dateutil@python.org <https://mail.python.org/mailman/listinfo/dateutil>`_. As it is hosted by the PSF, it is subject to the `PSF code of + +Contact +======= +Our mailing list is available at `dateutil@python.org <https://mail.python.org/mailman/listinfo/dateutil>`_. As it is hosted by the PSF, it is subject to the `PSF code of conduct <https://www.python.org/psf/conduct/>`_. - -License -======= - -All contributions after December 1, 2017 released under dual license - either `Apache 2.0 License <https://www.apache.org/licenses/LICENSE-2.0>`_ or the `BSD 3-Clause License <https://opensource.org/licenses/BSD-3-Clause>`_. Contributions before December 1, 2017 - except those those explicitly relicensed - are released only under the BSD 3-Clause License. - - -.. _6B49 ACBA DCF6 BD1C A206 67AB CD54 FCE3 D964 BEFB: - https://pgp.mit.edu/pks/lookup?op=vindex&search=0xCD54FCE3D964BEFB + +License +======= + +All contributions after December 1, 2017 released under dual license - either `Apache 2.0 License <https://www.apache.org/licenses/LICENSE-2.0>`_ or the `BSD 3-Clause License <https://opensource.org/licenses/BSD-3-Clause>`_. Contributions before December 1, 2017 - except those those explicitly relicensed - are released only under the BSD 3-Clause License. + + +.. _6B49 ACBA DCF6 BD1C A206 67AB CD54 FCE3 D964 BEFB: + https://pgp.mit.edu/pks/lookup?op=vindex&search=0xCD54FCE3D964BEFB diff --git a/contrib/python/dateutil/dateutil/__init__.py b/contrib/python/dateutil/dateutil/__init__.py index 0defb82e21..f0bc8352db 100644 --- a/contrib/python/dateutil/dateutil/__init__.py +++ b/contrib/python/dateutil/dateutil/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -try: - from ._version import version as __version__ -except ImportError: - __version__ = 'unknown' - -__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', - 'utils', 'zoneinfo'] +try: + from ._version import version as __version__ +except ImportError: + __version__ = 'unknown' + +__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', + 'utils', 'zoneinfo'] diff --git a/contrib/python/dateutil/dateutil/_common.py b/contrib/python/dateutil/dateutil/_common.py index 4eb2659bd2..30cc3f7520 100644 --- a/contrib/python/dateutil/dateutil/_common.py +++ b/contrib/python/dateutil/dateutil/_common.py @@ -24,20 +24,20 @@ class weekday(object): return False return True - def __hash__(self): - return hash(( - self.weekday, - self.n, - )) - - def __ne__(self, other): - return not (self == other) - + def __hash__(self): + return hash(( + self.weekday, + self.n, + )) + + def __ne__(self, other): + return not (self == other) + def __repr__(self): s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] if not self.n: return s else: return "%s(%+d)" % (s, self.n) - -# vim:ts=4:sw=4:et + +# vim:ts=4:sw=4:et diff --git a/contrib/python/dateutil/dateutil/easter.py b/contrib/python/dateutil/dateutil/easter.py index f74d1f7442..14a16edbf9 100644 --- a/contrib/python/dateutil/dateutil/easter.py +++ b/contrib/python/dateutil/dateutil/easter.py @@ -41,11 +41,11 @@ def easter(year, method=EASTER_WESTERN): More about the algorithm may be found at: - `GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_ + `GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_ and - `The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_ + `The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_ """ diff --git a/contrib/python/dateutil/dateutil/parser/__init__.py b/contrib/python/dateutil/dateutil/parser/__init__.py index d174b0e4dc..cda37f1a69 100644 --- a/contrib/python/dateutil/dateutil/parser/__init__.py +++ b/contrib/python/dateutil/dateutil/parser/__init__.py @@ -1,61 +1,61 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- from ._parser import parse, parser, parserinfo, ParserError -from ._parser import DEFAULTPARSER, DEFAULTTZPARSER -from ._parser import UnknownTimezoneWarning - -from ._parser import __doc__ - -from .isoparser import isoparser, isoparse - -__all__ = ['parse', 'parser', 'parserinfo', - 'isoparse', 'isoparser', +from ._parser import DEFAULTPARSER, DEFAULTTZPARSER +from ._parser import UnknownTimezoneWarning + +from ._parser import __doc__ + +from .isoparser import isoparser, isoparse + +__all__ = ['parse', 'parser', 'parserinfo', + 'isoparse', 'isoparser', 'ParserError', - 'UnknownTimezoneWarning'] - - -### -# Deprecate portions of the private interface so that downstream code that -# is improperly relying on it is given *some* notice. - - -def __deprecated_private_func(f): - from functools import wraps - import warnings - - msg = ('{name} is a private function and may break without warning, ' - 'it will be moved and or renamed in future versions.') - msg = msg.format(name=f.__name__) - - @wraps(f) - def deprecated_func(*args, **kwargs): - warnings.warn(msg, DeprecationWarning) - return f(*args, **kwargs) - - return deprecated_func - -def __deprecate_private_class(c): - import warnings - - msg = ('{name} is a private class and may break without warning, ' - 'it will be moved and or renamed in future versions.') - msg = msg.format(name=c.__name__) - - class private_class(c): - __doc__ = c.__doc__ - - def __init__(self, *args, **kwargs): - warnings.warn(msg, DeprecationWarning) - super(private_class, self).__init__(*args, **kwargs) - - private_class.__name__ = c.__name__ - - return private_class - - + 'UnknownTimezoneWarning'] + + +### +# Deprecate portions of the private interface so that downstream code that +# is improperly relying on it is given *some* notice. + + +def __deprecated_private_func(f): + from functools import wraps + import warnings + + msg = ('{name} is a private function and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=f.__name__) + + @wraps(f) + def deprecated_func(*args, **kwargs): + warnings.warn(msg, DeprecationWarning) + return f(*args, **kwargs) + + return deprecated_func + +def __deprecate_private_class(c): + import warnings + + msg = ('{name} is a private class and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=c.__name__) + + class private_class(c): + __doc__ = c.__doc__ + + def __init__(self, *args, **kwargs): + warnings.warn(msg, DeprecationWarning) + super(private_class, self).__init__(*args, **kwargs) + + private_class.__name__ = c.__name__ + + return private_class + + from ._parser import _timelex, _resultbase from ._parser import _tzparser, _parsetz - -_timelex = __deprecate_private_class(_timelex) -_tzparser = __deprecate_private_class(_tzparser) -_resultbase = __deprecate_private_class(_resultbase) -_parsetz = __deprecated_private_func(_parsetz) + +_timelex = __deprecate_private_class(_timelex) +_tzparser = __deprecate_private_class(_tzparser) +_resultbase = __deprecate_private_class(_resultbase) +_parsetz = __deprecated_private_func(_parsetz) diff --git a/contrib/python/dateutil/dateutil/parser/_parser.py b/contrib/python/dateutil/dateutil/parser/_parser.py index 37d1663b2f..fa81f84093 100644 --- a/contrib/python/dateutil/dateutil/parser/_parser.py +++ b/contrib/python/dateutil/dateutil/parser/_parser.py @@ -1,1135 +1,1135 @@ -# -*- coding: utf-8 -*- -""" -This module offers a generic date/time string parser which is able to parse -most known formats to represent a date and/or time. - -This module attempts to be forgiving with regards to unlikely input formats, -returning a datetime object even for dates which are ambiguous. If an element -of a date/time stamp is omitted, the following rules are applied: - -- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour - on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is - specified. -- If a time zone is omitted, a timezone-naive datetime is returned. - -If any other elements are missing, they are taken from the -:class:`datetime.datetime` object passed to the parameter ``default``. If this -results in a day number exceeding the valid number of days per month, the -value falls back to the end of the month. - -Additional resources about date/time string formats can be found below: - -- `A summary of the international standard date and time notation +# -*- coding: utf-8 -*- +""" +This module offers a generic date/time string parser which is able to parse +most known formats to represent a date and/or time. + +This module attempts to be forgiving with regards to unlikely input formats, +returning a datetime object even for dates which are ambiguous. If an element +of a date/time stamp is omitted, the following rules are applied: + +- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour + on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is + specified. +- If a time zone is omitted, a timezone-naive datetime is returned. + +If any other elements are missing, they are taken from the +:class:`datetime.datetime` object passed to the parameter ``default``. If this +results in a day number exceeding the valid number of days per month, the +value falls back to the end of the month. + +Additional resources about date/time string formats can be found below: + +- `A summary of the international standard date and time notation <https://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_ - `W3C Date and Time Formats <https://www.w3.org/TR/NOTE-datetime>`_ -- `Time Formats (Planetary Rings Node) <https://pds-rings.seti.org:443/tools/time_formats.html>`_ -- `CPAN ParseDate module +- `Time Formats (Planetary Rings Node) <https://pds-rings.seti.org:443/tools/time_formats.html>`_ +- `CPAN ParseDate module <https://metacpan.org/pod/release/MUIR/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_ -- `Java SimpleDateFormat Class - <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_ -""" -from __future__ import unicode_literals - -import datetime -import re -import string -import time -import warnings - -from calendar import monthrange -from io import StringIO - -import six +- `Java SimpleDateFormat Class + <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_ +""" +from __future__ import unicode_literals + +import datetime +import re +import string +import time +import warnings + +from calendar import monthrange +from io import StringIO + +import six from six import integer_types, text_type - -from decimal import Decimal - -from warnings import warn - -from .. import relativedelta -from .. import tz - + +from decimal import Decimal + +from warnings import warn + +from .. import relativedelta +from .. import tz + __all__ = ["parse", "parserinfo", "ParserError"] - - -# TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth -# making public and/or figuring out if there is something we can -# take off their plate. -class _timelex(object): - # Fractional seconds are sometimes split by a comma - _split_decimal = re.compile("([.,])") - - def __init__(self, instream): + + +# TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth +# making public and/or figuring out if there is something we can +# take off their plate. +class _timelex(object): + # Fractional seconds are sometimes split by a comma + _split_decimal = re.compile("([.,])") + + def __init__(self, instream): if isinstance(instream, (bytes, bytearray)): instream = instream.decode() - - if isinstance(instream, text_type): - instream = StringIO(instream) - elif getattr(instream, 'read', None) is None: - raise TypeError('Parser must be a string or character stream, not ' - '{itype}'.format(itype=instream.__class__.__name__)) - - self.instream = instream - self.charstack = [] - self.tokenstack = [] - self.eof = False - - def get_token(self): - """ - This function breaks the time string into lexical units (tokens), which - can be parsed by the parser. Lexical units are demarcated by changes in - the character set, so any continuous string of letters is considered - one unit, any continuous string of numbers is considered one unit. - - The main complication arises from the fact that dots ('.') can be used - both as separators (e.g. "Sep.20.2009") or decimal points (e.g. - "4:30:21.447"). As such, it is necessary to read the full context of - any dot-separated strings before breaking it into tokens; as such, this - function maintains a "token stack", for when the ambiguous context - demands that multiple tokens be parsed at once. - """ - if self.tokenstack: - return self.tokenstack.pop(0) - - seenletters = False - token = None - state = None - - while not self.eof: - # We only realize that we've reached the end of a token when we - # find a character that's not part of the current token - since - # that character may be part of the next token, it's stored in the - # charstack. - if self.charstack: - nextchar = self.charstack.pop(0) - else: - nextchar = self.instream.read(1) - while nextchar == '\x00': - nextchar = self.instream.read(1) - - if not nextchar: - self.eof = True - break - elif not state: - # First character of the token - determines if we're starting - # to parse a word, a number or something else. - token = nextchar - if self.isword(nextchar): - state = 'a' - elif self.isnum(nextchar): - state = '0' - elif self.isspace(nextchar): - token = ' ' - break # emit token - else: - break # emit token - elif state == 'a': - # If we've already started reading a word, we keep reading - # letters until we find something that's not part of a word. - seenletters = True - if self.isword(nextchar): - token += nextchar - elif nextchar == '.': - token += nextchar - state = 'a.' - else: - self.charstack.append(nextchar) - break # emit token - elif state == '0': - # If we've already started reading a number, we keep reading - # numbers until we find something that doesn't fit. - if self.isnum(nextchar): - token += nextchar - elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): - token += nextchar - state = '0.' - else: - self.charstack.append(nextchar) - break # emit token - elif state == 'a.': - # If we've seen some letters and a dot separator, continue - # parsing, and the tokens will be broken up later. - seenletters = True - if nextchar == '.' or self.isword(nextchar): - token += nextchar - elif self.isnum(nextchar) and token[-1] == '.': - token += nextchar - state = '0.' - else: - self.charstack.append(nextchar) - break # emit token - elif state == '0.': - # If we've seen at least one dot separator, keep going, we'll - # break up the tokens later. - if nextchar == '.' or self.isnum(nextchar): - token += nextchar - elif self.isword(nextchar) and token[-1] == '.': - token += nextchar - state = 'a.' - else: - self.charstack.append(nextchar) - break # emit token - - if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or - token[-1] in '.,')): - l = self._split_decimal.split(token) - token = l[0] - for tok in l[1:]: - if tok: - self.tokenstack.append(tok) - - if state == '0.' and token.count('.') == 0: - token = token.replace(',', '.') - - return token - - def __iter__(self): - return self - - def __next__(self): - token = self.get_token() - if token is None: - raise StopIteration - - return token - - def next(self): - return self.__next__() # Python 2.x support - - @classmethod - def split(cls, s): - return list(cls(s)) - - @classmethod - def isword(cls, nextchar): - """ Whether or not the next character is part of a word """ - return nextchar.isalpha() - - @classmethod - def isnum(cls, nextchar): - """ Whether the next character is part of a number """ - return nextchar.isdigit() - - @classmethod - def isspace(cls, nextchar): - """ Whether the next character is whitespace """ - return nextchar.isspace() - - -class _resultbase(object): - - def __init__(self): - for attr in self.__slots__: - setattr(self, attr, None) - - def _repr(self, classname): - l = [] - for attr in self.__slots__: - value = getattr(self, attr) - if value is not None: - l.append("%s=%s" % (attr, repr(value))) - return "%s(%s)" % (classname, ", ".join(l)) - - def __len__(self): - return (sum(getattr(self, attr) is not None - for attr in self.__slots__)) - - def __repr__(self): - return self._repr(self.__class__.__name__) - - -class parserinfo(object): - """ - Class which handles what inputs are accepted. Subclass this to customize - the language and acceptable values for each parameter. - - :param dayfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the day (``True``) or month (``False``). If - ``yearfirst`` is set to ``True``, this distinguishes between YDM - and YMD. Default is ``False``. - - :param yearfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the year. If ``True``, the first number is taken - to be the year, otherwise the last number is taken to be the year. - Default is ``False``. - """ - - # m from a.m/p.m, t from ISO T separator - JUMP = [" ", ".", ",", ";", "-", "/", "'", - "at", "on", "and", "ad", "m", "t", "of", - "st", "nd", "rd", "th"] - - WEEKDAYS = [("Mon", "Monday"), - ("Tue", "Tuesday"), # TODO: "Tues" - ("Wed", "Wednesday"), - ("Thu", "Thursday"), # TODO: "Thurs" - ("Fri", "Friday"), - ("Sat", "Saturday"), - ("Sun", "Sunday")] - MONTHS = [("Jan", "January"), - ("Feb", "February"), # TODO: "Febr" - ("Mar", "March"), - ("Apr", "April"), - ("May", "May"), - ("Jun", "June"), - ("Jul", "July"), - ("Aug", "August"), - ("Sep", "Sept", "September"), - ("Oct", "October"), - ("Nov", "November"), - ("Dec", "December")] - HMS = [("h", "hour", "hours"), - ("m", "minute", "minutes"), - ("s", "second", "seconds")] - AMPM = [("am", "a"), - ("pm", "p")] + + if isinstance(instream, text_type): + instream = StringIO(instream) + elif getattr(instream, 'read', None) is None: + raise TypeError('Parser must be a string or character stream, not ' + '{itype}'.format(itype=instream.__class__.__name__)) + + self.instream = instream + self.charstack = [] + self.tokenstack = [] + self.eof = False + + def get_token(self): + """ + This function breaks the time string into lexical units (tokens), which + can be parsed by the parser. Lexical units are demarcated by changes in + the character set, so any continuous string of letters is considered + one unit, any continuous string of numbers is considered one unit. + + The main complication arises from the fact that dots ('.') can be used + both as separators (e.g. "Sep.20.2009") or decimal points (e.g. + "4:30:21.447"). As such, it is necessary to read the full context of + any dot-separated strings before breaking it into tokens; as such, this + function maintains a "token stack", for when the ambiguous context + demands that multiple tokens be parsed at once. + """ + if self.tokenstack: + return self.tokenstack.pop(0) + + seenletters = False + token = None + state = None + + while not self.eof: + # We only realize that we've reached the end of a token when we + # find a character that's not part of the current token - since + # that character may be part of the next token, it's stored in the + # charstack. + if self.charstack: + nextchar = self.charstack.pop(0) + else: + nextchar = self.instream.read(1) + while nextchar == '\x00': + nextchar = self.instream.read(1) + + if not nextchar: + self.eof = True + break + elif not state: + # First character of the token - determines if we're starting + # to parse a word, a number or something else. + token = nextchar + if self.isword(nextchar): + state = 'a' + elif self.isnum(nextchar): + state = '0' + elif self.isspace(nextchar): + token = ' ' + break # emit token + else: + break # emit token + elif state == 'a': + # If we've already started reading a word, we keep reading + # letters until we find something that's not part of a word. + seenletters = True + if self.isword(nextchar): + token += nextchar + elif nextchar == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0': + # If we've already started reading a number, we keep reading + # numbers until we find something that doesn't fit. + if self.isnum(nextchar): + token += nextchar + elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == 'a.': + # If we've seen some letters and a dot separator, continue + # parsing, and the tokens will be broken up later. + seenletters = True + if nextchar == '.' or self.isword(nextchar): + token += nextchar + elif self.isnum(nextchar) and token[-1] == '.': + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0.': + # If we've seen at least one dot separator, keep going, we'll + # break up the tokens later. + if nextchar == '.' or self.isnum(nextchar): + token += nextchar + elif self.isword(nextchar) and token[-1] == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + + if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or + token[-1] in '.,')): + l = self._split_decimal.split(token) + token = l[0] + for tok in l[1:]: + if tok: + self.tokenstack.append(tok) + + if state == '0.' and token.count('.') == 0: + token = token.replace(',', '.') + + return token + + def __iter__(self): + return self + + def __next__(self): + token = self.get_token() + if token is None: + raise StopIteration + + return token + + def next(self): + return self.__next__() # Python 2.x support + + @classmethod + def split(cls, s): + return list(cls(s)) + + @classmethod + def isword(cls, nextchar): + """ Whether or not the next character is part of a word """ + return nextchar.isalpha() + + @classmethod + def isnum(cls, nextchar): + """ Whether the next character is part of a number """ + return nextchar.isdigit() + + @classmethod + def isspace(cls, nextchar): + """ Whether the next character is whitespace """ + return nextchar.isspace() + + +class _resultbase(object): + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def _repr(self, classname): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (classname, ", ".join(l)) + + def __len__(self): + return (sum(getattr(self, attr) is not None + for attr in self.__slots__)) + + def __repr__(self): + return self._repr(self.__class__.__name__) + + +class parserinfo(object): + """ + Class which handles what inputs are accepted. Subclass this to customize + the language and acceptable values for each parameter. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. Default is ``False``. + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + Default is ``False``. + """ + + # m from a.m/p.m, t from ISO T separator + JUMP = [" ", ".", ",", ";", "-", "/", "'", + "at", "on", "and", "ad", "m", "t", "of", + "st", "nd", "rd", "th"] + + WEEKDAYS = [("Mon", "Monday"), + ("Tue", "Tuesday"), # TODO: "Tues" + ("Wed", "Wednesday"), + ("Thu", "Thursday"), # TODO: "Thurs" + ("Fri", "Friday"), + ("Sat", "Saturday"), + ("Sun", "Sunday")] + MONTHS = [("Jan", "January"), + ("Feb", "February"), # TODO: "Febr" + ("Mar", "March"), + ("Apr", "April"), + ("May", "May"), + ("Jun", "June"), + ("Jul", "July"), + ("Aug", "August"), + ("Sep", "Sept", "September"), + ("Oct", "October"), + ("Nov", "November"), + ("Dec", "December")] + HMS = [("h", "hour", "hours"), + ("m", "minute", "minutes"), + ("s", "second", "seconds")] + AMPM = [("am", "a"), + ("pm", "p")] UTCZONE = ["UTC", "GMT", "Z", "z"] - PERTAIN = ["of"] - TZOFFSET = {} - # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate", - # "Anno Domini", "Year of Our Lord"] - - def __init__(self, dayfirst=False, yearfirst=False): - self._jump = self._convert(self.JUMP) - self._weekdays = self._convert(self.WEEKDAYS) - self._months = self._convert(self.MONTHS) - self._hms = self._convert(self.HMS) - self._ampm = self._convert(self.AMPM) - self._utczone = self._convert(self.UTCZONE) - self._pertain = self._convert(self.PERTAIN) - - self.dayfirst = dayfirst - self.yearfirst = yearfirst - - self._year = time.localtime().tm_year - self._century = self._year // 100 * 100 - - def _convert(self, lst): - dct = {} - for i, v in enumerate(lst): - if isinstance(v, tuple): - for v in v: - dct[v.lower()] = i - else: - dct[v.lower()] = i - return dct - - def jump(self, name): - return name.lower() in self._jump - - def weekday(self, name): - try: - return self._weekdays[name.lower()] - except KeyError: - pass - return None - - def month(self, name): - try: - return self._months[name.lower()] + 1 - except KeyError: - pass - return None - - def hms(self, name): - try: - return self._hms[name.lower()] - except KeyError: - return None - - def ampm(self, name): - try: - return self._ampm[name.lower()] - except KeyError: - return None - - def pertain(self, name): - return name.lower() in self._pertain - - def utczone(self, name): - return name.lower() in self._utczone - - def tzoffset(self, name): - if name in self._utczone: - return 0 - - return self.TZOFFSET.get(name) - - def convertyear(self, year, century_specified=False): - """ - Converts two-digit years to year within [-50, 49] - range of self._year (current local time) - """ - - # Function contract is that the year is always positive - assert year >= 0 - - if year < 100 and not century_specified: - # assume current century to start - year += self._century - - if year >= self._year + 50: # if too far in future - year -= 100 - elif year < self._year - 50: # if too far in past - year += 100 - - return year - - def validate(self, res): - # move to info - if res.year is not None: - res.year = self.convertyear(res.year, res.century_specified) - + PERTAIN = ["of"] + TZOFFSET = {} + # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate", + # "Anno Domini", "Year of Our Lord"] + + def __init__(self, dayfirst=False, yearfirst=False): + self._jump = self._convert(self.JUMP) + self._weekdays = self._convert(self.WEEKDAYS) + self._months = self._convert(self.MONTHS) + self._hms = self._convert(self.HMS) + self._ampm = self._convert(self.AMPM) + self._utczone = self._convert(self.UTCZONE) + self._pertain = self._convert(self.PERTAIN) + + self.dayfirst = dayfirst + self.yearfirst = yearfirst + + self._year = time.localtime().tm_year + self._century = self._year // 100 * 100 + + def _convert(self, lst): + dct = {} + for i, v in enumerate(lst): + if isinstance(v, tuple): + for v in v: + dct[v.lower()] = i + else: + dct[v.lower()] = i + return dct + + def jump(self, name): + return name.lower() in self._jump + + def weekday(self, name): + try: + return self._weekdays[name.lower()] + except KeyError: + pass + return None + + def month(self, name): + try: + return self._months[name.lower()] + 1 + except KeyError: + pass + return None + + def hms(self, name): + try: + return self._hms[name.lower()] + except KeyError: + return None + + def ampm(self, name): + try: + return self._ampm[name.lower()] + except KeyError: + return None + + def pertain(self, name): + return name.lower() in self._pertain + + def utczone(self, name): + return name.lower() in self._utczone + + def tzoffset(self, name): + if name in self._utczone: + return 0 + + return self.TZOFFSET.get(name) + + def convertyear(self, year, century_specified=False): + """ + Converts two-digit years to year within [-50, 49] + range of self._year (current local time) + """ + + # Function contract is that the year is always positive + assert year >= 0 + + if year < 100 and not century_specified: + # assume current century to start + year += self._century + + if year >= self._year + 50: # if too far in future + year -= 100 + elif year < self._year - 50: # if too far in past + year += 100 + + return year + + def validate(self, res): + # move to info + if res.year is not None: + res.year = self.convertyear(res.year, res.century_specified) + if ((res.tzoffset == 0 and not res.tzname) or (res.tzname == 'Z' or res.tzname == 'z')): - res.tzname = "UTC" - res.tzoffset = 0 - elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): - res.tzoffset = 0 - return True - - -class _ymd(list): - def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) - self.century_specified = False - self.dstridx = None - self.mstridx = None - self.ystridx = None - - @property - def has_year(self): - return self.ystridx is not None - - @property - def has_month(self): - return self.mstridx is not None - - @property - def has_day(self): - return self.dstridx is not None - - def could_be_day(self, value): - if self.has_day: - return False - elif not self.has_month: - return 1 <= value <= 31 - elif not self.has_year: + res.tzname = "UTC" + res.tzoffset = 0 + elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): + res.tzoffset = 0 + return True + + +class _ymd(list): + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + self.century_specified = False + self.dstridx = None + self.mstridx = None + self.ystridx = None + + @property + def has_year(self): + return self.ystridx is not None + + @property + def has_month(self): + return self.mstridx is not None + + @property + def has_day(self): + return self.dstridx is not None + + def could_be_day(self, value): + if self.has_day: + return False + elif not self.has_month: + return 1 <= value <= 31 + elif not self.has_year: # Be permissive, assume leap year - month = self[self.mstridx] - return 1 <= value <= monthrange(2000, month)[1] - else: - month = self[self.mstridx] - year = self[self.ystridx] - return 1 <= value <= monthrange(year, month)[1] - - def append(self, val, label=None): - if hasattr(val, '__len__'): - if val.isdigit() and len(val) > 2: - self.century_specified = True - if label not in [None, 'Y']: # pragma: no cover - raise ValueError(label) - label = 'Y' - elif val > 100: - self.century_specified = True - if label not in [None, 'Y']: # pragma: no cover - raise ValueError(label) - label = 'Y' - - super(self.__class__, self).append(int(val)) - - if label == 'M': - if self.has_month: - raise ValueError('Month is already set') - self.mstridx = len(self) - 1 - elif label == 'D': - if self.has_day: - raise ValueError('Day is already set') - self.dstridx = len(self) - 1 - elif label == 'Y': - if self.has_year: - raise ValueError('Year is already set') - self.ystridx = len(self) - 1 - - def _resolve_from_stridxs(self, strids): - """ - Try to resolve the identities of year/month/day elements using - ystridx, mstridx, and dstridx, if enough of these are specified. - """ - if len(self) == 3 and len(strids) == 2: - # we can back out the remaining stridx value - missing = [x for x in range(3) if x not in strids.values()] - key = [x for x in ['y', 'm', 'd'] if x not in strids] - assert len(missing) == len(key) == 1 - key = key[0] - val = missing[0] - strids[key] = val - - assert len(self) == len(strids) # otherwise this should not be called - out = {key: self[strids[key]] for key in strids} - return (out.get('y'), out.get('m'), out.get('d')) - - def resolve_ymd(self, yearfirst, dayfirst): - len_ymd = len(self) - year, month, day = (None, None, None) - - strids = (('y', self.ystridx), - ('m', self.mstridx), - ('d', self.dstridx)) - - strids = {key: val for key, val in strids if val is not None} - if (len(self) == len(strids) > 0 or - (len(self) == 3 and len(strids) == 2)): - return self._resolve_from_stridxs(strids) - - mstridx = self.mstridx - - if len_ymd > 3: - raise ValueError("More than three YMD values") - elif len_ymd == 1 or (mstridx is not None and len_ymd == 2): - # One member, or two members with a month string - if mstridx is not None: - month = self[mstridx] - # since mstridx is 0 or 1, self[mstridx-1] always - # looks up the other element - other = self[mstridx - 1] - else: - other = self[0] - - if len_ymd > 1 or mstridx is None: - if other > 31: - year = other - else: - day = other - - elif len_ymd == 2: - # Two members with numbers - if self[0] > 31: - # 99-01 - year, month = self - elif self[1] > 31: - # 01-99 - month, year = self - elif dayfirst and self[1] <= 12: - # 13-01 - day, month = self - else: - # 01-13 - month, day = self - - elif len_ymd == 3: - # Three members - if mstridx == 0: - if self[1] > 31: - # Apr-2003-25 - month, year, day = self - else: - month, day, year = self - elif mstridx == 1: - if self[0] > 31 or (yearfirst and self[2] <= 31): - # 99-Jan-01 - year, month, day = self - else: - # 01-Jan-01 + month = self[self.mstridx] + return 1 <= value <= monthrange(2000, month)[1] + else: + month = self[self.mstridx] + year = self[self.ystridx] + return 1 <= value <= monthrange(year, month)[1] + + def append(self, val, label=None): + if hasattr(val, '__len__'): + if val.isdigit() and len(val) > 2: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + elif val > 100: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + + super(self.__class__, self).append(int(val)) + + if label == 'M': + if self.has_month: + raise ValueError('Month is already set') + self.mstridx = len(self) - 1 + elif label == 'D': + if self.has_day: + raise ValueError('Day is already set') + self.dstridx = len(self) - 1 + elif label == 'Y': + if self.has_year: + raise ValueError('Year is already set') + self.ystridx = len(self) - 1 + + def _resolve_from_stridxs(self, strids): + """ + Try to resolve the identities of year/month/day elements using + ystridx, mstridx, and dstridx, if enough of these are specified. + """ + if len(self) == 3 and len(strids) == 2: + # we can back out the remaining stridx value + missing = [x for x in range(3) if x not in strids.values()] + key = [x for x in ['y', 'm', 'd'] if x not in strids] + assert len(missing) == len(key) == 1 + key = key[0] + val = missing[0] + strids[key] = val + + assert len(self) == len(strids) # otherwise this should not be called + out = {key: self[strids[key]] for key in strids} + return (out.get('y'), out.get('m'), out.get('d')) + + def resolve_ymd(self, yearfirst, dayfirst): + len_ymd = len(self) + year, month, day = (None, None, None) + + strids = (('y', self.ystridx), + ('m', self.mstridx), + ('d', self.dstridx)) + + strids = {key: val for key, val in strids if val is not None} + if (len(self) == len(strids) > 0 or + (len(self) == 3 and len(strids) == 2)): + return self._resolve_from_stridxs(strids) + + mstridx = self.mstridx + + if len_ymd > 3: + raise ValueError("More than three YMD values") + elif len_ymd == 1 or (mstridx is not None and len_ymd == 2): + # One member, or two members with a month string + if mstridx is not None: + month = self[mstridx] + # since mstridx is 0 or 1, self[mstridx-1] always + # looks up the other element + other = self[mstridx - 1] + else: + other = self[0] + + if len_ymd > 1 or mstridx is None: + if other > 31: + year = other + else: + day = other + + elif len_ymd == 2: + # Two members with numbers + if self[0] > 31: + # 99-01 + year, month = self + elif self[1] > 31: + # 01-99 + month, year = self + elif dayfirst and self[1] <= 12: + # 13-01 + day, month = self + else: + # 01-13 + month, day = self + + elif len_ymd == 3: + # Three members + if mstridx == 0: + if self[1] > 31: + # Apr-2003-25 + month, year, day = self + else: + month, day, year = self + elif mstridx == 1: + if self[0] > 31 or (yearfirst and self[2] <= 31): + # 99-Jan-01 + year, month, day = self + else: + # 01-Jan-01 # Give precedence to day-first, since - # two-digit years is usually hand-written. - day, month, year = self - - elif mstridx == 2: - # WTF!? - if self[1] > 31: - # 01-99-Jan - day, year, month = self - else: - # 99-01-Jan - year, day, month = self - - else: - if (self[0] > 31 or - self.ystridx == 0 or - (yearfirst and self[1] <= 12 and self[2] <= 31)): - # 99-01-01 - if dayfirst and self[2] <= 12: - year, day, month = self - else: - year, month, day = self - elif self[0] > 12 or (dayfirst and self[1] <= 12): - # 13-01-01 - day, month, year = self - else: - # 01-13-01 - month, day, year = self - - return year, month, day - - -class parser(object): - def __init__(self, info=None): - self.info = info or parserinfo() - - def parse(self, timestr, default=None, - ignoretz=False, tzinfos=None, **kwargs): - """ - Parse the date/time string into a :class:`datetime.datetime` object. - - :param timestr: - Any date/time string using the supported formats. - - :param default: - The default datetime object, if this is a datetime object and not - ``None``, elements specified in ``timestr`` replace elements in the - default object. - - :param ignoretz: - If set ``True``, time zones in parsed strings are ignored and a - naive :class:`datetime.datetime` object is returned. - - :param tzinfos: - Additional time zone names / aliases which may be present in the - string. This argument maps time zone names (and optionally offsets - from those time zones) to time zones. This parameter can be a - dictionary with timezone aliases mapping time zone names to time - zones or a function taking two parameters (``tzname`` and - ``tzoffset``) and returning a time zone. - - The timezones to which the names are mapped can be an integer - offset from UTC in seconds or a :class:`tzinfo` object. - - .. doctest:: - :options: +NORMALIZE_WHITESPACE - - >>> from dateutil.parser import parse - >>> from dateutil.tz import gettz - >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} - >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) - datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) - >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) - datetime.datetime(2012, 1, 19, 17, 21, - tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) - - This parameter is ignored if ``ignoretz`` is set. - - :param \\*\\*kwargs: - Keyword arguments as passed to ``_parse()``. - - :return: - Returns a :class:`datetime.datetime` object or, if the - ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the - first element being a :class:`datetime.datetime` object, the second - a tuple containing the fuzzy tokens. - + # two-digit years is usually hand-written. + day, month, year = self + + elif mstridx == 2: + # WTF!? + if self[1] > 31: + # 01-99-Jan + day, year, month = self + else: + # 99-01-Jan + year, day, month = self + + else: + if (self[0] > 31 or + self.ystridx == 0 or + (yearfirst and self[1] <= 12 and self[2] <= 31)): + # 99-01-01 + if dayfirst and self[2] <= 12: + year, day, month = self + else: + year, month, day = self + elif self[0] > 12 or (dayfirst and self[1] <= 12): + # 13-01-01 + day, month, year = self + else: + # 01-13-01 + month, day, year = self + + return year, month, day + + +class parser(object): + def __init__(self, info=None): + self.info = info or parserinfo() + + def parse(self, timestr, default=None, + ignoretz=False, tzinfos=None, **kwargs): + """ + Parse the date/time string into a :class:`datetime.datetime` object. + + :param timestr: + Any date/time string using the supported formats. + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a + naive :class:`datetime.datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param \\*\\*kwargs: + Keyword arguments as passed to ``_parse()``. + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + :raises ParserError: - Raised for invalid or unknown string format, if the provided - :class:`tzinfo` is not in a valid format, or if an invalid date - would be created. - - :raises TypeError: - Raised for non-string or character stream input. - - :raises OverflowError: - Raised if the parsed date exceeds the largest valid C integer on - your system. - """ - - if default is None: - default = datetime.datetime.now().replace(hour=0, minute=0, - second=0, microsecond=0) - - res, skipped_tokens = self._parse(timestr, **kwargs) - - if res is None: + Raised for invalid or unknown string format, if the provided + :class:`tzinfo` is not in a valid format, or if an invalid date + would be created. + + :raises TypeError: + Raised for non-string or character stream input. + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + + if default is None: + default = datetime.datetime.now().replace(hour=0, minute=0, + second=0, microsecond=0) + + res, skipped_tokens = self._parse(timestr, **kwargs) + + if res is None: raise ParserError("Unknown string format: %s", timestr) - - if len(res) == 0: + + if len(res) == 0: raise ParserError("String does not contain a date: %s", timestr) - + try: ret = self._build_naive(res, default) except ValueError as e: six.raise_from(ParserError(str(e) + ": %s", timestr), e) - - if not ignoretz: - ret = self._build_tzaware(ret, res, tzinfos) - - if kwargs.get('fuzzy_with_tokens', False): - return ret, skipped_tokens - else: - return ret - - class _result(_resultbase): - __slots__ = ["year", "month", "day", "weekday", - "hour", "minute", "second", "microsecond", - "tzname", "tzoffset", "ampm","any_unused_tokens"] - - def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, - fuzzy_with_tokens=False): - """ - Private method which performs the heavy lifting of parsing, called from - ``parse()``, which passes on its ``kwargs`` to this function. - - :param timestr: - The string to parse. - - :param dayfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the day (``True``) or month (``False``). If - ``yearfirst`` is set to ``True``, this distinguishes between YDM - and YMD. If set to ``None``, this value is retrieved from the - current :class:`parserinfo` object (which itself defaults to - ``False``). - - :param yearfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the year. If ``True``, the first number is taken - to be the year, otherwise the last number is taken to be the year. - If this is set to ``None``, the value is retrieved from the current - :class:`parserinfo` object (which itself defaults to ``False``). - - :param fuzzy: - Whether to allow fuzzy parsing, allowing for string like "Today is - January 1, 2047 at 8:21:00AM". - - :param fuzzy_with_tokens: - If ``True``, ``fuzzy`` is automatically set to True, and the parser - will return a tuple where the first element is the parsed - :class:`datetime.datetime` datetimestamp and the second element is - a tuple containing the portions of the string which were ignored: - - .. doctest:: - - >>> from dateutil.parser import parse - >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) - (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) - - """ - if fuzzy_with_tokens: - fuzzy = True - - info = self.info - - if dayfirst is None: - dayfirst = info.dayfirst - - if yearfirst is None: - yearfirst = info.yearfirst - - res = self._result() - l = _timelex.split(timestr) # Splits the timestr into tokens - - skipped_idxs = [] - - # year/month/day list - ymd = _ymd() - - len_l = len(l) - i = 0 - try: - while i < len_l: - - # Check if it's a number - value_repr = l[i] - try: - value = float(value_repr) - except ValueError: - value = None - - if value is not None: - # Numeric token - i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy) - - # Check weekday - elif info.weekday(l[i]) is not None: - value = info.weekday(l[i]) - res.weekday = value - - # Check month name - elif info.month(l[i]) is not None: - value = info.month(l[i]) - ymd.append(value, 'M') - - if i + 1 < len_l: - if l[i + 1] in ('-', '/'): - # Jan-01[-99] - sep = l[i + 1] - ymd.append(l[i + 2]) - - if i + 3 < len_l and l[i + 3] == sep: - # Jan-01-99 - ymd.append(l[i + 4]) - i += 2 - - i += 2 - - elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and - info.pertain(l[i + 2])): - # Jan of 01 - # In this case, 01 is clearly year - if l[i + 4].isdigit(): - # Convert it here to become unambiguous - value = int(l[i + 4]) - year = str(info.convertyear(value)) - ymd.append(year, 'Y') - else: - # Wrong guess - pass - # TODO: not hit in tests - i += 4 - - # Check am/pm - elif info.ampm(l[i]) is not None: - value = info.ampm(l[i]) - val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy) - - if val_is_ampm: - res.hour = self._adjust_ampm(res.hour, value) - res.ampm = value - - elif fuzzy: - skipped_idxs.append(i) - - # Check for a timezone name - elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]): - res.tzname = l[i] - res.tzoffset = info.tzoffset(res.tzname) - - # Check for something like GMT+3, or BRST+3. Notice - # that it doesn't mean "I am 3 hours after GMT", but - # "my time +3 is GMT". If found, we reverse the - # logic so that timezone parsing code will get it - # right. - if i + 1 < len_l and l[i + 1] in ('+', '-'): - l[i + 1] = ('+', '-')[l[i + 1] == '+'] - res.tzoffset = None - if info.utczone(res.tzname): - # With something like GMT+3, the timezone - # is *not* GMT. - res.tzname = None - - # Check for a numbered timezone - elif res.hour is not None and l[i] in ('+', '-'): - signal = (-1, 1)[l[i] == '+'] - len_li = len(l[i + 1]) - - # TODO: check that l[i + 1] is integer? - if len_li == 4: - # -0300 - hour_offset = int(l[i + 1][:2]) - min_offset = int(l[i + 1][2:]) - elif i + 2 < len_l and l[i + 2] == ':': - # -03:00 - hour_offset = int(l[i + 1]) - min_offset = int(l[i + 3]) # TODO: Check that l[i+3] is minute-like? - i += 2 - elif len_li <= 2: - # -[0]3 - hour_offset = int(l[i + 1][:2]) - min_offset = 0 - else: - raise ValueError(timestr) - - res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60) - - # Look for a timezone name between parenthesis - if (i + 5 < len_l and - info.jump(l[i + 2]) and l[i + 3] == '(' and - l[i + 5] == ')' and - 3 <= len(l[i + 4]) and - self._could_be_tzname(res.hour, res.tzname, - None, l[i + 4])): - # -0300 (BRST) - res.tzname = l[i + 4] - i += 4 - - i += 1 - - # Check jumps - elif not (info.jump(l[i]) or fuzzy): - raise ValueError(timestr) - - else: - skipped_idxs.append(i) - i += 1 - - # Process year/month/day - year, month, day = ymd.resolve_ymd(yearfirst, dayfirst) - - res.century_specified = ymd.century_specified - res.year = year - res.month = month - res.day = day - - except (IndexError, ValueError): - return None, None - - if not info.validate(res): - return None, None - - if fuzzy_with_tokens: - skipped_tokens = self._recombine_skipped(l, skipped_idxs) - return res, tuple(skipped_tokens) - else: - return res, None - - def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy): - # Token is a number - value_repr = tokens[idx] - try: - value = self._to_decimal(value_repr) - except Exception as e: - six.raise_from(ValueError('Unknown numeric token'), e) - - len_li = len(value_repr) - - len_l = len(tokens) - - if (len(ymd) == 3 and len_li in (2, 4) and - res.hour is None and - (idx + 1 >= len_l or - (tokens[idx + 1] != ':' and - info.hms(tokens[idx + 1]) is None))): - # 19990101T23[59] - s = tokens[idx] - res.hour = int(s[:2]) - - if len_li == 4: - res.minute = int(s[2:]) - - elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6): - # YYMMDD or HHMMSS[.ss] - s = tokens[idx] - - if not ymd and '.' not in tokens[idx]: - ymd.append(s[:2]) - ymd.append(s[2:4]) - ymd.append(s[4:]) - else: - # 19990101T235959[.59] - - # TODO: Check if res attributes already set. - res.hour = int(s[:2]) - res.minute = int(s[2:4]) - res.second, res.microsecond = self._parsems(s[4:]) - - elif len_li in (8, 12, 14): - # YYYYMMDD - s = tokens[idx] - ymd.append(s[:4], 'Y') - ymd.append(s[4:6]) - ymd.append(s[6:8]) - - if len_li > 8: - res.hour = int(s[8:10]) - res.minute = int(s[10:12]) - - if len_li > 12: - res.second = int(s[12:]) - - elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None: - # HH[ ]h or MM[ ]m or SS[.ss][ ]s - hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True) - (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx) - if hms is not None: - # TODO: checking that hour/minute/second are not - # already set? - self._assign_hms(res, value_repr, hms) - - elif idx + 2 < len_l and tokens[idx + 1] == ':': - # HH:MM[:SS[.ss]] - res.hour = int(value) - value = self._to_decimal(tokens[idx + 2]) # TODO: try/except for this? - (res.minute, res.second) = self._parse_min_sec(value) - - if idx + 4 < len_l and tokens[idx + 3] == ':': - res.second, res.microsecond = self._parsems(tokens[idx + 4]) - - idx += 2 - - idx += 2 - - elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'): - sep = tokens[idx + 1] - ymd.append(value_repr) - - if idx + 2 < len_l and not info.jump(tokens[idx + 2]): - if tokens[idx + 2].isdigit(): - # 01-01[-01] - ymd.append(tokens[idx + 2]) - else: - # 01-Jan[-01] - value = info.month(tokens[idx + 2]) - - if value is not None: - ymd.append(value, 'M') - else: - raise ValueError() - - if idx + 3 < len_l and tokens[idx + 3] == sep: - # We have three members - value = info.month(tokens[idx + 4]) - - if value is not None: - ymd.append(value, 'M') - else: - ymd.append(tokens[idx + 4]) - idx += 2 - - idx += 1 - idx += 1 - - elif idx + 1 >= len_l or info.jump(tokens[idx + 1]): - if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None: - # 12 am - hour = int(value) - res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2])) - idx += 1 - else: - # Year, month or day - ymd.append(value) - idx += 1 - - elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24): - # 12am - hour = int(value) - res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1])) - idx += 1 - - elif ymd.could_be_day(value): - ymd.append(value) - - elif not fuzzy: - raise ValueError() - - return idx - - def _find_hms_idx(self, idx, tokens, info, allow_jump): - len_l = len(tokens) - - if idx+1 < len_l and info.hms(tokens[idx+1]) is not None: - # There is an "h", "m", or "s" label following this token. We take - # assign the upcoming label to the current token. - # e.g. the "12" in 12h" - hms_idx = idx + 1 - - elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and - info.hms(tokens[idx+2]) is not None): - # There is a space and then an "h", "m", or "s" label. - # e.g. the "12" in "12 h" - hms_idx = idx + 2 - - elif idx > 0 and info.hms(tokens[idx-1]) is not None: + + if not ignoretz: + ret = self._build_tzaware(ret, res, tzinfos) + + if kwargs.get('fuzzy_with_tokens', False): + return ret, skipped_tokens + else: + return ret + + class _result(_resultbase): + __slots__ = ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond", + "tzname", "tzoffset", "ampm","any_unused_tokens"] + + def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, + fuzzy_with_tokens=False): + """ + Private method which performs the heavy lifting of parsing, called from + ``parse()``, which passes on its ``kwargs`` to this function. + + :param timestr: + The string to parse. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. If set to ``None``, this value is retrieved from the + current :class:`parserinfo` object (which itself defaults to + ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + If this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + """ + if fuzzy_with_tokens: + fuzzy = True + + info = self.info + + if dayfirst is None: + dayfirst = info.dayfirst + + if yearfirst is None: + yearfirst = info.yearfirst + + res = self._result() + l = _timelex.split(timestr) # Splits the timestr into tokens + + skipped_idxs = [] + + # year/month/day list + ymd = _ymd() + + len_l = len(l) + i = 0 + try: + while i < len_l: + + # Check if it's a number + value_repr = l[i] + try: + value = float(value_repr) + except ValueError: + value = None + + if value is not None: + # Numeric token + i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy) + + # Check weekday + elif info.weekday(l[i]) is not None: + value = info.weekday(l[i]) + res.weekday = value + + # Check month name + elif info.month(l[i]) is not None: + value = info.month(l[i]) + ymd.append(value, 'M') + + if i + 1 < len_l: + if l[i + 1] in ('-', '/'): + # Jan-01[-99] + sep = l[i + 1] + ymd.append(l[i + 2]) + + if i + 3 < len_l and l[i + 3] == sep: + # Jan-01-99 + ymd.append(l[i + 4]) + i += 2 + + i += 2 + + elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and + info.pertain(l[i + 2])): + # Jan of 01 + # In this case, 01 is clearly year + if l[i + 4].isdigit(): + # Convert it here to become unambiguous + value = int(l[i + 4]) + year = str(info.convertyear(value)) + ymd.append(year, 'Y') + else: + # Wrong guess + pass + # TODO: not hit in tests + i += 4 + + # Check am/pm + elif info.ampm(l[i]) is not None: + value = info.ampm(l[i]) + val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy) + + if val_is_ampm: + res.hour = self._adjust_ampm(res.hour, value) + res.ampm = value + + elif fuzzy: + skipped_idxs.append(i) + + # Check for a timezone name + elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]): + res.tzname = l[i] + res.tzoffset = info.tzoffset(res.tzname) + + # Check for something like GMT+3, or BRST+3. Notice + # that it doesn't mean "I am 3 hours after GMT", but + # "my time +3 is GMT". If found, we reverse the + # logic so that timezone parsing code will get it + # right. + if i + 1 < len_l and l[i + 1] in ('+', '-'): + l[i + 1] = ('+', '-')[l[i + 1] == '+'] + res.tzoffset = None + if info.utczone(res.tzname): + # With something like GMT+3, the timezone + # is *not* GMT. + res.tzname = None + + # Check for a numbered timezone + elif res.hour is not None and l[i] in ('+', '-'): + signal = (-1, 1)[l[i] == '+'] + len_li = len(l[i + 1]) + + # TODO: check that l[i + 1] is integer? + if len_li == 4: + # -0300 + hour_offset = int(l[i + 1][:2]) + min_offset = int(l[i + 1][2:]) + elif i + 2 < len_l and l[i + 2] == ':': + # -03:00 + hour_offset = int(l[i + 1]) + min_offset = int(l[i + 3]) # TODO: Check that l[i+3] is minute-like? + i += 2 + elif len_li <= 2: + # -[0]3 + hour_offset = int(l[i + 1][:2]) + min_offset = 0 + else: + raise ValueError(timestr) + + res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60) + + # Look for a timezone name between parenthesis + if (i + 5 < len_l and + info.jump(l[i + 2]) and l[i + 3] == '(' and + l[i + 5] == ')' and + 3 <= len(l[i + 4]) and + self._could_be_tzname(res.hour, res.tzname, + None, l[i + 4])): + # -0300 (BRST) + res.tzname = l[i + 4] + i += 4 + + i += 1 + + # Check jumps + elif not (info.jump(l[i]) or fuzzy): + raise ValueError(timestr) + + else: + skipped_idxs.append(i) + i += 1 + + # Process year/month/day + year, month, day = ymd.resolve_ymd(yearfirst, dayfirst) + + res.century_specified = ymd.century_specified + res.year = year + res.month = month + res.day = day + + except (IndexError, ValueError): + return None, None + + if not info.validate(res): + return None, None + + if fuzzy_with_tokens: + skipped_tokens = self._recombine_skipped(l, skipped_idxs) + return res, tuple(skipped_tokens) + else: + return res, None + + def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy): + # Token is a number + value_repr = tokens[idx] + try: + value = self._to_decimal(value_repr) + except Exception as e: + six.raise_from(ValueError('Unknown numeric token'), e) + + len_li = len(value_repr) + + len_l = len(tokens) + + if (len(ymd) == 3 and len_li in (2, 4) and + res.hour is None and + (idx + 1 >= len_l or + (tokens[idx + 1] != ':' and + info.hms(tokens[idx + 1]) is None))): + # 19990101T23[59] + s = tokens[idx] + res.hour = int(s[:2]) + + if len_li == 4: + res.minute = int(s[2:]) + + elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6): + # YYMMDD or HHMMSS[.ss] + s = tokens[idx] + + if not ymd and '.' not in tokens[idx]: + ymd.append(s[:2]) + ymd.append(s[2:4]) + ymd.append(s[4:]) + else: + # 19990101T235959[.59] + + # TODO: Check if res attributes already set. + res.hour = int(s[:2]) + res.minute = int(s[2:4]) + res.second, res.microsecond = self._parsems(s[4:]) + + elif len_li in (8, 12, 14): + # YYYYMMDD + s = tokens[idx] + ymd.append(s[:4], 'Y') + ymd.append(s[4:6]) + ymd.append(s[6:8]) + + if len_li > 8: + res.hour = int(s[8:10]) + res.minute = int(s[10:12]) + + if len_li > 12: + res.second = int(s[12:]) + + elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None: + # HH[ ]h or MM[ ]m or SS[.ss][ ]s + hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True) + (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx) + if hms is not None: + # TODO: checking that hour/minute/second are not + # already set? + self._assign_hms(res, value_repr, hms) + + elif idx + 2 < len_l and tokens[idx + 1] == ':': + # HH:MM[:SS[.ss]] + res.hour = int(value) + value = self._to_decimal(tokens[idx + 2]) # TODO: try/except for this? + (res.minute, res.second) = self._parse_min_sec(value) + + if idx + 4 < len_l and tokens[idx + 3] == ':': + res.second, res.microsecond = self._parsems(tokens[idx + 4]) + + idx += 2 + + idx += 2 + + elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'): + sep = tokens[idx + 1] + ymd.append(value_repr) + + if idx + 2 < len_l and not info.jump(tokens[idx + 2]): + if tokens[idx + 2].isdigit(): + # 01-01[-01] + ymd.append(tokens[idx + 2]) + else: + # 01-Jan[-01] + value = info.month(tokens[idx + 2]) + + if value is not None: + ymd.append(value, 'M') + else: + raise ValueError() + + if idx + 3 < len_l and tokens[idx + 3] == sep: + # We have three members + value = info.month(tokens[idx + 4]) + + if value is not None: + ymd.append(value, 'M') + else: + ymd.append(tokens[idx + 4]) + idx += 2 + + idx += 1 + idx += 1 + + elif idx + 1 >= len_l or info.jump(tokens[idx + 1]): + if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None: + # 12 am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2])) + idx += 1 + else: + # Year, month or day + ymd.append(value) + idx += 1 + + elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24): + # 12am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1])) + idx += 1 + + elif ymd.could_be_day(value): + ymd.append(value) + + elif not fuzzy: + raise ValueError() + + return idx + + def _find_hms_idx(self, idx, tokens, info, allow_jump): + len_l = len(tokens) + + if idx+1 < len_l and info.hms(tokens[idx+1]) is not None: + # There is an "h", "m", or "s" label following this token. We take + # assign the upcoming label to the current token. + # e.g. the "12" in 12h" + hms_idx = idx + 1 + + elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and + info.hms(tokens[idx+2]) is not None): + # There is a space and then an "h", "m", or "s" label. + # e.g. the "12" in "12 h" + hms_idx = idx + 2 + + elif idx > 0 and info.hms(tokens[idx-1]) is not None: # There is a "h", "m", or "s" preceding this token. Since neither - # of the previous cases was hit, there is no label following this - # token, so we use the previous label. - # e.g. the "04" in "12h04" - hms_idx = idx-1 - - elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and - info.hms(tokens[idx-2]) is not None): - # If we are looking at the final token, we allow for a - # backward-looking check to skip over a space. - # TODO: Are we sure this is the right condition here? - hms_idx = idx - 2 - - else: - hms_idx = None - - return hms_idx - - def _assign_hms(self, res, value_repr, hms): - # See GH issue #427, fixing float rounding - value = self._to_decimal(value_repr) - - if hms == 0: - # Hour - res.hour = int(value) - if value % 1: - res.minute = int(60*(value % 1)) - - elif hms == 1: - (res.minute, res.second) = self._parse_min_sec(value) - - elif hms == 2: - (res.second, res.microsecond) = self._parsems(value_repr) - - def _could_be_tzname(self, hour, tzname, tzoffset, token): - return (hour is not None and - tzname is None and - tzoffset is None and - len(token) <= 5 and + # of the previous cases was hit, there is no label following this + # token, so we use the previous label. + # e.g. the "04" in "12h04" + hms_idx = idx-1 + + elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and + info.hms(tokens[idx-2]) is not None): + # If we are looking at the final token, we allow for a + # backward-looking check to skip over a space. + # TODO: Are we sure this is the right condition here? + hms_idx = idx - 2 + + else: + hms_idx = None + + return hms_idx + + def _assign_hms(self, res, value_repr, hms): + # See GH issue #427, fixing float rounding + value = self._to_decimal(value_repr) + + if hms == 0: + # Hour + res.hour = int(value) + if value % 1: + res.minute = int(60*(value % 1)) + + elif hms == 1: + (res.minute, res.second) = self._parse_min_sec(value) + + elif hms == 2: + (res.second, res.microsecond) = self._parsems(value_repr) + + def _could_be_tzname(self, hour, tzname, tzoffset, token): + return (hour is not None and + tzname is None and + tzoffset is None and + len(token) <= 5 and (all(x in string.ascii_uppercase for x in token) or token in self.info.UTCZONE)) - - def _ampm_valid(self, hour, ampm, fuzzy): - """ - For fuzzy parsing, 'a' or 'am' (both valid English words) - may erroneously trigger the AM/PM flag. Deal with that - here. - """ - val_is_ampm = True - - # If there's already an AM/PM flag, this one isn't one. - if fuzzy and ampm is not None: - val_is_ampm = False - - # If AM/PM is found and hour is not, raise a ValueError - if hour is None: - if fuzzy: - val_is_ampm = False - else: - raise ValueError('No hour specified with AM or PM flag.') - elif not 0 <= hour <= 12: - # If AM/PM is found, it's a 12 hour clock, so raise - # an error for invalid range - if fuzzy: - val_is_ampm = False - else: - raise ValueError('Invalid hour specified for 12-hour clock.') - - return val_is_ampm - - def _adjust_ampm(self, hour, ampm): - if hour < 12 and ampm == 1: - hour += 12 - elif hour == 12 and ampm == 0: - hour = 0 - return hour - - def _parse_min_sec(self, value): - # TODO: Every usage of this function sets res.second to the return - # value. Are there any cases where second will be returned as None and + + def _ampm_valid(self, hour, ampm, fuzzy): + """ + For fuzzy parsing, 'a' or 'am' (both valid English words) + may erroneously trigger the AM/PM flag. Deal with that + here. + """ + val_is_ampm = True + + # If there's already an AM/PM flag, this one isn't one. + if fuzzy and ampm is not None: + val_is_ampm = False + + # If AM/PM is found and hour is not, raise a ValueError + if hour is None: + if fuzzy: + val_is_ampm = False + else: + raise ValueError('No hour specified with AM or PM flag.') + elif not 0 <= hour <= 12: + # If AM/PM is found, it's a 12 hour clock, so raise + # an error for invalid range + if fuzzy: + val_is_ampm = False + else: + raise ValueError('Invalid hour specified for 12-hour clock.') + + return val_is_ampm + + def _adjust_ampm(self, hour, ampm): + if hour < 12 and ampm == 1: + hour += 12 + elif hour == 12 and ampm == 0: + hour = 0 + return hour + + def _parse_min_sec(self, value): + # TODO: Every usage of this function sets res.second to the return + # value. Are there any cases where second will be returned as None and # we *don't* want to set res.second = None? - minute = int(value) - second = None - - sec_remainder = value % 1 - if sec_remainder: - second = int(60 * sec_remainder) - return (minute, second) - - def _parse_hms(self, idx, tokens, info, hms_idx): - # TODO: Is this going to admit a lot of false-positives for when we - # just happen to have digits and "h", "m" or "s" characters in non-date - # text? I guess hex hashes won't have that problem, but there's plenty - # of random junk out there. - if hms_idx is None: - hms = None - new_idx = idx - elif hms_idx > idx: - hms = info.hms(tokens[hms_idx]) - new_idx = hms_idx - else: - # Looking backwards, increment one. - hms = info.hms(tokens[hms_idx]) + 1 - new_idx = idx - - return (new_idx, hms) - + minute = int(value) + second = None + + sec_remainder = value % 1 + if sec_remainder: + second = int(60 * sec_remainder) + return (minute, second) + + def _parse_hms(self, idx, tokens, info, hms_idx): + # TODO: Is this going to admit a lot of false-positives for when we + # just happen to have digits and "h", "m" or "s" characters in non-date + # text? I guess hex hashes won't have that problem, but there's plenty + # of random junk out there. + if hms_idx is None: + hms = None + new_idx = idx + elif hms_idx > idx: + hms = info.hms(tokens[hms_idx]) + new_idx = hms_idx + else: + # Looking backwards, increment one. + hms = info.hms(tokens[hms_idx]) + 1 + new_idx = idx + + return (new_idx, hms) + # ------------------------------------------------------------------ # Handling for individual tokens. These are kept as methods instead # of functions for the sake of customizability via subclassing. - + def _parsems(self, value): """Parse a I[.F] seconds value into (seconds, microseconds).""" if "." not in value: @@ -1137,7 +1137,7 @@ class parser(object): else: i, f = value.split(".") return int(i), int(f.ljust(6, "0")[:6]) - + def _to_decimal(self, val): try: decimal_value = Decimal(val) @@ -1156,97 +1156,97 @@ class parser(object): # methods instead of functions for the sake of customizability via # subclassing. - def _build_tzinfo(self, tzinfos, tzname, tzoffset): - if callable(tzinfos): - tzdata = tzinfos(tzname, tzoffset) - else: - tzdata = tzinfos.get(tzname) - # handle case where tzinfo is paased an options that returns None - # eg tzinfos = {'BRST' : None} - if isinstance(tzdata, datetime.tzinfo) or tzdata is None: - tzinfo = tzdata - elif isinstance(tzdata, text_type): - tzinfo = tz.tzstr(tzdata) - elif isinstance(tzdata, integer_types): - tzinfo = tz.tzoffset(tzname, tzdata) + def _build_tzinfo(self, tzinfos, tzname, tzoffset): + if callable(tzinfos): + tzdata = tzinfos(tzname, tzoffset) + else: + tzdata = tzinfos.get(tzname) + # handle case where tzinfo is paased an options that returns None + # eg tzinfos = {'BRST' : None} + if isinstance(tzdata, datetime.tzinfo) or tzdata is None: + tzinfo = tzdata + elif isinstance(tzdata, text_type): + tzinfo = tz.tzstr(tzdata) + elif isinstance(tzdata, integer_types): + tzinfo = tz.tzoffset(tzname, tzdata) else: raise TypeError("Offset must be tzinfo subclass, tz string, " "or int offset.") - return tzinfo - - def _build_tzaware(self, naive, res, tzinfos): - if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)): - tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset) - aware = naive.replace(tzinfo=tzinfo) - aware = self._assign_tzname(aware, res.tzname) - - elif res.tzname and res.tzname in time.tzname: - aware = naive.replace(tzinfo=tz.tzlocal()) - - # Handle ambiguous local datetime - aware = self._assign_tzname(aware, res.tzname) - - # This is mostly relevant for winter GMT zones parsed in the UK - if (aware.tzname() != res.tzname and - res.tzname in self.info.UTCZONE): + return tzinfo + + def _build_tzaware(self, naive, res, tzinfos): + if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)): + tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset) + aware = naive.replace(tzinfo=tzinfo) + aware = self._assign_tzname(aware, res.tzname) + + elif res.tzname and res.tzname in time.tzname: + aware = naive.replace(tzinfo=tz.tzlocal()) + + # Handle ambiguous local datetime + aware = self._assign_tzname(aware, res.tzname) + + # This is mostly relevant for winter GMT zones parsed in the UK + if (aware.tzname() != res.tzname and + res.tzname in self.info.UTCZONE): aware = aware.replace(tzinfo=tz.UTC) - - elif res.tzoffset == 0: + + elif res.tzoffset == 0: aware = naive.replace(tzinfo=tz.UTC) - - elif res.tzoffset: - aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) - - elif not res.tzname and not res.tzoffset: - # i.e. no timezone information was found. - aware = naive - - elif res.tzname: - # tz-like string was parsed but we don't know what to do - # with it - warnings.warn("tzname {tzname} identified but not understood. " - "Pass `tzinfos` argument in order to correctly " - "return a timezone-aware datetime. In a future " - "version, this will raise an " - "exception.".format(tzname=res.tzname), - category=UnknownTimezoneWarning) - aware = naive - - return aware - - def _build_naive(self, res, default): - repl = {} - for attr in ("year", "month", "day", "hour", - "minute", "second", "microsecond"): - value = getattr(res, attr) - if value is not None: - repl[attr] = value - - if 'day' not in repl: - # If the default day exceeds the last day of the month, fall back - # to the end of the month. - cyear = default.year if res.year is None else res.year - cmonth = default.month if res.month is None else res.month - cday = default.day if res.day is None else res.day - - if cday > monthrange(cyear, cmonth)[1]: - repl['day'] = monthrange(cyear, cmonth)[1] - - naive = default.replace(**repl) - - if res.weekday is not None and not res.day: - naive = naive + relativedelta.relativedelta(weekday=res.weekday) - - return naive - - def _assign_tzname(self, dt, tzname): - if dt.tzname() != tzname: - new_dt = tz.enfold(dt, fold=1) - if new_dt.tzname() == tzname: - return new_dt - - return dt - + + elif res.tzoffset: + aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) + + elif not res.tzname and not res.tzoffset: + # i.e. no timezone information was found. + aware = naive + + elif res.tzname: + # tz-like string was parsed but we don't know what to do + # with it + warnings.warn("tzname {tzname} identified but not understood. " + "Pass `tzinfos` argument in order to correctly " + "return a timezone-aware datetime. In a future " + "version, this will raise an " + "exception.".format(tzname=res.tzname), + category=UnknownTimezoneWarning) + aware = naive + + return aware + + def _build_naive(self, res, default): + repl = {} + for attr in ("year", "month", "day", "hour", + "minute", "second", "microsecond"): + value = getattr(res, attr) + if value is not None: + repl[attr] = value + + if 'day' not in repl: + # If the default day exceeds the last day of the month, fall back + # to the end of the month. + cyear = default.year if res.year is None else res.year + cmonth = default.month if res.month is None else res.month + cday = default.day if res.day is None else res.day + + if cday > monthrange(cyear, cmonth)[1]: + repl['day'] = monthrange(cyear, cmonth)[1] + + naive = default.replace(**repl) + + if res.weekday is not None and not res.day: + naive = naive + relativedelta.relativedelta(weekday=res.weekday) + + return naive + + def _assign_tzname(self, dt, tzname): + if dt.tzname() != tzname: + new_dt = tz.enfold(dt, fold=1) + if new_dt.tzname() == tzname: + return new_dt + + return dt + def _recombine_skipped(self, tokens, skipped_idxs): """ >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"] @@ -1260,331 +1260,331 @@ class parser(object): skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx] else: skipped_tokens.append(tokens[idx]) - + return skipped_tokens - - -DEFAULTPARSER = parser() - - -def parse(timestr, parserinfo=None, **kwargs): - """ - - Parse a string in one of the supported formats, using the - ``parserinfo`` parameters. - - :param timestr: - A string containing a date/time stamp. - - :param parserinfo: - A :class:`parserinfo` object containing parameters for the parser. - If ``None``, the default arguments to the :class:`parserinfo` - constructor are used. - - The ``**kwargs`` parameter takes the following keyword arguments: - - :param default: - The default datetime object, if this is a datetime object and not - ``None``, elements specified in ``timestr`` replace elements in the - default object. - - :param ignoretz: - If set ``True``, time zones in parsed strings are ignored and a naive - :class:`datetime` object is returned. - - :param tzinfos: - Additional time zone names / aliases which may be present in the - string. This argument maps time zone names (and optionally offsets - from those time zones) to time zones. This parameter can be a - dictionary with timezone aliases mapping time zone names to time - zones or a function taking two parameters (``tzname`` and - ``tzoffset``) and returning a time zone. - - The timezones to which the names are mapped can be an integer - offset from UTC in seconds or a :class:`tzinfo` object. - - .. doctest:: - :options: +NORMALIZE_WHITESPACE - - >>> from dateutil.parser import parse - >>> from dateutil.tz import gettz - >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} - >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) - datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) - >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) - datetime.datetime(2012, 1, 19, 17, 21, - tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) - - This parameter is ignored if ``ignoretz`` is set. - - :param dayfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the day (``True``) or month (``False``). If - ``yearfirst`` is set to ``True``, this distinguishes between YDM and - YMD. If set to ``None``, this value is retrieved from the current - :class:`parserinfo` object (which itself defaults to ``False``). - - :param yearfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the year. If ``True``, the first number is taken to - be the year, otherwise the last number is taken to be the year. If - this is set to ``None``, the value is retrieved from the current - :class:`parserinfo` object (which itself defaults to ``False``). - - :param fuzzy: - Whether to allow fuzzy parsing, allowing for string like "Today is - January 1, 2047 at 8:21:00AM". - - :param fuzzy_with_tokens: - If ``True``, ``fuzzy`` is automatically set to True, and the parser - will return a tuple where the first element is the parsed - :class:`datetime.datetime` datetimestamp and the second element is - a tuple containing the portions of the string which were ignored: - - .. doctest:: - - >>> from dateutil.parser import parse - >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) - (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) - - :return: - Returns a :class:`datetime.datetime` object or, if the - ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the - first element being a :class:`datetime.datetime` object, the second - a tuple containing the fuzzy tokens. - + + +DEFAULTPARSER = parser() + + +def parse(timestr, parserinfo=None, **kwargs): + """ + + Parse a string in one of the supported formats, using the + ``parserinfo`` parameters. + + :param timestr: + A string containing a date/time stamp. + + :param parserinfo: + A :class:`parserinfo` object containing parameters for the parser. + If ``None``, the default arguments to the :class:`parserinfo` + constructor are used. + + The ``**kwargs`` parameter takes the following keyword arguments: + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a naive + :class:`datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM and + YMD. If set to ``None``, this value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken to + be the year, otherwise the last number is taken to be the year. If + this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + :raises ParserError: Raised for invalid or unknown string formats, if the provided :class:`tzinfo` is not in a valid format, or if an invalid date would be created. - - :raises OverflowError: - Raised if the parsed date exceeds the largest valid C integer on - your system. - """ - if parserinfo: - return parser(parserinfo).parse(timestr, **kwargs) - else: - return DEFAULTPARSER.parse(timestr, **kwargs) - - -class _tzparser(object): - - class _result(_resultbase): - - __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", - "start", "end"] - - class _attr(_resultbase): - __slots__ = ["month", "week", "weekday", - "yday", "jyday", "day", "time"] - - def __repr__(self): - return self._repr("") - - def __init__(self): - _resultbase.__init__(self) - self.start = self._attr() - self.end = self._attr() - - def parse(self, tzstr): - res = self._result() - l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x] - used_idxs = list() - try: - - len_l = len(l) - - i = 0 - while i < len_l: - # BRST+3[BRDT[+2]] - j = i - while j < len_l and not [x for x in l[j] - if x in "0123456789:,-+"]: - j += 1 - if j != i: - if not res.stdabbr: - offattr = "stdoffset" - res.stdabbr = "".join(l[i:j]) - else: - offattr = "dstoffset" - res.dstabbr = "".join(l[i:j]) - - for ii in range(j): - used_idxs.append(ii) - i = j - if (i < len_l and (l[i] in ('+', '-') or l[i][0] in - "0123456789")): - if l[i] in ('+', '-'): - # Yes, that's right. See the TZ variable - # documentation. - signal = (1, -1)[l[i] == '+'] - used_idxs.append(i) - i += 1 - else: - signal = -1 - len_li = len(l[i]) - if len_li == 4: - # -0300 - setattr(res, offattr, (int(l[i][:2]) * 3600 + - int(l[i][2:]) * 60) * signal) - elif i + 1 < len_l and l[i + 1] == ':': - # -03:00 - setattr(res, offattr, - (int(l[i]) * 3600 + - int(l[i + 2]) * 60) * signal) - used_idxs.append(i) - i += 2 - elif len_li <= 2: - # -[0]3 - setattr(res, offattr, - int(l[i][:2]) * 3600 * signal) - else: - return None - used_idxs.append(i) - i += 1 - if res.dstabbr: - break - else: - break - - - if i < len_l: - for j in range(i, len_l): - if l[j] == ';': - l[j] = ',' - - assert l[i] == ',' - - i += 1 - - if i >= len_l: - pass - elif (8 <= l.count(',') <= 9 and - not [y for x in l[i:] if x != ',' - for y in x if y not in "0123456789+-"]): - # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] - for x in (res.start, res.end): - x.month = int(l[i]) - used_idxs.append(i) - i += 2 - if l[i] == '-': - value = int(l[i + 1]) * -1 - used_idxs.append(i) - i += 1 - else: - value = int(l[i]) - used_idxs.append(i) - i += 2 - if value: - x.week = value - x.weekday = (int(l[i]) - 1) % 7 - else: - x.day = int(l[i]) - used_idxs.append(i) - i += 2 - x.time = int(l[i]) - used_idxs.append(i) - i += 2 - if i < len_l: - if l[i] in ('-', '+'): - signal = (-1, 1)[l[i] == "+"] - used_idxs.append(i) - i += 1 - else: - signal = 1 - used_idxs.append(i) - res.dstoffset = (res.stdoffset + int(l[i]) * signal) - - # This was a made-up format that is not in normal use - warn(('Parsed time zone "%s"' % tzstr) + - 'is in a non-standard dateutil-specific format, which ' + - 'is now deprecated; support for parsing this format ' + - 'will be removed in future versions. It is recommended ' + - 'that you switch to a standard format like the GNU ' + - 'TZ variable format.', tz.DeprecatedTzFormatWarning) - elif (l.count(',') == 2 and l[i:].count('/') <= 2 and - not [y for x in l[i:] if x not in (',', '/', 'J', 'M', - '.', '-', ':') - for y in x if y not in "0123456789"]): - for x in (res.start, res.end): - if l[i] == 'J': - # non-leap year day (1 based) - used_idxs.append(i) - i += 1 - x.jyday = int(l[i]) - elif l[i] == 'M': - # month[-.]week[-.]weekday - used_idxs.append(i) - i += 1 - x.month = int(l[i]) - used_idxs.append(i) - i += 1 - assert l[i] in ('-', '.') - used_idxs.append(i) - i += 1 - x.week = int(l[i]) - if x.week == 5: - x.week = -1 - used_idxs.append(i) - i += 1 - assert l[i] in ('-', '.') - used_idxs.append(i) - i += 1 - x.weekday = (int(l[i]) - 1) % 7 - else: - # year day (zero based) - x.yday = int(l[i]) + 1 - - used_idxs.append(i) - i += 1 - - if i < len_l and l[i] == '/': - used_idxs.append(i) - i += 1 - # start time - len_li = len(l[i]) - if len_li == 4: - # -0300 - x.time = (int(l[i][:2]) * 3600 + - int(l[i][2:]) * 60) - elif i + 1 < len_l and l[i + 1] == ':': - # -03:00 - x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60 - used_idxs.append(i) - i += 2 - if i + 1 < len_l and l[i + 1] == ':': - used_idxs.append(i) - i += 2 - x.time += int(l[i]) - elif len_li <= 2: - # -[0]3 - x.time = (int(l[i][:2]) * 3600) - else: - return None - used_idxs.append(i) - i += 1 - - assert i == len_l or l[i] == ',' - - i += 1 - - assert i >= len_l - - except (IndexError, ValueError, AssertionError): - return None - - unused_idxs = set(range(len_l)).difference(used_idxs) - res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"}) - return res - - -DEFAULTTZPARSER = _tzparser() - - -def _parsetz(tzstr): - return DEFAULTTZPARSER.parse(tzstr) - + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + if parserinfo: + return parser(parserinfo).parse(timestr, **kwargs) + else: + return DEFAULTPARSER.parse(timestr, **kwargs) + + +class _tzparser(object): + + class _result(_resultbase): + + __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", + "start", "end"] + + class _attr(_resultbase): + __slots__ = ["month", "week", "weekday", + "yday", "jyday", "day", "time"] + + def __repr__(self): + return self._repr("") + + def __init__(self): + _resultbase.__init__(self) + self.start = self._attr() + self.end = self._attr() + + def parse(self, tzstr): + res = self._result() + l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x] + used_idxs = list() + try: + + len_l = len(l) + + i = 0 + while i < len_l: + # BRST+3[BRDT[+2]] + j = i + while j < len_l and not [x for x in l[j] + if x in "0123456789:,-+"]: + j += 1 + if j != i: + if not res.stdabbr: + offattr = "stdoffset" + res.stdabbr = "".join(l[i:j]) + else: + offattr = "dstoffset" + res.dstabbr = "".join(l[i:j]) + + for ii in range(j): + used_idxs.append(ii) + i = j + if (i < len_l and (l[i] in ('+', '-') or l[i][0] in + "0123456789")): + if l[i] in ('+', '-'): + # Yes, that's right. See the TZ variable + # documentation. + signal = (1, -1)[l[i] == '+'] + used_idxs.append(i) + i += 1 + else: + signal = -1 + len_li = len(l[i]) + if len_li == 4: + # -0300 + setattr(res, offattr, (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) * signal) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + setattr(res, offattr, + (int(l[i]) * 3600 + + int(l[i + 2]) * 60) * signal) + used_idxs.append(i) + i += 2 + elif len_li <= 2: + # -[0]3 + setattr(res, offattr, + int(l[i][:2]) * 3600 * signal) + else: + return None + used_idxs.append(i) + i += 1 + if res.dstabbr: + break + else: + break + + + if i < len_l: + for j in range(i, len_l): + if l[j] == ';': + l[j] = ',' + + assert l[i] == ',' + + i += 1 + + if i >= len_l: + pass + elif (8 <= l.count(',') <= 9 and + not [y for x in l[i:] if x != ',' + for y in x if y not in "0123456789+-"]): + # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] + for x in (res.start, res.end): + x.month = int(l[i]) + used_idxs.append(i) + i += 2 + if l[i] == '-': + value = int(l[i + 1]) * -1 + used_idxs.append(i) + i += 1 + else: + value = int(l[i]) + used_idxs.append(i) + i += 2 + if value: + x.week = value + x.weekday = (int(l[i]) - 1) % 7 + else: + x.day = int(l[i]) + used_idxs.append(i) + i += 2 + x.time = int(l[i]) + used_idxs.append(i) + i += 2 + if i < len_l: + if l[i] in ('-', '+'): + signal = (-1, 1)[l[i] == "+"] + used_idxs.append(i) + i += 1 + else: + signal = 1 + used_idxs.append(i) + res.dstoffset = (res.stdoffset + int(l[i]) * signal) + + # This was a made-up format that is not in normal use + warn(('Parsed time zone "%s"' % tzstr) + + 'is in a non-standard dateutil-specific format, which ' + + 'is now deprecated; support for parsing this format ' + + 'will be removed in future versions. It is recommended ' + + 'that you switch to a standard format like the GNU ' + + 'TZ variable format.', tz.DeprecatedTzFormatWarning) + elif (l.count(',') == 2 and l[i:].count('/') <= 2 and + not [y for x in l[i:] if x not in (',', '/', 'J', 'M', + '.', '-', ':') + for y in x if y not in "0123456789"]): + for x in (res.start, res.end): + if l[i] == 'J': + # non-leap year day (1 based) + used_idxs.append(i) + i += 1 + x.jyday = int(l[i]) + elif l[i] == 'M': + # month[-.]week[-.]weekday + used_idxs.append(i) + i += 1 + x.month = int(l[i]) + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.week = int(l[i]) + if x.week == 5: + x.week = -1 + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.weekday = (int(l[i]) - 1) % 7 + else: + # year day (zero based) + x.yday = int(l[i]) + 1 + + used_idxs.append(i) + i += 1 + + if i < len_l and l[i] == '/': + used_idxs.append(i) + i += 1 + # start time + len_li = len(l[i]) + if len_li == 4: + # -0300 + x.time = (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60 + used_idxs.append(i) + i += 2 + if i + 1 < len_l and l[i + 1] == ':': + used_idxs.append(i) + i += 2 + x.time += int(l[i]) + elif len_li <= 2: + # -[0]3 + x.time = (int(l[i][:2]) * 3600) + else: + return None + used_idxs.append(i) + i += 1 + + assert i == len_l or l[i] == ',' + + i += 1 + + assert i >= len_l + + except (IndexError, ValueError, AssertionError): + return None + + unused_idxs = set(range(len_l)).difference(used_idxs) + res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"}) + return res + + +DEFAULTTZPARSER = _tzparser() + + +def _parsetz(tzstr): + return DEFAULTTZPARSER.parse(tzstr) + class ParserError(ValueError): """Exception subclass used for any failure to parse a datetime string. @@ -1605,9 +1605,9 @@ class ParserError(ValueError): return "%s(%s)" % (self.__class__.__name__, args) -class UnknownTimezoneWarning(RuntimeWarning): +class UnknownTimezoneWarning(RuntimeWarning): """Raised when the parser finds a timezone it cannot parse into a tzinfo. .. versionadded:: 2.7.0 """ -# vim:ts=4:sw=4:et +# vim:ts=4:sw=4:et diff --git a/contrib/python/dateutil/dateutil/parser/isoparser.py b/contrib/python/dateutil/dateutil/parser/isoparser.py index 5d7bee3800..f01a196757 100644 --- a/contrib/python/dateutil/dateutil/parser/isoparser.py +++ b/contrib/python/dateutil/dateutil/parser/isoparser.py @@ -1,352 +1,352 @@ -# -*- coding: utf-8 -*- -""" -This module offers a parser for ISO-8601 strings - -It is intended to support all valid date, time and datetime formats per the -ISO-8601 specification. - -..versionadded:: 2.7.0 -""" -from datetime import datetime, timedelta, time, date -import calendar -from dateutil import tz - -from functools import wraps - -import re -import six - -__all__ = ["isoparse", "isoparser"] - - -def _takes_ascii(f): - @wraps(f) - def func(self, str_in, *args, **kwargs): - # If it's a stream, read the whole thing - str_in = getattr(str_in, 'read', lambda: str_in)() - - # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII - if isinstance(str_in, six.text_type): - # ASCII is the same in UTF-8 - try: - str_in = str_in.encode('ascii') - except UnicodeEncodeError as e: - msg = 'ISO-8601 strings should contain only ASCII characters' - six.raise_from(ValueError(msg), e) - - return f(self, str_in, *args, **kwargs) - - return func - - -class isoparser(object): - def __init__(self, sep=None): - """ - :param sep: - A single character that separates date and time portions. If - ``None``, the parser will accept any single character. - For strict ISO-8601 adherence, pass ``'T'``. - """ - if sep is not None: - if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'): - raise ValueError('Separator must be a single, non-numeric ' + - 'ASCII character') - - sep = sep.encode('ascii') - - self._sep = sep - - @_takes_ascii - def isoparse(self, dt_str): - """ - Parse an ISO-8601 datetime string into a :class:`datetime.datetime`. - - An ISO-8601 datetime string consists of a date portion, followed - optionally by a time portion - the date and time portions are separated - by a single character separator, which is ``T`` in the official - standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be - combined with a time portion. - - Supported date formats are: - - Common: - - - ``YYYY`` - - ``YYYY-MM`` or ``YYYYMM`` - - ``YYYY-MM-DD`` or ``YYYYMMDD`` - - Uncommon: - - - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0) - - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day - - The ISO week and day numbering follows the same logic as - :func:`datetime.date.isocalendar`. - - Supported time formats are: - - - ``hh`` - - ``hh:mm`` or ``hhmm`` - - ``hh:mm:ss`` or ``hhmmss`` +# -*- coding: utf-8 -*- +""" +This module offers a parser for ISO-8601 strings + +It is intended to support all valid date, time and datetime formats per the +ISO-8601 specification. + +..versionadded:: 2.7.0 +""" +from datetime import datetime, timedelta, time, date +import calendar +from dateutil import tz + +from functools import wraps + +import re +import six + +__all__ = ["isoparse", "isoparser"] + + +def _takes_ascii(f): + @wraps(f) + def func(self, str_in, *args, **kwargs): + # If it's a stream, read the whole thing + str_in = getattr(str_in, 'read', lambda: str_in)() + + # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII + if isinstance(str_in, six.text_type): + # ASCII is the same in UTF-8 + try: + str_in = str_in.encode('ascii') + except UnicodeEncodeError as e: + msg = 'ISO-8601 strings should contain only ASCII characters' + six.raise_from(ValueError(msg), e) + + return f(self, str_in, *args, **kwargs) + + return func + + +class isoparser(object): + def __init__(self, sep=None): + """ + :param sep: + A single character that separates date and time portions. If + ``None``, the parser will accept any single character. + For strict ISO-8601 adherence, pass ``'T'``. + """ + if sep is not None: + if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'): + raise ValueError('Separator must be a single, non-numeric ' + + 'ASCII character') + + sep = sep.encode('ascii') + + self._sep = sep + + @_takes_ascii + def isoparse(self, dt_str): + """ + Parse an ISO-8601 datetime string into a :class:`datetime.datetime`. + + An ISO-8601 datetime string consists of a date portion, followed + optionally by a time portion - the date and time portions are separated + by a single character separator, which is ``T`` in the official + standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be + combined with a time portion. + + Supported date formats are: + + Common: + + - ``YYYY`` + - ``YYYY-MM`` or ``YYYYMM`` + - ``YYYY-MM-DD`` or ``YYYYMMDD`` + + Uncommon: + + - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0) + - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day + + The ISO week and day numbering follows the same logic as + :func:`datetime.date.isocalendar`. + + Supported time formats are: + + - ``hh`` + - ``hh:mm`` or ``hhmm`` + - ``hh:mm:ss`` or ``hhmmss`` - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits) - - Midnight is a special case for `hh`, as the standard supports both + + Midnight is a special case for `hh`, as the standard supports both 00:00 and 24:00 as a representation. The decimal separator can be either a dot or a comma. - - - .. caution:: - - Support for fractional components other than seconds is part of the - ISO-8601 standard, but is not currently implemented in this parser. - - Supported time zone offset formats are: - - - `Z` (UTC) - - `±HH:MM` - - `±HHMM` - - `±HH` - - Offsets will be represented as :class:`dateutil.tz.tzoffset` objects, - with the exception of UTC, which will be represented as - :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such - as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`. - - :param dt_str: - A string or stream containing only an ISO-8601 datetime string - - :return: - Returns a :class:`datetime.datetime` representing the string. - Unspecified components default to their lowest value. - - .. warning:: - - As of version 2.7.0, the strictness of the parser should not be - considered a stable part of the contract. Any valid ISO-8601 string - that parses correctly with the default settings will continue to - parse correctly in future versions, but invalid strings that - currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not - guaranteed to continue failing in future versions if they encode - a valid date. - - .. versionadded:: 2.7.0 - """ - components, pos = self._parse_isodate(dt_str) - - if len(dt_str) > pos: - if self._sep is None or dt_str[pos:pos + 1] == self._sep: - components += self._parse_isotime(dt_str[pos + 1:]) - else: - raise ValueError('String contains unknown ISO components') - + + + .. caution:: + + Support for fractional components other than seconds is part of the + ISO-8601 standard, but is not currently implemented in this parser. + + Supported time zone offset formats are: + + - `Z` (UTC) + - `±HH:MM` + - `±HHMM` + - `±HH` + + Offsets will be represented as :class:`dateutil.tz.tzoffset` objects, + with the exception of UTC, which will be represented as + :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such + as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`. + + :param dt_str: + A string or stream containing only an ISO-8601 datetime string + + :return: + Returns a :class:`datetime.datetime` representing the string. + Unspecified components default to their lowest value. + + .. warning:: + + As of version 2.7.0, the strictness of the parser should not be + considered a stable part of the contract. Any valid ISO-8601 string + that parses correctly with the default settings will continue to + parse correctly in future versions, but invalid strings that + currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not + guaranteed to continue failing in future versions if they encode + a valid date. + + .. versionadded:: 2.7.0 + """ + components, pos = self._parse_isodate(dt_str) + + if len(dt_str) > pos: + if self._sep is None or dt_str[pos:pos + 1] == self._sep: + components += self._parse_isotime(dt_str[pos + 1:]) + else: + raise ValueError('String contains unknown ISO components') + if len(components) > 3 and components[3] == 24: components[3] = 0 return datetime(*components) + timedelta(days=1) - return datetime(*components) - - @_takes_ascii - def parse_isodate(self, datestr): - """ - Parse the date portion of an ISO string. - - :param datestr: - The string portion of an ISO string, without a separator - - :return: - Returns a :class:`datetime.date` object - """ - components, pos = self._parse_isodate(datestr) - if pos < len(datestr): - raise ValueError('String contains unknown ISO ' + + return datetime(*components) + + @_takes_ascii + def parse_isodate(self, datestr): + """ + Parse the date portion of an ISO string. + + :param datestr: + The string portion of an ISO string, without a separator + + :return: + Returns a :class:`datetime.date` object + """ + components, pos = self._parse_isodate(datestr) + if pos < len(datestr): + raise ValueError('String contains unknown ISO ' + 'components: {!r}'.format(datestr.decode('ascii'))) - return date(*components) - - @_takes_ascii - def parse_isotime(self, timestr): - """ - Parse the time portion of an ISO string. - - :param timestr: - The time portion of an ISO string, without a separator - - :return: - Returns a :class:`datetime.time` object - """ + return date(*components) + + @_takes_ascii + def parse_isotime(self, timestr): + """ + Parse the time portion of an ISO string. + + :param timestr: + The time portion of an ISO string, without a separator + + :return: + Returns a :class:`datetime.time` object + """ components = self._parse_isotime(timestr) if components[0] == 24: components[0] = 0 return time(*components) - - @_takes_ascii - def parse_tzstr(self, tzstr, zero_as_utc=True): - """ - Parse a valid ISO time zone string. - - See :func:`isoparser.isoparse` for details on supported formats. - - :param tzstr: - A string representing an ISO time zone offset - - :param zero_as_utc: - Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones - - :return: - Returns :class:`dateutil.tz.tzoffset` for offsets and - :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is - specified) offsets equivalent to UTC. - """ - return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc) - - # Constants - _DATE_SEP = b'-' - _TIME_SEP = b':' + + @_takes_ascii + def parse_tzstr(self, tzstr, zero_as_utc=True): + """ + Parse a valid ISO time zone string. + + See :func:`isoparser.isoparse` for details on supported formats. + + :param tzstr: + A string representing an ISO time zone offset + + :param zero_as_utc: + Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones + + :return: + Returns :class:`dateutil.tz.tzoffset` for offsets and + :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is + specified) offsets equivalent to UTC. + """ + return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc) + + # Constants + _DATE_SEP = b'-' + _TIME_SEP = b':' _FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)') - - def _parse_isodate(self, dt_str): - try: - return self._parse_isodate_common(dt_str) - except ValueError: - return self._parse_isodate_uncommon(dt_str) - - def _parse_isodate_common(self, dt_str): - len_str = len(dt_str) - components = [1, 1, 1] - - if len_str < 4: - raise ValueError('ISO string too short') - - # Year - components[0] = int(dt_str[0:4]) - pos = 4 - if pos >= len_str: - return components, pos - - has_sep = dt_str[pos:pos + 1] == self._DATE_SEP - if has_sep: - pos += 1 - - # Month - if len_str - pos < 2: - raise ValueError('Invalid common month') - - components[1] = int(dt_str[pos:pos + 2]) - pos += 2 - - if pos >= len_str: - if has_sep: - return components, pos - else: - raise ValueError('Invalid ISO format') - - if has_sep: - if dt_str[pos:pos + 1] != self._DATE_SEP: - raise ValueError('Invalid separator in ISO string') - pos += 1 - - # Day - if len_str - pos < 2: - raise ValueError('Invalid common day') - components[2] = int(dt_str[pos:pos + 2]) - return components, pos + 2 - - def _parse_isodate_uncommon(self, dt_str): - if len(dt_str) < 4: - raise ValueError('ISO string too short') - - # All ISO formats start with the year - year = int(dt_str[0:4]) - - has_sep = dt_str[4:5] == self._DATE_SEP - - pos = 4 + has_sep # Skip '-' if it's there - if dt_str[pos:pos + 1] == b'W': - # YYYY-?Www-?D? - pos += 1 - weekno = int(dt_str[pos:pos + 2]) - pos += 2 - - dayno = 1 - if len(dt_str) > pos: - if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep: - raise ValueError('Inconsistent use of dash separator') - - pos += has_sep - - dayno = int(dt_str[pos:pos + 1]) - pos += 1 - - base_date = self._calculate_weekdate(year, weekno, dayno) - else: - # YYYYDDD or YYYY-DDD - if len(dt_str) - pos < 3: - raise ValueError('Invalid ordinal day') - - ordinal_day = int(dt_str[pos:pos + 3]) - pos += 3 - - if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)): - raise ValueError('Invalid ordinal day' + - ' {} for year {}'.format(ordinal_day, year)) - - base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1) - - components = [base_date.year, base_date.month, base_date.day] - return components, pos - - def _calculate_weekdate(self, year, week, day): - """ - Calculate the day of corresponding to the ISO year-week-day calendar. - - This function is effectively the inverse of - :func:`datetime.date.isocalendar`. - - :param year: - The year in the ISO calendar - - :param week: - The week in the ISO calendar - range is [1, 53] - - :param day: - The day in the ISO calendar - range is [1 (MON), 7 (SUN)] - - :return: - Returns a :class:`datetime.date` - """ - if not 0 < week < 54: - raise ValueError('Invalid week: {}'.format(week)) - - if not 0 < day < 8: # Range is 1-7 - raise ValueError('Invalid weekday: {}'.format(day)) - - # Get week 1 for the specific year: - jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it - week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1) - - # Now add the specific number of weeks and days to get what we want - week_offset = (week - 1) * 7 + (day - 1) - return week_1 + timedelta(days=week_offset) - - def _parse_isotime(self, timestr): - len_str = len(timestr) - components = [0, 0, 0, 0, None] - pos = 0 - comp = -1 - + + def _parse_isodate(self, dt_str): + try: + return self._parse_isodate_common(dt_str) + except ValueError: + return self._parse_isodate_uncommon(dt_str) + + def _parse_isodate_common(self, dt_str): + len_str = len(dt_str) + components = [1, 1, 1] + + if len_str < 4: + raise ValueError('ISO string too short') + + # Year + components[0] = int(dt_str[0:4]) + pos = 4 + if pos >= len_str: + return components, pos + + has_sep = dt_str[pos:pos + 1] == self._DATE_SEP + if has_sep: + pos += 1 + + # Month + if len_str - pos < 2: + raise ValueError('Invalid common month') + + components[1] = int(dt_str[pos:pos + 2]) + pos += 2 + + if pos >= len_str: + if has_sep: + return components, pos + else: + raise ValueError('Invalid ISO format') + + if has_sep: + if dt_str[pos:pos + 1] != self._DATE_SEP: + raise ValueError('Invalid separator in ISO string') + pos += 1 + + # Day + if len_str - pos < 2: + raise ValueError('Invalid common day') + components[2] = int(dt_str[pos:pos + 2]) + return components, pos + 2 + + def _parse_isodate_uncommon(self, dt_str): + if len(dt_str) < 4: + raise ValueError('ISO string too short') + + # All ISO formats start with the year + year = int(dt_str[0:4]) + + has_sep = dt_str[4:5] == self._DATE_SEP + + pos = 4 + has_sep # Skip '-' if it's there + if dt_str[pos:pos + 1] == b'W': + # YYYY-?Www-?D? + pos += 1 + weekno = int(dt_str[pos:pos + 2]) + pos += 2 + + dayno = 1 + if len(dt_str) > pos: + if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep: + raise ValueError('Inconsistent use of dash separator') + + pos += has_sep + + dayno = int(dt_str[pos:pos + 1]) + pos += 1 + + base_date = self._calculate_weekdate(year, weekno, dayno) + else: + # YYYYDDD or YYYY-DDD + if len(dt_str) - pos < 3: + raise ValueError('Invalid ordinal day') + + ordinal_day = int(dt_str[pos:pos + 3]) + pos += 3 + + if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)): + raise ValueError('Invalid ordinal day' + + ' {} for year {}'.format(ordinal_day, year)) + + base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1) + + components = [base_date.year, base_date.month, base_date.day] + return components, pos + + def _calculate_weekdate(self, year, week, day): + """ + Calculate the day of corresponding to the ISO year-week-day calendar. + + This function is effectively the inverse of + :func:`datetime.date.isocalendar`. + + :param year: + The year in the ISO calendar + + :param week: + The week in the ISO calendar - range is [1, 53] + + :param day: + The day in the ISO calendar - range is [1 (MON), 7 (SUN)] + + :return: + Returns a :class:`datetime.date` + """ + if not 0 < week < 54: + raise ValueError('Invalid week: {}'.format(week)) + + if not 0 < day < 8: # Range is 1-7 + raise ValueError('Invalid weekday: {}'.format(day)) + + # Get week 1 for the specific year: + jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it + week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1) + + # Now add the specific number of weeks and days to get what we want + week_offset = (week - 1) * 7 + (day - 1) + return week_1 + timedelta(days=week_offset) + + def _parse_isotime(self, timestr): + len_str = len(timestr) + components = [0, 0, 0, 0, None] + pos = 0 + comp = -1 + if len_str < 2: - raise ValueError('ISO time too short') - + raise ValueError('ISO time too short') + has_sep = False - - while pos < len_str and comp < 5: - comp += 1 - + + while pos < len_str and comp < 5: + comp += 1 + if timestr[pos:pos + 1] in b'-+Zz': - # Detect time zone boundary - components[-1] = self._parse_tzstr(timestr[pos:]) - pos = len_str - break - + # Detect time zone boundary + components[-1] = self._parse_tzstr(timestr[pos:]) + pos = len_str + break + if comp == 1 and timestr[pos:pos+1] == self._TIME_SEP: has_sep = True pos += 1 @@ -355,62 +355,62 @@ class isoparser(object): raise ValueError('Inconsistent use of colon separator') pos += 1 - if comp < 3: - # Hour, minute, second - components[comp] = int(timestr[pos:pos + 2]) - pos += 2 - - if comp == 3: + if comp < 3: + # Hour, minute, second + components[comp] = int(timestr[pos:pos + 2]) + pos += 2 + + if comp == 3: # Fraction of a second frac = self._FRACTION_REGEX.match(timestr[pos:]) if not frac: - continue - + continue + us_str = frac.group(1)[:6] # Truncate to microseconds - components[comp] = int(us_str) * 10**(6 - len(us_str)) + components[comp] = int(us_str) * 10**(6 - len(us_str)) pos += len(frac.group()) - - if pos < len_str: - raise ValueError('Unused components in ISO string') - - if components[0] == 24: - # Standard supports 00:00 and 24:00 as representations of midnight - if any(component != 0 for component in components[1:4]): - raise ValueError('Hour may only be 24 at 24:00:00.000') - - return components - - def _parse_tzstr(self, tzstr, zero_as_utc=True): + + if pos < len_str: + raise ValueError('Unused components in ISO string') + + if components[0] == 24: + # Standard supports 00:00 and 24:00 as representations of midnight + if any(component != 0 for component in components[1:4]): + raise ValueError('Hour may only be 24 at 24:00:00.000') + + return components + + def _parse_tzstr(self, tzstr, zero_as_utc=True): if tzstr == b'Z' or tzstr == b'z': return tz.UTC - - if len(tzstr) not in {3, 5, 6}: - raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters') - - if tzstr[0:1] == b'-': - mult = -1 - elif tzstr[0:1] == b'+': - mult = 1 - else: - raise ValueError('Time zone offset requires sign') - - hours = int(tzstr[1:3]) - if len(tzstr) == 3: - minutes = 0 - else: - minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):]) - - if zero_as_utc and hours == 0 and minutes == 0: + + if len(tzstr) not in {3, 5, 6}: + raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters') + + if tzstr[0:1] == b'-': + mult = -1 + elif tzstr[0:1] == b'+': + mult = 1 + else: + raise ValueError('Time zone offset requires sign') + + hours = int(tzstr[1:3]) + if len(tzstr) == 3: + minutes = 0 + else: + minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):]) + + if zero_as_utc and hours == 0 and minutes == 0: return tz.UTC - else: - if minutes > 59: - raise ValueError('Invalid minutes in time zone offset') - - if hours > 23: - raise ValueError('Invalid hours in time zone offset') - - return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60) - - -DEFAULT_ISOPARSER = isoparser() -isoparse = DEFAULT_ISOPARSER.isoparse + else: + if minutes > 59: + raise ValueError('Invalid minutes in time zone offset') + + if hours > 23: + raise ValueError('Invalid hours in time zone offset') + + return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60) + + +DEFAULT_ISOPARSER = isoparser() +isoparse = DEFAULT_ISOPARSER.isoparse diff --git a/contrib/python/dateutil/dateutil/relativedelta.py b/contrib/python/dateutil/dateutil/relativedelta.py index a9e85f7e6c..35c0ae546f 100644 --- a/contrib/python/dateutil/dateutil/relativedelta.py +++ b/contrib/python/dateutil/dateutil/relativedelta.py @@ -23,7 +23,7 @@ class relativedelta(object): It is based on the specification of the excellent work done by M.-A. Lemburg in his - `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension. + `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension. However, notice that this type does *NOT* implement the same algorithm as his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. @@ -38,7 +38,7 @@ class relativedelta(object): year, month, day, hour, minute, second, microsecond: Absolute information (argument is singular); adding or subtracting a - relativedelta with absolute information does not perform an arithmetic + relativedelta with absolute information does not perform an arithmetic operation, but rather REPLACES the corresponding value in the original datetime with the value(s) in relativedelta. @@ -48,7 +48,7 @@ class relativedelta(object): the corresponding arithmetic operation on the original datetime value with the information in the relativedelta. - weekday: + weekday: One of the weekday instances (MO, TU, etc) available in the relativedelta module. These instances may receive a parameter N, specifying the Nth weekday, which could be positive or negative @@ -67,38 +67,38 @@ class relativedelta(object): Set the yearday or the non-leap year day (jump leap days). These are converted to day/month/leapdays information. - There are relative and absolute forms of the keyword - arguments. The plural is relative, and the singular is - absolute. For each argument in the order below, the absolute form - is applied first (by setting each attribute to that value) and - then the relative form (by adding the value to the attribute). + There are relative and absolute forms of the keyword + arguments. The plural is relative, and the singular is + absolute. For each argument in the order below, the absolute form + is applied first (by setting each attribute to that value) and + then the relative form (by adding the value to the attribute). - The order of attributes considered when this relativedelta is - added to a datetime is: + The order of attributes considered when this relativedelta is + added to a datetime is: - 1. Year - 2. Month - 3. Day - 4. Hours - 5. Minutes - 6. Seconds - 7. Microseconds + 1. Year + 2. Month + 3. Day + 4. Hours + 5. Minutes + 6. Seconds + 7. Microseconds - Finally, weekday is applied, using the rule described above. + Finally, weekday is applied, using the rule described above. - For example + For example >>> from datetime import datetime >>> from dateutil.relativedelta import relativedelta, MO - >>> dt = datetime(2018, 4, 9, 13, 37, 0) - >>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) + >>> dt = datetime(2018, 4, 9, 13, 37, 0) + >>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) >>> dt + delta datetime.datetime(2018, 4, 2, 14, 37) - First, the day is set to 1 (the first of the month), then 25 hours - are added, to get to the 2nd day and 14th hour, finally the - weekday is applied, but since the 2nd is already a Monday there is - no effect. + First, the day is set to 1 (the first of the month), then 25 hours + are added, to get to the 2nd day and 14th hour, finally the + weekday is applied, but since the 2nd is already a Monday there is + no effect. """ @@ -168,14 +168,14 @@ class relativedelta(object): self.seconds = delta.seconds + delta.days * 86400 self.microseconds = delta.microseconds else: - # Check for non-integer values in integer-only quantities - if any(x is not None and x != int(x) for x in (years, months)): - raise ValueError("Non-integer years and months are " - "ambiguous and not currently supported.") - + # Check for non-integer values in integer-only quantities + if any(x is not None and x != int(x) for x in (years, months)): + raise ValueError("Non-integer years and months are " + "ambiguous and not currently supported.") + # Relative information - self.years = int(years) - self.months = int(months) + self.years = int(years) + self.months = int(months) self.days = days + weeks * 7 self.leapdays = leapdays self.hours = hours @@ -263,7 +263,7 @@ class relativedelta(object): @property def weeks(self): - return int(self.days / 7.0) + return int(self.days / 7.0) @weeks.setter def weeks(self, value): @@ -436,24 +436,24 @@ class relativedelta(object): is not None else other.microsecond)) - def __abs__(self): - return self.__class__(years=abs(self.years), - months=abs(self.months), - days=abs(self.days), - hours=abs(self.hours), - minutes=abs(self.minutes), - seconds=abs(self.seconds), - microseconds=abs(self.microseconds), - leapdays=self.leapdays, - year=self.year, - month=self.month, - day=self.day, - weekday=self.weekday, - hour=self.hour, - minute=self.minute, - second=self.second, - microsecond=self.microsecond) - + def __abs__(self): + return self.__class__(years=abs(self.years), + months=abs(self.months), + days=abs(self.days), + hours=abs(self.hours), + minutes=abs(self.minutes), + seconds=abs(self.seconds), + microseconds=abs(self.microseconds), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + def __neg__(self): return self.__class__(years=-self.years, months=-self.months, @@ -544,25 +544,25 @@ class relativedelta(object): self.second == other.second and self.microsecond == other.microsecond) - def __hash__(self): - return hash(( - self.weekday, - self.years, - self.months, - self.days, - self.hours, - self.minutes, - self.seconds, - self.microseconds, - self.leapdays, - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - )) + def __hash__(self): + return hash(( + self.weekday, + self.years, + self.months, + self.days, + self.hours, + self.minutes, + self.seconds, + self.microseconds, + self.leapdays, + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + )) def __ne__(self, other): return not self.__eq__(other) diff --git a/contrib/python/dateutil/dateutil/rrule.py b/contrib/python/dateutil/dateutil/rrule.py index b3203393c6..ad1c8b3dae 100644 --- a/contrib/python/dateutil/dateutil/rrule.py +++ b/contrib/python/dateutil/dateutil/rrule.py @@ -2,14 +2,14 @@ """ The rrule module offers a small, complete, and very fast, implementation of the recurrence rules documented in the -`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_, +`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_, including support for caching of results. """ import calendar import datetime import heapq import itertools -import re +import re import sys from functools import wraps # For warning about deprecation of until and count @@ -390,11 +390,11 @@ class rrule(rrulebase): :param byyearday: If given, it must be either an integer, or a sequence of integers, meaning the year days to apply the recurrence to. - :param byeaster: - If given, it must be either an integer, or a sequence of integers, - positive or negative. Each integer will define an offset from the - Easter Sunday. Passing the offset 0 to byeaster will yield the Easter - Sunday itself. This is an extension to the RFC specification. + :param byeaster: + If given, it must be either an integer, or a sequence of integers, + positive or negative. Each integer will define an offset from the + Easter Sunday. Passing the offset 0 to byeaster will yield the Easter + Sunday itself. This is an extension to the RFC specification. :param byweekno: If given, it must be either an integer, or a sequence of integers, meaning the week numbers to apply the recurrence to. Week numbers @@ -420,10 +420,10 @@ class rrule(rrulebase): :param bysecond: If given, it must be either an integer, or a sequence of integers, meaning the seconds to apply the recurrence to. - :param cache: - If given, it must be a boolean value specifying to enable or disable - caching of results. If you will use the same rrule instance multiple - times, enabling caching will improve the performance considerably. + :param cache: + If given, it must be a boolean value specifying to enable or disable + caching of results. If you will use the same rrule instance multiple + times, enabling caching will improve the performance considerably. """ def __init__(self, freq, dtstart=None, interval=1, wkst=None, count=None, until=None, bysetpos=None, @@ -434,10 +434,10 @@ class rrule(rrulebase): super(rrule, self).__init__(cache) global easter if not dtstart: - if until and until.tzinfo: - dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0) + if until and until.tzinfo: + dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0) else: - dtstart = datetime.datetime.now().replace(microsecond=0) + dtstart = datetime.datetime.now().replace(microsecond=0) elif not isinstance(dtstart, datetime.datetime): dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) else: @@ -458,22 +458,22 @@ class rrule(rrulebase): until = datetime.datetime.fromordinal(until.toordinal()) self._until = until - if self._dtstart and self._until: - if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None): - # According to RFC5545 Section 3.3.10: - # https://tools.ietf.org/html/rfc5545#section-3.3.10 - # - # > If the "DTSTART" property is specified as a date with UTC - # > time or a date with local time and time zone reference, - # > then the UNTIL rule part MUST be specified as a date with - # > UTC time. - raise ValueError( - 'RRULE UNTIL values must be specified in UTC when DTSTART ' - 'is timezone-aware' - ) - + if self._dtstart and self._until: + if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None): + # According to RFC5545 Section 3.3.10: + # https://tools.ietf.org/html/rfc5545#section-3.3.10 + # + # > If the "DTSTART" property is specified as a date with UTC + # > time or a date with local time and time zone reference, + # > then the UNTIL rule part MUST be specified as a date with + # > UTC time. + raise ValueError( + 'RRULE UNTIL values must be specified in UTC when DTSTART ' + 'is timezone-aware' + ) + if count is not None and until: - warn("Using both 'count' and 'until' is inconsistent with RFC 5545" + warn("Using both 'count' and 'until' is inconsistent with RFC 5545" " and has been deprecated in dateutil. Future versions will " "raise an error.", DeprecationWarning) @@ -610,13 +610,13 @@ class rrule(rrulebase): self._byweekday = tuple(sorted(self._byweekday)) orig_byweekday = [weekday(x) for x in self._byweekday] else: - orig_byweekday = () + orig_byweekday = () if self._bynweekday is not None: self._bynweekday = tuple(sorted(self._bynweekday)) orig_bynweekday = [weekday(*x) for x in self._bynweekday] else: - orig_bynweekday = () + orig_bynweekday = () if 'byweekday' not in self._original_rule: self._original_rule['byweekday'] = tuple(itertools.chain( @@ -625,7 +625,7 @@ class rrule(rrulebase): # byhour if byhour is None: if freq < HOURLY: - self._byhour = {dtstart.hour} + self._byhour = {dtstart.hour} else: self._byhour = None else: @@ -645,7 +645,7 @@ class rrule(rrulebase): # byminute if byminute is None: if freq < MINUTELY: - self._byminute = {dtstart.minute} + self._byminute = {dtstart.minute} else: self._byminute = None else: @@ -700,7 +700,7 @@ class rrule(rrulebase): def __str__(self): """ Output a string that would generate this RRULE if passed to rrulestr. - This is mostly compatible with RFC5545, except for the + This is mostly compatible with RFC5545, except for the dateutil-specific extension BYEASTER. """ @@ -725,7 +725,7 @@ class rrule(rrulebase): if self._original_rule.get('byweekday') is not None: # The str() method on weekday objects doesn't generate - # RFC5545-compliant strings, so we should modify that. + # RFC5545-compliant strings, so we should modify that. original_rule = dict(self._original_rule) wday_strings = [] for wday in original_rule['byweekday']: @@ -756,7 +756,7 @@ class rrule(rrulebase): parts.append(partfmt.format(name=name, vals=(','.join(str(v) for v in value)))) - output.append('RRULE:' + ';'.join(parts)) + output.append('RRULE:' + ';'.join(parts)) return '\n'.join(output) def replace(self, **kwargs): @@ -1619,17 +1619,17 @@ class _rrulestr(object): forceset=False, compatible=False, ignoretz=False, - tzids=None, + tzids=None, tzinfos=None): global parser if compatible: forceset = True unfold = True - - TZID_NAMES = dict(map( - lambda x: (x.upper(), x), - re.findall('TZID=(?P<name>[^:]+):', s) - )) + + TZID_NAMES = dict(map( + lambda x: (x.upper(), x), + re.findall('TZID=(?P<name>[^:]+):', s) + )) s = s.upper() if not s.strip(): raise ValueError("empty string") diff --git a/contrib/python/dateutil/dateutil/test/_common.py b/contrib/python/dateutil/dateutil/test/_common.py index b8d2047374..3444ee9c9c 100644 --- a/contrib/python/dateutil/dateutil/test/_common.py +++ b/contrib/python/dateutil/dateutil/test/_common.py @@ -1,233 +1,233 @@ -from __future__ import unicode_literals -import os -import time -import subprocess -import warnings -import tempfile -import pickle - +from __future__ import unicode_literals +import os +import time +import subprocess +import warnings +import tempfile +import pickle + import pytest - - -class PicklableMixin(object): - def _get_nobj_bytes(self, obj, dump_kwargs, load_kwargs): - """ - Pickle and unpickle an object using ``pickle.dumps`` / ``pickle.loads`` - """ - pkl = pickle.dumps(obj, **dump_kwargs) - return pickle.loads(pkl, **load_kwargs) - - def _get_nobj_file(self, obj, dump_kwargs, load_kwargs): - """ - Pickle and unpickle an object using ``pickle.dump`` / ``pickle.load`` on - a temporary file. - """ - with tempfile.TemporaryFile('w+b') as pkl: - pickle.dump(obj, pkl, **dump_kwargs) - pkl.seek(0) # Reset the file to the beginning to read it - nobj = pickle.load(pkl, **load_kwargs) - - return nobj - - def assertPicklable(self, obj, singleton=False, asfile=False, - dump_kwargs=None, load_kwargs=None): - """ - Assert that an object can be pickled and unpickled. This assertion - assumes that the desired behavior is that the unpickled object compares - equal to the original object, but is not the same object. - """ - get_nobj = self._get_nobj_file if asfile else self._get_nobj_bytes - dump_kwargs = dump_kwargs or {} - load_kwargs = load_kwargs or {} - - nobj = get_nobj(obj, dump_kwargs, load_kwargs) - if not singleton: - self.assertIsNot(obj, nobj) - self.assertEqual(obj, nobj) - - -class TZContextBase(object): - """ - Base class for a context manager which allows changing of time zones. - - Subclasses may define a guard variable to either block or or allow time - zone changes by redefining ``_guard_var_name`` and ``_guard_allows_change``. - The default is that the guard variable must be affirmatively set. - - Subclasses must define ``get_current_tz`` and ``set_current_tz``. - """ - _guard_var_name = "DATEUTIL_MAY_CHANGE_TZ" - _guard_allows_change = True - - def __init__(self, tzval): - self.tzval = tzval - self._old_tz = None - - @classmethod - def tz_change_allowed(cls): - """ - Class method used to query whether or not this class allows time zone - changes. - """ + + +class PicklableMixin(object): + def _get_nobj_bytes(self, obj, dump_kwargs, load_kwargs): + """ + Pickle and unpickle an object using ``pickle.dumps`` / ``pickle.loads`` + """ + pkl = pickle.dumps(obj, **dump_kwargs) + return pickle.loads(pkl, **load_kwargs) + + def _get_nobj_file(self, obj, dump_kwargs, load_kwargs): + """ + Pickle and unpickle an object using ``pickle.dump`` / ``pickle.load`` on + a temporary file. + """ + with tempfile.TemporaryFile('w+b') as pkl: + pickle.dump(obj, pkl, **dump_kwargs) + pkl.seek(0) # Reset the file to the beginning to read it + nobj = pickle.load(pkl, **load_kwargs) + + return nobj + + def assertPicklable(self, obj, singleton=False, asfile=False, + dump_kwargs=None, load_kwargs=None): + """ + Assert that an object can be pickled and unpickled. This assertion + assumes that the desired behavior is that the unpickled object compares + equal to the original object, but is not the same object. + """ + get_nobj = self._get_nobj_file if asfile else self._get_nobj_bytes + dump_kwargs = dump_kwargs or {} + load_kwargs = load_kwargs or {} + + nobj = get_nobj(obj, dump_kwargs, load_kwargs) + if not singleton: + self.assertIsNot(obj, nobj) + self.assertEqual(obj, nobj) + + +class TZContextBase(object): + """ + Base class for a context manager which allows changing of time zones. + + Subclasses may define a guard variable to either block or or allow time + zone changes by redefining ``_guard_var_name`` and ``_guard_allows_change``. + The default is that the guard variable must be affirmatively set. + + Subclasses must define ``get_current_tz`` and ``set_current_tz``. + """ + _guard_var_name = "DATEUTIL_MAY_CHANGE_TZ" + _guard_allows_change = True + + def __init__(self, tzval): + self.tzval = tzval + self._old_tz = None + + @classmethod + def tz_change_allowed(cls): + """ + Class method used to query whether or not this class allows time zone + changes. + """ guard = bool(os.environ.get(cls._guard_var_name, False)) - - # _guard_allows_change gives the "default" behavior - if True, the - # guard is overcoming a block. If false, the guard is causing a block. - # Whether tz_change is allowed is therefore the XNOR of the two. + + # _guard_allows_change gives the "default" behavior - if True, the + # guard is overcoming a block. If false, the guard is causing a block. + # Whether tz_change is allowed is therefore the XNOR of the two. return guard == cls._guard_allows_change - - @classmethod - def tz_change_disallowed_message(cls): - """ Generate instructions on how to allow tz changes """ - msg = ('Changing time zone not allowed. Set {envar} to {gval} ' - 'if you would like to allow this behavior') - - return msg.format(envar=cls._guard_var_name, - gval=cls._guard_allows_change) - - def __enter__(self): - if not self.tz_change_allowed(): + + @classmethod + def tz_change_disallowed_message(cls): + """ Generate instructions on how to allow tz changes """ + msg = ('Changing time zone not allowed. Set {envar} to {gval} ' + 'if you would like to allow this behavior') + + return msg.format(envar=cls._guard_var_name, + gval=cls._guard_allows_change) + + def __enter__(self): + if not self.tz_change_allowed(): msg = self.tz_change_disallowed_message() pytest.skip(msg) - + # If this is used outside of a test suite, we still want an error. raise ValueError(msg) # pragma: no cover - self._old_tz = self.get_current_tz() - self.set_current_tz(self.tzval) - - def __exit__(self, type, value, traceback): - if self._old_tz is not None: - self.set_current_tz(self._old_tz) - - self._old_tz = None - - def get_current_tz(self): - raise NotImplementedError - - def set_current_tz(self): - raise NotImplementedError - - -class TZEnvContext(TZContextBase): - """ - Context manager that temporarily sets the `TZ` variable (for use on - *nix-like systems). Because the effect is local to the shell anyway, this - will apply *unless* a guard is set. - - If you do not want the TZ environment variable set, you may set the - ``DATEUTIL_MAY_NOT_CHANGE_TZ_VAR`` variable to a truthy value. - """ - _guard_var_name = "DATEUTIL_MAY_NOT_CHANGE_TZ_VAR" - _guard_allows_change = False - - def get_current_tz(self): - return os.environ.get('TZ', UnsetTz) - - def set_current_tz(self, tzval): - if tzval is UnsetTz and 'TZ' in os.environ: - del os.environ['TZ'] - else: - os.environ['TZ'] = tzval - - time.tzset() - - -class TZWinContext(TZContextBase): - """ - Context manager for changing local time zone on Windows. - - Because the effect of this is system-wide and global, it may have - unintended side effect. Set the ``DATEUTIL_MAY_CHANGE_TZ`` environment - variable to a truthy value before using this context manager. - """ - def get_current_tz(self): - p = subprocess.Popen(['tzutil', '/g'], stdout=subprocess.PIPE) - - ctzname, err = p.communicate() - ctzname = ctzname.decode() # Popen returns - - if p.returncode: - raise OSError('Failed to get current time zone: ' + err) - - return ctzname - - def set_current_tz(self, tzname): - p = subprocess.Popen('tzutil /s "' + tzname + '"') - - out, err = p.communicate() - - if p.returncode: - raise OSError('Failed to set current time zone: ' + - (err or 'Unknown error.')) - - -### -# Utility classes -class NotAValueClass(object): - """ - A class analogous to NaN that has operations defined for any type. - """ - def _op(self, other): - return self # Operation with NotAValue returns NotAValue - - def _cmp(self, other): - return False - - __add__ = __radd__ = _op - __sub__ = __rsub__ = _op - __mul__ = __rmul__ = _op - __div__ = __rdiv__ = _op - __truediv__ = __rtruediv__ = _op - __floordiv__ = __rfloordiv__ = _op - - __lt__ = __rlt__ = _op - __gt__ = __rgt__ = _op - __eq__ = __req__ = _op - __le__ = __rle__ = _op - __ge__ = __rge__ = _op - - -NotAValue = NotAValueClass() - - -class ComparesEqualClass(object): - """ - A class that is always equal to whatever you compare it to. - """ - - def __eq__(self, other): - return True - - def __ne__(self, other): - return False - - def __le__(self, other): - return True - - def __ge__(self, other): - return True - - def __lt__(self, other): - return False - - def __gt__(self, other): - return False - - __req__ = __eq__ - __rne__ = __ne__ - __rle__ = __le__ - __rge__ = __ge__ - __rlt__ = __lt__ - __rgt__ = __gt__ - - -ComparesEqual = ComparesEqualClass() - - -class UnsetTzClass(object): - """ Sentinel class for unset time zone variable """ - pass - - -UnsetTz = UnsetTzClass() + self._old_tz = self.get_current_tz() + self.set_current_tz(self.tzval) + + def __exit__(self, type, value, traceback): + if self._old_tz is not None: + self.set_current_tz(self._old_tz) + + self._old_tz = None + + def get_current_tz(self): + raise NotImplementedError + + def set_current_tz(self): + raise NotImplementedError + + +class TZEnvContext(TZContextBase): + """ + Context manager that temporarily sets the `TZ` variable (for use on + *nix-like systems). Because the effect is local to the shell anyway, this + will apply *unless* a guard is set. + + If you do not want the TZ environment variable set, you may set the + ``DATEUTIL_MAY_NOT_CHANGE_TZ_VAR`` variable to a truthy value. + """ + _guard_var_name = "DATEUTIL_MAY_NOT_CHANGE_TZ_VAR" + _guard_allows_change = False + + def get_current_tz(self): + return os.environ.get('TZ', UnsetTz) + + def set_current_tz(self, tzval): + if tzval is UnsetTz and 'TZ' in os.environ: + del os.environ['TZ'] + else: + os.environ['TZ'] = tzval + + time.tzset() + + +class TZWinContext(TZContextBase): + """ + Context manager for changing local time zone on Windows. + + Because the effect of this is system-wide and global, it may have + unintended side effect. Set the ``DATEUTIL_MAY_CHANGE_TZ`` environment + variable to a truthy value before using this context manager. + """ + def get_current_tz(self): + p = subprocess.Popen(['tzutil', '/g'], stdout=subprocess.PIPE) + + ctzname, err = p.communicate() + ctzname = ctzname.decode() # Popen returns + + if p.returncode: + raise OSError('Failed to get current time zone: ' + err) + + return ctzname + + def set_current_tz(self, tzname): + p = subprocess.Popen('tzutil /s "' + tzname + '"') + + out, err = p.communicate() + + if p.returncode: + raise OSError('Failed to set current time zone: ' + + (err or 'Unknown error.')) + + +### +# Utility classes +class NotAValueClass(object): + """ + A class analogous to NaN that has operations defined for any type. + """ + def _op(self, other): + return self # Operation with NotAValue returns NotAValue + + def _cmp(self, other): + return False + + __add__ = __radd__ = _op + __sub__ = __rsub__ = _op + __mul__ = __rmul__ = _op + __div__ = __rdiv__ = _op + __truediv__ = __rtruediv__ = _op + __floordiv__ = __rfloordiv__ = _op + + __lt__ = __rlt__ = _op + __gt__ = __rgt__ = _op + __eq__ = __req__ = _op + __le__ = __rle__ = _op + __ge__ = __rge__ = _op + + +NotAValue = NotAValueClass() + + +class ComparesEqualClass(object): + """ + A class that is always equal to whatever you compare it to. + """ + + def __eq__(self, other): + return True + + def __ne__(self, other): + return False + + def __le__(self, other): + return True + + def __ge__(self, other): + return True + + def __lt__(self, other): + return False + + def __gt__(self, other): + return False + + __req__ = __eq__ + __rne__ = __ne__ + __rle__ = __le__ + __rge__ = __ge__ + __rlt__ = __lt__ + __rgt__ = __gt__ + + +ComparesEqual = ComparesEqualClass() + + +class UnsetTzClass(object): + """ Sentinel class for unset time zone variable """ + pass + + +UnsetTz = UnsetTzClass() diff --git a/contrib/python/dateutil/dateutil/test/property/test_isoparse_prop.py b/contrib/python/dateutil/dateutil/test/property/test_isoparse_prop.py index f8e288f3d6..9094886329 100644 --- a/contrib/python/dateutil/dateutil/test/property/test_isoparse_prop.py +++ b/contrib/python/dateutil/dateutil/test/property/test_isoparse_prop.py @@ -1,27 +1,27 @@ -from hypothesis import given, assume -from hypothesis import strategies as st - -from dateutil import tz -from dateutil.parser import isoparse - -import pytest - -# Strategies +from hypothesis import given, assume +from hypothesis import strategies as st + +from dateutil import tz +from dateutil.parser import isoparse + +import pytest + +# Strategies TIME_ZONE_STRATEGY = st.sampled_from([None, tz.UTC] + - [tz.gettz(zname) for zname in ('US/Eastern', 'US/Pacific', - 'Australia/Sydney', 'Europe/London')]) -ASCII_STRATEGY = st.characters(max_codepoint=127) - - -@pytest.mark.isoparser -@given(dt=st.datetimes(timezones=TIME_ZONE_STRATEGY), sep=ASCII_STRATEGY) -def test_timespec_auto(dt, sep): - if dt.tzinfo is not None: - # Assume offset has no sub-second components - assume(dt.utcoffset().total_seconds() % 60 == 0) - - sep = str(sep) # Python 2.7 requires bytes - dtstr = dt.isoformat(sep=sep) - dt_rt = isoparse(dtstr) - - assert dt_rt == dt + [tz.gettz(zname) for zname in ('US/Eastern', 'US/Pacific', + 'Australia/Sydney', 'Europe/London')]) +ASCII_STRATEGY = st.characters(max_codepoint=127) + + +@pytest.mark.isoparser +@given(dt=st.datetimes(timezones=TIME_ZONE_STRATEGY), sep=ASCII_STRATEGY) +def test_timespec_auto(dt, sep): + if dt.tzinfo is not None: + # Assume offset has no sub-second components + assume(dt.utcoffset().total_seconds() % 60 == 0) + + sep = str(sep) # Python 2.7 requires bytes + dtstr = dt.isoformat(sep=sep) + dt_rt = isoparse(dtstr) + + assert dt_rt == dt diff --git a/contrib/python/dateutil/dateutil/test/property/test_parser_prop.py b/contrib/python/dateutil/dateutil/test/property/test_parser_prop.py index fdfd171e86..f8f5769409 100644 --- a/contrib/python/dateutil/dateutil/test/property/test_parser_prop.py +++ b/contrib/python/dateutil/dateutil/test/property/test_parser_prop.py @@ -1,22 +1,22 @@ -from hypothesis.strategies import integers -from hypothesis import given - -import pytest - -from dateutil.parser import parserinfo - - -@pytest.mark.parserinfo -@given(integers(min_value=100, max_value=9999)) -def test_convertyear(n): - assert n == parserinfo().convertyear(n) - - -@pytest.mark.parserinfo -@given(integers(min_value=-50, - max_value=49)) -def test_convertyear_no_specified_century(n): - p = parserinfo() - new_year = p._year + n - result = p.convertyear(new_year % 100, century_specified=False) - assert result == new_year +from hypothesis.strategies import integers +from hypothesis import given + +import pytest + +from dateutil.parser import parserinfo + + +@pytest.mark.parserinfo +@given(integers(min_value=100, max_value=9999)) +def test_convertyear(n): + assert n == parserinfo().convertyear(n) + + +@pytest.mark.parserinfo +@given(integers(min_value=-50, + max_value=49)) +def test_convertyear_no_specified_century(n): + p = parserinfo() + new_year = p._year + n + result = p.convertyear(new_year % 100, century_specified=False) + assert result == new_year diff --git a/contrib/python/dateutil/dateutil/test/test_easter.py b/contrib/python/dateutil/dateutil/test/test_easter.py index cf2ec7f287..646ff98d0a 100644 --- a/contrib/python/dateutil/dateutil/test/test_easter.py +++ b/contrib/python/dateutil/dateutil/test/test_easter.py @@ -1,87 +1,87 @@ -from dateutil.easter import easter -from dateutil.easter import EASTER_WESTERN, EASTER_ORTHODOX, EASTER_JULIAN - -from datetime import date +from dateutil.easter import easter +from dateutil.easter import EASTER_WESTERN, EASTER_ORTHODOX, EASTER_JULIAN + +from datetime import date import pytest - -# List of easters between 1990 and 2050 -western_easter_dates = [ - date(1990, 4, 15), date(1991, 3, 31), date(1992, 4, 19), date(1993, 4, 11), - date(1994, 4, 3), date(1995, 4, 16), date(1996, 4, 7), date(1997, 3, 30), - date(1998, 4, 12), date(1999, 4, 4), - - date(2000, 4, 23), date(2001, 4, 15), date(2002, 3, 31), date(2003, 4, 20), - date(2004, 4, 11), date(2005, 3, 27), date(2006, 4, 16), date(2007, 4, 8), - date(2008, 3, 23), date(2009, 4, 12), - - date(2010, 4, 4), date(2011, 4, 24), date(2012, 4, 8), date(2013, 3, 31), - date(2014, 4, 20), date(2015, 4, 5), date(2016, 3, 27), date(2017, 4, 16), - date(2018, 4, 1), date(2019, 4, 21), - - date(2020, 4, 12), date(2021, 4, 4), date(2022, 4, 17), date(2023, 4, 9), - date(2024, 3, 31), date(2025, 4, 20), date(2026, 4, 5), date(2027, 3, 28), - date(2028, 4, 16), date(2029, 4, 1), - - date(2030, 4, 21), date(2031, 4, 13), date(2032, 3, 28), date(2033, 4, 17), - date(2034, 4, 9), date(2035, 3, 25), date(2036, 4, 13), date(2037, 4, 5), - date(2038, 4, 25), date(2039, 4, 10), - - date(2040, 4, 1), date(2041, 4, 21), date(2042, 4, 6), date(2043, 3, 29), - date(2044, 4, 17), date(2045, 4, 9), date(2046, 3, 25), date(2047, 4, 14), - date(2048, 4, 5), date(2049, 4, 18), date(2050, 4, 10) - ] - -orthodox_easter_dates = [ - date(1990, 4, 15), date(1991, 4, 7), date(1992, 4, 26), date(1993, 4, 18), - date(1994, 5, 1), date(1995, 4, 23), date(1996, 4, 14), date(1997, 4, 27), - date(1998, 4, 19), date(1999, 4, 11), - - date(2000, 4, 30), date(2001, 4, 15), date(2002, 5, 5), date(2003, 4, 27), - date(2004, 4, 11), date(2005, 5, 1), date(2006, 4, 23), date(2007, 4, 8), - date(2008, 4, 27), date(2009, 4, 19), - - date(2010, 4, 4), date(2011, 4, 24), date(2012, 4, 15), date(2013, 5, 5), - date(2014, 4, 20), date(2015, 4, 12), date(2016, 5, 1), date(2017, 4, 16), - date(2018, 4, 8), date(2019, 4, 28), - - date(2020, 4, 19), date(2021, 5, 2), date(2022, 4, 24), date(2023, 4, 16), - date(2024, 5, 5), date(2025, 4, 20), date(2026, 4, 12), date(2027, 5, 2), - date(2028, 4, 16), date(2029, 4, 8), - - date(2030, 4, 28), date(2031, 4, 13), date(2032, 5, 2), date(2033, 4, 24), - date(2034, 4, 9), date(2035, 4, 29), date(2036, 4, 20), date(2037, 4, 5), - date(2038, 4, 25), date(2039, 4, 17), - - date(2040, 5, 6), date(2041, 4, 21), date(2042, 4, 13), date(2043, 5, 3), - date(2044, 4, 24), date(2045, 4, 9), date(2046, 4, 29), date(2047, 4, 21), - date(2048, 4, 5), date(2049, 4, 25), date(2050, 4, 17) -] - -# A random smattering of Julian dates. -# Pulled values from http://www.kevinlaughery.com/east4099.html -julian_easter_dates = [ - date( 326, 4, 3), date( 375, 4, 5), date( 492, 4, 5), date( 552, 3, 31), - date( 562, 4, 9), date( 569, 4, 21), date( 597, 4, 14), date( 621, 4, 19), - date( 636, 3, 31), date( 655, 3, 29), date( 700, 4, 11), date( 725, 4, 8), - date( 750, 3, 29), date( 782, 4, 7), date( 835, 4, 18), date( 849, 4, 14), - date( 867, 3, 30), date( 890, 4, 12), date( 922, 4, 21), date( 934, 4, 6), - date(1049, 3, 26), date(1058, 4, 19), date(1113, 4, 6), date(1119, 3, 30), - date(1242, 4, 20), date(1255, 3, 28), date(1257, 4, 8), date(1258, 3, 24), - date(1261, 4, 24), date(1278, 4, 17), date(1333, 4, 4), date(1351, 4, 17), - date(1371, 4, 6), date(1391, 3, 26), date(1402, 3, 26), date(1412, 4, 3), - date(1439, 4, 5), date(1445, 3, 28), date(1531, 4, 9), date(1555, 4, 14) -] - - + +# List of easters between 1990 and 2050 +western_easter_dates = [ + date(1990, 4, 15), date(1991, 3, 31), date(1992, 4, 19), date(1993, 4, 11), + date(1994, 4, 3), date(1995, 4, 16), date(1996, 4, 7), date(1997, 3, 30), + date(1998, 4, 12), date(1999, 4, 4), + + date(2000, 4, 23), date(2001, 4, 15), date(2002, 3, 31), date(2003, 4, 20), + date(2004, 4, 11), date(2005, 3, 27), date(2006, 4, 16), date(2007, 4, 8), + date(2008, 3, 23), date(2009, 4, 12), + + date(2010, 4, 4), date(2011, 4, 24), date(2012, 4, 8), date(2013, 3, 31), + date(2014, 4, 20), date(2015, 4, 5), date(2016, 3, 27), date(2017, 4, 16), + date(2018, 4, 1), date(2019, 4, 21), + + date(2020, 4, 12), date(2021, 4, 4), date(2022, 4, 17), date(2023, 4, 9), + date(2024, 3, 31), date(2025, 4, 20), date(2026, 4, 5), date(2027, 3, 28), + date(2028, 4, 16), date(2029, 4, 1), + + date(2030, 4, 21), date(2031, 4, 13), date(2032, 3, 28), date(2033, 4, 17), + date(2034, 4, 9), date(2035, 3, 25), date(2036, 4, 13), date(2037, 4, 5), + date(2038, 4, 25), date(2039, 4, 10), + + date(2040, 4, 1), date(2041, 4, 21), date(2042, 4, 6), date(2043, 3, 29), + date(2044, 4, 17), date(2045, 4, 9), date(2046, 3, 25), date(2047, 4, 14), + date(2048, 4, 5), date(2049, 4, 18), date(2050, 4, 10) + ] + +orthodox_easter_dates = [ + date(1990, 4, 15), date(1991, 4, 7), date(1992, 4, 26), date(1993, 4, 18), + date(1994, 5, 1), date(1995, 4, 23), date(1996, 4, 14), date(1997, 4, 27), + date(1998, 4, 19), date(1999, 4, 11), + + date(2000, 4, 30), date(2001, 4, 15), date(2002, 5, 5), date(2003, 4, 27), + date(2004, 4, 11), date(2005, 5, 1), date(2006, 4, 23), date(2007, 4, 8), + date(2008, 4, 27), date(2009, 4, 19), + + date(2010, 4, 4), date(2011, 4, 24), date(2012, 4, 15), date(2013, 5, 5), + date(2014, 4, 20), date(2015, 4, 12), date(2016, 5, 1), date(2017, 4, 16), + date(2018, 4, 8), date(2019, 4, 28), + + date(2020, 4, 19), date(2021, 5, 2), date(2022, 4, 24), date(2023, 4, 16), + date(2024, 5, 5), date(2025, 4, 20), date(2026, 4, 12), date(2027, 5, 2), + date(2028, 4, 16), date(2029, 4, 8), + + date(2030, 4, 28), date(2031, 4, 13), date(2032, 5, 2), date(2033, 4, 24), + date(2034, 4, 9), date(2035, 4, 29), date(2036, 4, 20), date(2037, 4, 5), + date(2038, 4, 25), date(2039, 4, 17), + + date(2040, 5, 6), date(2041, 4, 21), date(2042, 4, 13), date(2043, 5, 3), + date(2044, 4, 24), date(2045, 4, 9), date(2046, 4, 29), date(2047, 4, 21), + date(2048, 4, 5), date(2049, 4, 25), date(2050, 4, 17) +] + +# A random smattering of Julian dates. +# Pulled values from http://www.kevinlaughery.com/east4099.html +julian_easter_dates = [ + date( 326, 4, 3), date( 375, 4, 5), date( 492, 4, 5), date( 552, 3, 31), + date( 562, 4, 9), date( 569, 4, 21), date( 597, 4, 14), date( 621, 4, 19), + date( 636, 3, 31), date( 655, 3, 29), date( 700, 4, 11), date( 725, 4, 8), + date( 750, 3, 29), date( 782, 4, 7), date( 835, 4, 18), date( 849, 4, 14), + date( 867, 3, 30), date( 890, 4, 12), date( 922, 4, 21), date( 934, 4, 6), + date(1049, 3, 26), date(1058, 4, 19), date(1113, 4, 6), date(1119, 3, 30), + date(1242, 4, 20), date(1255, 3, 28), date(1257, 4, 8), date(1258, 3, 24), + date(1261, 4, 24), date(1278, 4, 17), date(1333, 4, 4), date(1351, 4, 17), + date(1371, 4, 6), date(1391, 3, 26), date(1402, 3, 26), date(1412, 4, 3), + date(1439, 4, 5), date(1445, 3, 28), date(1531, 4, 9), date(1555, 4, 14) +] + + @pytest.mark.parametrize("easter_date", western_easter_dates) def test_easter_western(easter_date): assert easter_date == easter(easter_date.year, EASTER_WESTERN) - - + + @pytest.mark.parametrize("easter_date", orthodox_easter_dates) def test_easter_orthodox(easter_date): assert easter_date == easter(easter_date.year, EASTER_ORTHODOX) - + @pytest.mark.parametrize("easter_date", julian_easter_dates) def test_easter_julian(easter_date): diff --git a/contrib/python/dateutil/dateutil/test/test_import_star.py b/contrib/python/dateutil/dateutil/test/test_import_star.py index 2fb7098128..90ce0880c6 100644 --- a/contrib/python/dateutil/dateutil/test/test_import_star.py +++ b/contrib/python/dateutil/dateutil/test/test_import_star.py @@ -1,16 +1,16 @@ -"""Test for the "import *" functionality. - +"""Test for the "import *" functionality. + As import * can be only done at module level, it has been added in a separate file -""" +""" import pytest - -prev_locals = list(locals()) -from dateutil import * -new_locals = {name:value for name,value in locals().items() - if name not in prev_locals} -new_locals.pop('prev_locals') - - + +prev_locals = list(locals()) +from dateutil import * +new_locals = {name:value for name,value in locals().items() + if name not in prev_locals} +new_locals.pop('prev_locals') + + @pytest.mark.import_star def test_imported_modules(): """ Test that `from dateutil import *` adds modules in __all__ locally """ @@ -21,7 +21,7 @@ def test_imported_modules(): import dateutil.tz import dateutil.utils import dateutil.zoneinfo - + assert dateutil.easter == new_locals.pop("easter") assert dateutil.parser == new_locals.pop("parser") assert dateutil.relativedelta == new_locals.pop("relativedelta") @@ -29,5 +29,5 @@ def test_imported_modules(): assert dateutil.tz == new_locals.pop("tz") assert dateutil.utils == new_locals.pop("utils") assert dateutil.zoneinfo == new_locals.pop("zoneinfo") - + assert not new_locals diff --git a/contrib/python/dateutil/dateutil/test/test_imports.py b/contrib/python/dateutil/dateutil/test/test_imports.py index 60b86005ca..38aad8da46 100644 --- a/contrib/python/dateutil/dateutil/test/test_imports.py +++ b/contrib/python/dateutil/dateutil/test/test_imports.py @@ -1,83 +1,83 @@ -import sys +import sys import pytest - + HOST_IS_WINDOWS = sys.platform.startswith('win') def test_import_version_str(): - """ Test that dateutil.__version__ can be imported""" + """ Test that dateutil.__version__ can be imported""" from dateutil import __version__ - - + + def test_import_version_root(): import dateutil assert hasattr(dateutil, '__version__') - - + + # Test that dateutil.easter-related imports work properly def test_import_easter_direct(): import dateutil.easter - - + + def test_import_easter_from(): from dateutil import easter - - + + def test_import_easter_start(): from dateutil.easter import easter - - + + # Test that dateutil.parser-related imports work properly def test_import_parser_direct(): import dateutil.parser - - + + def test_import_parser_from(): from dateutil import parser - - + + def test_import_parser_all(): # All interface from dateutil.parser import parse from dateutil.parser import parserinfo - + # Other public classes from dateutil.parser import parser - + for var in (parse, parserinfo, parser): assert var is not None - - + + # Test that dateutil.relativedelta-related imports work properly def test_import_relative_delta_direct(): import dateutil.relativedelta - - + + def test_import_relative_delta_from(): from dateutil import relativedelta - + def test_import_relative_delta_all(): from dateutil.relativedelta import relativedelta from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU - + for var in (relativedelta, MO, TU, WE, TH, FR, SA, SU): assert var is not None - + # In the public interface but not in all from dateutil.relativedelta import weekday assert weekday is not None - - + + # Test that dateutil.rrule related imports work properly def test_import_rrule_direct(): import dateutil.rrule - - + + def test_import_rrule_from(): from dateutil import rrule - - + + def test_import_rrule_all(): from dateutil.rrule import rrule from dateutil.rrule import rruleset @@ -85,29 +85,29 @@ def test_import_rrule_all(): from dateutil.rrule import YEARLY, MONTHLY, WEEKLY, DAILY from dateutil.rrule import HOURLY, MINUTELY, SECONDLY from dateutil.rrule import MO, TU, WE, TH, FR, SA, SU - + rr_all = (rrule, rruleset, rrulestr, YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY, MO, TU, WE, TH, FR, SA, SU) - + for var in rr_all: assert var is not None - + # In the public interface but not in all from dateutil.rrule import weekday assert weekday is not None - - + + # Test that dateutil.tz related imports work properly def test_import_tztest_direct(): import dateutil.tz - - + + def test_import_tz_from(): from dateutil import tz - - + + def test_import_tz_all(): from dateutil.tz import tzutc from dateutil.tz import tzoffset @@ -123,27 +123,27 @@ def test_import_tz_all(): from dateutil.tz import datetime_ambiguous from dateutil.tz import datetime_exists from dateutil.tz import resolve_imaginary - + tz_all = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", "tzstr", "tzical", "gettz", "datetime_ambiguous", "datetime_exists", "resolve_imaginary", "UTC"] - + tz_all += ["tzwin", "tzwinlocal"] if sys.platform.startswith("win") else [] lvars = locals() - + for var in tz_all: assert lvars[var] is not None - + # Test that dateutil.tzwin related imports work properly @pytest.mark.skipif(not HOST_IS_WINDOWS, reason="Requires Windows") def test_import_tz_windows_direct(): import dateutil.tzwin - - + + @pytest.mark.skipif(not HOST_IS_WINDOWS, reason="Requires Windows") def test_import_tz_windows_from(): from dateutil import tzwin - + @pytest.mark.skipif(not HOST_IS_WINDOWS, reason="Requires Windows") def test_import_tz_windows_star(): diff --git a/contrib/python/dateutil/dateutil/test/test_internals.py b/contrib/python/dateutil/dateutil/test/test_internals.py index 530813147d..c96776f14d 100644 --- a/contrib/python/dateutil/dateutil/test/test_internals.py +++ b/contrib/python/dateutil/dateutil/test/test_internals.py @@ -1,91 +1,91 @@ -# -*- coding: utf-8 -*- -""" -Tests for implementation details, not necessarily part of the user-facing -API. - -The motivating case for these tests is #483, where we want to smoke-test -code that may be difficult to reach through the standard API calls. -""" - -import sys -import pytest - -from dateutil.parser._parser import _ymd -from dateutil import tz - -IS_PY32 = sys.version_info[0:2] == (3, 2) - - +# -*- coding: utf-8 -*- +""" +Tests for implementation details, not necessarily part of the user-facing +API. + +The motivating case for these tests is #483, where we want to smoke-test +code that may be difficult to reach through the standard API calls. +""" + +import sys +import pytest + +from dateutil.parser._parser import _ymd +from dateutil import tz + +IS_PY32 = sys.version_info[0:2] == (3, 2) + + @pytest.mark.smoke def test_YMD_could_be_day(): ymd = _ymd('foo bar 124 baz') - + ymd.append(2, 'M') assert ymd.has_month assert not ymd.has_year assert ymd.could_be_day(4) assert not ymd.could_be_day(-6) assert not ymd.could_be_day(32) - + # Assumes leap year assert ymd.could_be_day(29) - + ymd.append(1999) assert ymd.has_year assert not ymd.could_be_day(29) - + ymd.append(16, 'D') assert ymd.has_day assert not ymd.could_be_day(1) - + ymd = _ymd('foo bar 124 baz') ymd.append(1999) assert ymd.could_be_day(31) - - -### -# Test that private interfaces in _parser are deprecated properly -@pytest.mark.skipif(IS_PY32, reason='pytest.warns not supported on Python 3.2') -def test_parser_private_warns(): - from dateutil.parser import _timelex, _tzparser - from dateutil.parser import _parsetz - - with pytest.warns(DeprecationWarning): - _tzparser() - - with pytest.warns(DeprecationWarning): - _timelex('2014-03-03') - - with pytest.warns(DeprecationWarning): - _parsetz('+05:00') - - -@pytest.mark.skipif(IS_PY32, reason='pytest.warns not supported on Python 3.2') -def test_parser_parser_private_not_warns(): - from dateutil.parser._parser import _timelex, _tzparser - from dateutil.parser._parser import _parsetz - - with pytest.warns(None) as recorder: - _tzparser() - assert len(recorder) == 0 - - with pytest.warns(None) as recorder: - _timelex('2014-03-03') - - assert len(recorder) == 0 - - with pytest.warns(None) as recorder: - _parsetz('+05:00') - assert len(recorder) == 0 - - -@pytest.mark.tzstr -def test_tzstr_internal_timedeltas(): - with pytest.warns(tz.DeprecatedTzFormatWarning): - tz1 = tz.tzstr("EST5EDT,5,4,0,7200,11,-3,0,7200") - - with pytest.warns(tz.DeprecatedTzFormatWarning): - tz2 = tz.tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200") - - assert tz1._start_delta != tz2._start_delta - assert tz1._end_delta != tz2._end_delta + + +### +# Test that private interfaces in _parser are deprecated properly +@pytest.mark.skipif(IS_PY32, reason='pytest.warns not supported on Python 3.2') +def test_parser_private_warns(): + from dateutil.parser import _timelex, _tzparser + from dateutil.parser import _parsetz + + with pytest.warns(DeprecationWarning): + _tzparser() + + with pytest.warns(DeprecationWarning): + _timelex('2014-03-03') + + with pytest.warns(DeprecationWarning): + _parsetz('+05:00') + + +@pytest.mark.skipif(IS_PY32, reason='pytest.warns not supported on Python 3.2') +def test_parser_parser_private_not_warns(): + from dateutil.parser._parser import _timelex, _tzparser + from dateutil.parser._parser import _parsetz + + with pytest.warns(None) as recorder: + _tzparser() + assert len(recorder) == 0 + + with pytest.warns(None) as recorder: + _timelex('2014-03-03') + + assert len(recorder) == 0 + + with pytest.warns(None) as recorder: + _parsetz('+05:00') + assert len(recorder) == 0 + + +@pytest.mark.tzstr +def test_tzstr_internal_timedeltas(): + with pytest.warns(tz.DeprecatedTzFormatWarning): + tz1 = tz.tzstr("EST5EDT,5,4,0,7200,11,-3,0,7200") + + with pytest.warns(tz.DeprecatedTzFormatWarning): + tz2 = tz.tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200") + + assert tz1._start_delta != tz2._start_delta + assert tz1._end_delta != tz2._end_delta diff --git a/contrib/python/dateutil/dateutil/test/test_isoparser.py b/contrib/python/dateutil/dateutil/test/test_isoparser.py index 35899ab9b1..d58bdae5a3 100644 --- a/contrib/python/dateutil/dateutil/test/test_isoparser.py +++ b/contrib/python/dateutil/dateutil/test/test_isoparser.py @@ -1,135 +1,135 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from datetime import datetime, timedelta, date, time -import itertools as it - +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from datetime import datetime, timedelta, date, time +import itertools as it + from dateutil import tz from dateutil.tz import UTC -from dateutil.parser import isoparser, isoparse - -import pytest -import six - - -def _generate_tzoffsets(limited): - def _mkoffset(hmtuple, fmt): - h, m = hmtuple - m_td = (-1 if h < 0 else 1) * m - - tzo = tz.tzoffset(None, timedelta(hours=h, minutes=m_td)) - return tzo, fmt.format(h, m) - - out = [] - if not limited: - # The subset that's just hours - hm_out_h = [(h, 0) for h in (-23, -5, 0, 5, 23)] - out.extend([_mkoffset(hm, '{:+03d}') for hm in hm_out_h]) - - # Ones that have hours and minutes - hm_out = [] + hm_out_h - hm_out += [(-12, 15), (11, 30), (10, 2), (5, 15), (-5, 30)] - else: - hm_out = [(-5, -0)] - - fmts = ['{:+03d}:{:02d}', '{:+03d}{:02d}'] - out += [_mkoffset(hm, fmt) for hm in hm_out for fmt in fmts] - - # Also add in UTC and naive +from dateutil.parser import isoparser, isoparse + +import pytest +import six + + +def _generate_tzoffsets(limited): + def _mkoffset(hmtuple, fmt): + h, m = hmtuple + m_td = (-1 if h < 0 else 1) * m + + tzo = tz.tzoffset(None, timedelta(hours=h, minutes=m_td)) + return tzo, fmt.format(h, m) + + out = [] + if not limited: + # The subset that's just hours + hm_out_h = [(h, 0) for h in (-23, -5, 0, 5, 23)] + out.extend([_mkoffset(hm, '{:+03d}') for hm in hm_out_h]) + + # Ones that have hours and minutes + hm_out = [] + hm_out_h + hm_out += [(-12, 15), (11, 30), (10, 2), (5, 15), (-5, 30)] + else: + hm_out = [(-5, -0)] + + fmts = ['{:+03d}:{:02d}', '{:+03d}{:02d}'] + out += [_mkoffset(hm, fmt) for hm in hm_out for fmt in fmts] + + # Also add in UTC and naive out.append((UTC, 'Z')) - out.append((None, '')) - - return out - -FULL_TZOFFSETS = _generate_tzoffsets(False) -FULL_TZOFFSETS_AWARE = [x for x in FULL_TZOFFSETS if x[1]] -TZOFFSETS = _generate_tzoffsets(True) - -DATES = [datetime(1996, 1, 1), datetime(2017, 1, 1)] -@pytest.mark.parametrize('dt', tuple(DATES)) -def test_year_only(dt): - dtstr = dt.strftime('%Y') - - assert isoparse(dtstr) == dt - -DATES += [datetime(2000, 2, 1), datetime(2017, 4, 1)] -@pytest.mark.parametrize('dt', tuple(DATES)) -def test_year_month(dt): - fmt = '%Y-%m' - dtstr = dt.strftime(fmt) - - assert isoparse(dtstr) == dt - -DATES += [datetime(2016, 2, 29), datetime(2018, 3, 15)] -YMD_FMTS = ('%Y%m%d', '%Y-%m-%d') -@pytest.mark.parametrize('dt', tuple(DATES)) -@pytest.mark.parametrize('fmt', YMD_FMTS) -def test_year_month_day(dt, fmt): - dtstr = dt.strftime(fmt) - - assert isoparse(dtstr) == dt - -def _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset, - microsecond_precision=None): - tzi, offset_str = tzoffset - fmt = date_fmt + 'T' + time_fmt - dt = dt.replace(tzinfo=tzi) - dtstr = dt.strftime(fmt) - - if microsecond_precision is not None: + out.append((None, '')) + + return out + +FULL_TZOFFSETS = _generate_tzoffsets(False) +FULL_TZOFFSETS_AWARE = [x for x in FULL_TZOFFSETS if x[1]] +TZOFFSETS = _generate_tzoffsets(True) + +DATES = [datetime(1996, 1, 1), datetime(2017, 1, 1)] +@pytest.mark.parametrize('dt', tuple(DATES)) +def test_year_only(dt): + dtstr = dt.strftime('%Y') + + assert isoparse(dtstr) == dt + +DATES += [datetime(2000, 2, 1), datetime(2017, 4, 1)] +@pytest.mark.parametrize('dt', tuple(DATES)) +def test_year_month(dt): + fmt = '%Y-%m' + dtstr = dt.strftime(fmt) + + assert isoparse(dtstr) == dt + +DATES += [datetime(2016, 2, 29), datetime(2018, 3, 15)] +YMD_FMTS = ('%Y%m%d', '%Y-%m-%d') +@pytest.mark.parametrize('dt', tuple(DATES)) +@pytest.mark.parametrize('fmt', YMD_FMTS) +def test_year_month_day(dt, fmt): + dtstr = dt.strftime(fmt) + + assert isoparse(dtstr) == dt + +def _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset, + microsecond_precision=None): + tzi, offset_str = tzoffset + fmt = date_fmt + 'T' + time_fmt + dt = dt.replace(tzinfo=tzi) + dtstr = dt.strftime(fmt) + + if microsecond_precision is not None: if not fmt.endswith('%f'): # pragma: nocover - raise ValueError('Time format has no microseconds!') - + raise ValueError('Time format has no microseconds!') + if microsecond_precision != 6: - dtstr = dtstr[:-(6 - microsecond_precision)] + dtstr = dtstr[:-(6 - microsecond_precision)] elif microsecond_precision > 6: # pragma: nocover raise ValueError('Precision must be 1-6') - - dtstr += offset_str - - assert isoparse(dtstr) == dt - -DATETIMES = [datetime(1998, 4, 16, 12), - datetime(2019, 11, 18, 23), - datetime(2014, 12, 16, 4)] -@pytest.mark.parametrize('dt', tuple(DATETIMES)) -@pytest.mark.parametrize('date_fmt', YMD_FMTS) -@pytest.mark.parametrize('tzoffset', TZOFFSETS) -def test_ymd_h(dt, date_fmt, tzoffset): - _isoparse_date_and_time(dt, date_fmt, '%H', tzoffset) - -DATETIMES = [datetime(2012, 1, 6, 9, 37)] -@pytest.mark.parametrize('dt', tuple(DATETIMES)) -@pytest.mark.parametrize('date_fmt', YMD_FMTS) -@pytest.mark.parametrize('time_fmt', ('%H%M', '%H:%M')) -@pytest.mark.parametrize('tzoffset', TZOFFSETS) -def test_ymd_hm(dt, date_fmt, time_fmt, tzoffset): - _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset) - -DATETIMES = [datetime(2003, 9, 2, 22, 14, 2), - datetime(2003, 8, 8, 14, 9, 14), - datetime(2003, 4, 7, 6, 14, 59)] -HMS_FMTS = ('%H%M%S', '%H:%M:%S') -@pytest.mark.parametrize('dt', tuple(DATETIMES)) -@pytest.mark.parametrize('date_fmt', YMD_FMTS) -@pytest.mark.parametrize('time_fmt', HMS_FMTS) -@pytest.mark.parametrize('tzoffset', TZOFFSETS) -def test_ymd_hms(dt, date_fmt, time_fmt, tzoffset): - _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset) - -DATETIMES = [datetime(2017, 11, 27, 6, 14, 30, 123456)] -@pytest.mark.parametrize('dt', tuple(DATETIMES)) -@pytest.mark.parametrize('date_fmt', YMD_FMTS) + + dtstr += offset_str + + assert isoparse(dtstr) == dt + +DATETIMES = [datetime(1998, 4, 16, 12), + datetime(2019, 11, 18, 23), + datetime(2014, 12, 16, 4)] +@pytest.mark.parametrize('dt', tuple(DATETIMES)) +@pytest.mark.parametrize('date_fmt', YMD_FMTS) +@pytest.mark.parametrize('tzoffset', TZOFFSETS) +def test_ymd_h(dt, date_fmt, tzoffset): + _isoparse_date_and_time(dt, date_fmt, '%H', tzoffset) + +DATETIMES = [datetime(2012, 1, 6, 9, 37)] +@pytest.mark.parametrize('dt', tuple(DATETIMES)) +@pytest.mark.parametrize('date_fmt', YMD_FMTS) +@pytest.mark.parametrize('time_fmt', ('%H%M', '%H:%M')) +@pytest.mark.parametrize('tzoffset', TZOFFSETS) +def test_ymd_hm(dt, date_fmt, time_fmt, tzoffset): + _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset) + +DATETIMES = [datetime(2003, 9, 2, 22, 14, 2), + datetime(2003, 8, 8, 14, 9, 14), + datetime(2003, 4, 7, 6, 14, 59)] +HMS_FMTS = ('%H%M%S', '%H:%M:%S') +@pytest.mark.parametrize('dt', tuple(DATETIMES)) +@pytest.mark.parametrize('date_fmt', YMD_FMTS) +@pytest.mark.parametrize('time_fmt', HMS_FMTS) +@pytest.mark.parametrize('tzoffset', TZOFFSETS) +def test_ymd_hms(dt, date_fmt, time_fmt, tzoffset): + _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset) + +DATETIMES = [datetime(2017, 11, 27, 6, 14, 30, 123456)] +@pytest.mark.parametrize('dt', tuple(DATETIMES)) +@pytest.mark.parametrize('date_fmt', YMD_FMTS) @pytest.mark.parametrize('time_fmt', (x + sep + '%f' for x in HMS_FMTS for sep in '.,')) -@pytest.mark.parametrize('tzoffset', TZOFFSETS) -@pytest.mark.parametrize('precision', list(range(3, 7))) -def test_ymd_hms_micro(dt, date_fmt, time_fmt, tzoffset, precision): - # Truncate the microseconds to the desired precision for the representation - dt = dt.replace(microsecond=int(round(dt.microsecond, precision-6))) - - _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset, precision) - +@pytest.mark.parametrize('tzoffset', TZOFFSETS) +@pytest.mark.parametrize('precision', list(range(3, 7))) +def test_ymd_hms_micro(dt, date_fmt, time_fmt, tzoffset, precision): + # Truncate the microseconds to the desired precision for the representation + dt = dt.replace(microsecond=int(round(dt.microsecond, precision-6))) + + _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset, precision) + ### # Truncation of extra digits beyond microsecond precision @pytest.mark.parametrize('dt_str', [ @@ -139,262 +139,262 @@ def test_ymd_hms_micro(dt, date_fmt, time_fmt, tzoffset, precision): def test_extra_subsecond_digits(dt_str): assert isoparse(dt_str) == datetime(2018, 7, 3, 14, 7, 0, 123456) -@pytest.mark.parametrize('tzoffset', FULL_TZOFFSETS) -def test_full_tzoffsets(tzoffset): - dt = datetime(2017, 11, 27, 6, 14, 30, 123456) - date_fmt = '%Y-%m-%d' - time_fmt = '%H:%M:%S.%f' - - _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset) - -@pytest.mark.parametrize('dt_str', [ - '2014-04-11T00', +@pytest.mark.parametrize('tzoffset', FULL_TZOFFSETS) +def test_full_tzoffsets(tzoffset): + dt = datetime(2017, 11, 27, 6, 14, 30, 123456) + date_fmt = '%Y-%m-%d' + time_fmt = '%H:%M:%S.%f' + + _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset) + +@pytest.mark.parametrize('dt_str', [ + '2014-04-11T00', '2014-04-10T24', - '2014-04-11T00:00', + '2014-04-11T00:00', '2014-04-10T24:00', - '2014-04-11T00:00:00', + '2014-04-11T00:00:00', '2014-04-10T24:00:00', - '2014-04-11T00:00:00.000', + '2014-04-11T00:00:00.000', '2014-04-10T24:00:00.000', - '2014-04-11T00:00:00.000000', + '2014-04-11T00:00:00.000000', '2014-04-10T24:00:00.000000'] -) -def test_datetime_midnight(dt_str): - assert isoparse(dt_str) == datetime(2014, 4, 11, 0, 0, 0, 0) - -@pytest.mark.parametrize('datestr', [ - '2014-01-01', - '20140101', -]) -@pytest.mark.parametrize('sep', [' ', 'a', 'T', '_', '-']) -def test_isoparse_sep_none(datestr, sep): - isostr = datestr + sep + '14:33:09' - assert isoparse(isostr) == datetime(2014, 1, 1, 14, 33, 9) - -## -# Uncommon date formats -TIME_ARGS = ('time_args', - ((None, time(0), None), ) + tuple(('%H:%M:%S.%f', _t, _tz) - for _t, _tz in it.product([time(0), time(9, 30), time(14, 47)], - TZOFFSETS))) - -@pytest.mark.parametrize('isocal,dt_expected',[ - ((2017, 10), datetime(2017, 3, 6)), - ((2020, 1), datetime(2019, 12, 30)), # ISO year != Cal year - ((2004, 53), datetime(2004, 12, 27)), # Only half the week is in 2014 -]) -def test_isoweek(isocal, dt_expected): - # TODO: Figure out how to parametrize this on formats, too - for fmt in ('{:04d}-W{:02d}', '{:04d}W{:02d}'): - dtstr = fmt.format(*isocal) - assert isoparse(dtstr) == dt_expected - -@pytest.mark.parametrize('isocal,dt_expected',[ - ((2016, 13, 7), datetime(2016, 4, 3)), - ((2004, 53, 7), datetime(2005, 1, 2)), # ISO year != Cal year - ((2009, 1, 2), datetime(2008, 12, 30)), # ISO year < Cal year - ((2009, 53, 6), datetime(2010, 1, 2)) # ISO year > Cal year -]) -def test_isoweek_day(isocal, dt_expected): - # TODO: Figure out how to parametrize this on formats, too - for fmt in ('{:04d}-W{:02d}-{:d}', '{:04d}W{:02d}{:d}'): - dtstr = fmt.format(*isocal) - assert isoparse(dtstr) == dt_expected - -@pytest.mark.parametrize('isoord,dt_expected', [ - ((2004, 1), datetime(2004, 1, 1)), - ((2016, 60), datetime(2016, 2, 29)), - ((2017, 60), datetime(2017, 3, 1)), - ((2016, 366), datetime(2016, 12, 31)), - ((2017, 365), datetime(2017, 12, 31)) -]) -def test_iso_ordinal(isoord, dt_expected): - for fmt in ('{:04d}-{:03d}', '{:04d}{:03d}'): - dtstr = fmt.format(*isoord) - - assert isoparse(dtstr) == dt_expected - - -### -# Acceptance of bytes -@pytest.mark.parametrize('isostr,dt', [ - (b'2014', datetime(2014, 1, 1)), - (b'20140204', datetime(2014, 2, 4)), - (b'2014-02-04', datetime(2014, 2, 4)), - (b'2014-02-04T12', datetime(2014, 2, 4, 12)), - (b'2014-02-04T12:30', datetime(2014, 2, 4, 12, 30)), - (b'2014-02-04T12:30:15', datetime(2014, 2, 4, 12, 30, 15)), - (b'2014-02-04T12:30:15.224', datetime(2014, 2, 4, 12, 30, 15, 224000)), - (b'20140204T123015.224', datetime(2014, 2, 4, 12, 30, 15, 224000)), - (b'2014-02-04T12:30:15.224Z', datetime(2014, 2, 4, 12, 30, 15, 224000, +) +def test_datetime_midnight(dt_str): + assert isoparse(dt_str) == datetime(2014, 4, 11, 0, 0, 0, 0) + +@pytest.mark.parametrize('datestr', [ + '2014-01-01', + '20140101', +]) +@pytest.mark.parametrize('sep', [' ', 'a', 'T', '_', '-']) +def test_isoparse_sep_none(datestr, sep): + isostr = datestr + sep + '14:33:09' + assert isoparse(isostr) == datetime(2014, 1, 1, 14, 33, 9) + +## +# Uncommon date formats +TIME_ARGS = ('time_args', + ((None, time(0), None), ) + tuple(('%H:%M:%S.%f', _t, _tz) + for _t, _tz in it.product([time(0), time(9, 30), time(14, 47)], + TZOFFSETS))) + +@pytest.mark.parametrize('isocal,dt_expected',[ + ((2017, 10), datetime(2017, 3, 6)), + ((2020, 1), datetime(2019, 12, 30)), # ISO year != Cal year + ((2004, 53), datetime(2004, 12, 27)), # Only half the week is in 2014 +]) +def test_isoweek(isocal, dt_expected): + # TODO: Figure out how to parametrize this on formats, too + for fmt in ('{:04d}-W{:02d}', '{:04d}W{:02d}'): + dtstr = fmt.format(*isocal) + assert isoparse(dtstr) == dt_expected + +@pytest.mark.parametrize('isocal,dt_expected',[ + ((2016, 13, 7), datetime(2016, 4, 3)), + ((2004, 53, 7), datetime(2005, 1, 2)), # ISO year != Cal year + ((2009, 1, 2), datetime(2008, 12, 30)), # ISO year < Cal year + ((2009, 53, 6), datetime(2010, 1, 2)) # ISO year > Cal year +]) +def test_isoweek_day(isocal, dt_expected): + # TODO: Figure out how to parametrize this on formats, too + for fmt in ('{:04d}-W{:02d}-{:d}', '{:04d}W{:02d}{:d}'): + dtstr = fmt.format(*isocal) + assert isoparse(dtstr) == dt_expected + +@pytest.mark.parametrize('isoord,dt_expected', [ + ((2004, 1), datetime(2004, 1, 1)), + ((2016, 60), datetime(2016, 2, 29)), + ((2017, 60), datetime(2017, 3, 1)), + ((2016, 366), datetime(2016, 12, 31)), + ((2017, 365), datetime(2017, 12, 31)) +]) +def test_iso_ordinal(isoord, dt_expected): + for fmt in ('{:04d}-{:03d}', '{:04d}{:03d}'): + dtstr = fmt.format(*isoord) + + assert isoparse(dtstr) == dt_expected + + +### +# Acceptance of bytes +@pytest.mark.parametrize('isostr,dt', [ + (b'2014', datetime(2014, 1, 1)), + (b'20140204', datetime(2014, 2, 4)), + (b'2014-02-04', datetime(2014, 2, 4)), + (b'2014-02-04T12', datetime(2014, 2, 4, 12)), + (b'2014-02-04T12:30', datetime(2014, 2, 4, 12, 30)), + (b'2014-02-04T12:30:15', datetime(2014, 2, 4, 12, 30, 15)), + (b'2014-02-04T12:30:15.224', datetime(2014, 2, 4, 12, 30, 15, 224000)), + (b'20140204T123015.224', datetime(2014, 2, 4, 12, 30, 15, 224000)), + (b'2014-02-04T12:30:15.224Z', datetime(2014, 2, 4, 12, 30, 15, 224000, UTC)), (b'2014-02-04T12:30:15.224z', datetime(2014, 2, 4, 12, 30, 15, 224000, UTC)), - (b'2014-02-04T12:30:15.224+05:00', - datetime(2014, 2, 4, 12, 30, 15, 224000, - tzinfo=tz.tzoffset(None, timedelta(hours=5))))]) -def test_bytes(isostr, dt): - assert isoparse(isostr) == dt - - -### -# Invalid ISO strings -@pytest.mark.parametrize('isostr,exception', [ - ('201', ValueError), # ISO string too short - ('2012-0425', ValueError), # Inconsistent date separators - ('201204-25', ValueError), # Inconsistent date separators - ('20120425T0120:00', ValueError), # Inconsistent time separators + (b'2014-02-04T12:30:15.224+05:00', + datetime(2014, 2, 4, 12, 30, 15, 224000, + tzinfo=tz.tzoffset(None, timedelta(hours=5))))]) +def test_bytes(isostr, dt): + assert isoparse(isostr) == dt + + +### +# Invalid ISO strings +@pytest.mark.parametrize('isostr,exception', [ + ('201', ValueError), # ISO string too short + ('2012-0425', ValueError), # Inconsistent date separators + ('201204-25', ValueError), # Inconsistent date separators + ('20120425T0120:00', ValueError), # Inconsistent time separators ('20120425T01:2000', ValueError), # Inconsistent time separators ('14:3015', ValueError), # Inconsistent time separator - ('20120425T012500-334', ValueError), # Wrong microsecond separator - ('2001-1', ValueError), # YYYY-M not valid - ('2012-04-9', ValueError), # YYYY-MM-D not valid - ('201204', ValueError), # YYYYMM not valid - ('20120411T03:30+', ValueError), # Time zone too short - ('20120411T03:30+1234567', ValueError), # Time zone too long - ('20120411T03:30-25:40', ValueError), # Time zone invalid - ('2012-1a', ValueError), # Invalid month - ('20120411T03:30+00:60', ValueError), # Time zone invalid minutes - ('20120411T03:30+00:61', ValueError), # Time zone invalid minutes - ('20120411T033030.123456012:00', # No sign in time zone - ValueError), - ('2012-W00', ValueError), # Invalid ISO week - ('2012-W55', ValueError), # Invalid ISO week - ('2012-W01-0', ValueError), # Invalid ISO week day - ('2012-W01-8', ValueError), # Invalid ISO week day - ('2013-000', ValueError), # Invalid ordinal day - ('2013-366', ValueError), # Invalid ordinal day - ('2013366', ValueError), # Invalid ordinal day - ('2014-03-12Т12:30:14', ValueError), # Cyrillic T - ('2014-04-21T24:00:01', ValueError), # Invalid use of 24 for midnight - ('2014_W01-1', ValueError), # Invalid separator - ('2014W01-1', ValueError), # Inconsistent use of dashes - ('2014-W011', ValueError), # Inconsistent use of dashes - -]) -def test_iso_raises(isostr, exception): - with pytest.raises(exception): - isoparse(isostr) - - + ('20120425T012500-334', ValueError), # Wrong microsecond separator + ('2001-1', ValueError), # YYYY-M not valid + ('2012-04-9', ValueError), # YYYY-MM-D not valid + ('201204', ValueError), # YYYYMM not valid + ('20120411T03:30+', ValueError), # Time zone too short + ('20120411T03:30+1234567', ValueError), # Time zone too long + ('20120411T03:30-25:40', ValueError), # Time zone invalid + ('2012-1a', ValueError), # Invalid month + ('20120411T03:30+00:60', ValueError), # Time zone invalid minutes + ('20120411T03:30+00:61', ValueError), # Time zone invalid minutes + ('20120411T033030.123456012:00', # No sign in time zone + ValueError), + ('2012-W00', ValueError), # Invalid ISO week + ('2012-W55', ValueError), # Invalid ISO week + ('2012-W01-0', ValueError), # Invalid ISO week day + ('2012-W01-8', ValueError), # Invalid ISO week day + ('2013-000', ValueError), # Invalid ordinal day + ('2013-366', ValueError), # Invalid ordinal day + ('2013366', ValueError), # Invalid ordinal day + ('2014-03-12Т12:30:14', ValueError), # Cyrillic T + ('2014-04-21T24:00:01', ValueError), # Invalid use of 24 for midnight + ('2014_W01-1', ValueError), # Invalid separator + ('2014W01-1', ValueError), # Inconsistent use of dashes + ('2014-W011', ValueError), # Inconsistent use of dashes + +]) +def test_iso_raises(isostr, exception): + with pytest.raises(exception): + isoparse(isostr) + + @pytest.mark.parametrize('sep_act, valid_sep, exception', [ ('T', 'C', ValueError), ('C', 'T', ValueError), -]) +]) def test_iso_with_sep_raises(sep_act, valid_sep, exception): parser = isoparser(sep=valid_sep) - isostr = '2012-04-25' + sep_act + '01:25:00' + isostr = '2012-04-25' + sep_act + '01:25:00' with pytest.raises(exception): parser.isoparse(isostr) - - -### -# Test ISOParser constructor -@pytest.mark.parametrize('sep', [' ', '9', '🍛']) -def test_isoparser_invalid_sep(sep): - with pytest.raises(ValueError): - isoparser(sep=sep) - - -# This only fails on Python 3 + + +### +# Test ISOParser constructor +@pytest.mark.parametrize('sep', [' ', '9', '🍛']) +def test_isoparser_invalid_sep(sep): + with pytest.raises(ValueError): + isoparser(sep=sep) + + +# This only fails on Python 3 @pytest.mark.xfail(not six.PY2, reason="Fails on Python 3 only") -def test_isoparser_byte_sep(): - dt = datetime(2017, 12, 6, 12, 30, 45) - dt_str = dt.isoformat(sep=str('T')) - - dt_rt = isoparser(sep=b'T').isoparse(dt_str) - - assert dt == dt_rt - - -### -# Test parse_tzstr -@pytest.mark.parametrize('tzoffset', FULL_TZOFFSETS) -def test_parse_tzstr(tzoffset): - dt = datetime(2017, 11, 27, 6, 14, 30, 123456) - date_fmt = '%Y-%m-%d' - time_fmt = '%H:%M:%S.%f' - - _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset) - - -@pytest.mark.parametrize('tzstr', [ - '-00:00', '+00:00', '+00', '-00', '+0000', '-0000' -]) -@pytest.mark.parametrize('zero_as_utc', [True, False]) -def test_parse_tzstr_zero_as_utc(tzstr, zero_as_utc): - tzi = isoparser().parse_tzstr(tzstr, zero_as_utc=zero_as_utc) +def test_isoparser_byte_sep(): + dt = datetime(2017, 12, 6, 12, 30, 45) + dt_str = dt.isoformat(sep=str('T')) + + dt_rt = isoparser(sep=b'T').isoparse(dt_str) + + assert dt == dt_rt + + +### +# Test parse_tzstr +@pytest.mark.parametrize('tzoffset', FULL_TZOFFSETS) +def test_parse_tzstr(tzoffset): + dt = datetime(2017, 11, 27, 6, 14, 30, 123456) + date_fmt = '%Y-%m-%d' + time_fmt = '%H:%M:%S.%f' + + _isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset) + + +@pytest.mark.parametrize('tzstr', [ + '-00:00', '+00:00', '+00', '-00', '+0000', '-0000' +]) +@pytest.mark.parametrize('zero_as_utc', [True, False]) +def test_parse_tzstr_zero_as_utc(tzstr, zero_as_utc): + tzi = isoparser().parse_tzstr(tzstr, zero_as_utc=zero_as_utc) assert tzi == UTC - assert (type(tzi) == tz.tzutc) == zero_as_utc - - -@pytest.mark.parametrize('tzstr,exception', [ - ('00:00', ValueError), # No sign - ('05:00', ValueError), # No sign - ('_00:00', ValueError), # Invalid sign - ('+25:00', ValueError), # Offset too large - ('00:0000', ValueError), # String too long -]) -def test_parse_tzstr_fails(tzstr, exception): - with pytest.raises(exception): - isoparser().parse_tzstr(tzstr) - -### -# Test parse_isodate -def __make_date_examples(): - dates_no_day = [ - date(1999, 12, 1), - date(2016, 2, 1) - ] - + assert (type(tzi) == tz.tzutc) == zero_as_utc + + +@pytest.mark.parametrize('tzstr,exception', [ + ('00:00', ValueError), # No sign + ('05:00', ValueError), # No sign + ('_00:00', ValueError), # Invalid sign + ('+25:00', ValueError), # Offset too large + ('00:0000', ValueError), # String too long +]) +def test_parse_tzstr_fails(tzstr, exception): + with pytest.raises(exception): + isoparser().parse_tzstr(tzstr) + +### +# Test parse_isodate +def __make_date_examples(): + dates_no_day = [ + date(1999, 12, 1), + date(2016, 2, 1) + ] + if not six.PY2: - # strftime does not support dates before 1900 in Python 2 - dates_no_day.append(date(1000, 11, 1)) - - # Only one supported format for dates with no day - o = zip(dates_no_day, it.repeat('%Y-%m')) - - dates_w_day = [ - date(1969, 12, 31), - date(1900, 1, 1), - date(2016, 2, 29), - date(2017, 11, 14) - ] - - dates_w_day_fmts = ('%Y%m%d', '%Y-%m-%d') - o = it.chain(o, it.product(dates_w_day, dates_w_day_fmts)) - - return list(o) - - -@pytest.mark.parametrize('d,dt_fmt', __make_date_examples()) -@pytest.mark.parametrize('as_bytes', [True, False]) -def test_parse_isodate(d, dt_fmt, as_bytes): - d_str = d.strftime(dt_fmt) - if isinstance(d_str, six.text_type) and as_bytes: - d_str = d_str.encode('ascii') + # strftime does not support dates before 1900 in Python 2 + dates_no_day.append(date(1000, 11, 1)) + + # Only one supported format for dates with no day + o = zip(dates_no_day, it.repeat('%Y-%m')) + + dates_w_day = [ + date(1969, 12, 31), + date(1900, 1, 1), + date(2016, 2, 29), + date(2017, 11, 14) + ] + + dates_w_day_fmts = ('%Y%m%d', '%Y-%m-%d') + o = it.chain(o, it.product(dates_w_day, dates_w_day_fmts)) + + return list(o) + + +@pytest.mark.parametrize('d,dt_fmt', __make_date_examples()) +@pytest.mark.parametrize('as_bytes', [True, False]) +def test_parse_isodate(d, dt_fmt, as_bytes): + d_str = d.strftime(dt_fmt) + if isinstance(d_str, six.text_type) and as_bytes: + d_str = d_str.encode('ascii') elif isinstance(d_str, bytes) and not as_bytes: - d_str = d_str.decode('ascii') - - iparser = isoparser() - assert iparser.parse_isodate(d_str) == d - - -@pytest.mark.parametrize('isostr,exception', [ - ('243', ValueError), # ISO string too short - ('2014-0423', ValueError), # Inconsistent date separators - ('201404-23', ValueError), # Inconsistent date separators - ('2014日03月14', ValueError), # Not ASCII - ('2013-02-29', ValueError), # Not a leap year - ('2014/12/03', ValueError), # Wrong separators - ('2014-04-19T', ValueError), # Unknown components + d_str = d_str.decode('ascii') + + iparser = isoparser() + assert iparser.parse_isodate(d_str) == d + + +@pytest.mark.parametrize('isostr,exception', [ + ('243', ValueError), # ISO string too short + ('2014-0423', ValueError), # Inconsistent date separators + ('201404-23', ValueError), # Inconsistent date separators + ('2014日03月14', ValueError), # Not ASCII + ('2013-02-29', ValueError), # Not a leap year + ('2014/12/03', ValueError), # Wrong separators + ('2014-04-19T', ValueError), # Unknown components ('201202', ValueError), # Invalid format -]) -def test_isodate_raises(isostr, exception): - with pytest.raises(exception): - isoparser().parse_isodate(isostr) - - +]) +def test_isodate_raises(isostr, exception): + with pytest.raises(exception): + isoparser().parse_isodate(isostr) + + def test_parse_isodate_error_text(): with pytest.raises(ValueError) as excinfo: isoparser().parse_isodate('2014-0423') @@ -407,66 +407,66 @@ def test_parse_isodate_error_text(): assert expected_error == str(excinfo.value) -### -# Test parse_isotime -def __make_time_examples(): - outputs = [] - - # HH - time_h = [time(0), time(8), time(22)] - time_h_fmts = ['%H'] - - outputs.append(it.product(time_h, time_h_fmts)) - - # HHMM / HH:MM - time_hm = [time(0, 0), time(0, 30), time(8, 47), time(16, 1)] - time_hm_fmts = ['%H%M', '%H:%M'] - - outputs.append(it.product(time_hm, time_hm_fmts)) - - # HHMMSS / HH:MM:SS - time_hms = [time(0, 0, 0), time(0, 15, 30), - time(8, 2, 16), time(12, 0), time(16, 2), time(20, 45)] - - time_hms_fmts = ['%H%M%S', '%H:%M:%S'] - - outputs.append(it.product(time_hms, time_hms_fmts)) - - # HHMMSS.ffffff / HH:MM:SS.ffffff - time_hmsu = [time(0, 0, 0, 0), time(4, 15, 3, 247993), - time(14, 21, 59, 948730), - time(23, 59, 59, 999999)] - - time_hmsu_fmts = ['%H%M%S.%f', '%H:%M:%S.%f'] - - outputs.append(it.product(time_hmsu, time_hmsu_fmts)) - - outputs = list(map(list, outputs)) - - # Time zones - ex_naive = list(it.chain.from_iterable(x[0:2] for x in outputs)) - o = it.product(ex_naive, TZOFFSETS) # ((time, fmt), (tzinfo, offsetstr)) - o = ((t.replace(tzinfo=tzi), fmt + off_str) - for (t, fmt), (tzi, off_str) in o) - - outputs.append(o) - - return list(it.chain.from_iterable(outputs)) - - -@pytest.mark.parametrize('time_val,time_fmt', __make_time_examples()) -@pytest.mark.parametrize('as_bytes', [True, False]) -def test_isotime(time_val, time_fmt, as_bytes): - tstr = time_val.strftime(time_fmt) +### +# Test parse_isotime +def __make_time_examples(): + outputs = [] + + # HH + time_h = [time(0), time(8), time(22)] + time_h_fmts = ['%H'] + + outputs.append(it.product(time_h, time_h_fmts)) + + # HHMM / HH:MM + time_hm = [time(0, 0), time(0, 30), time(8, 47), time(16, 1)] + time_hm_fmts = ['%H%M', '%H:%M'] + + outputs.append(it.product(time_hm, time_hm_fmts)) + + # HHMMSS / HH:MM:SS + time_hms = [time(0, 0, 0), time(0, 15, 30), + time(8, 2, 16), time(12, 0), time(16, 2), time(20, 45)] + + time_hms_fmts = ['%H%M%S', '%H:%M:%S'] + + outputs.append(it.product(time_hms, time_hms_fmts)) + + # HHMMSS.ffffff / HH:MM:SS.ffffff + time_hmsu = [time(0, 0, 0, 0), time(4, 15, 3, 247993), + time(14, 21, 59, 948730), + time(23, 59, 59, 999999)] + + time_hmsu_fmts = ['%H%M%S.%f', '%H:%M:%S.%f'] + + outputs.append(it.product(time_hmsu, time_hmsu_fmts)) + + outputs = list(map(list, outputs)) + + # Time zones + ex_naive = list(it.chain.from_iterable(x[0:2] for x in outputs)) + o = it.product(ex_naive, TZOFFSETS) # ((time, fmt), (tzinfo, offsetstr)) + o = ((t.replace(tzinfo=tzi), fmt + off_str) + for (t, fmt), (tzi, off_str) in o) + + outputs.append(o) + + return list(it.chain.from_iterable(outputs)) + + +@pytest.mark.parametrize('time_val,time_fmt', __make_time_examples()) +@pytest.mark.parametrize('as_bytes', [True, False]) +def test_isotime(time_val, time_fmt, as_bytes): + tstr = time_val.strftime(time_fmt) if isinstance(tstr, six.text_type) and as_bytes: - tstr = tstr.encode('ascii') + tstr = tstr.encode('ascii') elif isinstance(tstr, bytes) and not as_bytes: - tstr = tstr.decode('ascii') - - iparser = isoparser() - - assert iparser.parse_isotime(tstr) == time_val - + tstr = tstr.decode('ascii') + + iparser = isoparser() + + assert iparser.parse_isotime(tstr) == time_val + @pytest.mark.parametrize('isostr', [ '24:00', @@ -483,27 +483,27 @@ def test_isotime_midnight(isostr): assert iparser.parse_isotime(isostr) == time(0, 0, 0, 0) -@pytest.mark.parametrize('isostr,exception', [ - ('3', ValueError), # ISO string too short - ('14時30分15秒', ValueError), # Not ASCII - ('14_30_15', ValueError), # Invalid separators - ('1430:15', ValueError), # Inconsistent separator use - ('25', ValueError), # Invalid hours - ('25:15', ValueError), # Invalid hours - ('14:60', ValueError), # Invalid minutes - ('14:59:61', ValueError), # Invalid seconds +@pytest.mark.parametrize('isostr,exception', [ + ('3', ValueError), # ISO string too short + ('14時30分15秒', ValueError), # Not ASCII + ('14_30_15', ValueError), # Invalid separators + ('1430:15', ValueError), # Inconsistent separator use + ('25', ValueError), # Invalid hours + ('25:15', ValueError), # Invalid hours + ('14:60', ValueError), # Invalid minutes + ('14:59:61', ValueError), # Invalid seconds ('14:30:15.34468305:00', ValueError), # No sign in time zone - ('14:30:15+', ValueError), # Time zone too short - ('14:30:15+1234567', ValueError), # Time zone invalid - ('14:59:59+25:00', ValueError), # Invalid tz hours - ('14:59:59+12:62', ValueError), # Invalid tz minutes - ('14:59:30_344583', ValueError), # Invalid microsecond separator + ('14:30:15+', ValueError), # Time zone too short + ('14:30:15+1234567', ValueError), # Time zone invalid + ('14:59:59+25:00', ValueError), # Invalid tz hours + ('14:59:59+12:62', ValueError), # Invalid tz minutes + ('14:59:30_344583', ValueError), # Invalid microsecond separator ('24:01', ValueError), # 24 used for non-midnight time ('24:00:01', ValueError), # 24 used for non-midnight time ('24:00:00.001', ValueError), # 24 used for non-midnight time ('24:00:00.000001', ValueError), # 24 used for non-midnight time -]) -def test_isotime_raises(isostr, exception): - iparser = isoparser() - with pytest.raises(exception): - iparser.parse_isotime(isostr) +]) +def test_isotime_raises(isostr, exception): + iparser = isoparser() + with pytest.raises(exception): + iparser.parse_isotime(isostr) diff --git a/contrib/python/dateutil/dateutil/test/test_parser.py b/contrib/python/dateutil/dateutil/test/test_parser.py index 08a34dafbc..bfdb7b1aeb 100644 --- a/contrib/python/dateutil/dateutil/test/test_parser.py +++ b/contrib/python/dateutil/dateutil/test/test_parser.py @@ -1,34 +1,34 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import itertools -from datetime import datetime, timedelta -import unittest -import sys - -from dateutil import tz -from dateutil.tz import tzoffset -from dateutil.parser import parse, parserinfo +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import itertools +from datetime import datetime, timedelta +import unittest +import sys + +from dateutil import tz +from dateutil.tz import tzoffset +from dateutil.parser import parse, parserinfo from dateutil.parser import ParserError -from dateutil.parser import UnknownTimezoneWarning - -from ._common import TZEnvContext - +from dateutil.parser import UnknownTimezoneWarning + +from ._common import TZEnvContext + from six import assertRaisesRegex, PY2 from io import StringIO - -import pytest - -# Platform info -IS_WIN = sys.platform.startswith('win') - + +import pytest + +# Platform info +IS_WIN = sys.platform.startswith('win') + PLATFORM_HAS_DASH_D = False -try: +try: if datetime.now().strftime('%-d'): PLATFORM_HAS_DASH_D = True -except ValueError: +except ValueError: pass - + @pytest.fixture(params=[True, False]) def fuzzy(request): @@ -103,7 +103,7 @@ PARSER_TEST_CASES = [ ] # Check that we don't have any duplicates assert len(set([x[0] for x in PARSER_TEST_CASES])) == len(PARSER_TEST_CASES) - + @pytest.mark.parametrize("parsable_text,expected_datetime,assertion_message", PARSER_TEST_CASES) def test_parser(parsable_text, expected_datetime, assertion_message): @@ -174,7 +174,7 @@ def test_parse_dayfirst(sep): dstr = expected.strftime(fmt) result = parse(dstr, dayfirst=True) assert result == expected - + @pytest.mark.parametrize('sep', ['-', '.', '/', ' ']) def test_parse_yearfirst(sep): @@ -225,31 +225,31 @@ def test_parse_with_tzoffset(dstr, expected): class TestFormat(object): - def test_ybd(self): - # If we have a 4-digit year, a non-numeric month (abbreviated or not), - # and a day (1 or 2 digits), then there is no ambiguity as to which - # token is a year/month/day. This holds regardless of what order the - # terms are in and for each of the separators below. - - seps = ['-', ' ', '/', '.'] - - year_tokens = ['%Y'] - month_tokens = ['%b', '%B'] - day_tokens = ['%d'] - if PLATFORM_HAS_DASH_D: - day_tokens.append('%-d') - - prods = itertools.product(year_tokens, month_tokens, day_tokens) - perms = [y for x in prods for y in itertools.permutations(x)] - unambig_fmts = [sep.join(perm) for sep in seps for perm in perms] - - actual = datetime(2003, 9, 25) - - for fmt in unambig_fmts: - dstr = actual.strftime(fmt) - res = parse(dstr) + def test_ybd(self): + # If we have a 4-digit year, a non-numeric month (abbreviated or not), + # and a day (1 or 2 digits), then there is no ambiguity as to which + # token is a year/month/day. This holds regardless of what order the + # terms are in and for each of the separators below. + + seps = ['-', ' ', '/', '.'] + + year_tokens = ['%Y'] + month_tokens = ['%b', '%B'] + day_tokens = ['%d'] + if PLATFORM_HAS_DASH_D: + day_tokens.append('%-d') + + prods = itertools.product(year_tokens, month_tokens, day_tokens) + perms = [y for x in prods for y in itertools.permutations(x)] + unambig_fmts = [sep.join(perm) for sep in seps for perm in perms] + + actual = datetime(2003, 9, 25) + + for fmt in unambig_fmts: + dstr = actual.strftime(fmt) + res = parse(dstr) assert res == actual - + # TODO: some redundancy with PARSER_TEST_CASES cases @pytest.mark.parametrize("fmt,dstr", [ ("%a %b %d %Y", "Thu Sep 25 2003"), @@ -282,7 +282,7 @@ class TestFormat(object): ]) def test_strftime_formats_2003Sep25(self, fmt, dstr): expected = datetime(2003, 9, 25) - + # First check that the format strings behave as expected # (not strictly necessary, but nice to have) assert expected.strftime(fmt) == dstr @@ -294,54 +294,54 @@ class TestFormat(object): class TestInputTypes(object): def test_empty_string_invalid(self): with pytest.raises(ParserError): - parse('') - + parse('') + def test_none_invalid(self): with pytest.raises(TypeError): - parse(None) - + parse(None) + def test_int_invalid(self): with pytest.raises(TypeError): - parse(13) - + parse(13) + def test_duck_typing(self): - # We want to support arbitrary classes that implement the stream - # interface. - - class StringPassThrough(object): - def __init__(self, stream): - self.stream = stream - - def read(self, *args, **kwargs): - return self.stream.read(*args, **kwargs) - - dstr = StringPassThrough(StringIO('2014 January 19')) - + # We want to support arbitrary classes that implement the stream + # interface. + + class StringPassThrough(object): + def __init__(self, stream): + self.stream = stream + + def read(self, *args, **kwargs): + return self.stream.read(*args, **kwargs) + + dstr = StringPassThrough(StringIO('2014 January 19')) + res = parse(dstr) expected = datetime(2014, 1, 19) assert res == expected - + def test_parse_stream(self): - dstr = StringIO('2014 January 19') - + dstr = StringIO('2014 January 19') + res = parse(dstr) expected = datetime(2014, 1, 19) assert res == expected - + def test_parse_str(self): # Parser should be able to handle bytestring and unicode uni_str = '2014-05-01 08:00:00' bytes_str = uni_str.encode() - + res = parse(bytes_str) expected = parse(uni_str) assert res == expected - + def test_parse_bytes(self): res = parse(b'2014 January 19') expected = datetime(2014, 1, 19) assert res == expected - + def test_parse_bytearray(self): # GH#417 res = parse(bytearray(b'2014 January 19')) @@ -417,315 +417,315 @@ class ParserTest(unittest.TestCase): cls.uni_str = '2014-05-01 08:00:00' cls.str_str = cls.uni_str.encode() - def testParserParseStr(self): - from dateutil.parser import parser - + def testParserParseStr(self): + from dateutil.parser import parser + assert parser().parse(self.str_str) == parser().parse(self.uni_str) - - def testParseUnicodeWords(self): - - class rus_parserinfo(parserinfo): - MONTHS = [("янв", "Январь"), - ("фев", "Февраль"), - ("мар", "Март"), - ("апр", "Апрель"), - ("май", "Май"), - ("июн", "Июнь"), - ("июл", "Июль"), - ("авг", "Август"), - ("сен", "Сентябрь"), - ("окт", "Октябрь"), - ("ноя", "Ноябрь"), - ("дек", "Декабрь")] - + + def testParseUnicodeWords(self): + + class rus_parserinfo(parserinfo): + MONTHS = [("янв", "Январь"), + ("фев", "Февраль"), + ("мар", "Март"), + ("апр", "Апрель"), + ("май", "Май"), + ("июн", "Июнь"), + ("июл", "Июль"), + ("авг", "Август"), + ("сен", "Сентябрь"), + ("окт", "Октябрь"), + ("ноя", "Ноябрь"), + ("дек", "Декабрь")] + expected = datetime(2015, 9, 10, 10, 20) res = parse('10 Сентябрь 2015 10:20', parserinfo=rus_parserinfo()) assert res == expected - - def testParseWithNulls(self): - # This relies on the from __future__ import unicode_literals, because - # explicitly specifying a unicode literal is a syntax error in Py 3.2 - # May want to switch to u'...' if we ever drop Python 3.2 support. - pstring = '\x00\x00August 29, 1924' - + + def testParseWithNulls(self): + # This relies on the from __future__ import unicode_literals, because + # explicitly specifying a unicode literal is a syntax error in Py 3.2 + # May want to switch to u'...' if we ever drop Python 3.2 support. + pstring = '\x00\x00August 29, 1924' + assert parse(pstring) == datetime(1924, 8, 29) - - def testDateCommandFormat(self): - self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", - tzinfos=self.tzinfos), - datetime(2003, 9, 25, 10, 36, 28, - tzinfo=self.brsttz)) - - def testDateCommandFormatReversed(self): - self.assertEqual(parse("2003 10:36:28 BRST 25 Sep Thu", - tzinfos=self.tzinfos), - datetime(2003, 9, 25, 10, 36, 28, - tzinfo=self.brsttz)) - - def testDateCommandFormatWithLong(self): + + def testDateCommandFormat(self): + self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", + tzinfos=self.tzinfos), + datetime(2003, 9, 25, 10, 36, 28, + tzinfo=self.brsttz)) + + def testDateCommandFormatReversed(self): + self.assertEqual(parse("2003 10:36:28 BRST 25 Sep Thu", + tzinfos=self.tzinfos), + datetime(2003, 9, 25, 10, 36, 28, + tzinfo=self.brsttz)) + + def testDateCommandFormatWithLong(self): if PY2: - self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", - tzinfos={"BRST": long(-10800)}), - datetime(2003, 9, 25, 10, 36, 28, - tzinfo=self.brsttz)) + self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", + tzinfos={"BRST": long(-10800)}), + datetime(2003, 9, 25, 10, 36, 28, + tzinfo=self.brsttz)) - def testISOFormatStrip2(self): + def testISOFormatStrip2(self): self.assertEqual(parse("2003-09-25T10:49:41+03:00"), datetime(2003, 9, 25, 10, 49, 41, tzinfo=tzoffset(None, 10800))) - - def testISOStrippedFormatStrip2(self): + + def testISOStrippedFormatStrip2(self): self.assertEqual(parse("20030925T104941+0300"), datetime(2003, 9, 25, 10, 49, 41, tzinfo=tzoffset(None, 10800))) - - def testAMPMNoHour(self): + + def testAMPMNoHour(self): with pytest.raises(ParserError): - parse("AM") - + parse("AM") + with pytest.raises(ParserError): - parse("Jan 20, 2015 PM") - - def testAMPMRange(self): + parse("Jan 20, 2015 PM") + + def testAMPMRange(self): with pytest.raises(ParserError): - parse("13:44 AM") - + parse("13:44 AM") + with pytest.raises(ParserError): - parse("January 25, 1921 23:13 PM") - - def testPertain(self): - self.assertEqual(parse("Sep 03", default=self.default), - datetime(2003, 9, 3)) - self.assertEqual(parse("Sep of 03", default=self.default), - datetime(2003, 9, 25)) - - def testFuzzy(self): - s = "Today is 25 of September of 2003, exactly " \ - "at 10:49:41 with timezone -03:00." - self.assertEqual(parse(s, fuzzy=True), - datetime(2003, 9, 25, 10, 49, 41, - tzinfo=self.brsttz)) - - def testFuzzyWithTokens(self): - s1 = "Today is 25 of September of 2003, exactly " \ - "at 10:49:41 with timezone -03:00." - self.assertEqual(parse(s1, fuzzy_with_tokens=True), - (datetime(2003, 9, 25, 10, 49, 41, - tzinfo=self.brsttz), - ('Today is ', 'of ', ', exactly at ', - ' with timezone ', '.'))) - - s2 = "http://biz.yahoo.com/ipo/p/600221.html" - self.assertEqual(parse(s2, fuzzy_with_tokens=True), - (datetime(2060, 2, 21, 0, 0, 0), - ('http://biz.yahoo.com/ipo/p/', '.html'))) - - def testFuzzyAMPMProblem(self): - # Sometimes fuzzy parsing results in AM/PM flag being set without - # hours - if it's fuzzy it should ignore that. - s1 = "I have a meeting on March 1, 1974." - s2 = "On June 8th, 2020, I am going to be the first man on Mars" - - # Also don't want any erroneous AM or PMs changing the parsed time - s3 = "Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003" - s4 = "Meet me at 3:00AM on December 3rd, 2003 at the AM/PM on Sunset" - - self.assertEqual(parse(s1, fuzzy=True), datetime(1974, 3, 1)) - self.assertEqual(parse(s2, fuzzy=True), datetime(2020, 6, 8)) - self.assertEqual(parse(s3, fuzzy=True), datetime(2003, 12, 3, 3)) - self.assertEqual(parse(s4, fuzzy=True), datetime(2003, 12, 3, 3)) - - def testFuzzyIgnoreAMPM(self): - s1 = "Jan 29, 1945 14:45 AM I going to see you there?" - with pytest.warns(UnknownTimezoneWarning): - res = parse(s1, fuzzy=True) - self.assertEqual(res, datetime(1945, 1, 29, 14, 45)) - - def testRandomFormat24(self): - self.assertEqual(parse("0:00 PM, PST", default=self.default, - ignoretz=True), - datetime(2003, 9, 25, 12, 0)) - - def testRandomFormat26(self): - with pytest.warns(UnknownTimezoneWarning): - res = parse("5:50 A.M. on June 13, 1990") - - self.assertEqual(res, datetime(1990, 6, 13, 5, 50)) - - def testUnspecifiedDayFallback(self): - # Test that for an unspecified day, the fallback behavior is correct. - self.assertEqual(parse("April 2009", default=datetime(2010, 1, 31)), - datetime(2009, 4, 30)) - - def testUnspecifiedDayFallbackFebNoLeapYear(self): - self.assertEqual(parse("Feb 2007", default=datetime(2010, 1, 31)), - datetime(2007, 2, 28)) - - def testUnspecifiedDayFallbackFebLeapYear(self): - self.assertEqual(parse("Feb 2008", default=datetime(2010, 1, 31)), - datetime(2008, 2, 29)) - - def testErrorType01(self): + parse("January 25, 1921 23:13 PM") + + def testPertain(self): + self.assertEqual(parse("Sep 03", default=self.default), + datetime(2003, 9, 3)) + self.assertEqual(parse("Sep of 03", default=self.default), + datetime(2003, 9, 25)) + + def testFuzzy(self): + s = "Today is 25 of September of 2003, exactly " \ + "at 10:49:41 with timezone -03:00." + self.assertEqual(parse(s, fuzzy=True), + datetime(2003, 9, 25, 10, 49, 41, + tzinfo=self.brsttz)) + + def testFuzzyWithTokens(self): + s1 = "Today is 25 of September of 2003, exactly " \ + "at 10:49:41 with timezone -03:00." + self.assertEqual(parse(s1, fuzzy_with_tokens=True), + (datetime(2003, 9, 25, 10, 49, 41, + tzinfo=self.brsttz), + ('Today is ', 'of ', ', exactly at ', + ' with timezone ', '.'))) + + s2 = "http://biz.yahoo.com/ipo/p/600221.html" + self.assertEqual(parse(s2, fuzzy_with_tokens=True), + (datetime(2060, 2, 21, 0, 0, 0), + ('http://biz.yahoo.com/ipo/p/', '.html'))) + + def testFuzzyAMPMProblem(self): + # Sometimes fuzzy parsing results in AM/PM flag being set without + # hours - if it's fuzzy it should ignore that. + s1 = "I have a meeting on March 1, 1974." + s2 = "On June 8th, 2020, I am going to be the first man on Mars" + + # Also don't want any erroneous AM or PMs changing the parsed time + s3 = "Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003" + s4 = "Meet me at 3:00AM on December 3rd, 2003 at the AM/PM on Sunset" + + self.assertEqual(parse(s1, fuzzy=True), datetime(1974, 3, 1)) + self.assertEqual(parse(s2, fuzzy=True), datetime(2020, 6, 8)) + self.assertEqual(parse(s3, fuzzy=True), datetime(2003, 12, 3, 3)) + self.assertEqual(parse(s4, fuzzy=True), datetime(2003, 12, 3, 3)) + + def testFuzzyIgnoreAMPM(self): + s1 = "Jan 29, 1945 14:45 AM I going to see you there?" + with pytest.warns(UnknownTimezoneWarning): + res = parse(s1, fuzzy=True) + self.assertEqual(res, datetime(1945, 1, 29, 14, 45)) + + def testRandomFormat24(self): + self.assertEqual(parse("0:00 PM, PST", default=self.default, + ignoretz=True), + datetime(2003, 9, 25, 12, 0)) + + def testRandomFormat26(self): + with pytest.warns(UnknownTimezoneWarning): + res = parse("5:50 A.M. on June 13, 1990") + + self.assertEqual(res, datetime(1990, 6, 13, 5, 50)) + + def testUnspecifiedDayFallback(self): + # Test that for an unspecified day, the fallback behavior is correct. + self.assertEqual(parse("April 2009", default=datetime(2010, 1, 31)), + datetime(2009, 4, 30)) + + def testUnspecifiedDayFallbackFebNoLeapYear(self): + self.assertEqual(parse("Feb 2007", default=datetime(2010, 1, 31)), + datetime(2007, 2, 28)) + + def testUnspecifiedDayFallbackFebLeapYear(self): + self.assertEqual(parse("Feb 2008", default=datetime(2010, 1, 31)), + datetime(2008, 2, 29)) + + def testErrorType01(self): with pytest.raises(ParserError): parse('shouldfail') - - def testCorrectErrorOnFuzzyWithTokens(self): + + def testCorrectErrorOnFuzzyWithTokens(self): assertRaisesRegex(self, ParserError, 'Unknown string format', - parse, '04/04/32/423', fuzzy_with_tokens=True) + parse, '04/04/32/423', fuzzy_with_tokens=True) assertRaisesRegex(self, ParserError, 'Unknown string format', - parse, '04/04/04 +32423', fuzzy_with_tokens=True) + parse, '04/04/04 +32423', fuzzy_with_tokens=True) assertRaisesRegex(self, ParserError, 'Unknown string format', - parse, '04/04/0d4', fuzzy_with_tokens=True) - - def testIncreasingCTime(self): - # This test will check 200 different years, every month, every day, - # every hour, every minute, every second, and every weekday, using - # a delta of more or less 1 year, 1 month, 1 day, 1 minute and - # 1 second. - delta = timedelta(days=365+31+1, seconds=1+60+60*60) - dt = datetime(1900, 1, 1, 0, 0, 0, 0) - for i in range(200): + parse, '04/04/0d4', fuzzy_with_tokens=True) + + def testIncreasingCTime(self): + # This test will check 200 different years, every month, every day, + # every hour, every minute, every second, and every weekday, using + # a delta of more or less 1 year, 1 month, 1 day, 1 minute and + # 1 second. + delta = timedelta(days=365+31+1, seconds=1+60+60*60) + dt = datetime(1900, 1, 1, 0, 0, 0, 0) + for i in range(200): assert parse(dt.ctime()) == dt - dt += delta - - def testIncreasingISOFormat(self): - delta = timedelta(days=365+31+1, seconds=1+60+60*60) - dt = datetime(1900, 1, 1, 0, 0, 0, 0) - for i in range(200): + dt += delta + + def testIncreasingISOFormat(self): + delta = timedelta(days=365+31+1, seconds=1+60+60*60) + dt = datetime(1900, 1, 1, 0, 0, 0, 0) + for i in range(200): assert parse(dt.isoformat()) == dt - dt += delta - - def testMicrosecondsPrecisionError(self): - # Skip found out that sad precision problem. :-( - dt1 = parse("00:11:25.01") - dt2 = parse("00:12:10.01") + dt += delta + + def testMicrosecondsPrecisionError(self): + # Skip found out that sad precision problem. :-( + dt1 = parse("00:11:25.01") + dt2 = parse("00:12:10.01") assert dt1.microsecond == 10000 assert dt2.microsecond == 10000 - - def testMicrosecondPrecisionErrorReturns(self): - # One more precision issue, discovered by Eric Brown. This should - # be the last one, as we're no longer using floating points. - for ms in [100001, 100000, 99999, 99998, - 10001, 10000, 9999, 9998, - 1001, 1000, 999, 998, - 101, 100, 99, 98]: - dt = datetime(2008, 2, 27, 21, 26, 1, ms) + + def testMicrosecondPrecisionErrorReturns(self): + # One more precision issue, discovered by Eric Brown. This should + # be the last one, as we're no longer using floating points. + for ms in [100001, 100000, 99999, 99998, + 10001, 10000, 9999, 9998, + 1001, 1000, 999, 998, + 101, 100, 99, 98]: + dt = datetime(2008, 2, 27, 21, 26, 1, ms) assert parse(dt.isoformat()) == dt - - def testCustomParserInfo(self): - # Custom parser info wasn't working, as Michael Elsdörfer discovered. - from dateutil.parser import parserinfo, parser - - class myparserinfo(parserinfo): - MONTHS = parserinfo.MONTHS[:] - MONTHS[0] = ("Foo", "Foo") - myparser = parser(myparserinfo()) - dt = myparser.parse("01/Foo/2007") + + def testCustomParserInfo(self): + # Custom parser info wasn't working, as Michael Elsdörfer discovered. + from dateutil.parser import parserinfo, parser + + class myparserinfo(parserinfo): + MONTHS = parserinfo.MONTHS[:] + MONTHS[0] = ("Foo", "Foo") + myparser = parser(myparserinfo()) + dt = myparser.parse("01/Foo/2007") assert dt == datetime(2007, 1, 1) - - def testCustomParserShortDaynames(self): - # Horacio Hoyos discovered that day names shorter than 3 characters, - # for example two letter German day name abbreviations, don't work: - # https://github.com/dateutil/dateutil/issues/343 - from dateutil.parser import parserinfo, parser - - class GermanParserInfo(parserinfo): - WEEKDAYS = [("Mo", "Montag"), - ("Di", "Dienstag"), - ("Mi", "Mittwoch"), - ("Do", "Donnerstag"), - ("Fr", "Freitag"), - ("Sa", "Samstag"), - ("So", "Sonntag")] - - myparser = parser(GermanParserInfo()) - dt = myparser.parse("Sa 21. Jan 2017") - self.assertEqual(dt, datetime(2017, 1, 21)) - - def testNoYearFirstNoDayFirst(self): - dtstr = '090107' - - # Should be MMDDYY - self.assertEqual(parse(dtstr), - datetime(2007, 9, 1)) - - self.assertEqual(parse(dtstr, yearfirst=False, dayfirst=False), - datetime(2007, 9, 1)) - - def testYearFirst(self): - dtstr = '090107' - - # Should be MMDDYY - self.assertEqual(parse(dtstr, yearfirst=True), - datetime(2009, 1, 7)) - - self.assertEqual(parse(dtstr, yearfirst=True, dayfirst=False), - datetime(2009, 1, 7)) - - def testDayFirst(self): - dtstr = '090107' - - # Should be DDMMYY - self.assertEqual(parse(dtstr, dayfirst=True), - datetime(2007, 1, 9)) - - self.assertEqual(parse(dtstr, yearfirst=False, dayfirst=True), - datetime(2007, 1, 9)) - - def testDayFirstYearFirst(self): - dtstr = '090107' - # Should be YYDDMM - self.assertEqual(parse(dtstr, yearfirst=True, dayfirst=True), - datetime(2009, 7, 1)) - - def testUnambiguousYearFirst(self): - dtstr = '2015 09 25' - self.assertEqual(parse(dtstr, yearfirst=True), - datetime(2015, 9, 25)) - - def testUnambiguousDayFirst(self): - dtstr = '2015 09 25' - self.assertEqual(parse(dtstr, dayfirst=True), - datetime(2015, 9, 25)) - - def testUnambiguousDayFirstYearFirst(self): - dtstr = '2015 09 25' - self.assertEqual(parse(dtstr, dayfirst=True, yearfirst=True), - datetime(2015, 9, 25)) - - def test_mstridx(self): - # See GH408 - dtstr = '2015-15-May' - self.assertEqual(parse(dtstr), - datetime(2015, 5, 15)) - - def test_idx_check(self): - dtstr = '2017-07-17 06:15:' - # Pre-PR, the trailing colon will cause an IndexError at 824-825 - # when checking `i < len_l` and then accessing `l[i+1]` - res = parse(dtstr, fuzzy=True) + + def testCustomParserShortDaynames(self): + # Horacio Hoyos discovered that day names shorter than 3 characters, + # for example two letter German day name abbreviations, don't work: + # https://github.com/dateutil/dateutil/issues/343 + from dateutil.parser import parserinfo, parser + + class GermanParserInfo(parserinfo): + WEEKDAYS = [("Mo", "Montag"), + ("Di", "Dienstag"), + ("Mi", "Mittwoch"), + ("Do", "Donnerstag"), + ("Fr", "Freitag"), + ("Sa", "Samstag"), + ("So", "Sonntag")] + + myparser = parser(GermanParserInfo()) + dt = myparser.parse("Sa 21. Jan 2017") + self.assertEqual(dt, datetime(2017, 1, 21)) + + def testNoYearFirstNoDayFirst(self): + dtstr = '090107' + + # Should be MMDDYY + self.assertEqual(parse(dtstr), + datetime(2007, 9, 1)) + + self.assertEqual(parse(dtstr, yearfirst=False, dayfirst=False), + datetime(2007, 9, 1)) + + def testYearFirst(self): + dtstr = '090107' + + # Should be MMDDYY + self.assertEqual(parse(dtstr, yearfirst=True), + datetime(2009, 1, 7)) + + self.assertEqual(parse(dtstr, yearfirst=True, dayfirst=False), + datetime(2009, 1, 7)) + + def testDayFirst(self): + dtstr = '090107' + + # Should be DDMMYY + self.assertEqual(parse(dtstr, dayfirst=True), + datetime(2007, 1, 9)) + + self.assertEqual(parse(dtstr, yearfirst=False, dayfirst=True), + datetime(2007, 1, 9)) + + def testDayFirstYearFirst(self): + dtstr = '090107' + # Should be YYDDMM + self.assertEqual(parse(dtstr, yearfirst=True, dayfirst=True), + datetime(2009, 7, 1)) + + def testUnambiguousYearFirst(self): + dtstr = '2015 09 25' + self.assertEqual(parse(dtstr, yearfirst=True), + datetime(2015, 9, 25)) + + def testUnambiguousDayFirst(self): + dtstr = '2015 09 25' + self.assertEqual(parse(dtstr, dayfirst=True), + datetime(2015, 9, 25)) + + def testUnambiguousDayFirstYearFirst(self): + dtstr = '2015 09 25' + self.assertEqual(parse(dtstr, dayfirst=True, yearfirst=True), + datetime(2015, 9, 25)) + + def test_mstridx(self): + # See GH408 + dtstr = '2015-15-May' + self.assertEqual(parse(dtstr), + datetime(2015, 5, 15)) + + def test_idx_check(self): + dtstr = '2017-07-17 06:15:' + # Pre-PR, the trailing colon will cause an IndexError at 824-825 + # when checking `i < len_l` and then accessing `l[i+1]` + res = parse(dtstr, fuzzy=True) assert res == datetime(2017, 7, 17, 6, 15) - - def test_hmBY(self): - # See GH#483 - dtstr = '02:17NOV2017' - res = parse(dtstr, default=self.default) + + def test_hmBY(self): + # See GH#483 + dtstr = '02:17NOV2017' + res = parse(dtstr, default=self.default) assert res == datetime(2017, 11, self.default.day, 2, 17) - - def test_validate_hour(self): - # See GH353 - invalid = "201A-01-01T23:58:39.239769+03:00" + + def test_validate_hour(self): + # See GH353 + invalid = "201A-01-01T23:58:39.239769+03:00" with pytest.raises(ParserError): - parse(invalid) - - def test_era_trailing_year(self): - dstr = 'AD2001' - res = parse(dstr) - assert res.year == 2001, res - + parse(invalid) + + def test_era_trailing_year(self): + dstr = 'AD2001' + res = parse(dstr) + assert res.year == 2001, res + def test_includes_timestr(self): timestr = "2020-13-97T44:61:83" - + try: parse(timestr) except ParserError as e: @@ -769,113 +769,113 @@ class TestOutOfBounds(object): parse(dstr, fuzzy=fuzzy) -class TestParseUnimplementedCases(object): - @pytest.mark.xfail - def test_somewhat_ambiguous_string(self): - # Ref: github issue #487 - # The parser is choosing the wrong part for hour - # causing datetime to raise an exception. - dtstr = '1237 PM BRST Mon Oct 30 2017' - res = parse(dtstr, tzinfo=self.tzinfos) - assert res == datetime(2017, 10, 30, 12, 37, tzinfo=self.tzinfos) - - @pytest.mark.xfail - def test_YmdH_M_S(self): - # found in nasdaq's ftp data - dstr = '1991041310:19:24' - expected = datetime(1991, 4, 13, 10, 19, 24) - res = parse(dstr) - assert res == expected, (res, expected) - - @pytest.mark.xfail - def test_first_century(self): - dstr = '0031 Nov 03' - expected = datetime(31, 11, 3) - res = parse(dstr) - assert res == expected, res - - @pytest.mark.xfail - def test_era_trailing_year_with_dots(self): - dstr = 'A.D.2001' - res = parse(dstr) - assert res.year == 2001, res - - @pytest.mark.xfail - def test_ad_nospace(self): - expected = datetime(6, 5, 19) - for dstr in [' 6AD May 19', ' 06AD May 19', - ' 006AD May 19', ' 0006AD May 19']: - res = parse(dstr) - assert res == expected, (dstr, res) - - @pytest.mark.xfail - def test_four_letter_day(self): - dstr = 'Frid Dec 30, 2016' - expected = datetime(2016, 12, 30) - res = parse(dstr) - assert res == expected - - @pytest.mark.xfail - def test_non_date_number(self): - dstr = '1,700' +class TestParseUnimplementedCases(object): + @pytest.mark.xfail + def test_somewhat_ambiguous_string(self): + # Ref: github issue #487 + # The parser is choosing the wrong part for hour + # causing datetime to raise an exception. + dtstr = '1237 PM BRST Mon Oct 30 2017' + res = parse(dtstr, tzinfo=self.tzinfos) + assert res == datetime(2017, 10, 30, 12, 37, tzinfo=self.tzinfos) + + @pytest.mark.xfail + def test_YmdH_M_S(self): + # found in nasdaq's ftp data + dstr = '1991041310:19:24' + expected = datetime(1991, 4, 13, 10, 19, 24) + res = parse(dstr) + assert res == expected, (res, expected) + + @pytest.mark.xfail + def test_first_century(self): + dstr = '0031 Nov 03' + expected = datetime(31, 11, 3) + res = parse(dstr) + assert res == expected, res + + @pytest.mark.xfail + def test_era_trailing_year_with_dots(self): + dstr = 'A.D.2001' + res = parse(dstr) + assert res.year == 2001, res + + @pytest.mark.xfail + def test_ad_nospace(self): + expected = datetime(6, 5, 19) + for dstr in [' 6AD May 19', ' 06AD May 19', + ' 006AD May 19', ' 0006AD May 19']: + res = parse(dstr) + assert res == expected, (dstr, res) + + @pytest.mark.xfail + def test_four_letter_day(self): + dstr = 'Frid Dec 30, 2016' + expected = datetime(2016, 12, 30) + res = parse(dstr) + assert res == expected + + @pytest.mark.xfail + def test_non_date_number(self): + dstr = '1,700' with pytest.raises(ParserError): - parse(dstr) - - @pytest.mark.xfail - def test_on_era(self): - # This could be classified as an "eras" test, but the relevant part - # about this is the ` on ` - dstr = '2:15 PM on January 2nd 1973 A.D.' - expected = datetime(1973, 1, 2, 14, 15) - res = parse(dstr) - assert res == expected - - @pytest.mark.xfail - def test_extraneous_year(self): - # This was found in the wild at insidertrading.org - dstr = "2011 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d NOVEMBER 7, 2012" - res = parse(dstr, fuzzy_with_tokens=True) - expected = datetime(2012, 11, 7) - assert res == expected - - @pytest.mark.xfail - def test_extraneous_year_tokens(self): - # This was found in the wild at insidertrading.org - # Unlike in the case above, identifying the first "2012" as the year + parse(dstr) + + @pytest.mark.xfail + def test_on_era(self): + # This could be classified as an "eras" test, but the relevant part + # about this is the ` on ` + dstr = '2:15 PM on January 2nd 1973 A.D.' + expected = datetime(1973, 1, 2, 14, 15) + res = parse(dstr) + assert res == expected + + @pytest.mark.xfail + def test_extraneous_year(self): + # This was found in the wild at insidertrading.org + dstr = "2011 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d NOVEMBER 7, 2012" + res = parse(dstr, fuzzy_with_tokens=True) + expected = datetime(2012, 11, 7) + assert res == expected + + @pytest.mark.xfail + def test_extraneous_year_tokens(self): + # This was found in the wild at insidertrading.org + # Unlike in the case above, identifying the first "2012" as the year # would not be a problem, but inferring that the latter 2012 is hhmm - # is a problem. - dstr = "2012 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d NOVEMBER 7, 2012" - expected = datetime(2012, 11, 7) - (res, tokens) = parse(dstr, fuzzy_with_tokens=True) - assert res == expected - assert tokens == ("2012 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d ",) - - @pytest.mark.xfail - def test_extraneous_year2(self): - # This was found in the wild at insidertrading.org - dstr = ("Berylson Amy Smith 1998 Grantor Retained Annuity Trust " - "u/d/t November 2, 1998 f/b/o Jennifer L Berylson") - res = parse(dstr, fuzzy_with_tokens=True) - expected = datetime(1998, 11, 2) - assert res == expected - - @pytest.mark.xfail - def test_extraneous_year3(self): - # This was found in the wild at insidertrading.org - dstr = "SMITH R & WEISS D 94 CHILD TR FBO M W SMITH UDT 12/1/1994" - res = parse(dstr, fuzzy_with_tokens=True) - expected = datetime(1994, 12, 1) - assert res == expected - - @pytest.mark.xfail - def test_unambiguous_YYYYMM(self): - # 171206 can be parsed as YYMMDD. However, 201712 cannot be parsed - # as instance of YYMMDD and parser could fallback to YYYYMM format. - dstr = "201712" - res = parse(dstr) - expected = datetime(2017, 12, 1) - assert res == expected - + # is a problem. + dstr = "2012 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d NOVEMBER 7, 2012" + expected = datetime(2012, 11, 7) + (res, tokens) = parse(dstr, fuzzy_with_tokens=True) + assert res == expected + assert tokens == ("2012 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d ",) + + @pytest.mark.xfail + def test_extraneous_year2(self): + # This was found in the wild at insidertrading.org + dstr = ("Berylson Amy Smith 1998 Grantor Retained Annuity Trust " + "u/d/t November 2, 1998 f/b/o Jennifer L Berylson") + res = parse(dstr, fuzzy_with_tokens=True) + expected = datetime(1998, 11, 2) + assert res == expected + + @pytest.mark.xfail + def test_extraneous_year3(self): + # This was found in the wild at insidertrading.org + dstr = "SMITH R & WEISS D 94 CHILD TR FBO M W SMITH UDT 12/1/1994" + res = parse(dstr, fuzzy_with_tokens=True) + expected = datetime(1994, 12, 1) + assert res == expected + + @pytest.mark.xfail + def test_unambiguous_YYYYMM(self): + # 171206 can be parsed as YYMMDD. However, 201712 cannot be parsed + # as instance of YYMMDD and parser could fallback to YYYYMM format. + dstr = "201712" + res = parse(dstr) + expected = datetime(2017, 12, 1) + assert res == expected + @pytest.mark.xfail def test_extraneous_numerical_content(self): # ref: https://github.com/dateutil/dateutil/issues/1029 @@ -884,7 +884,7 @@ class TestParseUnimplementedCases(object): res = parse(dstr, fuzzy=True, default=datetime(2000, 1, 1)) expected = datetime(2000, 4, 20) assert res == expected - + @pytest.mark.skipif(IS_WIN, reason="Windows does not use TZ var") class TestTZVar(object): @@ -894,10 +894,10 @@ class TestTZVar(object): with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'): dt_exp = datetime(2011, 8, 1, 12, 30, tzinfo=tz.tzlocal()) dt = parse('2011-08-01T12:30 EST') - + assert dt.tzname() == 'EDT' assert dt == dt_exp - + def test_tzlocal_in_gmt(self): # GH #318 with TZEnvContext('GMT0BST,M3.5.0,M10.5.0'): @@ -905,16 +905,16 @@ class TestTZVar(object): # parse using the GMT-as-alias-for-UTC rule dt = parse('2004-05-01T12:00 GMT') dt_exp = datetime(2004, 5, 1, 12, tzinfo=tz.UTC) - + assert dt == dt_exp - + def test_tzlocal_parse_fold(self): # One manifestion of GH #318 with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'): dt_exp = datetime(2011, 11, 6, 1, 30, tzinfo=tz.tzlocal()) dt_exp = tz.enfold(dt_exp, fold=1) dt = parse('2011-11-06T01:30 EST') - + # Because this is ambiguous, until `tz.tzlocal() is tz.tzlocal()` # we'll just check the attributes we care about rather than # dt == dt_exp @@ -922,37 +922,37 @@ class TestTZVar(object): assert dt.replace(tzinfo=None) == dt_exp.replace(tzinfo=None) assert getattr(dt, 'fold') == getattr(dt_exp, 'fold') assert dt.astimezone(tz.UTC) == dt_exp.astimezone(tz.UTC) - - -def test_parse_tzinfos_fold(): - NYC = tz.gettz('America/New_York') - tzinfos = {'EST': NYC, 'EDT': NYC} - - dt_exp = tz.enfold(datetime(2011, 11, 6, 1, 30, tzinfo=NYC), fold=1) - dt = parse('2011-11-06T01:30 EST', tzinfos=tzinfos) - - assert dt == dt_exp - assert dt.tzinfo is dt_exp.tzinfo - assert getattr(dt, 'fold') == getattr(dt_exp, 'fold') + + +def test_parse_tzinfos_fold(): + NYC = tz.gettz('America/New_York') + tzinfos = {'EST': NYC, 'EDT': NYC} + + dt_exp = tz.enfold(datetime(2011, 11, 6, 1, 30, tzinfo=NYC), fold=1) + dt = parse('2011-11-06T01:30 EST', tzinfos=tzinfos) + + assert dt == dt_exp + assert dt.tzinfo is dt_exp.tzinfo + assert getattr(dt, 'fold') == getattr(dt_exp, 'fold') assert dt.astimezone(tz.UTC) == dt_exp.astimezone(tz.UTC) - - -@pytest.mark.parametrize('dtstr,dt', [ - ('5.6h', datetime(2003, 9, 25, 5, 36)), - ('5.6m', datetime(2003, 9, 25, 0, 5, 36)), - # '5.6s' never had a rounding problem, test added for completeness - ('5.6s', datetime(2003, 9, 25, 0, 0, 5, 600000)) -]) -def test_rounding_floatlike_strings(dtstr, dt): - assert parse(dtstr, default=datetime(2003, 9, 25)) == dt - - -@pytest.mark.parametrize('value', ['1: test', 'Nan']) -def test_decimal_error(value): + + +@pytest.mark.parametrize('dtstr,dt', [ + ('5.6h', datetime(2003, 9, 25, 5, 36)), + ('5.6m', datetime(2003, 9, 25, 0, 5, 36)), + # '5.6s' never had a rounding problem, test added for completeness + ('5.6s', datetime(2003, 9, 25, 0, 0, 5, 600000)) +]) +def test_rounding_floatlike_strings(dtstr, dt): + assert parse(dtstr, default=datetime(2003, 9, 25)) == dt + + +@pytest.mark.parametrize('value', ['1: test', 'Nan']) +def test_decimal_error(value): # GH 632, GH 662 - decimal.Decimal raises some non-ParserError exception # when constructed with an invalid value with pytest.raises(ParserError): - parse(value) + parse(value) def test_parsererror_repr(): # GH 991 — the __repr__ was not properly indented and so was never defined. diff --git a/contrib/python/dateutil/dateutil/test/test_relativedelta.py b/contrib/python/dateutil/dateutil/test/test_relativedelta.py index 1e5d170449..0fecdd7823 100644 --- a/contrib/python/dateutil/dateutil/test/test_relativedelta.py +++ b/contrib/python/dateutil/dateutil/test/test_relativedelta.py @@ -1,124 +1,124 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals +# -*- coding: utf-8 -*- +from __future__ import unicode_literals from ._common import NotAValue - -import calendar -from datetime import datetime, date, timedelta -import unittest - + +import calendar +from datetime import datetime, date, timedelta +import unittest + import pytest -from dateutil.relativedelta import relativedelta, MO, TU, WE, FR, SU - - +from dateutil.relativedelta import relativedelta, MO, TU, WE, FR, SU + + class RelativeDeltaTest(unittest.TestCase): - now = datetime(2003, 9, 17, 20, 54, 47, 282310) - today = date(2003, 9, 17) - - def testInheritance(self): - # Ensure that relativedelta is inheritance-friendly. - class rdChildClass(relativedelta): - pass - - ccRD = rdChildClass(years=1, months=1, days=1, leapdays=1, weeks=1, - hours=1, minutes=1, seconds=1, microseconds=1) - - rd = relativedelta(years=1, months=1, days=1, leapdays=1, weeks=1, - hours=1, minutes=1, seconds=1, microseconds=1) - - self.assertEqual(type(ccRD + rd), type(ccRD), - msg='Addition does not inherit type.') - - self.assertEqual(type(ccRD - rd), type(ccRD), - msg='Subtraction does not inherit type.') - - self.assertEqual(type(-ccRD), type(ccRD), - msg='Negation does not inherit type.') - - self.assertEqual(type(ccRD * 5.0), type(ccRD), - msg='Multiplication does not inherit type.') - - self.assertEqual(type(ccRD / 5.0), type(ccRD), - msg='Division does not inherit type.') - - def testMonthEndMonthBeginning(self): - self.assertEqual(relativedelta(datetime(2003, 1, 31, 23, 59, 59), - datetime(2003, 3, 1, 0, 0, 0)), - relativedelta(months=-1, seconds=-1)) - - self.assertEqual(relativedelta(datetime(2003, 3, 1, 0, 0, 0), - datetime(2003, 1, 31, 23, 59, 59)), - relativedelta(months=1, seconds=1)) - - def testMonthEndMonthBeginningLeapYear(self): - self.assertEqual(relativedelta(datetime(2012, 1, 31, 23, 59, 59), - datetime(2012, 3, 1, 0, 0, 0)), - relativedelta(months=-1, seconds=-1)) - - self.assertEqual(relativedelta(datetime(2003, 3, 1, 0, 0, 0), - datetime(2003, 1, 31, 23, 59, 59)), - relativedelta(months=1, seconds=1)) - - def testNextMonth(self): - self.assertEqual(self.now+relativedelta(months=+1), - datetime(2003, 10, 17, 20, 54, 47, 282310)) - - def testNextMonthPlusOneWeek(self): - self.assertEqual(self.now+relativedelta(months=+1, weeks=+1), - datetime(2003, 10, 24, 20, 54, 47, 282310)) - - def testNextMonthPlusOneWeek10am(self): - self.assertEqual(self.today + - relativedelta(months=+1, weeks=+1, hour=10), - datetime(2003, 10, 24, 10, 0)) - - def testNextMonthPlusOneWeek10amDiff(self): - self.assertEqual(relativedelta(datetime(2003, 10, 24, 10, 0), - self.today), - relativedelta(months=+1, days=+7, hours=+10)) - - def testOneMonthBeforeOneYear(self): - self.assertEqual(self.now+relativedelta(years=+1, months=-1), - datetime(2004, 8, 17, 20, 54, 47, 282310)) - - def testMonthsOfDiffNumOfDays(self): - self.assertEqual(date(2003, 1, 27)+relativedelta(months=+1), - date(2003, 2, 27)) - self.assertEqual(date(2003, 1, 31)+relativedelta(months=+1), - date(2003, 2, 28)) - self.assertEqual(date(2003, 1, 31)+relativedelta(months=+2), - date(2003, 3, 31)) - - def testMonthsOfDiffNumOfDaysWithYears(self): - self.assertEqual(date(2000, 2, 28)+relativedelta(years=+1), - date(2001, 2, 28)) - self.assertEqual(date(2000, 2, 29)+relativedelta(years=+1), - date(2001, 2, 28)) - - self.assertEqual(date(1999, 2, 28)+relativedelta(years=+1), - date(2000, 2, 28)) - self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1), - date(2000, 3, 1)) - self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1), - date(2000, 3, 1)) - - self.assertEqual(date(2001, 2, 28)+relativedelta(years=-1), - date(2000, 2, 28)) - self.assertEqual(date(2001, 3, 1)+relativedelta(years=-1), - date(2000, 3, 1)) - - def testNextFriday(self): - self.assertEqual(self.today+relativedelta(weekday=FR), - date(2003, 9, 19)) - - def testNextFridayInt(self): - self.assertEqual(self.today+relativedelta(weekday=calendar.FRIDAY), - date(2003, 9, 19)) - - def testLastFridayInThisMonth(self): - self.assertEqual(self.today+relativedelta(day=31, weekday=FR(-1)), - date(2003, 9, 26)) - + now = datetime(2003, 9, 17, 20, 54, 47, 282310) + today = date(2003, 9, 17) + + def testInheritance(self): + # Ensure that relativedelta is inheritance-friendly. + class rdChildClass(relativedelta): + pass + + ccRD = rdChildClass(years=1, months=1, days=1, leapdays=1, weeks=1, + hours=1, minutes=1, seconds=1, microseconds=1) + + rd = relativedelta(years=1, months=1, days=1, leapdays=1, weeks=1, + hours=1, minutes=1, seconds=1, microseconds=1) + + self.assertEqual(type(ccRD + rd), type(ccRD), + msg='Addition does not inherit type.') + + self.assertEqual(type(ccRD - rd), type(ccRD), + msg='Subtraction does not inherit type.') + + self.assertEqual(type(-ccRD), type(ccRD), + msg='Negation does not inherit type.') + + self.assertEqual(type(ccRD * 5.0), type(ccRD), + msg='Multiplication does not inherit type.') + + self.assertEqual(type(ccRD / 5.0), type(ccRD), + msg='Division does not inherit type.') + + def testMonthEndMonthBeginning(self): + self.assertEqual(relativedelta(datetime(2003, 1, 31, 23, 59, 59), + datetime(2003, 3, 1, 0, 0, 0)), + relativedelta(months=-1, seconds=-1)) + + self.assertEqual(relativedelta(datetime(2003, 3, 1, 0, 0, 0), + datetime(2003, 1, 31, 23, 59, 59)), + relativedelta(months=1, seconds=1)) + + def testMonthEndMonthBeginningLeapYear(self): + self.assertEqual(relativedelta(datetime(2012, 1, 31, 23, 59, 59), + datetime(2012, 3, 1, 0, 0, 0)), + relativedelta(months=-1, seconds=-1)) + + self.assertEqual(relativedelta(datetime(2003, 3, 1, 0, 0, 0), + datetime(2003, 1, 31, 23, 59, 59)), + relativedelta(months=1, seconds=1)) + + def testNextMonth(self): + self.assertEqual(self.now+relativedelta(months=+1), + datetime(2003, 10, 17, 20, 54, 47, 282310)) + + def testNextMonthPlusOneWeek(self): + self.assertEqual(self.now+relativedelta(months=+1, weeks=+1), + datetime(2003, 10, 24, 20, 54, 47, 282310)) + + def testNextMonthPlusOneWeek10am(self): + self.assertEqual(self.today + + relativedelta(months=+1, weeks=+1, hour=10), + datetime(2003, 10, 24, 10, 0)) + + def testNextMonthPlusOneWeek10amDiff(self): + self.assertEqual(relativedelta(datetime(2003, 10, 24, 10, 0), + self.today), + relativedelta(months=+1, days=+7, hours=+10)) + + def testOneMonthBeforeOneYear(self): + self.assertEqual(self.now+relativedelta(years=+1, months=-1), + datetime(2004, 8, 17, 20, 54, 47, 282310)) + + def testMonthsOfDiffNumOfDays(self): + self.assertEqual(date(2003, 1, 27)+relativedelta(months=+1), + date(2003, 2, 27)) + self.assertEqual(date(2003, 1, 31)+relativedelta(months=+1), + date(2003, 2, 28)) + self.assertEqual(date(2003, 1, 31)+relativedelta(months=+2), + date(2003, 3, 31)) + + def testMonthsOfDiffNumOfDaysWithYears(self): + self.assertEqual(date(2000, 2, 28)+relativedelta(years=+1), + date(2001, 2, 28)) + self.assertEqual(date(2000, 2, 29)+relativedelta(years=+1), + date(2001, 2, 28)) + + self.assertEqual(date(1999, 2, 28)+relativedelta(years=+1), + date(2000, 2, 28)) + self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1), + date(2000, 3, 1)) + self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1), + date(2000, 3, 1)) + + self.assertEqual(date(2001, 2, 28)+relativedelta(years=-1), + date(2000, 2, 28)) + self.assertEqual(date(2001, 3, 1)+relativedelta(years=-1), + date(2000, 3, 1)) + + def testNextFriday(self): + self.assertEqual(self.today+relativedelta(weekday=FR), + date(2003, 9, 19)) + + def testNextFridayInt(self): + self.assertEqual(self.today+relativedelta(weekday=calendar.FRIDAY), + date(2003, 9, 19)) + + def testLastFridayInThisMonth(self): + self.assertEqual(self.today+relativedelta(day=31, weekday=FR(-1)), + date(2003, 9, 26)) + def testLastDayOfFebruary(self): self.assertEqual(date(2021, 2, 1) + relativedelta(day=31), date(2021, 2, 28)) @@ -127,14 +127,14 @@ class RelativeDeltaTest(unittest.TestCase): self.assertEqual(date(2020, 2, 1) + relativedelta(day=31), date(2020, 2, 29)) - def testNextWednesdayIsToday(self): - self.assertEqual(self.today+relativedelta(weekday=WE), - date(2003, 9, 17)) - + def testNextWednesdayIsToday(self): + self.assertEqual(self.today+relativedelta(weekday=WE), + date(2003, 9, 17)) + def testNextWednesdayNotToday(self): - self.assertEqual(self.today+relativedelta(days=+1, weekday=WE), - date(2003, 9, 24)) - + self.assertEqual(self.today+relativedelta(days=+1, weekday=WE), + date(2003, 9, 24)) + def testAddMoreThan12Months(self): self.assertEqual(date(2003, 12, 1) + relativedelta(months=+13), date(2005, 1, 1)) @@ -143,231 +143,231 @@ class RelativeDeltaTest(unittest.TestCase): self.assertEqual(date(2003, 1, 1) + relativedelta(months=-2), date(2002, 11, 1)) - def test15thISOYearWeek(self): - self.assertEqual(date(2003, 1, 1) + - relativedelta(day=4, weeks=+14, weekday=MO(-1)), - date(2003, 4, 7)) - - def testMillenniumAge(self): - self.assertEqual(relativedelta(self.now, date(2001, 1, 1)), - relativedelta(years=+2, months=+8, days=+16, - hours=+20, minutes=+54, seconds=+47, - microseconds=+282310)) - - def testJohnAge(self): - self.assertEqual(relativedelta(self.now, - datetime(1978, 4, 5, 12, 0)), - relativedelta(years=+25, months=+5, days=+12, - hours=+8, minutes=+54, seconds=+47, - microseconds=+282310)) - - def testJohnAgeWithDate(self): - self.assertEqual(relativedelta(self.today, - datetime(1978, 4, 5, 12, 0)), - relativedelta(years=+25, months=+5, days=+11, - hours=+12)) - - def testYearDay(self): - self.assertEqual(date(2003, 1, 1)+relativedelta(yearday=260), - date(2003, 9, 17)) - self.assertEqual(date(2002, 1, 1)+relativedelta(yearday=260), - date(2002, 9, 17)) - self.assertEqual(date(2000, 1, 1)+relativedelta(yearday=260), - date(2000, 9, 16)) - self.assertEqual(self.today+relativedelta(yearday=261), - date(2003, 9, 18)) - - def testYearDayBug(self): - # Tests a problem reported by Adam Ryan. - self.assertEqual(date(2010, 1, 1)+relativedelta(yearday=15), - date(2010, 1, 15)) - - def testNonLeapYearDay(self): - self.assertEqual(date(2003, 1, 1)+relativedelta(nlyearday=260), - date(2003, 9, 17)) - self.assertEqual(date(2002, 1, 1)+relativedelta(nlyearday=260), - date(2002, 9, 17)) - self.assertEqual(date(2000, 1, 1)+relativedelta(nlyearday=260), - date(2000, 9, 17)) - self.assertEqual(self.today+relativedelta(yearday=261), - date(2003, 9, 18)) - - def testAddition(self): - self.assertEqual(relativedelta(days=10) + - relativedelta(years=1, months=2, days=3, hours=4, - minutes=5, microseconds=6), - relativedelta(years=1, months=2, days=13, hours=4, - minutes=5, microseconds=6)) - - def testAbsoluteAddition(self): - self.assertEqual(relativedelta() + relativedelta(day=0, hour=0), - relativedelta(day=0, hour=0)) - self.assertEqual(relativedelta(day=0, hour=0) + relativedelta(), - relativedelta(day=0, hour=0)) - - def testAdditionToDatetime(self): - self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1), - datetime(2000, 1, 2)) - - def testRightAdditionToDatetime(self): - self.assertEqual(relativedelta(days=1) + datetime(2000, 1, 1), - datetime(2000, 1, 2)) - - def testAdditionInvalidType(self): - with self.assertRaises(TypeError): - relativedelta(days=3) + 9 - - def testAdditionUnsupportedType(self): - # For unsupported types that define their own comparators, etc. - self.assertIs(relativedelta(days=1) + NotAValue, NotAValue) - - def testAdditionFloatValue(self): - self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=float(1)), - datetime(2000, 1, 2)) - self.assertEqual(datetime(2000, 1, 1) + relativedelta(months=float(1)), - datetime(2000, 2, 1)) - self.assertEqual(datetime(2000, 1, 1) + relativedelta(years=float(1)), - datetime(2001, 1, 1)) - - def testAdditionFloatFractionals(self): - self.assertEqual(datetime(2000, 1, 1, 0) + - relativedelta(days=float(0.5)), - datetime(2000, 1, 1, 12)) - self.assertEqual(datetime(2000, 1, 1, 0, 0) + - relativedelta(hours=float(0.5)), - datetime(2000, 1, 1, 0, 30)) - self.assertEqual(datetime(2000, 1, 1, 0, 0, 0) + - relativedelta(minutes=float(0.5)), - datetime(2000, 1, 1, 0, 0, 30)) - self.assertEqual(datetime(2000, 1, 1, 0, 0, 0, 0) + - relativedelta(seconds=float(0.5)), - datetime(2000, 1, 1, 0, 0, 0, 500000)) - self.assertEqual(datetime(2000, 1, 1, 0, 0, 0, 0) + - relativedelta(microseconds=float(500000.25)), - datetime(2000, 1, 1, 0, 0, 0, 500000)) - - def testSubtraction(self): - self.assertEqual(relativedelta(days=10) - - relativedelta(years=1, months=2, days=3, hours=4, - minutes=5, microseconds=6), - relativedelta(years=-1, months=-2, days=7, hours=-4, - minutes=-5, microseconds=-6)) - - def testRightSubtractionFromDatetime(self): - self.assertEqual(datetime(2000, 1, 2) - relativedelta(days=1), - datetime(2000, 1, 1)) - - def testSubractionWithDatetime(self): - self.assertRaises(TypeError, lambda x, y: x - y, - (relativedelta(days=1), datetime(2000, 1, 1))) - - def testSubtractionInvalidType(self): - with self.assertRaises(TypeError): - relativedelta(hours=12) - 14 - - def testSubtractionUnsupportedType(self): - self.assertIs(relativedelta(days=1) + NotAValue, NotAValue) - - def testMultiplication(self): - self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1) * 28, - datetime(2000, 1, 29)) - self.assertEqual(datetime(2000, 1, 1) + 28 * relativedelta(days=1), - datetime(2000, 1, 29)) - - def testMultiplicationUnsupportedType(self): - self.assertIs(relativedelta(days=1) * NotAValue, NotAValue) - - def testDivision(self): - self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=28) / 28, - datetime(2000, 1, 2)) - - def testDivisionUnsupportedType(self): - self.assertIs(relativedelta(days=1) / NotAValue, NotAValue) - - def testBoolean(self): - self.assertFalse(relativedelta(days=0)) - self.assertTrue(relativedelta(days=1)) - - def testAbsoluteValueNegative(self): - rd_base = relativedelta(years=-1, months=-5, days=-2, hours=-3, - minutes=-5, seconds=-2, microseconds=-12) - rd_expected = relativedelta(years=1, months=5, days=2, hours=3, - minutes=5, seconds=2, microseconds=12) - self.assertEqual(abs(rd_base), rd_expected) - - def testAbsoluteValuePositive(self): - rd_base = relativedelta(years=1, months=5, days=2, hours=3, - minutes=5, seconds=2, microseconds=12) - rd_expected = rd_base - - self.assertEqual(abs(rd_base), rd_expected) - - def testComparison(self): - d1 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1, - minutes=1, seconds=1, microseconds=1) - d2 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1, - minutes=1, seconds=1, microseconds=1) - d3 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1, - minutes=1, seconds=1, microseconds=2) - - self.assertEqual(d1, d2) - self.assertNotEqual(d1, d3) - - def testInequalityTypeMismatch(self): - # Different type - self.assertFalse(relativedelta(year=1) == 19) - - def testInequalityUnsupportedType(self): - self.assertIs(relativedelta(hours=3) == NotAValue, NotAValue) - - def testInequalityWeekdays(self): - # Different weekdays - no_wday = relativedelta(year=1997, month=4) - wday_mo_1 = relativedelta(year=1997, month=4, weekday=MO(+1)) - wday_mo_2 = relativedelta(year=1997, month=4, weekday=MO(+2)) - wday_tu = relativedelta(year=1997, month=4, weekday=TU) - - self.assertTrue(wday_mo_1 == wday_mo_1) - - self.assertFalse(no_wday == wday_mo_1) - self.assertFalse(wday_mo_1 == no_wday) - - self.assertFalse(wday_mo_1 == wday_mo_2) - self.assertFalse(wday_mo_2 == wday_mo_1) - - self.assertFalse(wday_mo_1 == wday_tu) - self.assertFalse(wday_tu == wday_mo_1) - - def testMonthOverflow(self): - self.assertEqual(relativedelta(months=273), - relativedelta(years=22, months=9)) - - def testWeeks(self): - # Test that the weeks property is working properly. - rd = relativedelta(years=4, months=2, weeks=8, days=6) - self.assertEqual((rd.weeks, rd.days), (8, 8 * 7 + 6)) - - rd.weeks = 3 - self.assertEqual((rd.weeks, rd.days), (3, 3 * 7 + 6)) - - def testRelativeDeltaRepr(self): - self.assertEqual(repr(relativedelta(years=1, months=-1, days=15)), - 'relativedelta(years=+1, months=-1, days=+15)') - - self.assertEqual(repr(relativedelta(months=14, seconds=-25)), - 'relativedelta(years=+1, months=+2, seconds=-25)') - - self.assertEqual(repr(relativedelta(month=3, hour=3, weekday=SU(3))), - 'relativedelta(month=3, weekday=SU(+3), hour=3)') - - def testRelativeDeltaFractionalYear(self): - with self.assertRaises(ValueError): - relativedelta(years=1.5) - - def testRelativeDeltaFractionalMonth(self): - with self.assertRaises(ValueError): - relativedelta(months=1.5) - + def test15thISOYearWeek(self): + self.assertEqual(date(2003, 1, 1) + + relativedelta(day=4, weeks=+14, weekday=MO(-1)), + date(2003, 4, 7)) + + def testMillenniumAge(self): + self.assertEqual(relativedelta(self.now, date(2001, 1, 1)), + relativedelta(years=+2, months=+8, days=+16, + hours=+20, minutes=+54, seconds=+47, + microseconds=+282310)) + + def testJohnAge(self): + self.assertEqual(relativedelta(self.now, + datetime(1978, 4, 5, 12, 0)), + relativedelta(years=+25, months=+5, days=+12, + hours=+8, minutes=+54, seconds=+47, + microseconds=+282310)) + + def testJohnAgeWithDate(self): + self.assertEqual(relativedelta(self.today, + datetime(1978, 4, 5, 12, 0)), + relativedelta(years=+25, months=+5, days=+11, + hours=+12)) + + def testYearDay(self): + self.assertEqual(date(2003, 1, 1)+relativedelta(yearday=260), + date(2003, 9, 17)) + self.assertEqual(date(2002, 1, 1)+relativedelta(yearday=260), + date(2002, 9, 17)) + self.assertEqual(date(2000, 1, 1)+relativedelta(yearday=260), + date(2000, 9, 16)) + self.assertEqual(self.today+relativedelta(yearday=261), + date(2003, 9, 18)) + + def testYearDayBug(self): + # Tests a problem reported by Adam Ryan. + self.assertEqual(date(2010, 1, 1)+relativedelta(yearday=15), + date(2010, 1, 15)) + + def testNonLeapYearDay(self): + self.assertEqual(date(2003, 1, 1)+relativedelta(nlyearday=260), + date(2003, 9, 17)) + self.assertEqual(date(2002, 1, 1)+relativedelta(nlyearday=260), + date(2002, 9, 17)) + self.assertEqual(date(2000, 1, 1)+relativedelta(nlyearday=260), + date(2000, 9, 17)) + self.assertEqual(self.today+relativedelta(yearday=261), + date(2003, 9, 18)) + + def testAddition(self): + self.assertEqual(relativedelta(days=10) + + relativedelta(years=1, months=2, days=3, hours=4, + minutes=5, microseconds=6), + relativedelta(years=1, months=2, days=13, hours=4, + minutes=5, microseconds=6)) + + def testAbsoluteAddition(self): + self.assertEqual(relativedelta() + relativedelta(day=0, hour=0), + relativedelta(day=0, hour=0)) + self.assertEqual(relativedelta(day=0, hour=0) + relativedelta(), + relativedelta(day=0, hour=0)) + + def testAdditionToDatetime(self): + self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1), + datetime(2000, 1, 2)) + + def testRightAdditionToDatetime(self): + self.assertEqual(relativedelta(days=1) + datetime(2000, 1, 1), + datetime(2000, 1, 2)) + + def testAdditionInvalidType(self): + with self.assertRaises(TypeError): + relativedelta(days=3) + 9 + + def testAdditionUnsupportedType(self): + # For unsupported types that define their own comparators, etc. + self.assertIs(relativedelta(days=1) + NotAValue, NotAValue) + + def testAdditionFloatValue(self): + self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=float(1)), + datetime(2000, 1, 2)) + self.assertEqual(datetime(2000, 1, 1) + relativedelta(months=float(1)), + datetime(2000, 2, 1)) + self.assertEqual(datetime(2000, 1, 1) + relativedelta(years=float(1)), + datetime(2001, 1, 1)) + + def testAdditionFloatFractionals(self): + self.assertEqual(datetime(2000, 1, 1, 0) + + relativedelta(days=float(0.5)), + datetime(2000, 1, 1, 12)) + self.assertEqual(datetime(2000, 1, 1, 0, 0) + + relativedelta(hours=float(0.5)), + datetime(2000, 1, 1, 0, 30)) + self.assertEqual(datetime(2000, 1, 1, 0, 0, 0) + + relativedelta(minutes=float(0.5)), + datetime(2000, 1, 1, 0, 0, 30)) + self.assertEqual(datetime(2000, 1, 1, 0, 0, 0, 0) + + relativedelta(seconds=float(0.5)), + datetime(2000, 1, 1, 0, 0, 0, 500000)) + self.assertEqual(datetime(2000, 1, 1, 0, 0, 0, 0) + + relativedelta(microseconds=float(500000.25)), + datetime(2000, 1, 1, 0, 0, 0, 500000)) + + def testSubtraction(self): + self.assertEqual(relativedelta(days=10) - + relativedelta(years=1, months=2, days=3, hours=4, + minutes=5, microseconds=6), + relativedelta(years=-1, months=-2, days=7, hours=-4, + minutes=-5, microseconds=-6)) + + def testRightSubtractionFromDatetime(self): + self.assertEqual(datetime(2000, 1, 2) - relativedelta(days=1), + datetime(2000, 1, 1)) + + def testSubractionWithDatetime(self): + self.assertRaises(TypeError, lambda x, y: x - y, + (relativedelta(days=1), datetime(2000, 1, 1))) + + def testSubtractionInvalidType(self): + with self.assertRaises(TypeError): + relativedelta(hours=12) - 14 + + def testSubtractionUnsupportedType(self): + self.assertIs(relativedelta(days=1) + NotAValue, NotAValue) + + def testMultiplication(self): + self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1) * 28, + datetime(2000, 1, 29)) + self.assertEqual(datetime(2000, 1, 1) + 28 * relativedelta(days=1), + datetime(2000, 1, 29)) + + def testMultiplicationUnsupportedType(self): + self.assertIs(relativedelta(days=1) * NotAValue, NotAValue) + + def testDivision(self): + self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=28) / 28, + datetime(2000, 1, 2)) + + def testDivisionUnsupportedType(self): + self.assertIs(relativedelta(days=1) / NotAValue, NotAValue) + + def testBoolean(self): + self.assertFalse(relativedelta(days=0)) + self.assertTrue(relativedelta(days=1)) + + def testAbsoluteValueNegative(self): + rd_base = relativedelta(years=-1, months=-5, days=-2, hours=-3, + minutes=-5, seconds=-2, microseconds=-12) + rd_expected = relativedelta(years=1, months=5, days=2, hours=3, + minutes=5, seconds=2, microseconds=12) + self.assertEqual(abs(rd_base), rd_expected) + + def testAbsoluteValuePositive(self): + rd_base = relativedelta(years=1, months=5, days=2, hours=3, + minutes=5, seconds=2, microseconds=12) + rd_expected = rd_base + + self.assertEqual(abs(rd_base), rd_expected) + + def testComparison(self): + d1 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1, + minutes=1, seconds=1, microseconds=1) + d2 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1, + minutes=1, seconds=1, microseconds=1) + d3 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1, + minutes=1, seconds=1, microseconds=2) + + self.assertEqual(d1, d2) + self.assertNotEqual(d1, d3) + + def testInequalityTypeMismatch(self): + # Different type + self.assertFalse(relativedelta(year=1) == 19) + + def testInequalityUnsupportedType(self): + self.assertIs(relativedelta(hours=3) == NotAValue, NotAValue) + + def testInequalityWeekdays(self): + # Different weekdays + no_wday = relativedelta(year=1997, month=4) + wday_mo_1 = relativedelta(year=1997, month=4, weekday=MO(+1)) + wday_mo_2 = relativedelta(year=1997, month=4, weekday=MO(+2)) + wday_tu = relativedelta(year=1997, month=4, weekday=TU) + + self.assertTrue(wday_mo_1 == wday_mo_1) + + self.assertFalse(no_wday == wday_mo_1) + self.assertFalse(wday_mo_1 == no_wday) + + self.assertFalse(wday_mo_1 == wday_mo_2) + self.assertFalse(wday_mo_2 == wday_mo_1) + + self.assertFalse(wday_mo_1 == wday_tu) + self.assertFalse(wday_tu == wday_mo_1) + + def testMonthOverflow(self): + self.assertEqual(relativedelta(months=273), + relativedelta(years=22, months=9)) + + def testWeeks(self): + # Test that the weeks property is working properly. + rd = relativedelta(years=4, months=2, weeks=8, days=6) + self.assertEqual((rd.weeks, rd.days), (8, 8 * 7 + 6)) + + rd.weeks = 3 + self.assertEqual((rd.weeks, rd.days), (3, 3 * 7 + 6)) + + def testRelativeDeltaRepr(self): + self.assertEqual(repr(relativedelta(years=1, months=-1, days=15)), + 'relativedelta(years=+1, months=-1, days=+15)') + + self.assertEqual(repr(relativedelta(months=14, seconds=-25)), + 'relativedelta(years=+1, months=+2, seconds=-25)') + + self.assertEqual(repr(relativedelta(month=3, hour=3, weekday=SU(3))), + 'relativedelta(month=3, weekday=SU(+3), hour=3)') + + def testRelativeDeltaFractionalYear(self): + with self.assertRaises(ValueError): + relativedelta(years=1.5) + + def testRelativeDeltaFractionalMonth(self): + with self.assertRaises(ValueError): + relativedelta(months=1.5) + def testRelativeDeltaInvalidDatetimeObject(self): with self.assertRaises(TypeError): relativedelta(dt1='2018-01-01', dt2='2018-01-02') @@ -378,329 +378,329 @@ class RelativeDeltaTest(unittest.TestCase): with self.assertRaises(TypeError): relativedelta(dt1='2018-01-01', dt2=datetime(2018, 1, 2)) - def testRelativeDeltaFractionalAbsolutes(self): - # Fractional absolute values will soon be unsupported, - # check for the deprecation warning. + def testRelativeDeltaFractionalAbsolutes(self): + # Fractional absolute values will soon be unsupported, + # check for the deprecation warning. with pytest.warns(DeprecationWarning): - relativedelta(year=2.86) - + relativedelta(year=2.86) + with pytest.warns(DeprecationWarning): - relativedelta(month=1.29) - + relativedelta(month=1.29) + with pytest.warns(DeprecationWarning): - relativedelta(day=0.44) - + relativedelta(day=0.44) + with pytest.warns(DeprecationWarning): - relativedelta(hour=23.98) - + relativedelta(hour=23.98) + with pytest.warns(DeprecationWarning): - relativedelta(minute=45.21) - + relativedelta(minute=45.21) + with pytest.warns(DeprecationWarning): - relativedelta(second=13.2) - + relativedelta(second=13.2) + with pytest.warns(DeprecationWarning): - relativedelta(microsecond=157221.93) - - def testRelativeDeltaFractionalRepr(self): - rd = relativedelta(years=3, months=-2, days=1.25) - - self.assertEqual(repr(rd), - 'relativedelta(years=+3, months=-2, days=+1.25)') - - rd = relativedelta(hours=0.5, seconds=9.22) - self.assertEqual(repr(rd), - 'relativedelta(hours=+0.5, seconds=+9.22)') - - def testRelativeDeltaFractionalWeeks(self): - # Equivalent to days=8, hours=18 - rd = relativedelta(weeks=1.25) - d1 = datetime(2009, 9, 3, 0, 0) - self.assertEqual(d1 + rd, - datetime(2009, 9, 11, 18)) - - def testRelativeDeltaFractionalDays(self): - rd1 = relativedelta(days=1.48) - - d1 = datetime(2009, 9, 3, 0, 0) - self.assertEqual(d1 + rd1, - datetime(2009, 9, 4, 11, 31, 12)) - - rd2 = relativedelta(days=1.5) - self.assertEqual(d1 + rd2, - datetime(2009, 9, 4, 12, 0, 0)) - - def testRelativeDeltaFractionalHours(self): - rd = relativedelta(days=1, hours=12.5) - d1 = datetime(2009, 9, 3, 0, 0) - self.assertEqual(d1 + rd, - datetime(2009, 9, 4, 12, 30, 0)) - - def testRelativeDeltaFractionalMinutes(self): - rd = relativedelta(hours=1, minutes=30.5) - d1 = datetime(2009, 9, 3, 0, 0) - self.assertEqual(d1 + rd, - datetime(2009, 9, 3, 1, 30, 30)) - - def testRelativeDeltaFractionalSeconds(self): - rd = relativedelta(hours=5, minutes=30, seconds=30.5) - d1 = datetime(2009, 9, 3, 0, 0) - self.assertEqual(d1 + rd, - datetime(2009, 9, 3, 5, 30, 30, 500000)) - - def testRelativeDeltaFractionalPositiveOverflow(self): - # Equivalent to (days=1, hours=14) - rd1 = relativedelta(days=1.5, hours=2) - d1 = datetime(2009, 9, 3, 0, 0) - self.assertEqual(d1 + rd1, - datetime(2009, 9, 4, 14, 0, 0)) - - # Equivalent to (days=1, hours=14, minutes=45) - rd2 = relativedelta(days=1.5, hours=2.5, minutes=15) - d1 = datetime(2009, 9, 3, 0, 0) - self.assertEqual(d1 + rd2, - datetime(2009, 9, 4, 14, 45)) - - # Carry back up - equivalent to (days=2, hours=2, minutes=0, seconds=1) - rd3 = relativedelta(days=1.5, hours=13, minutes=59.5, seconds=31) - self.assertEqual(d1 + rd3, - datetime(2009, 9, 5, 2, 0, 1)) - - def testRelativeDeltaFractionalNegativeDays(self): - # Equivalent to (days=-1, hours=-1) - rd1 = relativedelta(days=-1.5, hours=11) - d1 = datetime(2009, 9, 3, 12, 0) - self.assertEqual(d1 + rd1, - datetime(2009, 9, 2, 11, 0, 0)) - - # Equivalent to (days=-1, hours=-9) - rd2 = relativedelta(days=-1.25, hours=-3) - self.assertEqual(d1 + rd2, - datetime(2009, 9, 2, 3)) - - def testRelativeDeltaNormalizeFractionalDays(self): - # Equivalent to (days=2, hours=18) - rd1 = relativedelta(days=2.75) - - self.assertEqual(rd1.normalized(), relativedelta(days=2, hours=18)) - + relativedelta(microsecond=157221.93) + + def testRelativeDeltaFractionalRepr(self): + rd = relativedelta(years=3, months=-2, days=1.25) + + self.assertEqual(repr(rd), + 'relativedelta(years=+3, months=-2, days=+1.25)') + + rd = relativedelta(hours=0.5, seconds=9.22) + self.assertEqual(repr(rd), + 'relativedelta(hours=+0.5, seconds=+9.22)') + + def testRelativeDeltaFractionalWeeks(self): + # Equivalent to days=8, hours=18 + rd = relativedelta(weeks=1.25) + d1 = datetime(2009, 9, 3, 0, 0) + self.assertEqual(d1 + rd, + datetime(2009, 9, 11, 18)) + + def testRelativeDeltaFractionalDays(self): + rd1 = relativedelta(days=1.48) + + d1 = datetime(2009, 9, 3, 0, 0) + self.assertEqual(d1 + rd1, + datetime(2009, 9, 4, 11, 31, 12)) + + rd2 = relativedelta(days=1.5) + self.assertEqual(d1 + rd2, + datetime(2009, 9, 4, 12, 0, 0)) + + def testRelativeDeltaFractionalHours(self): + rd = relativedelta(days=1, hours=12.5) + d1 = datetime(2009, 9, 3, 0, 0) + self.assertEqual(d1 + rd, + datetime(2009, 9, 4, 12, 30, 0)) + + def testRelativeDeltaFractionalMinutes(self): + rd = relativedelta(hours=1, minutes=30.5) + d1 = datetime(2009, 9, 3, 0, 0) + self.assertEqual(d1 + rd, + datetime(2009, 9, 3, 1, 30, 30)) + + def testRelativeDeltaFractionalSeconds(self): + rd = relativedelta(hours=5, minutes=30, seconds=30.5) + d1 = datetime(2009, 9, 3, 0, 0) + self.assertEqual(d1 + rd, + datetime(2009, 9, 3, 5, 30, 30, 500000)) + + def testRelativeDeltaFractionalPositiveOverflow(self): + # Equivalent to (days=1, hours=14) + rd1 = relativedelta(days=1.5, hours=2) + d1 = datetime(2009, 9, 3, 0, 0) + self.assertEqual(d1 + rd1, + datetime(2009, 9, 4, 14, 0, 0)) + + # Equivalent to (days=1, hours=14, minutes=45) + rd2 = relativedelta(days=1.5, hours=2.5, minutes=15) + d1 = datetime(2009, 9, 3, 0, 0) + self.assertEqual(d1 + rd2, + datetime(2009, 9, 4, 14, 45)) + + # Carry back up - equivalent to (days=2, hours=2, minutes=0, seconds=1) + rd3 = relativedelta(days=1.5, hours=13, minutes=59.5, seconds=31) + self.assertEqual(d1 + rd3, + datetime(2009, 9, 5, 2, 0, 1)) + + def testRelativeDeltaFractionalNegativeDays(self): + # Equivalent to (days=-1, hours=-1) + rd1 = relativedelta(days=-1.5, hours=11) + d1 = datetime(2009, 9, 3, 12, 0) + self.assertEqual(d1 + rd1, + datetime(2009, 9, 2, 11, 0, 0)) + + # Equivalent to (days=-1, hours=-9) + rd2 = relativedelta(days=-1.25, hours=-3) + self.assertEqual(d1 + rd2, + datetime(2009, 9, 2, 3)) + + def testRelativeDeltaNormalizeFractionalDays(self): + # Equivalent to (days=2, hours=18) + rd1 = relativedelta(days=2.75) + + self.assertEqual(rd1.normalized(), relativedelta(days=2, hours=18)) + # Equivalent to (days=1, hours=11, minutes=31, seconds=12) - rd2 = relativedelta(days=1.48) - - self.assertEqual(rd2.normalized(), - relativedelta(days=1, hours=11, minutes=31, seconds=12)) - - def testRelativeDeltaNormalizeFractionalDays2(self): - # Equivalent to (hours=1, minutes=30) - rd1 = relativedelta(hours=1.5) - - self.assertEqual(rd1.normalized(), relativedelta(hours=1, minutes=30)) - - # Equivalent to (hours=3, minutes=17, seconds=5, microseconds=100) - rd2 = relativedelta(hours=3.28472225) - - self.assertEqual(rd2.normalized(), - relativedelta(hours=3, minutes=17, seconds=5, microseconds=100)) - - def testRelativeDeltaNormalizeFractionalMinutes(self): - # Equivalent to (minutes=15, seconds=36) - rd1 = relativedelta(minutes=15.6) - - self.assertEqual(rd1.normalized(), - relativedelta(minutes=15, seconds=36)) - - # Equivalent to (minutes=25, seconds=20, microseconds=25000) - rd2 = relativedelta(minutes=25.33375) - - self.assertEqual(rd2.normalized(), - relativedelta(minutes=25, seconds=20, microseconds=25000)) - - def testRelativeDeltaNormalizeFractionalSeconds(self): - # Equivalent to (seconds=45, microseconds=25000) - rd1 = relativedelta(seconds=45.025) - self.assertEqual(rd1.normalized(), - relativedelta(seconds=45, microseconds=25000)) - - def testRelativeDeltaFractionalPositiveOverflow2(self): - # Equivalent to (days=1, hours=14) - rd1 = relativedelta(days=1.5, hours=2) - self.assertEqual(rd1.normalized(), - relativedelta(days=1, hours=14)) - - # Equivalent to (days=1, hours=14, minutes=45) - rd2 = relativedelta(days=1.5, hours=2.5, minutes=15) - self.assertEqual(rd2.normalized(), - relativedelta(days=1, hours=14, minutes=45)) - - # Carry back up - equivalent to: - # (days=2, hours=2, minutes=0, seconds=2, microseconds=3) - rd3 = relativedelta(days=1.5, hours=13, minutes=59.50045, - seconds=31.473, microseconds=500003) - self.assertEqual(rd3.normalized(), - relativedelta(days=2, hours=2, minutes=0, - seconds=2, microseconds=3)) - - def testRelativeDeltaFractionalNegativeOverflow(self): - # Equivalent to (days=-1) - rd1 = relativedelta(days=-0.5, hours=-12) - self.assertEqual(rd1.normalized(), - relativedelta(days=-1)) - - # Equivalent to (days=-1) - rd2 = relativedelta(days=-1.5, hours=12) - self.assertEqual(rd2.normalized(), - relativedelta(days=-1)) - - # Equivalent to (days=-1, hours=-14, minutes=-45) - rd3 = relativedelta(days=-1.5, hours=-2.5, minutes=-15) - self.assertEqual(rd3.normalized(), - relativedelta(days=-1, hours=-14, minutes=-45)) - - # Equivalent to (days=-1, hours=-14, minutes=+15) - rd4 = relativedelta(days=-1.5, hours=-2.5, minutes=45) - self.assertEqual(rd4.normalized(), - relativedelta(days=-1, hours=-14, minutes=+15)) - - # Carry back up - equivalent to: - # (days=-2, hours=-2, minutes=0, seconds=-2, microseconds=-3) - rd3 = relativedelta(days=-1.5, hours=-13, minutes=-59.50045, - seconds=-31.473, microseconds=-500003) - self.assertEqual(rd3.normalized(), - relativedelta(days=-2, hours=-2, minutes=0, - seconds=-2, microseconds=-3)) - - def testInvalidYearDay(self): - with self.assertRaises(ValueError): - relativedelta(yearday=367) - - def testAddTimedeltaToUnpopulatedRelativedelta(self): - td = timedelta( - days=1, - seconds=1, - microseconds=1, - milliseconds=1, - minutes=1, - hours=1, - weeks=1 - ) - - expected = relativedelta( - weeks=1, - days=1, - hours=1, - minutes=1, - seconds=1, - microseconds=1001 - ) - - self.assertEqual(expected, relativedelta() + td) - - def testAddTimedeltaToPopulatedRelativeDelta(self): - td = timedelta( - days=1, - seconds=1, - microseconds=1, - milliseconds=1, - minutes=1, - hours=1, - weeks=1 - ) - - rd = relativedelta( - year=1, - month=1, - day=1, - hour=1, - minute=1, - second=1, - microsecond=1, - years=1, - months=1, - days=1, - weeks=1, - hours=1, - minutes=1, - seconds=1, - microseconds=1 - ) - - expected = relativedelta( - year=1, - month=1, - day=1, - hour=1, - minute=1, - second=1, - microsecond=1, - years=1, - months=1, - weeks=2, - days=2, - hours=2, - minutes=2, - seconds=2, - microseconds=1002, - ) - - self.assertEqual(expected, rd + td) - - def testHashable(self): - try: - {relativedelta(minute=1): 'test'} - except: - self.fail("relativedelta() failed to hash!") - - -class RelativeDeltaWeeksPropertyGetterTest(unittest.TestCase): - """Test the weeks property getter""" - - def test_one_day(self): - rd = relativedelta(days=1) - self.assertEqual(rd.days, 1) - self.assertEqual(rd.weeks, 0) - - def test_minus_one_day(self): - rd = relativedelta(days=-1) - self.assertEqual(rd.days, -1) - self.assertEqual(rd.weeks, 0) - - def test_height_days(self): - rd = relativedelta(days=8) - self.assertEqual(rd.days, 8) - self.assertEqual(rd.weeks, 1) - - def test_minus_height_days(self): - rd = relativedelta(days=-8) - self.assertEqual(rd.days, -8) - self.assertEqual(rd.weeks, -1) - - -class RelativeDeltaWeeksPropertySetterTest(unittest.TestCase): - """Test the weeks setter which makes a "smart" update of the days attribute""" - - def test_one_day_set_one_week(self): - rd = relativedelta(days=1) - rd.weeks = 1 # add 7 days - self.assertEqual(rd.days, 8) - self.assertEqual(rd.weeks, 1) - - def test_minus_one_day_set_one_week(self): - rd = relativedelta(days=-1) - rd.weeks = 1 # add 7 days - self.assertEqual(rd.days, 6) - self.assertEqual(rd.weeks, 0) - - def test_height_days_set_minus_one_week(self): - rd = relativedelta(days=8) - rd.weeks = -1 # change from 1 week, 1 day to -1 week, 1 day - self.assertEqual(rd.days, -6) - self.assertEqual(rd.weeks, 0) - - def test_minus_height_days_set_minus_one_week(self): - rd = relativedelta(days=-8) - rd.weeks = -1 # does not change anything - self.assertEqual(rd.days, -8) - self.assertEqual(rd.weeks, -1) - - -# vim:ts=4:sw=4:et + rd2 = relativedelta(days=1.48) + + self.assertEqual(rd2.normalized(), + relativedelta(days=1, hours=11, minutes=31, seconds=12)) + + def testRelativeDeltaNormalizeFractionalDays2(self): + # Equivalent to (hours=1, minutes=30) + rd1 = relativedelta(hours=1.5) + + self.assertEqual(rd1.normalized(), relativedelta(hours=1, minutes=30)) + + # Equivalent to (hours=3, minutes=17, seconds=5, microseconds=100) + rd2 = relativedelta(hours=3.28472225) + + self.assertEqual(rd2.normalized(), + relativedelta(hours=3, minutes=17, seconds=5, microseconds=100)) + + def testRelativeDeltaNormalizeFractionalMinutes(self): + # Equivalent to (minutes=15, seconds=36) + rd1 = relativedelta(minutes=15.6) + + self.assertEqual(rd1.normalized(), + relativedelta(minutes=15, seconds=36)) + + # Equivalent to (minutes=25, seconds=20, microseconds=25000) + rd2 = relativedelta(minutes=25.33375) + + self.assertEqual(rd2.normalized(), + relativedelta(minutes=25, seconds=20, microseconds=25000)) + + def testRelativeDeltaNormalizeFractionalSeconds(self): + # Equivalent to (seconds=45, microseconds=25000) + rd1 = relativedelta(seconds=45.025) + self.assertEqual(rd1.normalized(), + relativedelta(seconds=45, microseconds=25000)) + + def testRelativeDeltaFractionalPositiveOverflow2(self): + # Equivalent to (days=1, hours=14) + rd1 = relativedelta(days=1.5, hours=2) + self.assertEqual(rd1.normalized(), + relativedelta(days=1, hours=14)) + + # Equivalent to (days=1, hours=14, minutes=45) + rd2 = relativedelta(days=1.5, hours=2.5, minutes=15) + self.assertEqual(rd2.normalized(), + relativedelta(days=1, hours=14, minutes=45)) + + # Carry back up - equivalent to: + # (days=2, hours=2, minutes=0, seconds=2, microseconds=3) + rd3 = relativedelta(days=1.5, hours=13, minutes=59.50045, + seconds=31.473, microseconds=500003) + self.assertEqual(rd3.normalized(), + relativedelta(days=2, hours=2, minutes=0, + seconds=2, microseconds=3)) + + def testRelativeDeltaFractionalNegativeOverflow(self): + # Equivalent to (days=-1) + rd1 = relativedelta(days=-0.5, hours=-12) + self.assertEqual(rd1.normalized(), + relativedelta(days=-1)) + + # Equivalent to (days=-1) + rd2 = relativedelta(days=-1.5, hours=12) + self.assertEqual(rd2.normalized(), + relativedelta(days=-1)) + + # Equivalent to (days=-1, hours=-14, minutes=-45) + rd3 = relativedelta(days=-1.5, hours=-2.5, minutes=-15) + self.assertEqual(rd3.normalized(), + relativedelta(days=-1, hours=-14, minutes=-45)) + + # Equivalent to (days=-1, hours=-14, minutes=+15) + rd4 = relativedelta(days=-1.5, hours=-2.5, minutes=45) + self.assertEqual(rd4.normalized(), + relativedelta(days=-1, hours=-14, minutes=+15)) + + # Carry back up - equivalent to: + # (days=-2, hours=-2, minutes=0, seconds=-2, microseconds=-3) + rd3 = relativedelta(days=-1.5, hours=-13, minutes=-59.50045, + seconds=-31.473, microseconds=-500003) + self.assertEqual(rd3.normalized(), + relativedelta(days=-2, hours=-2, minutes=0, + seconds=-2, microseconds=-3)) + + def testInvalidYearDay(self): + with self.assertRaises(ValueError): + relativedelta(yearday=367) + + def testAddTimedeltaToUnpopulatedRelativedelta(self): + td = timedelta( + days=1, + seconds=1, + microseconds=1, + milliseconds=1, + minutes=1, + hours=1, + weeks=1 + ) + + expected = relativedelta( + weeks=1, + days=1, + hours=1, + minutes=1, + seconds=1, + microseconds=1001 + ) + + self.assertEqual(expected, relativedelta() + td) + + def testAddTimedeltaToPopulatedRelativeDelta(self): + td = timedelta( + days=1, + seconds=1, + microseconds=1, + milliseconds=1, + minutes=1, + hours=1, + weeks=1 + ) + + rd = relativedelta( + year=1, + month=1, + day=1, + hour=1, + minute=1, + second=1, + microsecond=1, + years=1, + months=1, + days=1, + weeks=1, + hours=1, + minutes=1, + seconds=1, + microseconds=1 + ) + + expected = relativedelta( + year=1, + month=1, + day=1, + hour=1, + minute=1, + second=1, + microsecond=1, + years=1, + months=1, + weeks=2, + days=2, + hours=2, + minutes=2, + seconds=2, + microseconds=1002, + ) + + self.assertEqual(expected, rd + td) + + def testHashable(self): + try: + {relativedelta(minute=1): 'test'} + except: + self.fail("relativedelta() failed to hash!") + + +class RelativeDeltaWeeksPropertyGetterTest(unittest.TestCase): + """Test the weeks property getter""" + + def test_one_day(self): + rd = relativedelta(days=1) + self.assertEqual(rd.days, 1) + self.assertEqual(rd.weeks, 0) + + def test_minus_one_day(self): + rd = relativedelta(days=-1) + self.assertEqual(rd.days, -1) + self.assertEqual(rd.weeks, 0) + + def test_height_days(self): + rd = relativedelta(days=8) + self.assertEqual(rd.days, 8) + self.assertEqual(rd.weeks, 1) + + def test_minus_height_days(self): + rd = relativedelta(days=-8) + self.assertEqual(rd.days, -8) + self.assertEqual(rd.weeks, -1) + + +class RelativeDeltaWeeksPropertySetterTest(unittest.TestCase): + """Test the weeks setter which makes a "smart" update of the days attribute""" + + def test_one_day_set_one_week(self): + rd = relativedelta(days=1) + rd.weeks = 1 # add 7 days + self.assertEqual(rd.days, 8) + self.assertEqual(rd.weeks, 1) + + def test_minus_one_day_set_one_week(self): + rd = relativedelta(days=-1) + rd.weeks = 1 # add 7 days + self.assertEqual(rd.days, 6) + self.assertEqual(rd.weeks, 0) + + def test_height_days_set_minus_one_week(self): + rd = relativedelta(days=8) + rd.weeks = -1 # change from 1 week, 1 day to -1 week, 1 day + self.assertEqual(rd.days, -6) + self.assertEqual(rd.weeks, 0) + + def test_minus_height_days_set_minus_one_week(self): + rd = relativedelta(days=-8) + rd.weeks = -1 # does not change anything + self.assertEqual(rd.days, -8) + self.assertEqual(rd.weeks, -1) + + +# vim:ts=4:sw=4:et diff --git a/contrib/python/dateutil/dateutil/test/test_rrule.py b/contrib/python/dateutil/dateutil/test/test_rrule.py index 52673ecc26..ccd33bb234 100644 --- a/contrib/python/dateutil/dateutil/test/test_rrule.py +++ b/contrib/python/dateutil/dateutil/test/test_rrule.py @@ -1,2857 +1,2857 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from datetime import datetime, date -import unittest +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from datetime import datetime, date +import unittest from six import PY2 - -from dateutil import tz -from dateutil.rrule import ( - rrule, rruleset, rrulestr, - YEARLY, MONTHLY, WEEKLY, DAILY, - HOURLY, MINUTELY, SECONDLY, - MO, TU, WE, TH, FR, SA, SU -) - -from freezegun import freeze_time - -import pytest - - -@pytest.mark.rrule + +from dateutil import tz +from dateutil.rrule import ( + rrule, rruleset, rrulestr, + YEARLY, MONTHLY, WEEKLY, DAILY, + HOURLY, MINUTELY, SECONDLY, + MO, TU, WE, TH, FR, SA, SU +) + +from freezegun import freeze_time + +import pytest + + +@pytest.mark.rrule class RRuleTest(unittest.TestCase): - def _rrulestr_reverse_test(self, rule): - """ - Call with an `rrule` and it will test that `str(rrule)` generates a - string which generates the same `rrule` as the input when passed to - `rrulestr()` - """ - rr_str = str(rule) - rrulestr_rrule = rrulestr(rr_str) - - self.assertEqual(list(rule), list(rrulestr_rrule)) - - def testStrAppendRRULEToken(self): - # `_rrulestr_reverse_test` does not check if the "RRULE:" prefix - # property is appended properly, so give it a dedicated test - self.assertEqual(str(rrule(YEARLY, - count=5, - dtstart=datetime(1997, 9, 2, 9, 0))), - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=5") - - rr_str = ( - 'DTSTART:19970105T083000\nRRULE:FREQ=YEARLY;INTERVAL=2' - ) - self.assertEqual(str(rrulestr(rr_str)), rr_str) - - def testYearly(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1998, 9, 2, 9, 0), - datetime(1999, 9, 2, 9, 0)]) - - def testYearlyInterval(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1999, 9, 2, 9, 0), - datetime(2001, 9, 2, 9, 0)]) - - def testYearlyIntervalLarge(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - interval=100, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(2097, 9, 2, 9, 0), - datetime(2197, 9, 2, 9, 0)]) - - def testYearlyByMonth(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 2, 9, 0), - datetime(1998, 3, 2, 9, 0), - datetime(1999, 1, 2, 9, 0)]) - - def testYearlyByMonthDay(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 3, 9, 0), - datetime(1997, 10, 1, 9, 0), - datetime(1997, 10, 3, 9, 0)]) - - def testYearlyByMonthAndMonthDay(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 5, 9, 0), - datetime(1998, 1, 7, 9, 0), - datetime(1998, 3, 5, 9, 0)]) - - def testYearlyByWeekDay(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testYearlyByNWeekDay(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 25, 9, 0), - datetime(1998, 1, 6, 9, 0), - datetime(1998, 12, 31, 9, 0)]) - - def testYearlyByNWeekDayLarge(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 11, 9, 0), - datetime(1998, 1, 20, 9, 0), - datetime(1998, 12, 17, 9, 0)]) - - def testYearlyByMonthAndWeekDay(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 1, 6, 9, 0), - datetime(1998, 1, 8, 9, 0)]) - - def testYearlyByMonthAndNWeekDay(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 6, 9, 0), - datetime(1998, 1, 29, 9, 0), - datetime(1998, 3, 3, 9, 0)]) - - def testYearlyByMonthAndNWeekDayLarge(self): - # This is interesting because the TH(-3) ends up before - # the TU(3). - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 15, 9, 0), - datetime(1998, 1, 20, 9, 0), - datetime(1998, 3, 12, 9, 0)]) - - def testYearlyByMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 2, 3, 9, 0), - datetime(1998, 3, 3, 9, 0)]) - - def testYearlyByMonthAndMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 3, 3, 9, 0), - datetime(2001, 3, 1, 9, 0)]) - - def testYearlyByYearDay(self): - self.assertEqual(list(rrule(YEARLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 9, 0), - datetime(1998, 1, 1, 9, 0), - datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0)]) - - def testYearlyByYearDayNeg(self): - self.assertEqual(list(rrule(YEARLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 9, 0), - datetime(1998, 1, 1, 9, 0), - datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0)]) - - def testYearlyByMonthAndYearDay(self): - self.assertEqual(list(rrule(YEARLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0), - datetime(1999, 4, 10, 9, 0), - datetime(1999, 7, 19, 9, 0)]) - - def testYearlyByMonthAndYearDayNeg(self): - self.assertEqual(list(rrule(YEARLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0), - datetime(1999, 4, 10, 9, 0), - datetime(1999, 7, 19, 9, 0)]) - - def testYearlyByWeekNo(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 5, 11, 9, 0), - datetime(1998, 5, 12, 9, 0), - datetime(1998, 5, 13, 9, 0)]) - - def testYearlyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self.assertEqual(list(rrule(YEARLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 29, 9, 0), - datetime(1999, 1, 4, 9, 0), - datetime(2000, 1, 3, 9, 0)]) - - def testYearlyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self.assertEqual(list(rrule(YEARLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 9, 0), - datetime(1998, 12, 27, 9, 0), - datetime(2000, 1, 2, 9, 0)]) - - def testYearlyByWeekNoAndWeekDayLast(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 9, 0), - datetime(1999, 1, 3, 9, 0), - datetime(2000, 1, 2, 9, 0)]) - - def testYearlyByEaster(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 12, 9, 0), - datetime(1999, 4, 4, 9, 0), - datetime(2000, 4, 23, 9, 0)]) - - def testYearlyByEasterPos(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 13, 9, 0), - datetime(1999, 4, 5, 9, 0), - datetime(2000, 4, 24, 9, 0)]) - - def testYearlyByEasterNeg(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 11, 9, 0), - datetime(1999, 4, 3, 9, 0), - datetime(2000, 4, 22, 9, 0)]) - - def testYearlyByWeekNoAndWeekDay53(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 12, 28, 9, 0), - datetime(2004, 12, 27, 9, 0), - datetime(2009, 12, 28, 9, 0)]) - - def testYearlyByHour(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0), - datetime(1998, 9, 2, 6, 0), - datetime(1998, 9, 2, 18, 0)]) - - def testYearlyByMinute(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6), - datetime(1997, 9, 2, 9, 18), - datetime(1998, 9, 2, 9, 6)]) - - def testYearlyBySecond(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 6), - datetime(1997, 9, 2, 9, 0, 18), - datetime(1998, 9, 2, 9, 0, 6)]) - - def testYearlyByHourAndMinute(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6), - datetime(1997, 9, 2, 18, 18), - datetime(1998, 9, 2, 6, 6)]) - - def testYearlyByHourAndSecond(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0, 6), - datetime(1997, 9, 2, 18, 0, 18), - datetime(1998, 9, 2, 6, 0, 6)]) - - def testYearlyByMinuteAndSecond(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6, 6), - datetime(1997, 9, 2, 9, 6, 18), - datetime(1997, 9, 2, 9, 18, 6)]) - - def testYearlyByHourAndMinuteAndSecond(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6, 6), - datetime(1997, 9, 2, 18, 6, 18), - datetime(1997, 9, 2, 18, 18, 6)]) - - def testYearlyBySetPos(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonthday=15, - byhour=(6, 18), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 11, 15, 18, 0), - datetime(1998, 2, 15, 6, 0), - datetime(1998, 11, 15, 18, 0)]) - - def testMonthly(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 10, 2, 9, 0), - datetime(1997, 11, 2, 9, 0)]) - - def testMonthlyInterval(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 11, 2, 9, 0), - datetime(1998, 1, 2, 9, 0)]) - - def testMonthlyIntervalLarge(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - interval=18, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1999, 3, 2, 9, 0), - datetime(2000, 9, 2, 9, 0)]) - - def testMonthlyByMonth(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 2, 9, 0), - datetime(1998, 3, 2, 9, 0), - datetime(1999, 1, 2, 9, 0)]) - - def testMonthlyByMonthDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 3, 9, 0), - datetime(1997, 10, 1, 9, 0), - datetime(1997, 10, 3, 9, 0)]) - - def testMonthlyByMonthAndMonthDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 5, 9, 0), - datetime(1998, 1, 7, 9, 0), - datetime(1998, 3, 5, 9, 0)]) - - def testMonthlyByWeekDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - # Third Monday of the month - self.assertEqual(rrule(MONTHLY, - byweekday=(MO(+3)), - dtstart=datetime(1997, 9, 1)).between(datetime(1997, 9, 1), - datetime(1997, 12, 1)), - [datetime(1997, 9, 15, 0, 0), - datetime(1997, 10, 20, 0, 0), - datetime(1997, 11, 17, 0, 0)]) - - def testMonthlyByNWeekDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 25, 9, 0), - datetime(1997, 10, 7, 9, 0)]) - - def testMonthlyByNWeekDayLarge(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 11, 9, 0), - datetime(1997, 9, 16, 9, 0), - datetime(1997, 10, 16, 9, 0)]) - - def testMonthlyByMonthAndWeekDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 1, 6, 9, 0), - datetime(1998, 1, 8, 9, 0)]) - - def testMonthlyByMonthAndNWeekDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 6, 9, 0), - datetime(1998, 1, 29, 9, 0), - datetime(1998, 3, 3, 9, 0)]) - - def testMonthlyByMonthAndNWeekDayLarge(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 15, 9, 0), - datetime(1998, 1, 20, 9, 0), - datetime(1998, 3, 12, 9, 0)]) - - def testMonthlyByMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 2, 3, 9, 0), - datetime(1998, 3, 3, 9, 0)]) - - def testMonthlyByMonthAndMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 3, 3, 9, 0), - datetime(2001, 3, 1, 9, 0)]) - - def testMonthlyByYearDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 9, 0), - datetime(1998, 1, 1, 9, 0), - datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0)]) - - def testMonthlyByYearDayNeg(self): - self.assertEqual(list(rrule(MONTHLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 9, 0), - datetime(1998, 1, 1, 9, 0), - datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0)]) - - def testMonthlyByMonthAndYearDay(self): - self.assertEqual(list(rrule(MONTHLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0), - datetime(1999, 4, 10, 9, 0), - datetime(1999, 7, 19, 9, 0)]) - - def testMonthlyByMonthAndYearDayNeg(self): - self.assertEqual(list(rrule(MONTHLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0), - datetime(1999, 4, 10, 9, 0), - datetime(1999, 7, 19, 9, 0)]) - - def testMonthlyByWeekNo(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 5, 11, 9, 0), - datetime(1998, 5, 12, 9, 0), - datetime(1998, 5, 13, 9, 0)]) - - def testMonthlyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self.assertEqual(list(rrule(MONTHLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 29, 9, 0), - datetime(1999, 1, 4, 9, 0), - datetime(2000, 1, 3, 9, 0)]) - - def testMonthlyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self.assertEqual(list(rrule(MONTHLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 9, 0), - datetime(1998, 12, 27, 9, 0), - datetime(2000, 1, 2, 9, 0)]) - - def testMonthlyByWeekNoAndWeekDayLast(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 9, 0), - datetime(1999, 1, 3, 9, 0), - datetime(2000, 1, 2, 9, 0)]) - - def testMonthlyByWeekNoAndWeekDay53(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 12, 28, 9, 0), - datetime(2004, 12, 27, 9, 0), - datetime(2009, 12, 28, 9, 0)]) - - def testMonthlyByEaster(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 12, 9, 0), - datetime(1999, 4, 4, 9, 0), - datetime(2000, 4, 23, 9, 0)]) - - def testMonthlyByEasterPos(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 13, 9, 0), - datetime(1999, 4, 5, 9, 0), - datetime(2000, 4, 24, 9, 0)]) - - def testMonthlyByEasterNeg(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 11, 9, 0), - datetime(1999, 4, 3, 9, 0), - datetime(2000, 4, 22, 9, 0)]) - - def testMonthlyByHour(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0), - datetime(1997, 10, 2, 6, 0), - datetime(1997, 10, 2, 18, 0)]) - - def testMonthlyByMinute(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6), - datetime(1997, 9, 2, 9, 18), - datetime(1997, 10, 2, 9, 6)]) - - def testMonthlyBySecond(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 6), - datetime(1997, 9, 2, 9, 0, 18), - datetime(1997, 10, 2, 9, 0, 6)]) - - def testMonthlyByHourAndMinute(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6), - datetime(1997, 9, 2, 18, 18), - datetime(1997, 10, 2, 6, 6)]) - - def testMonthlyByHourAndSecond(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0, 6), - datetime(1997, 9, 2, 18, 0, 18), - datetime(1997, 10, 2, 6, 0, 6)]) - - def testMonthlyByMinuteAndSecond(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6, 6), - datetime(1997, 9, 2, 9, 6, 18), - datetime(1997, 9, 2, 9, 18, 6)]) - - def testMonthlyByHourAndMinuteAndSecond(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6, 6), - datetime(1997, 9, 2, 18, 6, 18), - datetime(1997, 9, 2, 18, 18, 6)]) - - def testMonthlyBySetPos(self): - self.assertEqual(list(rrule(MONTHLY, - count=3, - bymonthday=(13, 17), - byhour=(6, 18), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 13, 18, 0), - datetime(1997, 9, 17, 6, 0), - datetime(1997, 10, 13, 18, 0)]) - - def testWeekly(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testWeeklyInterval(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 16, 9, 0), - datetime(1997, 9, 30, 9, 0)]) - - def testWeeklyIntervalLarge(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - interval=20, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1998, 1, 20, 9, 0), - datetime(1998, 6, 9, 9, 0)]) - - def testWeeklyByMonth(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 6, 9, 0), - datetime(1998, 1, 13, 9, 0), - datetime(1998, 1, 20, 9, 0)]) - - def testWeeklyByMonthDay(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 3, 9, 0), - datetime(1997, 10, 1, 9, 0), - datetime(1997, 10, 3, 9, 0)]) - - def testWeeklyByMonthAndMonthDay(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 5, 9, 0), - datetime(1998, 1, 7, 9, 0), - datetime(1998, 3, 5, 9, 0)]) - - def testWeeklyByWeekDay(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testWeeklyByNWeekDay(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testWeeklyByMonthAndWeekDay(self): - # This test is interesting, because it crosses the year - # boundary in a weekly period to find day '1' as a - # valid recurrence. - self.assertEqual(list(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 1, 6, 9, 0), - datetime(1998, 1, 8, 9, 0)]) - - def testWeeklyByMonthAndNWeekDay(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 1, 6, 9, 0), - datetime(1998, 1, 8, 9, 0)]) - - def testWeeklyByMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 2, 3, 9, 0), - datetime(1998, 3, 3, 9, 0)]) - - def testWeeklyByMonthAndMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 3, 3, 9, 0), - datetime(2001, 3, 1, 9, 0)]) - - def testWeeklyByYearDay(self): - self.assertEqual(list(rrule(WEEKLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 9, 0), - datetime(1998, 1, 1, 9, 0), - datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0)]) - - def testWeeklyByYearDayNeg(self): - self.assertEqual(list(rrule(WEEKLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 9, 0), - datetime(1998, 1, 1, 9, 0), - datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0)]) - - def testWeeklyByMonthAndYearDay(self): - self.assertEqual(list(rrule(WEEKLY, - count=4, - bymonth=(1, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 7, 19, 9, 0), - datetime(1999, 1, 1, 9, 0), - datetime(1999, 7, 19, 9, 0)]) - - def testWeeklyByMonthAndYearDayNeg(self): - self.assertEqual(list(rrule(WEEKLY, - count=4, - bymonth=(1, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 7, 19, 9, 0), - datetime(1999, 1, 1, 9, 0), - datetime(1999, 7, 19, 9, 0)]) - - def testWeeklyByWeekNo(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 5, 11, 9, 0), - datetime(1998, 5, 12, 9, 0), - datetime(1998, 5, 13, 9, 0)]) - - def testWeeklyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self.assertEqual(list(rrule(WEEKLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 29, 9, 0), - datetime(1999, 1, 4, 9, 0), - datetime(2000, 1, 3, 9, 0)]) - - def testWeeklyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self.assertEqual(list(rrule(WEEKLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 9, 0), - datetime(1998, 12, 27, 9, 0), - datetime(2000, 1, 2, 9, 0)]) - - def testWeeklyByWeekNoAndWeekDayLast(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 9, 0), - datetime(1999, 1, 3, 9, 0), - datetime(2000, 1, 2, 9, 0)]) - - def testWeeklyByWeekNoAndWeekDay53(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 12, 28, 9, 0), - datetime(2004, 12, 27, 9, 0), - datetime(2009, 12, 28, 9, 0)]) - - def testWeeklyByEaster(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 12, 9, 0), - datetime(1999, 4, 4, 9, 0), - datetime(2000, 4, 23, 9, 0)]) - - def testWeeklyByEasterPos(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 13, 9, 0), - datetime(1999, 4, 5, 9, 0), - datetime(2000, 4, 24, 9, 0)]) - - def testWeeklyByEasterNeg(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 11, 9, 0), - datetime(1999, 4, 3, 9, 0), - datetime(2000, 4, 22, 9, 0)]) - - def testWeeklyByHour(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0), - datetime(1997, 9, 9, 6, 0), - datetime(1997, 9, 9, 18, 0)]) - - def testWeeklyByMinute(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6), - datetime(1997, 9, 2, 9, 18), - datetime(1997, 9, 9, 9, 6)]) - - def testWeeklyBySecond(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 6), - datetime(1997, 9, 2, 9, 0, 18), - datetime(1997, 9, 9, 9, 0, 6)]) - - def testWeeklyByHourAndMinute(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6), - datetime(1997, 9, 2, 18, 18), - datetime(1997, 9, 9, 6, 6)]) - - def testWeeklyByHourAndSecond(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0, 6), - datetime(1997, 9, 2, 18, 0, 18), - datetime(1997, 9, 9, 6, 0, 6)]) - - def testWeeklyByMinuteAndSecond(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6, 6), - datetime(1997, 9, 2, 9, 6, 18), - datetime(1997, 9, 2, 9, 18, 6)]) - - def testWeeklyByHourAndMinuteAndSecond(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6, 6), - datetime(1997, 9, 2, 18, 6, 18), - datetime(1997, 9, 2, 18, 18, 6)]) - - def testWeeklyBySetPos(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - byweekday=(TU, TH), - byhour=(6, 18), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0), - datetime(1997, 9, 4, 6, 0), - datetime(1997, 9, 9, 18, 0)]) - - def testDaily(self): - self.assertEqual(list(rrule(DAILY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0)]) - - def testDailyInterval(self): - self.assertEqual(list(rrule(DAILY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 6, 9, 0)]) - - def testDailyIntervalLarge(self): - self.assertEqual(list(rrule(DAILY, - count=3, - interval=92, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 12, 3, 9, 0), - datetime(1998, 3, 5, 9, 0)]) - - def testDailyByMonth(self): - self.assertEqual(list(rrule(DAILY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 1, 2, 9, 0), - datetime(1998, 1, 3, 9, 0)]) - - def testDailyByMonthDay(self): - self.assertEqual(list(rrule(DAILY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 3, 9, 0), - datetime(1997, 10, 1, 9, 0), - datetime(1997, 10, 3, 9, 0)]) - - def testDailyByMonthAndMonthDay(self): - self.assertEqual(list(rrule(DAILY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 5, 9, 0), - datetime(1998, 1, 7, 9, 0), - datetime(1998, 3, 5, 9, 0)]) - - def testDailyByWeekDay(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testDailyByNWeekDay(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testDailyByMonthAndWeekDay(self): - self.assertEqual(list(rrule(DAILY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 1, 6, 9, 0), - datetime(1998, 1, 8, 9, 0)]) - - def testDailyByMonthAndNWeekDay(self): - self.assertEqual(list(rrule(DAILY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 1, 6, 9, 0), - datetime(1998, 1, 8, 9, 0)]) - - def testDailyByMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(DAILY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 2, 3, 9, 0), - datetime(1998, 3, 3, 9, 0)]) - - def testDailyByMonthAndMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(DAILY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 3, 3, 9, 0), - datetime(2001, 3, 1, 9, 0)]) - - def testDailyByYearDay(self): - self.assertEqual(list(rrule(DAILY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 9, 0), - datetime(1998, 1, 1, 9, 0), - datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0)]) - - def testDailyByYearDayNeg(self): - self.assertEqual(list(rrule(DAILY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 9, 0), - datetime(1998, 1, 1, 9, 0), - datetime(1998, 4, 10, 9, 0), - datetime(1998, 7, 19, 9, 0)]) - - def testDailyByMonthAndYearDay(self): - self.assertEqual(list(rrule(DAILY, - count=4, - bymonth=(1, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 7, 19, 9, 0), - datetime(1999, 1, 1, 9, 0), - datetime(1999, 7, 19, 9, 0)]) - - def testDailyByMonthAndYearDayNeg(self): - self.assertEqual(list(rrule(DAILY, - count=4, - bymonth=(1, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 9, 0), - datetime(1998, 7, 19, 9, 0), - datetime(1999, 1, 1, 9, 0), - datetime(1999, 7, 19, 9, 0)]) - - def testDailyByWeekNo(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 5, 11, 9, 0), - datetime(1998, 5, 12, 9, 0), - datetime(1998, 5, 13, 9, 0)]) - - def testDailyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self.assertEqual(list(rrule(DAILY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 29, 9, 0), - datetime(1999, 1, 4, 9, 0), - datetime(2000, 1, 3, 9, 0)]) - - def testDailyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self.assertEqual(list(rrule(DAILY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 9, 0), - datetime(1998, 12, 27, 9, 0), - datetime(2000, 1, 2, 9, 0)]) - - def testDailyByWeekNoAndWeekDayLast(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 9, 0), - datetime(1999, 1, 3, 9, 0), - datetime(2000, 1, 2, 9, 0)]) - - def testDailyByWeekNoAndWeekDay53(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 12, 28, 9, 0), - datetime(2004, 12, 27, 9, 0), - datetime(2009, 12, 28, 9, 0)]) - - def testDailyByEaster(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 12, 9, 0), - datetime(1999, 4, 4, 9, 0), - datetime(2000, 4, 23, 9, 0)]) - - def testDailyByEasterPos(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 13, 9, 0), - datetime(1999, 4, 5, 9, 0), - datetime(2000, 4, 24, 9, 0)]) - - def testDailyByEasterNeg(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 11, 9, 0), - datetime(1999, 4, 3, 9, 0), - datetime(2000, 4, 22, 9, 0)]) - - def testDailyByHour(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0), - datetime(1997, 9, 3, 6, 0), - datetime(1997, 9, 3, 18, 0)]) - - def testDailyByMinute(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6), - datetime(1997, 9, 2, 9, 18), - datetime(1997, 9, 3, 9, 6)]) - - def testDailyBySecond(self): - self.assertEqual(list(rrule(DAILY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 6), - datetime(1997, 9, 2, 9, 0, 18), - datetime(1997, 9, 3, 9, 0, 6)]) - - def testDailyByHourAndMinute(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6), - datetime(1997, 9, 2, 18, 18), - datetime(1997, 9, 3, 6, 6)]) - - def testDailyByHourAndSecond(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0, 6), - datetime(1997, 9, 2, 18, 0, 18), - datetime(1997, 9, 3, 6, 0, 6)]) - - def testDailyByMinuteAndSecond(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6, 6), - datetime(1997, 9, 2, 9, 6, 18), - datetime(1997, 9, 2, 9, 18, 6)]) - - def testDailyByHourAndMinuteAndSecond(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6, 6), - datetime(1997, 9, 2, 18, 6, 18), - datetime(1997, 9, 2, 18, 18, 6)]) - - def testDailyBySetPos(self): - self.assertEqual(list(rrule(DAILY, - count=3, - byhour=(6, 18), - byminute=(15, 45), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 15), - datetime(1997, 9, 3, 6, 45), - datetime(1997, 9, 3, 18, 15)]) - - def testHourly(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 2, 10, 0), - datetime(1997, 9, 2, 11, 0)]) - - def testHourlyInterval(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 2, 11, 0), - datetime(1997, 9, 2, 13, 0)]) - - def testHourlyIntervalLarge(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - interval=769, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 10, 4, 10, 0), - datetime(1997, 11, 5, 11, 0)]) - - def testHourlyByMonth(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 1, 0), - datetime(1998, 1, 1, 2, 0)]) - - def testHourlyByMonthDay(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 3, 0, 0), - datetime(1997, 9, 3, 1, 0), - datetime(1997, 9, 3, 2, 0)]) - - def testHourlyByMonthAndMonthDay(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 5, 0, 0), - datetime(1998, 1, 5, 1, 0), - datetime(1998, 1, 5, 2, 0)]) - - def testHourlyByWeekDay(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 2, 10, 0), - datetime(1997, 9, 2, 11, 0)]) - - def testHourlyByNWeekDay(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 2, 10, 0), - datetime(1997, 9, 2, 11, 0)]) - - def testHourlyByMonthAndWeekDay(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 1, 0), - datetime(1998, 1, 1, 2, 0)]) - - def testHourlyByMonthAndNWeekDay(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 1, 0), - datetime(1998, 1, 1, 2, 0)]) - - def testHourlyByMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 1, 0), - datetime(1998, 1, 1, 2, 0)]) - - def testHourlyByMonthAndMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 1, 0), - datetime(1998, 1, 1, 2, 0)]) - - def testHourlyByYearDay(self): - self.assertEqual(list(rrule(HOURLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 0, 0), - datetime(1997, 12, 31, 1, 0), - datetime(1997, 12, 31, 2, 0), - datetime(1997, 12, 31, 3, 0)]) - - def testHourlyByYearDayNeg(self): - self.assertEqual(list(rrule(HOURLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 0, 0), - datetime(1997, 12, 31, 1, 0), - datetime(1997, 12, 31, 2, 0), - datetime(1997, 12, 31, 3, 0)]) - - def testHourlyByMonthAndYearDay(self): - self.assertEqual(list(rrule(HOURLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 0, 0), - datetime(1998, 4, 10, 1, 0), - datetime(1998, 4, 10, 2, 0), - datetime(1998, 4, 10, 3, 0)]) - - def testHourlyByMonthAndYearDayNeg(self): - self.assertEqual(list(rrule(HOURLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 0, 0), - datetime(1998, 4, 10, 1, 0), - datetime(1998, 4, 10, 2, 0), - datetime(1998, 4, 10, 3, 0)]) - - def testHourlyByWeekNo(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 5, 11, 0, 0), - datetime(1998, 5, 11, 1, 0), - datetime(1998, 5, 11, 2, 0)]) - - def testHourlyByWeekNoAndWeekDay(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 29, 0, 0), - datetime(1997, 12, 29, 1, 0), - datetime(1997, 12, 29, 2, 0)]) - - def testHourlyByWeekNoAndWeekDayLarge(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 0, 0), - datetime(1997, 12, 28, 1, 0), - datetime(1997, 12, 28, 2, 0)]) - - def testHourlyByWeekNoAndWeekDayLast(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 0, 0), - datetime(1997, 12, 28, 1, 0), - datetime(1997, 12, 28, 2, 0)]) - - def testHourlyByWeekNoAndWeekDay53(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 12, 28, 0, 0), - datetime(1998, 12, 28, 1, 0), - datetime(1998, 12, 28, 2, 0)]) - - def testHourlyByEaster(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 12, 0, 0), - datetime(1998, 4, 12, 1, 0), - datetime(1998, 4, 12, 2, 0)]) - - def testHourlyByEasterPos(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 13, 0, 0), - datetime(1998, 4, 13, 1, 0), - datetime(1998, 4, 13, 2, 0)]) - - def testHourlyByEasterNeg(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 11, 0, 0), - datetime(1998, 4, 11, 1, 0), - datetime(1998, 4, 11, 2, 0)]) - - def testHourlyByHour(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0), - datetime(1997, 9, 3, 6, 0), - datetime(1997, 9, 3, 18, 0)]) - - def testHourlyByMinute(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6), - datetime(1997, 9, 2, 9, 18), - datetime(1997, 9, 2, 10, 6)]) - - def testHourlyBySecond(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 6), - datetime(1997, 9, 2, 9, 0, 18), - datetime(1997, 9, 2, 10, 0, 6)]) - - def testHourlyByHourAndMinute(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6), - datetime(1997, 9, 2, 18, 18), - datetime(1997, 9, 3, 6, 6)]) - - def testHourlyByHourAndSecond(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0, 6), - datetime(1997, 9, 2, 18, 0, 18), - datetime(1997, 9, 3, 6, 0, 6)]) - - def testHourlyByMinuteAndSecond(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6, 6), - datetime(1997, 9, 2, 9, 6, 18), - datetime(1997, 9, 2, 9, 18, 6)]) - - def testHourlyByHourAndMinuteAndSecond(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6, 6), - datetime(1997, 9, 2, 18, 6, 18), - datetime(1997, 9, 2, 18, 18, 6)]) - - def testHourlyBySetPos(self): - self.assertEqual(list(rrule(HOURLY, - count=3, - byminute=(15, 45), - bysecond=(15, 45), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 15, 45), - datetime(1997, 9, 2, 9, 45, 15), - datetime(1997, 9, 2, 10, 15, 45)]) - - def testMinutely(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 2, 9, 1), - datetime(1997, 9, 2, 9, 2)]) - - def testMinutelyInterval(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 2, 9, 2), - datetime(1997, 9, 2, 9, 4)]) - - def testMinutelyIntervalLarge(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - interval=1501, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 10, 1), - datetime(1997, 9, 4, 11, 2)]) - - def testMinutelyByMonth(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 0, 1), - datetime(1998, 1, 1, 0, 2)]) - - def testMinutelyByMonthDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 3, 0, 0), - datetime(1997, 9, 3, 0, 1), - datetime(1997, 9, 3, 0, 2)]) - - def testMinutelyByMonthAndMonthDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 5, 0, 0), - datetime(1998, 1, 5, 0, 1), - datetime(1998, 1, 5, 0, 2)]) - - def testMinutelyByWeekDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 2, 9, 1), - datetime(1997, 9, 2, 9, 2)]) - - def testMinutelyByNWeekDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 2, 9, 1), - datetime(1997, 9, 2, 9, 2)]) - - def testMinutelyByMonthAndWeekDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 0, 1), - datetime(1998, 1, 1, 0, 2)]) - - def testMinutelyByMonthAndNWeekDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 0, 1), - datetime(1998, 1, 1, 0, 2)]) - - def testMinutelyByMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 0, 1), - datetime(1998, 1, 1, 0, 2)]) - - def testMinutelyByMonthAndMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0), - datetime(1998, 1, 1, 0, 1), - datetime(1998, 1, 1, 0, 2)]) - - def testMinutelyByYearDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 0, 0), - datetime(1997, 12, 31, 0, 1), - datetime(1997, 12, 31, 0, 2), - datetime(1997, 12, 31, 0, 3)]) - - def testMinutelyByYearDayNeg(self): - self.assertEqual(list(rrule(MINUTELY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 0, 0), - datetime(1997, 12, 31, 0, 1), - datetime(1997, 12, 31, 0, 2), - datetime(1997, 12, 31, 0, 3)]) - - def testMinutelyByMonthAndYearDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 0, 0), - datetime(1998, 4, 10, 0, 1), - datetime(1998, 4, 10, 0, 2), - datetime(1998, 4, 10, 0, 3)]) - - def testMinutelyByMonthAndYearDayNeg(self): - self.assertEqual(list(rrule(MINUTELY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 0, 0), - datetime(1998, 4, 10, 0, 1), - datetime(1998, 4, 10, 0, 2), - datetime(1998, 4, 10, 0, 3)]) - - def testMinutelyByWeekNo(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 5, 11, 0, 0), - datetime(1998, 5, 11, 0, 1), - datetime(1998, 5, 11, 0, 2)]) - - def testMinutelyByWeekNoAndWeekDay(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 29, 0, 0), - datetime(1997, 12, 29, 0, 1), - datetime(1997, 12, 29, 0, 2)]) - - def testMinutelyByWeekNoAndWeekDayLarge(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 0, 0), - datetime(1997, 12, 28, 0, 1), - datetime(1997, 12, 28, 0, 2)]) - - def testMinutelyByWeekNoAndWeekDayLast(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 0, 0), - datetime(1997, 12, 28, 0, 1), - datetime(1997, 12, 28, 0, 2)]) - - def testMinutelyByWeekNoAndWeekDay53(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 12, 28, 0, 0), - datetime(1998, 12, 28, 0, 1), - datetime(1998, 12, 28, 0, 2)]) - - def testMinutelyByEaster(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 12, 0, 0), - datetime(1998, 4, 12, 0, 1), - datetime(1998, 4, 12, 0, 2)]) - - def testMinutelyByEasterPos(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 13, 0, 0), - datetime(1998, 4, 13, 0, 1), - datetime(1998, 4, 13, 0, 2)]) - - def testMinutelyByEasterNeg(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 11, 0, 0), - datetime(1998, 4, 11, 0, 1), - datetime(1998, 4, 11, 0, 2)]) - - def testMinutelyByHour(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0), - datetime(1997, 9, 2, 18, 1), - datetime(1997, 9, 2, 18, 2)]) - - def testMinutelyByMinute(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6), - datetime(1997, 9, 2, 9, 18), - datetime(1997, 9, 2, 10, 6)]) - - def testMinutelyBySecond(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 6), - datetime(1997, 9, 2, 9, 0, 18), - datetime(1997, 9, 2, 9, 1, 6)]) - - def testMinutelyByHourAndMinute(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6), - datetime(1997, 9, 2, 18, 18), - datetime(1997, 9, 3, 6, 6)]) - - def testMinutelyByHourAndSecond(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0, 6), - datetime(1997, 9, 2, 18, 0, 18), - datetime(1997, 9, 2, 18, 1, 6)]) - - def testMinutelyByMinuteAndSecond(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6, 6), - datetime(1997, 9, 2, 9, 6, 18), - datetime(1997, 9, 2, 9, 18, 6)]) - - def testMinutelyByHourAndMinuteAndSecond(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6, 6), - datetime(1997, 9, 2, 18, 6, 18), - datetime(1997, 9, 2, 18, 18, 6)]) - - def testMinutelyBySetPos(self): - self.assertEqual(list(rrule(MINUTELY, - count=3, - bysecond=(15, 30, 45), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 15), - datetime(1997, 9, 2, 9, 0, 45), - datetime(1997, 9, 2, 9, 1, 15)]) - - def testSecondly(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 0), - datetime(1997, 9, 2, 9, 0, 1), - datetime(1997, 9, 2, 9, 0, 2)]) - - def testSecondlyInterval(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 0), - datetime(1997, 9, 2, 9, 0, 2), - datetime(1997, 9, 2, 9, 0, 4)]) - - def testSecondlyIntervalLarge(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - interval=90061, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 0), - datetime(1997, 9, 3, 10, 1, 1), - datetime(1997, 9, 4, 11, 2, 2)]) - - def testSecondlyByMonth(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0, 0), - datetime(1998, 1, 1, 0, 0, 1), - datetime(1998, 1, 1, 0, 0, 2)]) - - def testSecondlyByMonthDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 3, 0, 0, 0), - datetime(1997, 9, 3, 0, 0, 1), - datetime(1997, 9, 3, 0, 0, 2)]) - - def testSecondlyByMonthAndMonthDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 5, 0, 0, 0), - datetime(1998, 1, 5, 0, 0, 1), - datetime(1998, 1, 5, 0, 0, 2)]) - - def testSecondlyByWeekDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 0), - datetime(1997, 9, 2, 9, 0, 1), - datetime(1997, 9, 2, 9, 0, 2)]) - - def testSecondlyByNWeekDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 0), - datetime(1997, 9, 2, 9, 0, 1), - datetime(1997, 9, 2, 9, 0, 2)]) - - def testSecondlyByMonthAndWeekDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0, 0), - datetime(1998, 1, 1, 0, 0, 1), - datetime(1998, 1, 1, 0, 0, 2)]) - - def testSecondlyByMonthAndNWeekDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0, 0), - datetime(1998, 1, 1, 0, 0, 1), - datetime(1998, 1, 1, 0, 0, 2)]) - - def testSecondlyByMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0, 0), - datetime(1998, 1, 1, 0, 0, 1), - datetime(1998, 1, 1, 0, 0, 2)]) - - def testSecondlyByMonthAndMonthDayAndWeekDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 1, 0, 0, 0), - datetime(1998, 1, 1, 0, 0, 1), - datetime(1998, 1, 1, 0, 0, 2)]) - - def testSecondlyByYearDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 0, 0, 0), - datetime(1997, 12, 31, 0, 0, 1), - datetime(1997, 12, 31, 0, 0, 2), - datetime(1997, 12, 31, 0, 0, 3)]) - - def testSecondlyByYearDayNeg(self): - self.assertEqual(list(rrule(SECONDLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 31, 0, 0, 0), - datetime(1997, 12, 31, 0, 0, 1), - datetime(1997, 12, 31, 0, 0, 2), - datetime(1997, 12, 31, 0, 0, 3)]) - - def testSecondlyByMonthAndYearDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 0, 0, 0), - datetime(1998, 4, 10, 0, 0, 1), - datetime(1998, 4, 10, 0, 0, 2), - datetime(1998, 4, 10, 0, 0, 3)]) - - def testSecondlyByMonthAndYearDayNeg(self): - self.assertEqual(list(rrule(SECONDLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 10, 0, 0, 0), - datetime(1998, 4, 10, 0, 0, 1), - datetime(1998, 4, 10, 0, 0, 2), - datetime(1998, 4, 10, 0, 0, 3)]) - - def testSecondlyByWeekNo(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 5, 11, 0, 0, 0), - datetime(1998, 5, 11, 0, 0, 1), - datetime(1998, 5, 11, 0, 0, 2)]) - - def testSecondlyByWeekNoAndWeekDay(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 29, 0, 0, 0), - datetime(1997, 12, 29, 0, 0, 1), - datetime(1997, 12, 29, 0, 0, 2)]) - - def testSecondlyByWeekNoAndWeekDayLarge(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 0, 0, 0), - datetime(1997, 12, 28, 0, 0, 1), - datetime(1997, 12, 28, 0, 0, 2)]) - - def testSecondlyByWeekNoAndWeekDayLast(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 12, 28, 0, 0, 0), - datetime(1997, 12, 28, 0, 0, 1), - datetime(1997, 12, 28, 0, 0, 2)]) - - def testSecondlyByWeekNoAndWeekDay53(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 12, 28, 0, 0, 0), - datetime(1998, 12, 28, 0, 0, 1), - datetime(1998, 12, 28, 0, 0, 2)]) - - def testSecondlyByEaster(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 12, 0, 0, 0), - datetime(1998, 4, 12, 0, 0, 1), - datetime(1998, 4, 12, 0, 0, 2)]) - - def testSecondlyByEasterPos(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 13, 0, 0, 0), - datetime(1998, 4, 13, 0, 0, 1), - datetime(1998, 4, 13, 0, 0, 2)]) - - def testSecondlyByEasterNeg(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 4, 11, 0, 0, 0), - datetime(1998, 4, 11, 0, 0, 1), - datetime(1998, 4, 11, 0, 0, 2)]) - - def testSecondlyByHour(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0, 0), - datetime(1997, 9, 2, 18, 0, 1), - datetime(1997, 9, 2, 18, 0, 2)]) - - def testSecondlyByMinute(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6, 0), - datetime(1997, 9, 2, 9, 6, 1), - datetime(1997, 9, 2, 9, 6, 2)]) - - def testSecondlyBySecond(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0, 6), - datetime(1997, 9, 2, 9, 0, 18), - datetime(1997, 9, 2, 9, 1, 6)]) - - def testSecondlyByHourAndMinute(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6, 0), - datetime(1997, 9, 2, 18, 6, 1), - datetime(1997, 9, 2, 18, 6, 2)]) - - def testSecondlyByHourAndSecond(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 0, 6), - datetime(1997, 9, 2, 18, 0, 18), - datetime(1997, 9, 2, 18, 1, 6)]) - - def testSecondlyByMinuteAndSecond(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 6, 6), - datetime(1997, 9, 2, 9, 6, 18), - datetime(1997, 9, 2, 9, 18, 6)]) - - def testSecondlyByHourAndMinuteAndSecond(self): - self.assertEqual(list(rrule(SECONDLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 18, 6, 6), - datetime(1997, 9, 2, 18, 6, 18), - datetime(1997, 9, 2, 18, 18, 6)]) - - def testSecondlyByHourAndMinuteAndSecondBug(self): - # This explores a bug found by Mathieu Bridon. - self.assertEqual(list(rrule(SECONDLY, - count=3, - bysecond=(0,), - byminute=(1,), - dtstart=datetime(2010, 3, 22, 12, 1))), - [datetime(2010, 3, 22, 12, 1), - datetime(2010, 3, 22, 13, 1), - datetime(2010, 3, 22, 14, 1)]) - - def testLongIntegers(self): + def _rrulestr_reverse_test(self, rule): + """ + Call with an `rrule` and it will test that `str(rrule)` generates a + string which generates the same `rrule` as the input when passed to + `rrulestr()` + """ + rr_str = str(rule) + rrulestr_rrule = rrulestr(rr_str) + + self.assertEqual(list(rule), list(rrulestr_rrule)) + + def testStrAppendRRULEToken(self): + # `_rrulestr_reverse_test` does not check if the "RRULE:" prefix + # property is appended properly, so give it a dedicated test + self.assertEqual(str(rrule(YEARLY, + count=5, + dtstart=datetime(1997, 9, 2, 9, 0))), + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=5") + + rr_str = ( + 'DTSTART:19970105T083000\nRRULE:FREQ=YEARLY;INTERVAL=2' + ) + self.assertEqual(str(rrulestr(rr_str)), rr_str) + + def testYearly(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1998, 9, 2, 9, 0), + datetime(1999, 9, 2, 9, 0)]) + + def testYearlyInterval(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1999, 9, 2, 9, 0), + datetime(2001, 9, 2, 9, 0)]) + + def testYearlyIntervalLarge(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + interval=100, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(2097, 9, 2, 9, 0), + datetime(2197, 9, 2, 9, 0)]) + + def testYearlyByMonth(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 2, 9, 0), + datetime(1998, 3, 2, 9, 0), + datetime(1999, 1, 2, 9, 0)]) + + def testYearlyByMonthDay(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 3, 9, 0), + datetime(1997, 10, 1, 9, 0), + datetime(1997, 10, 3, 9, 0)]) + + def testYearlyByMonthAndMonthDay(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 5, 9, 0), + datetime(1998, 1, 7, 9, 0), + datetime(1998, 3, 5, 9, 0)]) + + def testYearlyByWeekDay(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testYearlyByNWeekDay(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 25, 9, 0), + datetime(1998, 1, 6, 9, 0), + datetime(1998, 12, 31, 9, 0)]) + + def testYearlyByNWeekDayLarge(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byweekday=(TU(3), TH(-3)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 11, 9, 0), + datetime(1998, 1, 20, 9, 0), + datetime(1998, 12, 17, 9, 0)]) + + def testYearlyByMonthAndWeekDay(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 1, 6, 9, 0), + datetime(1998, 1, 8, 9, 0)]) + + def testYearlyByMonthAndNWeekDay(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 6, 9, 0), + datetime(1998, 1, 29, 9, 0), + datetime(1998, 3, 3, 9, 0)]) + + def testYearlyByMonthAndNWeekDayLarge(self): + # This is interesting because the TH(-3) ends up before + # the TU(3). + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(3), TH(-3)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 15, 9, 0), + datetime(1998, 1, 20, 9, 0), + datetime(1998, 3, 12, 9, 0)]) + + def testYearlyByMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 2, 3, 9, 0), + datetime(1998, 3, 3, 9, 0)]) + + def testYearlyByMonthAndMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 3, 3, 9, 0), + datetime(2001, 3, 1, 9, 0)]) + + def testYearlyByYearDay(self): + self.assertEqual(list(rrule(YEARLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 9, 0), + datetime(1998, 1, 1, 9, 0), + datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0)]) + + def testYearlyByYearDayNeg(self): + self.assertEqual(list(rrule(YEARLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 9, 0), + datetime(1998, 1, 1, 9, 0), + datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0)]) + + def testYearlyByMonthAndYearDay(self): + self.assertEqual(list(rrule(YEARLY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0), + datetime(1999, 4, 10, 9, 0), + datetime(1999, 7, 19, 9, 0)]) + + def testYearlyByMonthAndYearDayNeg(self): + self.assertEqual(list(rrule(YEARLY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0), + datetime(1999, 4, 10, 9, 0), + datetime(1999, 7, 19, 9, 0)]) + + def testYearlyByWeekNo(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 5, 11, 9, 0), + datetime(1998, 5, 12, 9, 0), + datetime(1998, 5, 13, 9, 0)]) + + def testYearlyByWeekNoAndWeekDay(self): + # That's a nice one. The first days of week number one + # may be in the last year. + self.assertEqual(list(rrule(YEARLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 29, 9, 0), + datetime(1999, 1, 4, 9, 0), + datetime(2000, 1, 3, 9, 0)]) + + def testYearlyByWeekNoAndWeekDayLarge(self): + # Another nice test. The last days of week number 52/53 + # may be in the next year. + self.assertEqual(list(rrule(YEARLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 9, 0), + datetime(1998, 12, 27, 9, 0), + datetime(2000, 1, 2, 9, 0)]) + + def testYearlyByWeekNoAndWeekDayLast(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 9, 0), + datetime(1999, 1, 3, 9, 0), + datetime(2000, 1, 2, 9, 0)]) + + def testYearlyByEaster(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 12, 9, 0), + datetime(1999, 4, 4, 9, 0), + datetime(2000, 4, 23, 9, 0)]) + + def testYearlyByEasterPos(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 13, 9, 0), + datetime(1999, 4, 5, 9, 0), + datetime(2000, 4, 24, 9, 0)]) + + def testYearlyByEasterNeg(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 11, 9, 0), + datetime(1999, 4, 3, 9, 0), + datetime(2000, 4, 22, 9, 0)]) + + def testYearlyByWeekNoAndWeekDay53(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 12, 28, 9, 0), + datetime(2004, 12, 27, 9, 0), + datetime(2009, 12, 28, 9, 0)]) + + def testYearlyByHour(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0), + datetime(1998, 9, 2, 6, 0), + datetime(1998, 9, 2, 18, 0)]) + + def testYearlyByMinute(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6), + datetime(1997, 9, 2, 9, 18), + datetime(1998, 9, 2, 9, 6)]) + + def testYearlyBySecond(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 6), + datetime(1997, 9, 2, 9, 0, 18), + datetime(1998, 9, 2, 9, 0, 6)]) + + def testYearlyByHourAndMinute(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6), + datetime(1997, 9, 2, 18, 18), + datetime(1998, 9, 2, 6, 6)]) + + def testYearlyByHourAndSecond(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0, 6), + datetime(1997, 9, 2, 18, 0, 18), + datetime(1998, 9, 2, 6, 0, 6)]) + + def testYearlyByMinuteAndSecond(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6, 6), + datetime(1997, 9, 2, 9, 6, 18), + datetime(1997, 9, 2, 9, 18, 6)]) + + def testYearlyByHourAndMinuteAndSecond(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6, 6), + datetime(1997, 9, 2, 18, 6, 18), + datetime(1997, 9, 2, 18, 18, 6)]) + + def testYearlyBySetPos(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonthday=15, + byhour=(6, 18), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 11, 15, 18, 0), + datetime(1998, 2, 15, 6, 0), + datetime(1998, 11, 15, 18, 0)]) + + def testMonthly(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 10, 2, 9, 0), + datetime(1997, 11, 2, 9, 0)]) + + def testMonthlyInterval(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 11, 2, 9, 0), + datetime(1998, 1, 2, 9, 0)]) + + def testMonthlyIntervalLarge(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + interval=18, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1999, 3, 2, 9, 0), + datetime(2000, 9, 2, 9, 0)]) + + def testMonthlyByMonth(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 2, 9, 0), + datetime(1998, 3, 2, 9, 0), + datetime(1999, 1, 2, 9, 0)]) + + def testMonthlyByMonthDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 3, 9, 0), + datetime(1997, 10, 1, 9, 0), + datetime(1997, 10, 3, 9, 0)]) + + def testMonthlyByMonthAndMonthDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 5, 9, 0), + datetime(1998, 1, 7, 9, 0), + datetime(1998, 3, 5, 9, 0)]) + + def testMonthlyByWeekDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + # Third Monday of the month + self.assertEqual(rrule(MONTHLY, + byweekday=(MO(+3)), + dtstart=datetime(1997, 9, 1)).between(datetime(1997, 9, 1), + datetime(1997, 12, 1)), + [datetime(1997, 9, 15, 0, 0), + datetime(1997, 10, 20, 0, 0), + datetime(1997, 11, 17, 0, 0)]) + + def testMonthlyByNWeekDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 25, 9, 0), + datetime(1997, 10, 7, 9, 0)]) + + def testMonthlyByNWeekDayLarge(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byweekday=(TU(3), TH(-3)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 11, 9, 0), + datetime(1997, 9, 16, 9, 0), + datetime(1997, 10, 16, 9, 0)]) + + def testMonthlyByMonthAndWeekDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 1, 6, 9, 0), + datetime(1998, 1, 8, 9, 0)]) + + def testMonthlyByMonthAndNWeekDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 6, 9, 0), + datetime(1998, 1, 29, 9, 0), + datetime(1998, 3, 3, 9, 0)]) + + def testMonthlyByMonthAndNWeekDayLarge(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(3), TH(-3)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 15, 9, 0), + datetime(1998, 1, 20, 9, 0), + datetime(1998, 3, 12, 9, 0)]) + + def testMonthlyByMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 2, 3, 9, 0), + datetime(1998, 3, 3, 9, 0)]) + + def testMonthlyByMonthAndMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 3, 3, 9, 0), + datetime(2001, 3, 1, 9, 0)]) + + def testMonthlyByYearDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 9, 0), + datetime(1998, 1, 1, 9, 0), + datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0)]) + + def testMonthlyByYearDayNeg(self): + self.assertEqual(list(rrule(MONTHLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 9, 0), + datetime(1998, 1, 1, 9, 0), + datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0)]) + + def testMonthlyByMonthAndYearDay(self): + self.assertEqual(list(rrule(MONTHLY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0), + datetime(1999, 4, 10, 9, 0), + datetime(1999, 7, 19, 9, 0)]) + + def testMonthlyByMonthAndYearDayNeg(self): + self.assertEqual(list(rrule(MONTHLY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0), + datetime(1999, 4, 10, 9, 0), + datetime(1999, 7, 19, 9, 0)]) + + def testMonthlyByWeekNo(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 5, 11, 9, 0), + datetime(1998, 5, 12, 9, 0), + datetime(1998, 5, 13, 9, 0)]) + + def testMonthlyByWeekNoAndWeekDay(self): + # That's a nice one. The first days of week number one + # may be in the last year. + self.assertEqual(list(rrule(MONTHLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 29, 9, 0), + datetime(1999, 1, 4, 9, 0), + datetime(2000, 1, 3, 9, 0)]) + + def testMonthlyByWeekNoAndWeekDayLarge(self): + # Another nice test. The last days of week number 52/53 + # may be in the next year. + self.assertEqual(list(rrule(MONTHLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 9, 0), + datetime(1998, 12, 27, 9, 0), + datetime(2000, 1, 2, 9, 0)]) + + def testMonthlyByWeekNoAndWeekDayLast(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 9, 0), + datetime(1999, 1, 3, 9, 0), + datetime(2000, 1, 2, 9, 0)]) + + def testMonthlyByWeekNoAndWeekDay53(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 12, 28, 9, 0), + datetime(2004, 12, 27, 9, 0), + datetime(2009, 12, 28, 9, 0)]) + + def testMonthlyByEaster(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 12, 9, 0), + datetime(1999, 4, 4, 9, 0), + datetime(2000, 4, 23, 9, 0)]) + + def testMonthlyByEasterPos(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 13, 9, 0), + datetime(1999, 4, 5, 9, 0), + datetime(2000, 4, 24, 9, 0)]) + + def testMonthlyByEasterNeg(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 11, 9, 0), + datetime(1999, 4, 3, 9, 0), + datetime(2000, 4, 22, 9, 0)]) + + def testMonthlyByHour(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0), + datetime(1997, 10, 2, 6, 0), + datetime(1997, 10, 2, 18, 0)]) + + def testMonthlyByMinute(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6), + datetime(1997, 9, 2, 9, 18), + datetime(1997, 10, 2, 9, 6)]) + + def testMonthlyBySecond(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 6), + datetime(1997, 9, 2, 9, 0, 18), + datetime(1997, 10, 2, 9, 0, 6)]) + + def testMonthlyByHourAndMinute(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6), + datetime(1997, 9, 2, 18, 18), + datetime(1997, 10, 2, 6, 6)]) + + def testMonthlyByHourAndSecond(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0, 6), + datetime(1997, 9, 2, 18, 0, 18), + datetime(1997, 10, 2, 6, 0, 6)]) + + def testMonthlyByMinuteAndSecond(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6, 6), + datetime(1997, 9, 2, 9, 6, 18), + datetime(1997, 9, 2, 9, 18, 6)]) + + def testMonthlyByHourAndMinuteAndSecond(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6, 6), + datetime(1997, 9, 2, 18, 6, 18), + datetime(1997, 9, 2, 18, 18, 6)]) + + def testMonthlyBySetPos(self): + self.assertEqual(list(rrule(MONTHLY, + count=3, + bymonthday=(13, 17), + byhour=(6, 18), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 13, 18, 0), + datetime(1997, 9, 17, 6, 0), + datetime(1997, 10, 13, 18, 0)]) + + def testWeekly(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testWeeklyInterval(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 16, 9, 0), + datetime(1997, 9, 30, 9, 0)]) + + def testWeeklyIntervalLarge(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + interval=20, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1998, 1, 20, 9, 0), + datetime(1998, 6, 9, 9, 0)]) + + def testWeeklyByMonth(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 6, 9, 0), + datetime(1998, 1, 13, 9, 0), + datetime(1998, 1, 20, 9, 0)]) + + def testWeeklyByMonthDay(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 3, 9, 0), + datetime(1997, 10, 1, 9, 0), + datetime(1997, 10, 3, 9, 0)]) + + def testWeeklyByMonthAndMonthDay(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 5, 9, 0), + datetime(1998, 1, 7, 9, 0), + datetime(1998, 3, 5, 9, 0)]) + + def testWeeklyByWeekDay(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testWeeklyByNWeekDay(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testWeeklyByMonthAndWeekDay(self): + # This test is interesting, because it crosses the year + # boundary in a weekly period to find day '1' as a + # valid recurrence. + self.assertEqual(list(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 1, 6, 9, 0), + datetime(1998, 1, 8, 9, 0)]) + + def testWeeklyByMonthAndNWeekDay(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 1, 6, 9, 0), + datetime(1998, 1, 8, 9, 0)]) + + def testWeeklyByMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 2, 3, 9, 0), + datetime(1998, 3, 3, 9, 0)]) + + def testWeeklyByMonthAndMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 3, 3, 9, 0), + datetime(2001, 3, 1, 9, 0)]) + + def testWeeklyByYearDay(self): + self.assertEqual(list(rrule(WEEKLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 9, 0), + datetime(1998, 1, 1, 9, 0), + datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0)]) + + def testWeeklyByYearDayNeg(self): + self.assertEqual(list(rrule(WEEKLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 9, 0), + datetime(1998, 1, 1, 9, 0), + datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0)]) + + def testWeeklyByMonthAndYearDay(self): + self.assertEqual(list(rrule(WEEKLY, + count=4, + bymonth=(1, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 7, 19, 9, 0), + datetime(1999, 1, 1, 9, 0), + datetime(1999, 7, 19, 9, 0)]) + + def testWeeklyByMonthAndYearDayNeg(self): + self.assertEqual(list(rrule(WEEKLY, + count=4, + bymonth=(1, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 7, 19, 9, 0), + datetime(1999, 1, 1, 9, 0), + datetime(1999, 7, 19, 9, 0)]) + + def testWeeklyByWeekNo(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 5, 11, 9, 0), + datetime(1998, 5, 12, 9, 0), + datetime(1998, 5, 13, 9, 0)]) + + def testWeeklyByWeekNoAndWeekDay(self): + # That's a nice one. The first days of week number one + # may be in the last year. + self.assertEqual(list(rrule(WEEKLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 29, 9, 0), + datetime(1999, 1, 4, 9, 0), + datetime(2000, 1, 3, 9, 0)]) + + def testWeeklyByWeekNoAndWeekDayLarge(self): + # Another nice test. The last days of week number 52/53 + # may be in the next year. + self.assertEqual(list(rrule(WEEKLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 9, 0), + datetime(1998, 12, 27, 9, 0), + datetime(2000, 1, 2, 9, 0)]) + + def testWeeklyByWeekNoAndWeekDayLast(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 9, 0), + datetime(1999, 1, 3, 9, 0), + datetime(2000, 1, 2, 9, 0)]) + + def testWeeklyByWeekNoAndWeekDay53(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 12, 28, 9, 0), + datetime(2004, 12, 27, 9, 0), + datetime(2009, 12, 28, 9, 0)]) + + def testWeeklyByEaster(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 12, 9, 0), + datetime(1999, 4, 4, 9, 0), + datetime(2000, 4, 23, 9, 0)]) + + def testWeeklyByEasterPos(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 13, 9, 0), + datetime(1999, 4, 5, 9, 0), + datetime(2000, 4, 24, 9, 0)]) + + def testWeeklyByEasterNeg(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 11, 9, 0), + datetime(1999, 4, 3, 9, 0), + datetime(2000, 4, 22, 9, 0)]) + + def testWeeklyByHour(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0), + datetime(1997, 9, 9, 6, 0), + datetime(1997, 9, 9, 18, 0)]) + + def testWeeklyByMinute(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6), + datetime(1997, 9, 2, 9, 18), + datetime(1997, 9, 9, 9, 6)]) + + def testWeeklyBySecond(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 6), + datetime(1997, 9, 2, 9, 0, 18), + datetime(1997, 9, 9, 9, 0, 6)]) + + def testWeeklyByHourAndMinute(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6), + datetime(1997, 9, 2, 18, 18), + datetime(1997, 9, 9, 6, 6)]) + + def testWeeklyByHourAndSecond(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0, 6), + datetime(1997, 9, 2, 18, 0, 18), + datetime(1997, 9, 9, 6, 0, 6)]) + + def testWeeklyByMinuteAndSecond(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6, 6), + datetime(1997, 9, 2, 9, 6, 18), + datetime(1997, 9, 2, 9, 18, 6)]) + + def testWeeklyByHourAndMinuteAndSecond(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6, 6), + datetime(1997, 9, 2, 18, 6, 18), + datetime(1997, 9, 2, 18, 18, 6)]) + + def testWeeklyBySetPos(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + byweekday=(TU, TH), + byhour=(6, 18), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0), + datetime(1997, 9, 4, 6, 0), + datetime(1997, 9, 9, 18, 0)]) + + def testDaily(self): + self.assertEqual(list(rrule(DAILY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0)]) + + def testDailyInterval(self): + self.assertEqual(list(rrule(DAILY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 6, 9, 0)]) + + def testDailyIntervalLarge(self): + self.assertEqual(list(rrule(DAILY, + count=3, + interval=92, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 12, 3, 9, 0), + datetime(1998, 3, 5, 9, 0)]) + + def testDailyByMonth(self): + self.assertEqual(list(rrule(DAILY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 1, 2, 9, 0), + datetime(1998, 1, 3, 9, 0)]) + + def testDailyByMonthDay(self): + self.assertEqual(list(rrule(DAILY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 3, 9, 0), + datetime(1997, 10, 1, 9, 0), + datetime(1997, 10, 3, 9, 0)]) + + def testDailyByMonthAndMonthDay(self): + self.assertEqual(list(rrule(DAILY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 5, 9, 0), + datetime(1998, 1, 7, 9, 0), + datetime(1998, 3, 5, 9, 0)]) + + def testDailyByWeekDay(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testDailyByNWeekDay(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testDailyByMonthAndWeekDay(self): + self.assertEqual(list(rrule(DAILY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 1, 6, 9, 0), + datetime(1998, 1, 8, 9, 0)]) + + def testDailyByMonthAndNWeekDay(self): + self.assertEqual(list(rrule(DAILY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 1, 6, 9, 0), + datetime(1998, 1, 8, 9, 0)]) + + def testDailyByMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(DAILY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 2, 3, 9, 0), + datetime(1998, 3, 3, 9, 0)]) + + def testDailyByMonthAndMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(DAILY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 3, 3, 9, 0), + datetime(2001, 3, 1, 9, 0)]) + + def testDailyByYearDay(self): + self.assertEqual(list(rrule(DAILY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 9, 0), + datetime(1998, 1, 1, 9, 0), + datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0)]) + + def testDailyByYearDayNeg(self): + self.assertEqual(list(rrule(DAILY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 9, 0), + datetime(1998, 1, 1, 9, 0), + datetime(1998, 4, 10, 9, 0), + datetime(1998, 7, 19, 9, 0)]) + + def testDailyByMonthAndYearDay(self): + self.assertEqual(list(rrule(DAILY, + count=4, + bymonth=(1, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 7, 19, 9, 0), + datetime(1999, 1, 1, 9, 0), + datetime(1999, 7, 19, 9, 0)]) + + def testDailyByMonthAndYearDayNeg(self): + self.assertEqual(list(rrule(DAILY, + count=4, + bymonth=(1, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 9, 0), + datetime(1998, 7, 19, 9, 0), + datetime(1999, 1, 1, 9, 0), + datetime(1999, 7, 19, 9, 0)]) + + def testDailyByWeekNo(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 5, 11, 9, 0), + datetime(1998, 5, 12, 9, 0), + datetime(1998, 5, 13, 9, 0)]) + + def testDailyByWeekNoAndWeekDay(self): + # That's a nice one. The first days of week number one + # may be in the last year. + self.assertEqual(list(rrule(DAILY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 29, 9, 0), + datetime(1999, 1, 4, 9, 0), + datetime(2000, 1, 3, 9, 0)]) + + def testDailyByWeekNoAndWeekDayLarge(self): + # Another nice test. The last days of week number 52/53 + # may be in the next year. + self.assertEqual(list(rrule(DAILY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 9, 0), + datetime(1998, 12, 27, 9, 0), + datetime(2000, 1, 2, 9, 0)]) + + def testDailyByWeekNoAndWeekDayLast(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 9, 0), + datetime(1999, 1, 3, 9, 0), + datetime(2000, 1, 2, 9, 0)]) + + def testDailyByWeekNoAndWeekDay53(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 12, 28, 9, 0), + datetime(2004, 12, 27, 9, 0), + datetime(2009, 12, 28, 9, 0)]) + + def testDailyByEaster(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 12, 9, 0), + datetime(1999, 4, 4, 9, 0), + datetime(2000, 4, 23, 9, 0)]) + + def testDailyByEasterPos(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 13, 9, 0), + datetime(1999, 4, 5, 9, 0), + datetime(2000, 4, 24, 9, 0)]) + + def testDailyByEasterNeg(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 11, 9, 0), + datetime(1999, 4, 3, 9, 0), + datetime(2000, 4, 22, 9, 0)]) + + def testDailyByHour(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0), + datetime(1997, 9, 3, 6, 0), + datetime(1997, 9, 3, 18, 0)]) + + def testDailyByMinute(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6), + datetime(1997, 9, 2, 9, 18), + datetime(1997, 9, 3, 9, 6)]) + + def testDailyBySecond(self): + self.assertEqual(list(rrule(DAILY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 6), + datetime(1997, 9, 2, 9, 0, 18), + datetime(1997, 9, 3, 9, 0, 6)]) + + def testDailyByHourAndMinute(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6), + datetime(1997, 9, 2, 18, 18), + datetime(1997, 9, 3, 6, 6)]) + + def testDailyByHourAndSecond(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0, 6), + datetime(1997, 9, 2, 18, 0, 18), + datetime(1997, 9, 3, 6, 0, 6)]) + + def testDailyByMinuteAndSecond(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6, 6), + datetime(1997, 9, 2, 9, 6, 18), + datetime(1997, 9, 2, 9, 18, 6)]) + + def testDailyByHourAndMinuteAndSecond(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6, 6), + datetime(1997, 9, 2, 18, 6, 18), + datetime(1997, 9, 2, 18, 18, 6)]) + + def testDailyBySetPos(self): + self.assertEqual(list(rrule(DAILY, + count=3, + byhour=(6, 18), + byminute=(15, 45), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 15), + datetime(1997, 9, 3, 6, 45), + datetime(1997, 9, 3, 18, 15)]) + + def testHourly(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 2, 10, 0), + datetime(1997, 9, 2, 11, 0)]) + + def testHourlyInterval(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 2, 11, 0), + datetime(1997, 9, 2, 13, 0)]) + + def testHourlyIntervalLarge(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + interval=769, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 10, 4, 10, 0), + datetime(1997, 11, 5, 11, 0)]) + + def testHourlyByMonth(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 1, 0), + datetime(1998, 1, 1, 2, 0)]) + + def testHourlyByMonthDay(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 3, 0, 0), + datetime(1997, 9, 3, 1, 0), + datetime(1997, 9, 3, 2, 0)]) + + def testHourlyByMonthAndMonthDay(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 5, 0, 0), + datetime(1998, 1, 5, 1, 0), + datetime(1998, 1, 5, 2, 0)]) + + def testHourlyByWeekDay(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 2, 10, 0), + datetime(1997, 9, 2, 11, 0)]) + + def testHourlyByNWeekDay(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 2, 10, 0), + datetime(1997, 9, 2, 11, 0)]) + + def testHourlyByMonthAndWeekDay(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 1, 0), + datetime(1998, 1, 1, 2, 0)]) + + def testHourlyByMonthAndNWeekDay(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 1, 0), + datetime(1998, 1, 1, 2, 0)]) + + def testHourlyByMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 1, 0), + datetime(1998, 1, 1, 2, 0)]) + + def testHourlyByMonthAndMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 1, 0), + datetime(1998, 1, 1, 2, 0)]) + + def testHourlyByYearDay(self): + self.assertEqual(list(rrule(HOURLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 0, 0), + datetime(1997, 12, 31, 1, 0), + datetime(1997, 12, 31, 2, 0), + datetime(1997, 12, 31, 3, 0)]) + + def testHourlyByYearDayNeg(self): + self.assertEqual(list(rrule(HOURLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 0, 0), + datetime(1997, 12, 31, 1, 0), + datetime(1997, 12, 31, 2, 0), + datetime(1997, 12, 31, 3, 0)]) + + def testHourlyByMonthAndYearDay(self): + self.assertEqual(list(rrule(HOURLY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 0, 0), + datetime(1998, 4, 10, 1, 0), + datetime(1998, 4, 10, 2, 0), + datetime(1998, 4, 10, 3, 0)]) + + def testHourlyByMonthAndYearDayNeg(self): + self.assertEqual(list(rrule(HOURLY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 0, 0), + datetime(1998, 4, 10, 1, 0), + datetime(1998, 4, 10, 2, 0), + datetime(1998, 4, 10, 3, 0)]) + + def testHourlyByWeekNo(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 5, 11, 0, 0), + datetime(1998, 5, 11, 1, 0), + datetime(1998, 5, 11, 2, 0)]) + + def testHourlyByWeekNoAndWeekDay(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 29, 0, 0), + datetime(1997, 12, 29, 1, 0), + datetime(1997, 12, 29, 2, 0)]) + + def testHourlyByWeekNoAndWeekDayLarge(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 0, 0), + datetime(1997, 12, 28, 1, 0), + datetime(1997, 12, 28, 2, 0)]) + + def testHourlyByWeekNoAndWeekDayLast(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 0, 0), + datetime(1997, 12, 28, 1, 0), + datetime(1997, 12, 28, 2, 0)]) + + def testHourlyByWeekNoAndWeekDay53(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 12, 28, 0, 0), + datetime(1998, 12, 28, 1, 0), + datetime(1998, 12, 28, 2, 0)]) + + def testHourlyByEaster(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 12, 0, 0), + datetime(1998, 4, 12, 1, 0), + datetime(1998, 4, 12, 2, 0)]) + + def testHourlyByEasterPos(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 13, 0, 0), + datetime(1998, 4, 13, 1, 0), + datetime(1998, 4, 13, 2, 0)]) + + def testHourlyByEasterNeg(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 11, 0, 0), + datetime(1998, 4, 11, 1, 0), + datetime(1998, 4, 11, 2, 0)]) + + def testHourlyByHour(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0), + datetime(1997, 9, 3, 6, 0), + datetime(1997, 9, 3, 18, 0)]) + + def testHourlyByMinute(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6), + datetime(1997, 9, 2, 9, 18), + datetime(1997, 9, 2, 10, 6)]) + + def testHourlyBySecond(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 6), + datetime(1997, 9, 2, 9, 0, 18), + datetime(1997, 9, 2, 10, 0, 6)]) + + def testHourlyByHourAndMinute(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6), + datetime(1997, 9, 2, 18, 18), + datetime(1997, 9, 3, 6, 6)]) + + def testHourlyByHourAndSecond(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0, 6), + datetime(1997, 9, 2, 18, 0, 18), + datetime(1997, 9, 3, 6, 0, 6)]) + + def testHourlyByMinuteAndSecond(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6, 6), + datetime(1997, 9, 2, 9, 6, 18), + datetime(1997, 9, 2, 9, 18, 6)]) + + def testHourlyByHourAndMinuteAndSecond(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6, 6), + datetime(1997, 9, 2, 18, 6, 18), + datetime(1997, 9, 2, 18, 18, 6)]) + + def testHourlyBySetPos(self): + self.assertEqual(list(rrule(HOURLY, + count=3, + byminute=(15, 45), + bysecond=(15, 45), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 15, 45), + datetime(1997, 9, 2, 9, 45, 15), + datetime(1997, 9, 2, 10, 15, 45)]) + + def testMinutely(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 2, 9, 1), + datetime(1997, 9, 2, 9, 2)]) + + def testMinutelyInterval(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 2, 9, 2), + datetime(1997, 9, 2, 9, 4)]) + + def testMinutelyIntervalLarge(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + interval=1501, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 10, 1), + datetime(1997, 9, 4, 11, 2)]) + + def testMinutelyByMonth(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 0, 1), + datetime(1998, 1, 1, 0, 2)]) + + def testMinutelyByMonthDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 3, 0, 0), + datetime(1997, 9, 3, 0, 1), + datetime(1997, 9, 3, 0, 2)]) + + def testMinutelyByMonthAndMonthDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 5, 0, 0), + datetime(1998, 1, 5, 0, 1), + datetime(1998, 1, 5, 0, 2)]) + + def testMinutelyByWeekDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 2, 9, 1), + datetime(1997, 9, 2, 9, 2)]) + + def testMinutelyByNWeekDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 2, 9, 1), + datetime(1997, 9, 2, 9, 2)]) + + def testMinutelyByMonthAndWeekDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 0, 1), + datetime(1998, 1, 1, 0, 2)]) + + def testMinutelyByMonthAndNWeekDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 0, 1), + datetime(1998, 1, 1, 0, 2)]) + + def testMinutelyByMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 0, 1), + datetime(1998, 1, 1, 0, 2)]) + + def testMinutelyByMonthAndMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0), + datetime(1998, 1, 1, 0, 1), + datetime(1998, 1, 1, 0, 2)]) + + def testMinutelyByYearDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 0, 0), + datetime(1997, 12, 31, 0, 1), + datetime(1997, 12, 31, 0, 2), + datetime(1997, 12, 31, 0, 3)]) + + def testMinutelyByYearDayNeg(self): + self.assertEqual(list(rrule(MINUTELY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 0, 0), + datetime(1997, 12, 31, 0, 1), + datetime(1997, 12, 31, 0, 2), + datetime(1997, 12, 31, 0, 3)]) + + def testMinutelyByMonthAndYearDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 0, 0), + datetime(1998, 4, 10, 0, 1), + datetime(1998, 4, 10, 0, 2), + datetime(1998, 4, 10, 0, 3)]) + + def testMinutelyByMonthAndYearDayNeg(self): + self.assertEqual(list(rrule(MINUTELY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 0, 0), + datetime(1998, 4, 10, 0, 1), + datetime(1998, 4, 10, 0, 2), + datetime(1998, 4, 10, 0, 3)]) + + def testMinutelyByWeekNo(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 5, 11, 0, 0), + datetime(1998, 5, 11, 0, 1), + datetime(1998, 5, 11, 0, 2)]) + + def testMinutelyByWeekNoAndWeekDay(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 29, 0, 0), + datetime(1997, 12, 29, 0, 1), + datetime(1997, 12, 29, 0, 2)]) + + def testMinutelyByWeekNoAndWeekDayLarge(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 0, 0), + datetime(1997, 12, 28, 0, 1), + datetime(1997, 12, 28, 0, 2)]) + + def testMinutelyByWeekNoAndWeekDayLast(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 0, 0), + datetime(1997, 12, 28, 0, 1), + datetime(1997, 12, 28, 0, 2)]) + + def testMinutelyByWeekNoAndWeekDay53(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 12, 28, 0, 0), + datetime(1998, 12, 28, 0, 1), + datetime(1998, 12, 28, 0, 2)]) + + def testMinutelyByEaster(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 12, 0, 0), + datetime(1998, 4, 12, 0, 1), + datetime(1998, 4, 12, 0, 2)]) + + def testMinutelyByEasterPos(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 13, 0, 0), + datetime(1998, 4, 13, 0, 1), + datetime(1998, 4, 13, 0, 2)]) + + def testMinutelyByEasterNeg(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 11, 0, 0), + datetime(1998, 4, 11, 0, 1), + datetime(1998, 4, 11, 0, 2)]) + + def testMinutelyByHour(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0), + datetime(1997, 9, 2, 18, 1), + datetime(1997, 9, 2, 18, 2)]) + + def testMinutelyByMinute(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6), + datetime(1997, 9, 2, 9, 18), + datetime(1997, 9, 2, 10, 6)]) + + def testMinutelyBySecond(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 6), + datetime(1997, 9, 2, 9, 0, 18), + datetime(1997, 9, 2, 9, 1, 6)]) + + def testMinutelyByHourAndMinute(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6), + datetime(1997, 9, 2, 18, 18), + datetime(1997, 9, 3, 6, 6)]) + + def testMinutelyByHourAndSecond(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0, 6), + datetime(1997, 9, 2, 18, 0, 18), + datetime(1997, 9, 2, 18, 1, 6)]) + + def testMinutelyByMinuteAndSecond(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6, 6), + datetime(1997, 9, 2, 9, 6, 18), + datetime(1997, 9, 2, 9, 18, 6)]) + + def testMinutelyByHourAndMinuteAndSecond(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6, 6), + datetime(1997, 9, 2, 18, 6, 18), + datetime(1997, 9, 2, 18, 18, 6)]) + + def testMinutelyBySetPos(self): + self.assertEqual(list(rrule(MINUTELY, + count=3, + bysecond=(15, 30, 45), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 15), + datetime(1997, 9, 2, 9, 0, 45), + datetime(1997, 9, 2, 9, 1, 15)]) + + def testSecondly(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 0), + datetime(1997, 9, 2, 9, 0, 1), + datetime(1997, 9, 2, 9, 0, 2)]) + + def testSecondlyInterval(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 0), + datetime(1997, 9, 2, 9, 0, 2), + datetime(1997, 9, 2, 9, 0, 4)]) + + def testSecondlyIntervalLarge(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + interval=90061, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 0), + datetime(1997, 9, 3, 10, 1, 1), + datetime(1997, 9, 4, 11, 2, 2)]) + + def testSecondlyByMonth(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0, 0), + datetime(1998, 1, 1, 0, 0, 1), + datetime(1998, 1, 1, 0, 0, 2)]) + + def testSecondlyByMonthDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 3, 0, 0, 0), + datetime(1997, 9, 3, 0, 0, 1), + datetime(1997, 9, 3, 0, 0, 2)]) + + def testSecondlyByMonthAndMonthDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 5, 0, 0, 0), + datetime(1998, 1, 5, 0, 0, 1), + datetime(1998, 1, 5, 0, 0, 2)]) + + def testSecondlyByWeekDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 0), + datetime(1997, 9, 2, 9, 0, 1), + datetime(1997, 9, 2, 9, 0, 2)]) + + def testSecondlyByNWeekDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 0), + datetime(1997, 9, 2, 9, 0, 1), + datetime(1997, 9, 2, 9, 0, 2)]) + + def testSecondlyByMonthAndWeekDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0, 0), + datetime(1998, 1, 1, 0, 0, 1), + datetime(1998, 1, 1, 0, 0, 2)]) + + def testSecondlyByMonthAndNWeekDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0, 0), + datetime(1998, 1, 1, 0, 0, 1), + datetime(1998, 1, 1, 0, 0, 2)]) + + def testSecondlyByMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0, 0), + datetime(1998, 1, 1, 0, 0, 1), + datetime(1998, 1, 1, 0, 0, 2)]) + + def testSecondlyByMonthAndMonthDayAndWeekDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 1, 0, 0, 0), + datetime(1998, 1, 1, 0, 0, 1), + datetime(1998, 1, 1, 0, 0, 2)]) + + def testSecondlyByYearDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 0, 0, 0), + datetime(1997, 12, 31, 0, 0, 1), + datetime(1997, 12, 31, 0, 0, 2), + datetime(1997, 12, 31, 0, 0, 3)]) + + def testSecondlyByYearDayNeg(self): + self.assertEqual(list(rrule(SECONDLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 31, 0, 0, 0), + datetime(1997, 12, 31, 0, 0, 1), + datetime(1997, 12, 31, 0, 0, 2), + datetime(1997, 12, 31, 0, 0, 3)]) + + def testSecondlyByMonthAndYearDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 0, 0, 0), + datetime(1998, 4, 10, 0, 0, 1), + datetime(1998, 4, 10, 0, 0, 2), + datetime(1998, 4, 10, 0, 0, 3)]) + + def testSecondlyByMonthAndYearDayNeg(self): + self.assertEqual(list(rrule(SECONDLY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 10, 0, 0, 0), + datetime(1998, 4, 10, 0, 0, 1), + datetime(1998, 4, 10, 0, 0, 2), + datetime(1998, 4, 10, 0, 0, 3)]) + + def testSecondlyByWeekNo(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 5, 11, 0, 0, 0), + datetime(1998, 5, 11, 0, 0, 1), + datetime(1998, 5, 11, 0, 0, 2)]) + + def testSecondlyByWeekNoAndWeekDay(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 29, 0, 0, 0), + datetime(1997, 12, 29, 0, 0, 1), + datetime(1997, 12, 29, 0, 0, 2)]) + + def testSecondlyByWeekNoAndWeekDayLarge(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 0, 0, 0), + datetime(1997, 12, 28, 0, 0, 1), + datetime(1997, 12, 28, 0, 0, 2)]) + + def testSecondlyByWeekNoAndWeekDayLast(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 12, 28, 0, 0, 0), + datetime(1997, 12, 28, 0, 0, 1), + datetime(1997, 12, 28, 0, 0, 2)]) + + def testSecondlyByWeekNoAndWeekDay53(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 12, 28, 0, 0, 0), + datetime(1998, 12, 28, 0, 0, 1), + datetime(1998, 12, 28, 0, 0, 2)]) + + def testSecondlyByEaster(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 12, 0, 0, 0), + datetime(1998, 4, 12, 0, 0, 1), + datetime(1998, 4, 12, 0, 0, 2)]) + + def testSecondlyByEasterPos(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 13, 0, 0, 0), + datetime(1998, 4, 13, 0, 0, 1), + datetime(1998, 4, 13, 0, 0, 2)]) + + def testSecondlyByEasterNeg(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 4, 11, 0, 0, 0), + datetime(1998, 4, 11, 0, 0, 1), + datetime(1998, 4, 11, 0, 0, 2)]) + + def testSecondlyByHour(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0, 0), + datetime(1997, 9, 2, 18, 0, 1), + datetime(1997, 9, 2, 18, 0, 2)]) + + def testSecondlyByMinute(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6, 0), + datetime(1997, 9, 2, 9, 6, 1), + datetime(1997, 9, 2, 9, 6, 2)]) + + def testSecondlyBySecond(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0, 6), + datetime(1997, 9, 2, 9, 0, 18), + datetime(1997, 9, 2, 9, 1, 6)]) + + def testSecondlyByHourAndMinute(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6, 0), + datetime(1997, 9, 2, 18, 6, 1), + datetime(1997, 9, 2, 18, 6, 2)]) + + def testSecondlyByHourAndSecond(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 0, 6), + datetime(1997, 9, 2, 18, 0, 18), + datetime(1997, 9, 2, 18, 1, 6)]) + + def testSecondlyByMinuteAndSecond(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 6, 6), + datetime(1997, 9, 2, 9, 6, 18), + datetime(1997, 9, 2, 9, 18, 6)]) + + def testSecondlyByHourAndMinuteAndSecond(self): + self.assertEqual(list(rrule(SECONDLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 18, 6, 6), + datetime(1997, 9, 2, 18, 6, 18), + datetime(1997, 9, 2, 18, 18, 6)]) + + def testSecondlyByHourAndMinuteAndSecondBug(self): + # This explores a bug found by Mathieu Bridon. + self.assertEqual(list(rrule(SECONDLY, + count=3, + bysecond=(0,), + byminute=(1,), + dtstart=datetime(2010, 3, 22, 12, 1))), + [datetime(2010, 3, 22, 12, 1), + datetime(2010, 3, 22, 13, 1), + datetime(2010, 3, 22, 14, 1)]) + + def testLongIntegers(self): if PY2: # There are no longs in python3 - self.assertEqual(list(rrule(MINUTELY, - count=long(2), - interval=long(2), - bymonth=long(2), - byweekday=long(3), - byhour=long(6), - byminute=long(6), - bysecond=long(6), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 2, 5, 6, 6, 6), - datetime(1998, 2, 12, 6, 6, 6)]) - self.assertEqual(list(rrule(YEARLY, - count=long(2), - bymonthday=long(5), - byweekno=long(2), - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1998, 1, 5, 9, 0), - datetime(2004, 1, 5, 9, 0)]) - - def testHourlyBadRRule(self): - """ - When `byhour` is specified with `freq=HOURLY`, there are certain - combinations of `dtstart` and `byhour` which result in an rrule with no - valid values. - - See https://github.com/dateutil/dateutil/issues/4 - """ - - self.assertRaises(ValueError, rrule, HOURLY, - **dict(interval=4, byhour=(7, 11, 15, 19), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testMinutelyBadRRule(self): - """ - See :func:`testHourlyBadRRule` for details. - """ - - self.assertRaises(ValueError, rrule, MINUTELY, - **dict(interval=12, byminute=(10, 11, 25, 39, 50), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testSecondlyBadRRule(self): - """ - See :func:`testHourlyBadRRule` for details. - """ - - self.assertRaises(ValueError, rrule, SECONDLY, - **dict(interval=10, bysecond=(2, 15, 37, 42, 59), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testMinutelyBadComboRRule(self): - """ - Certain values of :param:`interval` in :class:`rrule`, when combined - with certain values of :param:`byhour` create rules which apply to no - valid dates. The library should detect this case in the iterator and - raise a :exception:`ValueError`. - """ - - # In Python 2.7 you can use a context manager for this. - def make_bad_rrule(): - list(rrule(MINUTELY, interval=120, byhour=(10, 12, 14, 16), - count=2, dtstart=datetime(1997, 9, 2, 9, 0))) - - self.assertRaises(ValueError, make_bad_rrule) - - def testSecondlyBadComboRRule(self): - """ - See :func:`testMinutelyBadComboRRule' for details. - """ - - # In Python 2.7 you can use a context manager for this. - def make_bad_minute_rrule(): - list(rrule(SECONDLY, interval=360, byminute=(10, 28, 49), - count=4, dtstart=datetime(1997, 9, 2, 9, 0))) - - def make_bad_hour_rrule(): - list(rrule(SECONDLY, interval=43200, byhour=(2, 10, 18, 23), - count=4, dtstart=datetime(1997, 9, 2, 9, 0))) - - self.assertRaises(ValueError, make_bad_minute_rrule) - self.assertRaises(ValueError, make_bad_hour_rrule) - - def testBadUntilCountRRule(self): - """ - See rfc-5545 3.3.10 - This checks for the deprecation warning, and will - eventually check for an error. - """ + self.assertEqual(list(rrule(MINUTELY, + count=long(2), + interval=long(2), + bymonth=long(2), + byweekday=long(3), + byhour=long(6), + byminute=long(6), + bysecond=long(6), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 2, 5, 6, 6, 6), + datetime(1998, 2, 12, 6, 6, 6)]) + self.assertEqual(list(rrule(YEARLY, + count=long(2), + bymonthday=long(5), + byweekno=long(2), + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1998, 1, 5, 9, 0), + datetime(2004, 1, 5, 9, 0)]) + + def testHourlyBadRRule(self): + """ + When `byhour` is specified with `freq=HOURLY`, there are certain + combinations of `dtstart` and `byhour` which result in an rrule with no + valid values. + + See https://github.com/dateutil/dateutil/issues/4 + """ + + self.assertRaises(ValueError, rrule, HOURLY, + **dict(interval=4, byhour=(7, 11, 15, 19), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testMinutelyBadRRule(self): + """ + See :func:`testHourlyBadRRule` for details. + """ + + self.assertRaises(ValueError, rrule, MINUTELY, + **dict(interval=12, byminute=(10, 11, 25, 39, 50), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testSecondlyBadRRule(self): + """ + See :func:`testHourlyBadRRule` for details. + """ + + self.assertRaises(ValueError, rrule, SECONDLY, + **dict(interval=10, bysecond=(2, 15, 37, 42, 59), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testMinutelyBadComboRRule(self): + """ + Certain values of :param:`interval` in :class:`rrule`, when combined + with certain values of :param:`byhour` create rules which apply to no + valid dates. The library should detect this case in the iterator and + raise a :exception:`ValueError`. + """ + + # In Python 2.7 you can use a context manager for this. + def make_bad_rrule(): + list(rrule(MINUTELY, interval=120, byhour=(10, 12, 14, 16), + count=2, dtstart=datetime(1997, 9, 2, 9, 0))) + + self.assertRaises(ValueError, make_bad_rrule) + + def testSecondlyBadComboRRule(self): + """ + See :func:`testMinutelyBadComboRRule' for details. + """ + + # In Python 2.7 you can use a context manager for this. + def make_bad_minute_rrule(): + list(rrule(SECONDLY, interval=360, byminute=(10, 28, 49), + count=4, dtstart=datetime(1997, 9, 2, 9, 0))) + + def make_bad_hour_rrule(): + list(rrule(SECONDLY, interval=43200, byhour=(2, 10, 18, 23), + count=4, dtstart=datetime(1997, 9, 2, 9, 0))) + + self.assertRaises(ValueError, make_bad_minute_rrule) + self.assertRaises(ValueError, make_bad_hour_rrule) + + def testBadUntilCountRRule(self): + """ + See rfc-5545 3.3.10 - This checks for the deprecation warning, and will + eventually check for an error. + """ with pytest.warns(DeprecationWarning): - rrule(DAILY, dtstart=datetime(1997, 9, 2, 9, 0), - count=3, until=datetime(1997, 9, 4, 9, 0)) - - def testUntilNotMatching(self): - self.assertEqual(list(rrule(DAILY, - dtstart=datetime(1997, 9, 2, 9, 0), - until=datetime(1997, 9, 5, 8, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0)]) - - def testUntilMatching(self): - self.assertEqual(list(rrule(DAILY, - dtstart=datetime(1997, 9, 2, 9, 0), - until=datetime(1997, 9, 4, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0)]) - - def testUntilSingle(self): - self.assertEqual(list(rrule(DAILY, - dtstart=datetime(1997, 9, 2, 9, 0), - until=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0)]) - - def testUntilEmpty(self): - self.assertEqual(list(rrule(DAILY, - dtstart=datetime(1997, 9, 2, 9, 0), - until=datetime(1997, 9, 1, 9, 0))), - []) - - def testUntilWithDate(self): - self.assertEqual(list(rrule(DAILY, - dtstart=datetime(1997, 9, 2, 9, 0), - until=date(1997, 9, 5))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0)]) - - def testWkStIntervalMO(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - interval=2, - byweekday=(TU, SU), - wkst=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 7, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testWkStIntervalSU(self): - self.assertEqual(list(rrule(WEEKLY, - count=3, - interval=2, - byweekday=(TU, SU), - wkst=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 14, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testDTStartIsDate(self): - self.assertEqual(list(rrule(DAILY, - count=3, - dtstart=date(1997, 9, 2))), - [datetime(1997, 9, 2, 0, 0), - datetime(1997, 9, 3, 0, 0), - datetime(1997, 9, 4, 0, 0)]) - - def testDTStartWithMicroseconds(self): - self.assertEqual(list(rrule(DAILY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0, 0, 500000))), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0)]) - - def testMaxYear(self): - self.assertEqual(list(rrule(YEARLY, - count=3, - bymonth=2, - bymonthday=31, - dtstart=datetime(9997, 9, 2, 9, 0, 0))), - []) - - def testGetItem(self): - self.assertEqual(rrule(DAILY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[0], - datetime(1997, 9, 2, 9, 0)) - - def testGetItemNeg(self): - self.assertEqual(rrule(DAILY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[-1], - datetime(1997, 9, 4, 9, 0)) - - def testGetItemSlice(self): - self.assertEqual(rrule(DAILY, - # count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[1:2], - [datetime(1997, 9, 3, 9, 0)]) - - def testGetItemSliceEmpty(self): - self.assertEqual(rrule(DAILY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[:], - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0)]) - - def testGetItemSliceStep(self): - self.assertEqual(rrule(DAILY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[::-2], - [datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 2, 9, 0)]) - - def testCount(self): - self.assertEqual(rrule(DAILY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0)).count(), - 3) - - def testCountZero(self): - self.assertEqual(rrule(YEARLY, - count=0, - dtstart=datetime(1997, 9, 2, 9, 0)).count(), - 0) - - def testContains(self): - rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) - self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) - - def testContainsNot(self): - rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) - self.assertEqual(datetime(1997, 9, 3, 9, 0) not in rr, False) - - def testBefore(self): - self.assertEqual(rrule(DAILY, # count=5 - dtstart=datetime(1997, 9, 2, 9, 0)).before(datetime(1997, 9, 5, 9, 0)), - datetime(1997, 9, 4, 9, 0)) - - def testBeforeInc(self): - self.assertEqual(rrule(DAILY, - #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .before(datetime(1997, 9, 5, 9, 0), inc=True), - datetime(1997, 9, 5, 9, 0)) - - def testAfter(self): - self.assertEqual(rrule(DAILY, - #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .after(datetime(1997, 9, 4, 9, 0)), - datetime(1997, 9, 5, 9, 0)) - - def testAfterInc(self): - self.assertEqual(rrule(DAILY, - #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .after(datetime(1997, 9, 4, 9, 0), inc=True), - datetime(1997, 9, 4, 9, 0)) - - def testXAfter(self): - self.assertEqual(list(rrule(DAILY, - dtstart=datetime(1997, 9, 2, 9, 0)) - .xafter(datetime(1997, 9, 8, 9, 0), count=12)), - [datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 10, 9, 0), - datetime(1997, 9, 11, 9, 0), - datetime(1997, 9, 12, 9, 0), - datetime(1997, 9, 13, 9, 0), - datetime(1997, 9, 14, 9, 0), - datetime(1997, 9, 15, 9, 0), - datetime(1997, 9, 16, 9, 0), - datetime(1997, 9, 17, 9, 0), - datetime(1997, 9, 18, 9, 0), - datetime(1997, 9, 19, 9, 0), - datetime(1997, 9, 20, 9, 0)]) - - def testXAfterInc(self): - self.assertEqual(list(rrule(DAILY, - dtstart=datetime(1997, 9, 2, 9, 0)) - .xafter(datetime(1997, 9, 8, 9, 0), count=12, inc=True)), - [datetime(1997, 9, 8, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 10, 9, 0), - datetime(1997, 9, 11, 9, 0), - datetime(1997, 9, 12, 9, 0), - datetime(1997, 9, 13, 9, 0), - datetime(1997, 9, 14, 9, 0), - datetime(1997, 9, 15, 9, 0), - datetime(1997, 9, 16, 9, 0), - datetime(1997, 9, 17, 9, 0), - datetime(1997, 9, 18, 9, 0), - datetime(1997, 9, 19, 9, 0)]) - - def testBetween(self): - self.assertEqual(rrule(DAILY, - #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .between(datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 6, 9, 0)), - [datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 5, 9, 0)]) - - def testBetweenInc(self): - self.assertEqual(rrule(DAILY, - #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .between(datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 6, 9, 0), inc=True), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 5, 9, 0), - datetime(1997, 9, 6, 9, 0)]) - - def testCachePre(self): - rr = rrule(DAILY, count=15, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) - self.assertEqual(list(rr), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 5, 9, 0), - datetime(1997, 9, 6, 9, 0), - datetime(1997, 9, 7, 9, 0), - datetime(1997, 9, 8, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 10, 9, 0), - datetime(1997, 9, 11, 9, 0), - datetime(1997, 9, 12, 9, 0), - datetime(1997, 9, 13, 9, 0), - datetime(1997, 9, 14, 9, 0), - datetime(1997, 9, 15, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testCachePost(self): - rr = rrule(DAILY, count=15, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) - for x in rr: pass - self.assertEqual(list(rr), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 5, 9, 0), - datetime(1997, 9, 6, 9, 0), - datetime(1997, 9, 7, 9, 0), - datetime(1997, 9, 8, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 10, 9, 0), - datetime(1997, 9, 11, 9, 0), - datetime(1997, 9, 12, 9, 0), - datetime(1997, 9, 13, 9, 0), - datetime(1997, 9, 14, 9, 0), - datetime(1997, 9, 15, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testCachePostInternal(self): - rr = rrule(DAILY, count=15, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) - for x in rr: pass - self.assertEqual(rr._cache, - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 3, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 5, 9, 0), - datetime(1997, 9, 6, 9, 0), - datetime(1997, 9, 7, 9, 0), - datetime(1997, 9, 8, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 10, 9, 0), - datetime(1997, 9, 11, 9, 0), - datetime(1997, 9, 12, 9, 0), - datetime(1997, 9, 13, 9, 0), - datetime(1997, 9, 14, 9, 0), - datetime(1997, 9, 15, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testCachePreContains(self): - rr = rrule(DAILY, count=3, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) - self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) - - def testCachePostContains(self): - rr = rrule(DAILY, count=3, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) - for x in rr: pass - self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) - - def testStr(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=3\n" - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1998, 9, 2, 9, 0), - datetime(1999, 9, 2, 9, 0)]) - - def testStrWithTZID(self): - NYC = tz.gettz('America/New_York') - self.assertEqual(list(rrulestr( - "DTSTART;TZID=America/New_York:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=3\n" - )), - [datetime(1997, 9, 2, 9, 0, tzinfo=NYC), - datetime(1998, 9, 2, 9, 0, tzinfo=NYC), - datetime(1999, 9, 2, 9, 0, tzinfo=NYC)]) - - def testStrWithTZIDMapping(self): - rrstr = ("DTSTART;TZID=Eastern:19970902T090000\n" + - "RRULE:FREQ=YEARLY;COUNT=3") - - NYC = tz.gettz('America/New_York') - rr = rrulestr(rrstr, tzids={'Eastern': NYC}) - exp = [datetime(1997, 9, 2, 9, 0, tzinfo=NYC), - datetime(1998, 9, 2, 9, 0, tzinfo=NYC), - datetime(1999, 9, 2, 9, 0, tzinfo=NYC)] - - self.assertEqual(list(rr), exp) - - def testStrWithTZIDCallable(self): - rrstr = ('DTSTART;TZID=UTC+04:19970902T090000\n' + - 'RRULE:FREQ=YEARLY;COUNT=3') - - TZ = tz.tzstr('UTC+04') - def parse_tzstr(tzstr): - if tzstr is None: - raise ValueError('Invalid tzstr') - - return tz.tzstr(tzstr) - - rr = rrulestr(rrstr, tzids=parse_tzstr) - - exp = [datetime(1997, 9, 2, 9, 0, tzinfo=TZ), - datetime(1998, 9, 2, 9, 0, tzinfo=TZ), - datetime(1999, 9, 2, 9, 0, tzinfo=TZ),] - - self.assertEqual(list(rr), exp) - - def testStrWithTZIDCallableFailure(self): - rrstr = ('DTSTART;TZID=America/New_York:19970902T090000\n' + - 'RRULE:FREQ=YEARLY;COUNT=3') - - class TzInfoError(Exception): - pass - - def tzinfos(tzstr): - if tzstr == 'America/New_York': - raise TzInfoError('Invalid!') - return None - - with self.assertRaises(TzInfoError): - rrulestr(rrstr, tzids=tzinfos) - - def testStrWithConflictingTZID(self): - # RFC 5545 Section 3.3.5, FORM #2: DATE WITH UTC TIME - # https://tools.ietf.org/html/rfc5545#section-3.3.5 - # The "TZID" property parameter MUST NOT be applied to DATE-TIME - with self.assertRaises(ValueError): - rrulestr("DTSTART;TZID=America/New_York:19970902T090000Z\n"+ - "RRULE:FREQ=YEARLY;COUNT=3\n") - - def testStrType(self): - self.assertEqual(isinstance(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=3\n" - ), rrule), True) - - def testStrForceSetType(self): - self.assertEqual(isinstance(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=3\n" - , forceset=True), rruleset), True) - - def testStrSetType(self): - self.assertEqual(isinstance(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU\n" - "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH\n" - ), rruleset), True) - - def testStrCase(self): - self.assertEqual(list(rrulestr( - "dtstart:19970902T090000\n" - "rrule:freq=yearly;count=3\n" - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1998, 9, 2, 9, 0), - datetime(1999, 9, 2, 9, 0)]) - - def testStrSpaces(self): - self.assertEqual(list(rrulestr( - " DTSTART:19970902T090000 " - " RRULE:FREQ=YEARLY;COUNT=3 " - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1998, 9, 2, 9, 0), - datetime(1999, 9, 2, 9, 0)]) - - def testStrSpacesAndLines(self): - self.assertEqual(list(rrulestr( - " DTSTART:19970902T090000 \n" - " \n" - " RRULE:FREQ=YEARLY;COUNT=3 \n" - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1998, 9, 2, 9, 0), - datetime(1999, 9, 2, 9, 0)]) - - def testStrNoDTStart(self): - self.assertEqual(list(rrulestr( - "RRULE:FREQ=YEARLY;COUNT=3\n" - , dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1998, 9, 2, 9, 0), - datetime(1999, 9, 2, 9, 0)]) - - def testStrValueOnly(self): - self.assertEqual(list(rrulestr( - "FREQ=YEARLY;COUNT=3\n" - , dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1998, 9, 2, 9, 0), - datetime(1999, 9, 2, 9, 0)]) - - def testStrUnfold(self): - self.assertEqual(list(rrulestr( - "FREQ=YEA\n RLY;COUNT=3\n", unfold=True, - dtstart=datetime(1997, 9, 2, 9, 0))), - [datetime(1997, 9, 2, 9, 0), - datetime(1998, 9, 2, 9, 0), - datetime(1999, 9, 2, 9, 0)]) - - def testStrSet(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU\n" - "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH\n" - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testStrSetDate(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TU\n" - "RDATE:19970904T090000\n" - "RDATE:19970909T090000\n" - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testStrSetExRule(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n" - "EXRULE:FREQ=YEARLY;COUNT=3;BYDAY=TH\n" - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testStrSetExDate(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n" - "EXDATE:19970904T090000\n" - "EXDATE:19970911T090000\n" - "EXDATE:19970918T090000\n" - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - + rrule(DAILY, dtstart=datetime(1997, 9, 2, 9, 0), + count=3, until=datetime(1997, 9, 4, 9, 0)) + + def testUntilNotMatching(self): + self.assertEqual(list(rrule(DAILY, + dtstart=datetime(1997, 9, 2, 9, 0), + until=datetime(1997, 9, 5, 8, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0)]) + + def testUntilMatching(self): + self.assertEqual(list(rrule(DAILY, + dtstart=datetime(1997, 9, 2, 9, 0), + until=datetime(1997, 9, 4, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0)]) + + def testUntilSingle(self): + self.assertEqual(list(rrule(DAILY, + dtstart=datetime(1997, 9, 2, 9, 0), + until=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0)]) + + def testUntilEmpty(self): + self.assertEqual(list(rrule(DAILY, + dtstart=datetime(1997, 9, 2, 9, 0), + until=datetime(1997, 9, 1, 9, 0))), + []) + + def testUntilWithDate(self): + self.assertEqual(list(rrule(DAILY, + dtstart=datetime(1997, 9, 2, 9, 0), + until=date(1997, 9, 5))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0)]) + + def testWkStIntervalMO(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + interval=2, + byweekday=(TU, SU), + wkst=MO, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 7, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testWkStIntervalSU(self): + self.assertEqual(list(rrule(WEEKLY, + count=3, + interval=2, + byweekday=(TU, SU), + wkst=SU, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 14, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testDTStartIsDate(self): + self.assertEqual(list(rrule(DAILY, + count=3, + dtstart=date(1997, 9, 2))), + [datetime(1997, 9, 2, 0, 0), + datetime(1997, 9, 3, 0, 0), + datetime(1997, 9, 4, 0, 0)]) + + def testDTStartWithMicroseconds(self): + self.assertEqual(list(rrule(DAILY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0, 0, 500000))), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0)]) + + def testMaxYear(self): + self.assertEqual(list(rrule(YEARLY, + count=3, + bymonth=2, + bymonthday=31, + dtstart=datetime(9997, 9, 2, 9, 0, 0))), + []) + + def testGetItem(self): + self.assertEqual(rrule(DAILY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))[0], + datetime(1997, 9, 2, 9, 0)) + + def testGetItemNeg(self): + self.assertEqual(rrule(DAILY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))[-1], + datetime(1997, 9, 4, 9, 0)) + + def testGetItemSlice(self): + self.assertEqual(rrule(DAILY, + # count=3, + dtstart=datetime(1997, 9, 2, 9, 0))[1:2], + [datetime(1997, 9, 3, 9, 0)]) + + def testGetItemSliceEmpty(self): + self.assertEqual(rrule(DAILY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))[:], + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0)]) + + def testGetItemSliceStep(self): + self.assertEqual(rrule(DAILY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))[::-2], + [datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 2, 9, 0)]) + + def testCount(self): + self.assertEqual(rrule(DAILY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0)).count(), + 3) + + def testCountZero(self): + self.assertEqual(rrule(YEARLY, + count=0, + dtstart=datetime(1997, 9, 2, 9, 0)).count(), + 0) + + def testContains(self): + rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) + self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) + + def testContainsNot(self): + rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) + self.assertEqual(datetime(1997, 9, 3, 9, 0) not in rr, False) + + def testBefore(self): + self.assertEqual(rrule(DAILY, # count=5 + dtstart=datetime(1997, 9, 2, 9, 0)).before(datetime(1997, 9, 5, 9, 0)), + datetime(1997, 9, 4, 9, 0)) + + def testBeforeInc(self): + self.assertEqual(rrule(DAILY, + #count=5, + dtstart=datetime(1997, 9, 2, 9, 0)) + .before(datetime(1997, 9, 5, 9, 0), inc=True), + datetime(1997, 9, 5, 9, 0)) + + def testAfter(self): + self.assertEqual(rrule(DAILY, + #count=5, + dtstart=datetime(1997, 9, 2, 9, 0)) + .after(datetime(1997, 9, 4, 9, 0)), + datetime(1997, 9, 5, 9, 0)) + + def testAfterInc(self): + self.assertEqual(rrule(DAILY, + #count=5, + dtstart=datetime(1997, 9, 2, 9, 0)) + .after(datetime(1997, 9, 4, 9, 0), inc=True), + datetime(1997, 9, 4, 9, 0)) + + def testXAfter(self): + self.assertEqual(list(rrule(DAILY, + dtstart=datetime(1997, 9, 2, 9, 0)) + .xafter(datetime(1997, 9, 8, 9, 0), count=12)), + [datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 10, 9, 0), + datetime(1997, 9, 11, 9, 0), + datetime(1997, 9, 12, 9, 0), + datetime(1997, 9, 13, 9, 0), + datetime(1997, 9, 14, 9, 0), + datetime(1997, 9, 15, 9, 0), + datetime(1997, 9, 16, 9, 0), + datetime(1997, 9, 17, 9, 0), + datetime(1997, 9, 18, 9, 0), + datetime(1997, 9, 19, 9, 0), + datetime(1997, 9, 20, 9, 0)]) + + def testXAfterInc(self): + self.assertEqual(list(rrule(DAILY, + dtstart=datetime(1997, 9, 2, 9, 0)) + .xafter(datetime(1997, 9, 8, 9, 0), count=12, inc=True)), + [datetime(1997, 9, 8, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 10, 9, 0), + datetime(1997, 9, 11, 9, 0), + datetime(1997, 9, 12, 9, 0), + datetime(1997, 9, 13, 9, 0), + datetime(1997, 9, 14, 9, 0), + datetime(1997, 9, 15, 9, 0), + datetime(1997, 9, 16, 9, 0), + datetime(1997, 9, 17, 9, 0), + datetime(1997, 9, 18, 9, 0), + datetime(1997, 9, 19, 9, 0)]) + + def testBetween(self): + self.assertEqual(rrule(DAILY, + #count=5, + dtstart=datetime(1997, 9, 2, 9, 0)) + .between(datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 6, 9, 0)), + [datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 5, 9, 0)]) + + def testBetweenInc(self): + self.assertEqual(rrule(DAILY, + #count=5, + dtstart=datetime(1997, 9, 2, 9, 0)) + .between(datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 6, 9, 0), inc=True), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 5, 9, 0), + datetime(1997, 9, 6, 9, 0)]) + + def testCachePre(self): + rr = rrule(DAILY, count=15, cache=True, + dtstart=datetime(1997, 9, 2, 9, 0)) + self.assertEqual(list(rr), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 5, 9, 0), + datetime(1997, 9, 6, 9, 0), + datetime(1997, 9, 7, 9, 0), + datetime(1997, 9, 8, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 10, 9, 0), + datetime(1997, 9, 11, 9, 0), + datetime(1997, 9, 12, 9, 0), + datetime(1997, 9, 13, 9, 0), + datetime(1997, 9, 14, 9, 0), + datetime(1997, 9, 15, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testCachePost(self): + rr = rrule(DAILY, count=15, cache=True, + dtstart=datetime(1997, 9, 2, 9, 0)) + for x in rr: pass + self.assertEqual(list(rr), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 5, 9, 0), + datetime(1997, 9, 6, 9, 0), + datetime(1997, 9, 7, 9, 0), + datetime(1997, 9, 8, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 10, 9, 0), + datetime(1997, 9, 11, 9, 0), + datetime(1997, 9, 12, 9, 0), + datetime(1997, 9, 13, 9, 0), + datetime(1997, 9, 14, 9, 0), + datetime(1997, 9, 15, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testCachePostInternal(self): + rr = rrule(DAILY, count=15, cache=True, + dtstart=datetime(1997, 9, 2, 9, 0)) + for x in rr: pass + self.assertEqual(rr._cache, + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 3, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 5, 9, 0), + datetime(1997, 9, 6, 9, 0), + datetime(1997, 9, 7, 9, 0), + datetime(1997, 9, 8, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 10, 9, 0), + datetime(1997, 9, 11, 9, 0), + datetime(1997, 9, 12, 9, 0), + datetime(1997, 9, 13, 9, 0), + datetime(1997, 9, 14, 9, 0), + datetime(1997, 9, 15, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testCachePreContains(self): + rr = rrule(DAILY, count=3, cache=True, + dtstart=datetime(1997, 9, 2, 9, 0)) + self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) + + def testCachePostContains(self): + rr = rrule(DAILY, count=3, cache=True, + dtstart=datetime(1997, 9, 2, 9, 0)) + for x in rr: pass + self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) + + def testStr(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=3\n" + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1998, 9, 2, 9, 0), + datetime(1999, 9, 2, 9, 0)]) + + def testStrWithTZID(self): + NYC = tz.gettz('America/New_York') + self.assertEqual(list(rrulestr( + "DTSTART;TZID=America/New_York:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=3\n" + )), + [datetime(1997, 9, 2, 9, 0, tzinfo=NYC), + datetime(1998, 9, 2, 9, 0, tzinfo=NYC), + datetime(1999, 9, 2, 9, 0, tzinfo=NYC)]) + + def testStrWithTZIDMapping(self): + rrstr = ("DTSTART;TZID=Eastern:19970902T090000\n" + + "RRULE:FREQ=YEARLY;COUNT=3") + + NYC = tz.gettz('America/New_York') + rr = rrulestr(rrstr, tzids={'Eastern': NYC}) + exp = [datetime(1997, 9, 2, 9, 0, tzinfo=NYC), + datetime(1998, 9, 2, 9, 0, tzinfo=NYC), + datetime(1999, 9, 2, 9, 0, tzinfo=NYC)] + + self.assertEqual(list(rr), exp) + + def testStrWithTZIDCallable(self): + rrstr = ('DTSTART;TZID=UTC+04:19970902T090000\n' + + 'RRULE:FREQ=YEARLY;COUNT=3') + + TZ = tz.tzstr('UTC+04') + def parse_tzstr(tzstr): + if tzstr is None: + raise ValueError('Invalid tzstr') + + return tz.tzstr(tzstr) + + rr = rrulestr(rrstr, tzids=parse_tzstr) + + exp = [datetime(1997, 9, 2, 9, 0, tzinfo=TZ), + datetime(1998, 9, 2, 9, 0, tzinfo=TZ), + datetime(1999, 9, 2, 9, 0, tzinfo=TZ),] + + self.assertEqual(list(rr), exp) + + def testStrWithTZIDCallableFailure(self): + rrstr = ('DTSTART;TZID=America/New_York:19970902T090000\n' + + 'RRULE:FREQ=YEARLY;COUNT=3') + + class TzInfoError(Exception): + pass + + def tzinfos(tzstr): + if tzstr == 'America/New_York': + raise TzInfoError('Invalid!') + return None + + with self.assertRaises(TzInfoError): + rrulestr(rrstr, tzids=tzinfos) + + def testStrWithConflictingTZID(self): + # RFC 5545 Section 3.3.5, FORM #2: DATE WITH UTC TIME + # https://tools.ietf.org/html/rfc5545#section-3.3.5 + # The "TZID" property parameter MUST NOT be applied to DATE-TIME + with self.assertRaises(ValueError): + rrulestr("DTSTART;TZID=America/New_York:19970902T090000Z\n"+ + "RRULE:FREQ=YEARLY;COUNT=3\n") + + def testStrType(self): + self.assertEqual(isinstance(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=3\n" + ), rrule), True) + + def testStrForceSetType(self): + self.assertEqual(isinstance(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=3\n" + , forceset=True), rruleset), True) + + def testStrSetType(self): + self.assertEqual(isinstance(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU\n" + "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH\n" + ), rruleset), True) + + def testStrCase(self): + self.assertEqual(list(rrulestr( + "dtstart:19970902T090000\n" + "rrule:freq=yearly;count=3\n" + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1998, 9, 2, 9, 0), + datetime(1999, 9, 2, 9, 0)]) + + def testStrSpaces(self): + self.assertEqual(list(rrulestr( + " DTSTART:19970902T090000 " + " RRULE:FREQ=YEARLY;COUNT=3 " + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1998, 9, 2, 9, 0), + datetime(1999, 9, 2, 9, 0)]) + + def testStrSpacesAndLines(self): + self.assertEqual(list(rrulestr( + " DTSTART:19970902T090000 \n" + " \n" + " RRULE:FREQ=YEARLY;COUNT=3 \n" + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1998, 9, 2, 9, 0), + datetime(1999, 9, 2, 9, 0)]) + + def testStrNoDTStart(self): + self.assertEqual(list(rrulestr( + "RRULE:FREQ=YEARLY;COUNT=3\n" + , dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1998, 9, 2, 9, 0), + datetime(1999, 9, 2, 9, 0)]) + + def testStrValueOnly(self): + self.assertEqual(list(rrulestr( + "FREQ=YEARLY;COUNT=3\n" + , dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1998, 9, 2, 9, 0), + datetime(1999, 9, 2, 9, 0)]) + + def testStrUnfold(self): + self.assertEqual(list(rrulestr( + "FREQ=YEA\n RLY;COUNT=3\n", unfold=True, + dtstart=datetime(1997, 9, 2, 9, 0))), + [datetime(1997, 9, 2, 9, 0), + datetime(1998, 9, 2, 9, 0), + datetime(1999, 9, 2, 9, 0)]) + + def testStrSet(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU\n" + "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH\n" + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testStrSetDate(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TU\n" + "RDATE:19970904T090000\n" + "RDATE:19970909T090000\n" + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testStrSetExRule(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n" + "EXRULE:FREQ=YEARLY;COUNT=3;BYDAY=TH\n" + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testStrSetExDate(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n" + "EXDATE:19970904T090000\n" + "EXDATE:19970911T090000\n" + "EXDATE:19970918T090000\n" + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + def testStrSetExDateMultiple(self): rrstr = ("DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n" @@ -2920,1995 +2920,1995 @@ class RRuleTest(unittest.TestCase): rr = rrulestr(rrstr) assert list(rr) == [datetime(1997, 9, 4), datetime(1997, 9, 11)] - def testStrSetDateAndExDate(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RDATE:19970902T090000\n" - "RDATE:19970904T090000\n" - "RDATE:19970909T090000\n" - "RDATE:19970911T090000\n" - "RDATE:19970916T090000\n" - "RDATE:19970918T090000\n" - "EXDATE:19970904T090000\n" - "EXDATE:19970911T090000\n" - "EXDATE:19970918T090000\n" - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testStrSetDateAndExRule(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RDATE:19970902T090000\n" - "RDATE:19970904T090000\n" - "RDATE:19970909T090000\n" - "RDATE:19970911T090000\n" - "RDATE:19970916T090000\n" - "RDATE:19970918T090000\n" - "EXRULE:FREQ=YEARLY;COUNT=3;BYDAY=TH\n" - )), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testStrKeywords(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=3;" - "BYMONTH=3;BYWEEKDAY=TH;BYMONTHDAY=3;" - "BYHOUR=3;BYMINUTE=3;BYSECOND=3\n" - )), - [datetime(2033, 3, 3, 3, 3, 3), - datetime(2039, 3, 3, 3, 3, 3), - datetime(2072, 3, 3, 3, 3, 3)]) - - def testStrNWeekDay(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=3;BYDAY=1TU,-1TH\n" - )), - [datetime(1997, 12, 25, 9, 0), - datetime(1998, 1, 6, 9, 0), - datetime(1998, 12, 31, 9, 0)]) - - def testStrUntil(self): - self.assertEqual(list(rrulestr( - "DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;" - "UNTIL=19990101T000000;BYDAY=1TU,-1TH\n" - )), - [datetime(1997, 12, 25, 9, 0), - datetime(1998, 1, 6, 9, 0), - datetime(1998, 12, 31, 9, 0)]) - - def testStrValueDatetime(self): - rr = rrulestr("DTSTART;VALUE=DATE-TIME:19970902T090000\n" - "RRULE:FREQ=YEARLY;COUNT=2") - - self.assertEqual(list(rr), [datetime(1997, 9, 2, 9, 0, 0), - datetime(1998, 9, 2, 9, 0, 0)]) - - def testStrValueDate(self): - rr = rrulestr("DTSTART;VALUE=DATE:19970902\n" - "RRULE:FREQ=YEARLY;COUNT=2") - - self.assertEqual(list(rr), [datetime(1997, 9, 2, 0, 0, 0), - datetime(1998, 9, 2, 0, 0, 0)]) - + def testStrSetDateAndExDate(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RDATE:19970902T090000\n" + "RDATE:19970904T090000\n" + "RDATE:19970909T090000\n" + "RDATE:19970911T090000\n" + "RDATE:19970916T090000\n" + "RDATE:19970918T090000\n" + "EXDATE:19970904T090000\n" + "EXDATE:19970911T090000\n" + "EXDATE:19970918T090000\n" + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testStrSetDateAndExRule(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RDATE:19970902T090000\n" + "RDATE:19970904T090000\n" + "RDATE:19970909T090000\n" + "RDATE:19970911T090000\n" + "RDATE:19970916T090000\n" + "RDATE:19970918T090000\n" + "EXRULE:FREQ=YEARLY;COUNT=3;BYDAY=TH\n" + )), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testStrKeywords(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=3;" + "BYMONTH=3;BYWEEKDAY=TH;BYMONTHDAY=3;" + "BYHOUR=3;BYMINUTE=3;BYSECOND=3\n" + )), + [datetime(2033, 3, 3, 3, 3, 3), + datetime(2039, 3, 3, 3, 3, 3), + datetime(2072, 3, 3, 3, 3, 3)]) + + def testStrNWeekDay(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=3;BYDAY=1TU,-1TH\n" + )), + [datetime(1997, 12, 25, 9, 0), + datetime(1998, 1, 6, 9, 0), + datetime(1998, 12, 31, 9, 0)]) + + def testStrUntil(self): + self.assertEqual(list(rrulestr( + "DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;" + "UNTIL=19990101T000000;BYDAY=1TU,-1TH\n" + )), + [datetime(1997, 12, 25, 9, 0), + datetime(1998, 1, 6, 9, 0), + datetime(1998, 12, 31, 9, 0)]) + + def testStrValueDatetime(self): + rr = rrulestr("DTSTART;VALUE=DATE-TIME:19970902T090000\n" + "RRULE:FREQ=YEARLY;COUNT=2") + + self.assertEqual(list(rr), [datetime(1997, 9, 2, 9, 0, 0), + datetime(1998, 9, 2, 9, 0, 0)]) + + def testStrValueDate(self): + rr = rrulestr("DTSTART;VALUE=DATE:19970902\n" + "RRULE:FREQ=YEARLY;COUNT=2") + + self.assertEqual(list(rr), [datetime(1997, 9, 2, 0, 0, 0), + datetime(1998, 9, 2, 0, 0, 0)]) + def testStrMultipleDTStartComma(self): with pytest.raises(ValueError): rr = rrulestr("DTSTART:19970101T000000,19970202T000000\n" "RRULE:FREQ=YEARLY;COUNT=1") - def testStrInvalidUntil(self): - with self.assertRaises(ValueError): - list(rrulestr("DTSTART:19970902T090000\n" - "RRULE:FREQ=YEARLY;" - "UNTIL=TheCowsComeHome;BYDAY=1TU,-1TH\n")) - - def testStrUntilMustBeUTC(self): - with self.assertRaises(ValueError): - list(rrulestr("DTSTART;TZID=America/New_York:19970902T090000\n" - "RRULE:FREQ=YEARLY;" - "UNTIL=19990101T000000;BYDAY=1TU,-1TH\n")) - - def testStrUntilWithTZ(self): - NYC = tz.gettz('America/New_York') - rr = list(rrulestr("DTSTART;TZID=America/New_York:19970101T000000\n" - "RRULE:FREQ=YEARLY;" - "UNTIL=19990101T000000Z\n")) - self.assertEqual(list(rr), [datetime(1997, 1, 1, 0, 0, 0, tzinfo=NYC), - datetime(1998, 1, 1, 0, 0, 0, tzinfo=NYC)]) - - def testStrEmptyByDay(self): - with self.assertRaises(ValueError): - list(rrulestr("DTSTART:19970902T090000\n" - "FREQ=WEEKLY;" - "BYDAY=;" # This part is invalid - "WKST=SU")) - - def testStrInvalidByDay(self): - with self.assertRaises(ValueError): - list(rrulestr("DTSTART:19970902T090000\n" - "FREQ=WEEKLY;" - "BYDAY=-1OK;" # This part is invalid - "WKST=SU")) - - def testBadBySetPos(self): - self.assertRaises(ValueError, - rrule, MONTHLY, - count=1, - bysetpos=0, - dtstart=datetime(1997, 9, 2, 9, 0)) - - def testBadBySetPosMany(self): - self.assertRaises(ValueError, - rrule, MONTHLY, - count=1, - bysetpos=(-1, 0, 1), - dtstart=datetime(1997, 9, 2, 9, 0)) - - # Tests to ensure that str(rrule) works - def testToStrYearly(self): - rule = rrule(YEARLY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) - self._rrulestr_reverse_test(rule) - - def testToStrYearlyInterval(self): - rule = rrule(YEARLY, count=3, interval=2, - dtstart=datetime(1997, 9, 2, 9, 0)) - self._rrulestr_reverse_test(rule) - - def testToStrYearlyByMonth(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByNWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthAndNWeekDayLarge(self): - # This is interesting because the TH(-3) ends up before - # the TU(3). - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByYearDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByWeekNo(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByEaster(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByEasterPos(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByHour(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMinute(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyBySecond(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyBySetPos(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonthday=15, - byhour=(6, 18), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthly(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyInterval(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - interval=18, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonth(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - # Third Monday of the month - self.assertEqual(rrule(MONTHLY, - byweekday=(MO(+3)), - dtstart=datetime(1997, 9, 1)).between(datetime(1997, - 9, - 1), - datetime(1997, - 12, - 1)), - [datetime(1997, 9, 15, 0, 0), - datetime(1997, 10, 20, 0, 0), - datetime(1997, 11, 17, 0, 0)]) - - def testToStrMonthlyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByNWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndNWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByYearDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNo(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByEaster(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByEasterPos(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByHour(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMinute(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyBySecond(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyBySetPos(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonthday=(13, 17), - byhour=(6, 18), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeekly(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyInterval(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - interval=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonth(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndWeekDay(self): - # This test is interesting, because it crosses the year - # boundary in a weekly period to find day '1' as a - # valid recurrence. - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByYearDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=4, - bymonth=(1, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=4, - bymonth=(1, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNo(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByEaster(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByEasterPos(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByHour(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMinute(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyBySecond(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyBySetPos(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekday=(TU, TH), - byhour=(6, 18), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDaily(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyInterval(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - interval=92, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonth(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByYearDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=4, - bymonth=(1, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=4, - bymonth=(1, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNo(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByEaster(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByEasterPos(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByHour(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMinute(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyBySecond(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyBySetPos(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - byminute=(15, 45), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourly(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyInterval(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - interval=769, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonth(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByYearDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNo(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNoAndWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNoAndWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByEaster(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByEasterPos(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByHour(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMinute(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyBySecond(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyBySetPos(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byminute=(15, 45), - bysecond=(15, 45), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutely(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyInterval(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - interval=1501, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonth(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByYearDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNo(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNoAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNoAndWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByEaster(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByEasterPos(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByHour(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMinute(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyBySecond(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyBySetPos(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bysecond=(15, 30, 45), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondly(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyInterval(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - interval=90061, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonth(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByYearDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNo(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNoAndWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNoAndWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByEaster(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByEasterPos(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHour(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMinute(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyBySecond(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHourAndMinuteAndSecondBug(self): - # This explores a bug found by Mathieu Bridon. - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bysecond=(0,), - byminute=(1,), - dtstart=datetime(2010, 3, 22, 12, 1))) - - def testToStrWithWkSt(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - wkst=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrLongIntegers(self): + def testStrInvalidUntil(self): + with self.assertRaises(ValueError): + list(rrulestr("DTSTART:19970902T090000\n" + "RRULE:FREQ=YEARLY;" + "UNTIL=TheCowsComeHome;BYDAY=1TU,-1TH\n")) + + def testStrUntilMustBeUTC(self): + with self.assertRaises(ValueError): + list(rrulestr("DTSTART;TZID=America/New_York:19970902T090000\n" + "RRULE:FREQ=YEARLY;" + "UNTIL=19990101T000000;BYDAY=1TU,-1TH\n")) + + def testStrUntilWithTZ(self): + NYC = tz.gettz('America/New_York') + rr = list(rrulestr("DTSTART;TZID=America/New_York:19970101T000000\n" + "RRULE:FREQ=YEARLY;" + "UNTIL=19990101T000000Z\n")) + self.assertEqual(list(rr), [datetime(1997, 1, 1, 0, 0, 0, tzinfo=NYC), + datetime(1998, 1, 1, 0, 0, 0, tzinfo=NYC)]) + + def testStrEmptyByDay(self): + with self.assertRaises(ValueError): + list(rrulestr("DTSTART:19970902T090000\n" + "FREQ=WEEKLY;" + "BYDAY=;" # This part is invalid + "WKST=SU")) + + def testStrInvalidByDay(self): + with self.assertRaises(ValueError): + list(rrulestr("DTSTART:19970902T090000\n" + "FREQ=WEEKLY;" + "BYDAY=-1OK;" # This part is invalid + "WKST=SU")) + + def testBadBySetPos(self): + self.assertRaises(ValueError, + rrule, MONTHLY, + count=1, + bysetpos=0, + dtstart=datetime(1997, 9, 2, 9, 0)) + + def testBadBySetPosMany(self): + self.assertRaises(ValueError, + rrule, MONTHLY, + count=1, + bysetpos=(-1, 0, 1), + dtstart=datetime(1997, 9, 2, 9, 0)) + + # Tests to ensure that str(rrule) works + def testToStrYearly(self): + rule = rrule(YEARLY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) + self._rrulestr_reverse_test(rule) + + def testToStrYearlyInterval(self): + rule = rrule(YEARLY, count=3, interval=2, + dtstart=datetime(1997, 9, 2, 9, 0)) + self._rrulestr_reverse_test(rule) + + def testToStrYearlyByMonth(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMonthDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMonthAndMonthDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByWeekDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByNWeekDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByNWeekDayLarge(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byweekday=(TU(3), TH(-3)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMonthAndWeekDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMonthAndNWeekDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMonthAndNWeekDayLarge(self): + # This is interesting because the TH(-3) ends up before + # the TU(3). + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(3), TH(-3)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMonthAndMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByYearDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByYearDayNeg(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMonthAndYearDay(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMonthAndYearDayNeg(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByWeekNo(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByWeekNoAndWeekDay(self): + # That's a nice one. The first days of week number one + # may be in the last year. + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByWeekNoAndWeekDayLarge(self): + # Another nice test. The last days of week number 52/53 + # may be in the next year. + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByWeekNoAndWeekDayLast(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByEaster(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByEasterPos(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByEasterNeg(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByWeekNoAndWeekDay53(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByHour(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMinute(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyBySecond(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByHourAndMinute(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByHourAndSecond(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyByHourAndMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrYearlyBySetPos(self): + self._rrulestr_reverse_test(rrule(YEARLY, + count=3, + bymonthday=15, + byhour=(6, 18), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthly(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyInterval(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyIntervalLarge(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + interval=18, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonth(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonthDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonthAndMonthDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByWeekDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + # Third Monday of the month + self.assertEqual(rrule(MONTHLY, + byweekday=(MO(+3)), + dtstart=datetime(1997, 9, 1)).between(datetime(1997, + 9, + 1), + datetime(1997, + 12, + 1)), + [datetime(1997, 9, 15, 0, 0), + datetime(1997, 10, 20, 0, 0), + datetime(1997, 11, 17, 0, 0)]) + + def testToStrMonthlyByNWeekDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByNWeekDayLarge(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byweekday=(TU(3), TH(-3)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonthAndWeekDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonthAndNWeekDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonthAndNWeekDayLarge(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(3), TH(-3)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonthAndMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByYearDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByYearDayNeg(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonthAndYearDay(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMonthAndYearDayNeg(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByWeekNo(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByWeekNoAndWeekDay(self): + # That's a nice one. The first days of week number one + # may be in the last year. + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByWeekNoAndWeekDayLarge(self): + # Another nice test. The last days of week number 52/53 + # may be in the next year. + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByWeekNoAndWeekDayLast(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByWeekNoAndWeekDay53(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByEaster(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByEasterPos(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByEasterNeg(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByHour(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMinute(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyBySecond(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByHourAndMinute(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByHourAndSecond(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyByHourAndMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMonthlyBySetPos(self): + self._rrulestr_reverse_test(rrule(MONTHLY, + count=3, + bymonthday=(13, 17), + byhour=(6, 18), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeekly(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyInterval(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyIntervalLarge(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + interval=20, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMonth(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMonthDay(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMonthAndMonthDay(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByWeekDay(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByNWeekDay(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMonthAndWeekDay(self): + # This test is interesting, because it crosses the year + # boundary in a weekly period to find day '1' as a + # valid recurrence. + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMonthAndNWeekDay(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMonthAndMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByYearDay(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByYearDayNeg(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMonthAndYearDay(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=4, + bymonth=(1, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMonthAndYearDayNeg(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=4, + bymonth=(1, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByWeekNo(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByWeekNoAndWeekDay(self): + # That's a nice one. The first days of week number one + # may be in the last year. + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByWeekNoAndWeekDayLarge(self): + # Another nice test. The last days of week number 52/53 + # may be in the next year. + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByWeekNoAndWeekDayLast(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByWeekNoAndWeekDay53(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByEaster(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByEasterPos(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByEasterNeg(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByHour(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMinute(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyBySecond(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByHourAndMinute(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByHourAndSecond(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyByHourAndMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrWeeklyBySetPos(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + byweekday=(TU, TH), + byhour=(6, 18), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDaily(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyInterval(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyIntervalLarge(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + interval=92, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMonth(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMonthDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMonthAndMonthDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByWeekDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByNWeekDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMonthAndWeekDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMonthAndNWeekDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMonthAndMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByYearDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByYearDayNeg(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMonthAndYearDay(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=4, + bymonth=(1, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMonthAndYearDayNeg(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=4, + bymonth=(1, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByWeekNo(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByWeekNoAndWeekDay(self): + # That's a nice one. The first days of week number one + # may be in the last year. + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByWeekNoAndWeekDayLarge(self): + # Another nice test. The last days of week number 52/53 + # may be in the next year. + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByWeekNoAndWeekDayLast(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByWeekNoAndWeekDay53(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByEaster(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByEasterPos(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByEasterNeg(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByHour(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMinute(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyBySecond(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByHourAndMinute(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByHourAndSecond(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyByHourAndMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrDailyBySetPos(self): + self._rrulestr_reverse_test(rrule(DAILY, + count=3, + byhour=(6, 18), + byminute=(15, 45), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourly(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyInterval(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyIntervalLarge(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + interval=769, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMonth(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMonthDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMonthAndMonthDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByWeekDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByNWeekDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMonthAndWeekDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMonthAndNWeekDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMonthAndMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByYearDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByYearDayNeg(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMonthAndYearDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMonthAndYearDayNeg(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByWeekNo(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByWeekNoAndWeekDay(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByWeekNoAndWeekDayLarge(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByWeekNoAndWeekDayLast(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByWeekNoAndWeekDay53(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByEaster(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByEasterPos(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByEasterNeg(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByHour(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMinute(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyBySecond(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByHourAndMinute(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByHourAndSecond(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyByHourAndMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrHourlyBySetPos(self): + self._rrulestr_reverse_test(rrule(HOURLY, + count=3, + byminute=(15, 45), + bysecond=(15, 45), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutely(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyInterval(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyIntervalLarge(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + interval=1501, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMonth(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMonthDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMonthAndMonthDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByWeekDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByNWeekDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMonthAndWeekDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMonthAndNWeekDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMonthAndMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByYearDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByYearDayNeg(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMonthAndYearDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMonthAndYearDayNeg(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByWeekNo(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByWeekNoAndWeekDay(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByWeekNoAndWeekDayLarge(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByWeekNoAndWeekDayLast(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByWeekNoAndWeekDay53(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByEaster(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByEasterPos(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByEasterNeg(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByHour(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMinute(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyBySecond(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByHourAndMinute(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByHourAndSecond(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyByHourAndMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrMinutelyBySetPos(self): + self._rrulestr_reverse_test(rrule(MINUTELY, + count=3, + bysecond=(15, 30, 45), + bysetpos=(3, -3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondly(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyInterval(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + interval=2, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyIntervalLarge(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + interval=90061, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMonth(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMonthDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + bymonthday=(1, 3), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMonthAndMonthDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + bymonthday=(5, 7), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByWeekDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByNWeekDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMonthAndWeekDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMonthAndNWeekDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + byweekday=(TU(1), TH(-1)), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMonthAndMonthDayAndWeekDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + bymonth=(1, 3), + bymonthday=(1, 3), + byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByYearDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=4, + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByYearDayNeg(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=4, + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMonthAndYearDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=4, + bymonth=(4, 7), + byyearday=(1, 100, 200, 365), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMonthAndYearDayNeg(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=4, + bymonth=(4, 7), + byyearday=(-365, -266, -166, -1), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByWeekNo(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byweekno=20, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByWeekNoAndWeekDay(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byweekno=1, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByWeekNoAndWeekDayLarge(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byweekno=52, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByWeekNoAndWeekDayLast(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byweekno=-1, + byweekday=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByWeekNoAndWeekDay53(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byweekno=53, + byweekday=MO, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByEaster(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byeaster=0, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByEasterPos(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byeaster=1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByEasterNeg(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byeaster=-1, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByHour(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byhour=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMinute(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyBySecond(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByHourAndMinute(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByHourAndSecond(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byhour=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByHourAndMinuteAndSecond(self): + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + byhour=(6, 18), + byminute=(6, 18), + bysecond=(6, 18), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrSecondlyByHourAndMinuteAndSecondBug(self): + # This explores a bug found by Mathieu Bridon. + self._rrulestr_reverse_test(rrule(SECONDLY, + count=3, + bysecond=(0,), + byminute=(1,), + dtstart=datetime(2010, 3, 22, 12, 1))) + + def testToStrWithWkSt(self): + self._rrulestr_reverse_test(rrule(WEEKLY, + count=3, + wkst=SU, + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testToStrLongIntegers(self): if PY2: # There are no longs in python3 - self._rrulestr_reverse_test(rrule(MINUTELY, - count=long(2), - interval=long(2), - bymonth=long(2), - byweekday=long(3), - byhour=long(6), - byminute=long(6), - bysecond=long(6), - dtstart=datetime(1997, 9, 2, 9, 0))) - - self._rrulestr_reverse_test(rrule(YEARLY, - count=long(2), - bymonthday=long(5), - byweekno=long(2), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testReplaceIfSet(self): - rr = rrule(YEARLY, - count=1, - bymonthday=5, - dtstart=datetime(1997, 1, 1)) - newrr = rr.replace(bymonthday=6) - self.assertEqual(list(rr), [datetime(1997, 1, 5)]) - self.assertEqual(list(newrr), - [datetime(1997, 1, 6)]) - - def testReplaceIfNotSet(self): - rr = rrule(YEARLY, - count=1, - dtstart=datetime(1997, 1, 1)) - newrr = rr.replace(bymonthday=6) - self.assertEqual(list(rr), [datetime(1997, 1, 1)]) - self.assertEqual(list(newrr), - [datetime(1997, 1, 6)]) - - -@pytest.mark.rrule -@freeze_time(datetime(2018, 3, 6, 5, 36, tzinfo=tz.UTC)) -def test_generated_aware_dtstart(): - dtstart_exp = datetime(2018, 3, 6, 5, 36, tzinfo=tz.UTC) - UNTIL = datetime(2018, 3, 6, 8, 0, tzinfo=tz.UTC) - - rule_without_dtstart = rrule(freq=HOURLY, until=UNTIL) - rule_with_dtstart = rrule(freq=HOURLY, dtstart=dtstart_exp, until=UNTIL) - assert list(rule_without_dtstart) == list(rule_with_dtstart) - - -@pytest.mark.rrule -@pytest.mark.rrulestr -@pytest.mark.xfail(reason="rrulestr loses time zone, gh issue #637") -@freeze_time(datetime(2018, 3, 6, 5, 36, tzinfo=tz.UTC)) -def test_generated_aware_dtstart_rrulestr(): - rrule_without_dtstart = rrule(freq=HOURLY, - until=datetime(2018, 3, 6, 8, 0, - tzinfo=tz.UTC)) - rrule_r = rrulestr(str(rrule_without_dtstart)) - - assert list(rrule_r) == list(rrule_without_dtstart) - - -@pytest.mark.rruleset -class RRuleSetTest(unittest.TestCase): - def testSet(self): - rrset = rruleset() - rrset.rrule(rrule(YEARLY, count=2, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) - rrset.rrule(rrule(YEARLY, count=1, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) - self.assertEqual(list(rrset), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testSetDate(self): - rrset = rruleset() - rrset.rrule(rrule(YEARLY, count=1, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) - rrset.rdate(datetime(1997, 9, 4, 9)) - rrset.rdate(datetime(1997, 9, 9, 9)) - self.assertEqual(list(rrset), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testSetExRule(self): - rrset = rruleset() - rrset.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - rrset.exrule(rrule(YEARLY, count=3, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) - self.assertEqual(list(rrset), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testSetExDate(self): - rrset = rruleset() - rrset.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - rrset.exdate(datetime(1997, 9, 4, 9)) - rrset.exdate(datetime(1997, 9, 11, 9)) - rrset.exdate(datetime(1997, 9, 18, 9)) - self.assertEqual(list(rrset), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testSetExDateRevOrder(self): - rrset = rruleset() - rrset.rrule(rrule(MONTHLY, count=5, bymonthday=10, - dtstart=datetime(2004, 1, 1, 9, 0))) - rrset.exdate(datetime(2004, 4, 10, 9, 0)) - rrset.exdate(datetime(2004, 2, 10, 9, 0)) - self.assertEqual(list(rrset), - [datetime(2004, 1, 10, 9, 0), - datetime(2004, 3, 10, 9, 0), - datetime(2004, 5, 10, 9, 0)]) - - def testSetDateAndExDate(self): - rrset = rruleset() - rrset.rdate(datetime(1997, 9, 2, 9)) - rrset.rdate(datetime(1997, 9, 4, 9)) - rrset.rdate(datetime(1997, 9, 9, 9)) - rrset.rdate(datetime(1997, 9, 11, 9)) - rrset.rdate(datetime(1997, 9, 16, 9)) - rrset.rdate(datetime(1997, 9, 18, 9)) - rrset.exdate(datetime(1997, 9, 4, 9)) - rrset.exdate(datetime(1997, 9, 11, 9)) - rrset.exdate(datetime(1997, 9, 18, 9)) - self.assertEqual(list(rrset), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testSetDateAndExRule(self): - rrset = rruleset() - rrset.rdate(datetime(1997, 9, 2, 9)) - rrset.rdate(datetime(1997, 9, 4, 9)) - rrset.rdate(datetime(1997, 9, 9, 9)) - rrset.rdate(datetime(1997, 9, 11, 9)) - rrset.rdate(datetime(1997, 9, 16, 9)) - rrset.rdate(datetime(1997, 9, 18, 9)) - rrset.exrule(rrule(YEARLY, count=3, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) - self.assertEqual(list(rrset), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 16, 9, 0)]) - - def testSetCount(self): - rrset = rruleset() - rrset.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - rrset.exrule(rrule(YEARLY, count=3, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) - self.assertEqual(rrset.count(), 3) - - def testSetCachePre(self): - rrset = rruleset() - rrset.rrule(rrule(YEARLY, count=2, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) - rrset.rrule(rrule(YEARLY, count=1, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) - self.assertEqual(list(rrset), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testSetCachePost(self): - rrset = rruleset(cache=True) - rrset.rrule(rrule(YEARLY, count=2, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) - rrset.rrule(rrule(YEARLY, count=1, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) - for x in rrset: pass - self.assertEqual(list(rrset), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testSetCachePostInternal(self): - rrset = rruleset(cache=True) - rrset.rrule(rrule(YEARLY, count=2, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) - rrset.rrule(rrule(YEARLY, count=1, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) - for x in rrset: pass - self.assertEqual(list(rrset._cache), - [datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 4, 9, 0), - datetime(1997, 9, 9, 9, 0)]) - - def testSetRRuleCount(self): - # Test that the count is updated when an rrule is added - rrset = rruleset(cache=False) - for cache in (True, False): - rrset = rruleset(cache=cache) - rrset.rrule(rrule(YEARLY, count=2, byweekday=TH, - dtstart=datetime(1983, 4, 1))) - rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR, - dtstart=datetime(1991, 6, 3))) - - # Check the length twice - first one sets a cache, second reads it - self.assertEqual(rrset.count(), 6) - self.assertEqual(rrset.count(), 6) - - # This should invalidate the cache and force an update - rrset.rrule(rrule(MONTHLY, count=3, dtstart=datetime(1994, 1, 3))) - - self.assertEqual(rrset.count(), 9) - self.assertEqual(rrset.count(), 9) - - def testSetRDateCount(self): - # Test that the count is updated when an rdate is added - rrset = rruleset(cache=False) - for cache in (True, False): - rrset = rruleset(cache=cache) - rrset.rrule(rrule(YEARLY, count=2, byweekday=TH, - dtstart=datetime(1983, 4, 1))) - rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR, - dtstart=datetime(1991, 6, 3))) - - # Check the length twice - first one sets a cache, second reads it - self.assertEqual(rrset.count(), 6) - self.assertEqual(rrset.count(), 6) - - # This should invalidate the cache and force an update - rrset.rdate(datetime(1993, 2, 14)) - - self.assertEqual(rrset.count(), 7) - self.assertEqual(rrset.count(), 7) - - def testSetExRuleCount(self): - # Test that the count is updated when an exrule is added - rrset = rruleset(cache=False) - for cache in (True, False): - rrset = rruleset(cache=cache) - rrset.rrule(rrule(YEARLY, count=2, byweekday=TH, - dtstart=datetime(1983, 4, 1))) - rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR, - dtstart=datetime(1991, 6, 3))) - - # Check the length twice - first one sets a cache, second reads it - self.assertEqual(rrset.count(), 6) - self.assertEqual(rrset.count(), 6) - - # This should invalidate the cache and force an update - rrset.exrule(rrule(WEEKLY, count=2, interval=2, - dtstart=datetime(1991, 6, 14))) - - self.assertEqual(rrset.count(), 4) - self.assertEqual(rrset.count(), 4) - - def testSetExDateCount(self): - # Test that the count is updated when an rdate is added - for cache in (True, False): - rrset = rruleset(cache=cache) - rrset.rrule(rrule(YEARLY, count=2, byweekday=TH, - dtstart=datetime(1983, 4, 1))) - rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR, - dtstart=datetime(1991, 6, 3))) - - # Check the length twice - first one sets a cache, second reads it - self.assertEqual(rrset.count(), 6) - self.assertEqual(rrset.count(), 6) - - # This should invalidate the cache and force an update - rrset.exdate(datetime(1991, 6, 28)) - - self.assertEqual(rrset.count(), 5) - self.assertEqual(rrset.count(), 5) - - -class WeekdayTest(unittest.TestCase): - def testInvalidNthWeekday(self): - with self.assertRaises(ValueError): - FR(0) - - def testWeekdayCallable(self): - # Calling a weekday instance generates a new weekday instance with the - # value of n changed. - from dateutil.rrule import weekday - self.assertEqual(MO(1), weekday(0, 1)) - - # Calling a weekday instance with the identical n returns the original - # object - FR_3 = weekday(4, 3) - self.assertIs(FR_3(3), FR_3) - - def testWeekdayEquality(self): - # Two weekday objects are not equal if they have different values for n - self.assertNotEqual(TH, TH(-1)) - self.assertNotEqual(SA(3), SA(2)) - - def testWeekdayEqualitySubclass(self): - # Two weekday objects equal if their "weekday" and "n" attributes are - # available and the same - class BasicWeekday(object): - def __init__(self, weekday): - self.weekday = weekday - - class BasicNWeekday(BasicWeekday): - def __init__(self, weekday, n=None): - super(BasicNWeekday, self).__init__(weekday) - self.n = n - - MO_Basic = BasicWeekday(0) - - self.assertNotEqual(MO, MO_Basic) - self.assertNotEqual(MO(1), MO_Basic) - - TU_BasicN = BasicNWeekday(1) - - self.assertEqual(TU, TU_BasicN) - self.assertNotEqual(TU(3), TU_BasicN) - - WE_Basic3 = BasicNWeekday(2, 3) - self.assertEqual(WE(3), WE_Basic3) - self.assertNotEqual(WE(2), WE_Basic3) - - def testWeekdayReprNoN(self): - no_n_reprs = ('MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU') - no_n_wdays = (MO, TU, WE, TH, FR, SA, SU) - - for repstr, wday in zip(no_n_reprs, no_n_wdays): - self.assertEqual(repr(wday), repstr) - - def testWeekdayReprWithN(self): - with_n_reprs = ('WE(+1)', 'TH(-2)', 'SU(+3)') - with_n_wdays = (WE(1), TH(-2), SU(+3)) - - for repstr, wday in zip(with_n_reprs, with_n_wdays): - self.assertEqual(repr(wday), repstr) + self._rrulestr_reverse_test(rrule(MINUTELY, + count=long(2), + interval=long(2), + bymonth=long(2), + byweekday=long(3), + byhour=long(6), + byminute=long(6), + bysecond=long(6), + dtstart=datetime(1997, 9, 2, 9, 0))) + + self._rrulestr_reverse_test(rrule(YEARLY, + count=long(2), + bymonthday=long(5), + byweekno=long(2), + dtstart=datetime(1997, 9, 2, 9, 0))) + + def testReplaceIfSet(self): + rr = rrule(YEARLY, + count=1, + bymonthday=5, + dtstart=datetime(1997, 1, 1)) + newrr = rr.replace(bymonthday=6) + self.assertEqual(list(rr), [datetime(1997, 1, 5)]) + self.assertEqual(list(newrr), + [datetime(1997, 1, 6)]) + + def testReplaceIfNotSet(self): + rr = rrule(YEARLY, + count=1, + dtstart=datetime(1997, 1, 1)) + newrr = rr.replace(bymonthday=6) + self.assertEqual(list(rr), [datetime(1997, 1, 1)]) + self.assertEqual(list(newrr), + [datetime(1997, 1, 6)]) + + +@pytest.mark.rrule +@freeze_time(datetime(2018, 3, 6, 5, 36, tzinfo=tz.UTC)) +def test_generated_aware_dtstart(): + dtstart_exp = datetime(2018, 3, 6, 5, 36, tzinfo=tz.UTC) + UNTIL = datetime(2018, 3, 6, 8, 0, tzinfo=tz.UTC) + + rule_without_dtstart = rrule(freq=HOURLY, until=UNTIL) + rule_with_dtstart = rrule(freq=HOURLY, dtstart=dtstart_exp, until=UNTIL) + assert list(rule_without_dtstart) == list(rule_with_dtstart) + + +@pytest.mark.rrule +@pytest.mark.rrulestr +@pytest.mark.xfail(reason="rrulestr loses time zone, gh issue #637") +@freeze_time(datetime(2018, 3, 6, 5, 36, tzinfo=tz.UTC)) +def test_generated_aware_dtstart_rrulestr(): + rrule_without_dtstart = rrule(freq=HOURLY, + until=datetime(2018, 3, 6, 8, 0, + tzinfo=tz.UTC)) + rrule_r = rrulestr(str(rrule_without_dtstart)) + + assert list(rrule_r) == list(rrule_without_dtstart) + + +@pytest.mark.rruleset +class RRuleSetTest(unittest.TestCase): + def testSet(self): + rrset = rruleset() + rrset.rrule(rrule(YEARLY, count=2, byweekday=TU, + dtstart=datetime(1997, 9, 2, 9, 0))) + rrset.rrule(rrule(YEARLY, count=1, byweekday=TH, + dtstart=datetime(1997, 9, 2, 9, 0))) + self.assertEqual(list(rrset), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testSetDate(self): + rrset = rruleset() + rrset.rrule(rrule(YEARLY, count=1, byweekday=TU, + dtstart=datetime(1997, 9, 2, 9, 0))) + rrset.rdate(datetime(1997, 9, 4, 9)) + rrset.rdate(datetime(1997, 9, 9, 9)) + self.assertEqual(list(rrset), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testSetExRule(self): + rrset = rruleset() + rrset.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + rrset.exrule(rrule(YEARLY, count=3, byweekday=TH, + dtstart=datetime(1997, 9, 2, 9, 0))) + self.assertEqual(list(rrset), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testSetExDate(self): + rrset = rruleset() + rrset.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + rrset.exdate(datetime(1997, 9, 4, 9)) + rrset.exdate(datetime(1997, 9, 11, 9)) + rrset.exdate(datetime(1997, 9, 18, 9)) + self.assertEqual(list(rrset), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testSetExDateRevOrder(self): + rrset = rruleset() + rrset.rrule(rrule(MONTHLY, count=5, bymonthday=10, + dtstart=datetime(2004, 1, 1, 9, 0))) + rrset.exdate(datetime(2004, 4, 10, 9, 0)) + rrset.exdate(datetime(2004, 2, 10, 9, 0)) + self.assertEqual(list(rrset), + [datetime(2004, 1, 10, 9, 0), + datetime(2004, 3, 10, 9, 0), + datetime(2004, 5, 10, 9, 0)]) + + def testSetDateAndExDate(self): + rrset = rruleset() + rrset.rdate(datetime(1997, 9, 2, 9)) + rrset.rdate(datetime(1997, 9, 4, 9)) + rrset.rdate(datetime(1997, 9, 9, 9)) + rrset.rdate(datetime(1997, 9, 11, 9)) + rrset.rdate(datetime(1997, 9, 16, 9)) + rrset.rdate(datetime(1997, 9, 18, 9)) + rrset.exdate(datetime(1997, 9, 4, 9)) + rrset.exdate(datetime(1997, 9, 11, 9)) + rrset.exdate(datetime(1997, 9, 18, 9)) + self.assertEqual(list(rrset), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testSetDateAndExRule(self): + rrset = rruleset() + rrset.rdate(datetime(1997, 9, 2, 9)) + rrset.rdate(datetime(1997, 9, 4, 9)) + rrset.rdate(datetime(1997, 9, 9, 9)) + rrset.rdate(datetime(1997, 9, 11, 9)) + rrset.rdate(datetime(1997, 9, 16, 9)) + rrset.rdate(datetime(1997, 9, 18, 9)) + rrset.exrule(rrule(YEARLY, count=3, byweekday=TH, + dtstart=datetime(1997, 9, 2, 9, 0))) + self.assertEqual(list(rrset), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 9, 9, 0), + datetime(1997, 9, 16, 9, 0)]) + + def testSetCount(self): + rrset = rruleset() + rrset.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), + dtstart=datetime(1997, 9, 2, 9, 0))) + rrset.exrule(rrule(YEARLY, count=3, byweekday=TH, + dtstart=datetime(1997, 9, 2, 9, 0))) + self.assertEqual(rrset.count(), 3) + + def testSetCachePre(self): + rrset = rruleset() + rrset.rrule(rrule(YEARLY, count=2, byweekday=TU, + dtstart=datetime(1997, 9, 2, 9, 0))) + rrset.rrule(rrule(YEARLY, count=1, byweekday=TH, + dtstart=datetime(1997, 9, 2, 9, 0))) + self.assertEqual(list(rrset), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testSetCachePost(self): + rrset = rruleset(cache=True) + rrset.rrule(rrule(YEARLY, count=2, byweekday=TU, + dtstart=datetime(1997, 9, 2, 9, 0))) + rrset.rrule(rrule(YEARLY, count=1, byweekday=TH, + dtstart=datetime(1997, 9, 2, 9, 0))) + for x in rrset: pass + self.assertEqual(list(rrset), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testSetCachePostInternal(self): + rrset = rruleset(cache=True) + rrset.rrule(rrule(YEARLY, count=2, byweekday=TU, + dtstart=datetime(1997, 9, 2, 9, 0))) + rrset.rrule(rrule(YEARLY, count=1, byweekday=TH, + dtstart=datetime(1997, 9, 2, 9, 0))) + for x in rrset: pass + self.assertEqual(list(rrset._cache), + [datetime(1997, 9, 2, 9, 0), + datetime(1997, 9, 4, 9, 0), + datetime(1997, 9, 9, 9, 0)]) + + def testSetRRuleCount(self): + # Test that the count is updated when an rrule is added + rrset = rruleset(cache=False) + for cache in (True, False): + rrset = rruleset(cache=cache) + rrset.rrule(rrule(YEARLY, count=2, byweekday=TH, + dtstart=datetime(1983, 4, 1))) + rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR, + dtstart=datetime(1991, 6, 3))) + + # Check the length twice - first one sets a cache, second reads it + self.assertEqual(rrset.count(), 6) + self.assertEqual(rrset.count(), 6) + + # This should invalidate the cache and force an update + rrset.rrule(rrule(MONTHLY, count=3, dtstart=datetime(1994, 1, 3))) + + self.assertEqual(rrset.count(), 9) + self.assertEqual(rrset.count(), 9) + + def testSetRDateCount(self): + # Test that the count is updated when an rdate is added + rrset = rruleset(cache=False) + for cache in (True, False): + rrset = rruleset(cache=cache) + rrset.rrule(rrule(YEARLY, count=2, byweekday=TH, + dtstart=datetime(1983, 4, 1))) + rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR, + dtstart=datetime(1991, 6, 3))) + + # Check the length twice - first one sets a cache, second reads it + self.assertEqual(rrset.count(), 6) + self.assertEqual(rrset.count(), 6) + + # This should invalidate the cache and force an update + rrset.rdate(datetime(1993, 2, 14)) + + self.assertEqual(rrset.count(), 7) + self.assertEqual(rrset.count(), 7) + + def testSetExRuleCount(self): + # Test that the count is updated when an exrule is added + rrset = rruleset(cache=False) + for cache in (True, False): + rrset = rruleset(cache=cache) + rrset.rrule(rrule(YEARLY, count=2, byweekday=TH, + dtstart=datetime(1983, 4, 1))) + rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR, + dtstart=datetime(1991, 6, 3))) + + # Check the length twice - first one sets a cache, second reads it + self.assertEqual(rrset.count(), 6) + self.assertEqual(rrset.count(), 6) + + # This should invalidate the cache and force an update + rrset.exrule(rrule(WEEKLY, count=2, interval=2, + dtstart=datetime(1991, 6, 14))) + + self.assertEqual(rrset.count(), 4) + self.assertEqual(rrset.count(), 4) + + def testSetExDateCount(self): + # Test that the count is updated when an rdate is added + for cache in (True, False): + rrset = rruleset(cache=cache) + rrset.rrule(rrule(YEARLY, count=2, byweekday=TH, + dtstart=datetime(1983, 4, 1))) + rrset.rrule(rrule(WEEKLY, count=4, byweekday=FR, + dtstart=datetime(1991, 6, 3))) + + # Check the length twice - first one sets a cache, second reads it + self.assertEqual(rrset.count(), 6) + self.assertEqual(rrset.count(), 6) + + # This should invalidate the cache and force an update + rrset.exdate(datetime(1991, 6, 28)) + + self.assertEqual(rrset.count(), 5) + self.assertEqual(rrset.count(), 5) + + +class WeekdayTest(unittest.TestCase): + def testInvalidNthWeekday(self): + with self.assertRaises(ValueError): + FR(0) + + def testWeekdayCallable(self): + # Calling a weekday instance generates a new weekday instance with the + # value of n changed. + from dateutil.rrule import weekday + self.assertEqual(MO(1), weekday(0, 1)) + + # Calling a weekday instance with the identical n returns the original + # object + FR_3 = weekday(4, 3) + self.assertIs(FR_3(3), FR_3) + + def testWeekdayEquality(self): + # Two weekday objects are not equal if they have different values for n + self.assertNotEqual(TH, TH(-1)) + self.assertNotEqual(SA(3), SA(2)) + + def testWeekdayEqualitySubclass(self): + # Two weekday objects equal if their "weekday" and "n" attributes are + # available and the same + class BasicWeekday(object): + def __init__(self, weekday): + self.weekday = weekday + + class BasicNWeekday(BasicWeekday): + def __init__(self, weekday, n=None): + super(BasicNWeekday, self).__init__(weekday) + self.n = n + + MO_Basic = BasicWeekday(0) + + self.assertNotEqual(MO, MO_Basic) + self.assertNotEqual(MO(1), MO_Basic) + + TU_BasicN = BasicNWeekday(1) + + self.assertEqual(TU, TU_BasicN) + self.assertNotEqual(TU(3), TU_BasicN) + + WE_Basic3 = BasicNWeekday(2, 3) + self.assertEqual(WE(3), WE_Basic3) + self.assertNotEqual(WE(2), WE_Basic3) + + def testWeekdayReprNoN(self): + no_n_reprs = ('MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU') + no_n_wdays = (MO, TU, WE, TH, FR, SA, SU) + + for repstr, wday in zip(no_n_reprs, no_n_wdays): + self.assertEqual(repr(wday), repstr) + + def testWeekdayReprWithN(self): + with_n_reprs = ('WE(+1)', 'TH(-2)', 'SU(+3)') + with_n_wdays = (WE(1), TH(-2), SU(+3)) + + for repstr, wday in zip(with_n_reprs, with_n_wdays): + self.assertEqual(repr(wday), repstr) diff --git a/contrib/python/dateutil/dateutil/test/test_tz.py b/contrib/python/dateutil/dateutil/test/test_tz.py index e5e4772d9a..926e730b30 100644 --- a/contrib/python/dateutil/dateutil/test/test_tz.py +++ b/contrib/python/dateutil/dateutil/test/test_tz.py @@ -1,743 +1,743 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from ._common import PicklableMixin -from ._common import TZEnvContext, TZWinContext -from ._common import ComparesEqual - -from datetime import datetime, timedelta -from datetime import time as dt_time -from datetime import tzinfo +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from ._common import PicklableMixin +from ._common import TZEnvContext, TZWinContext +from ._common import ComparesEqual + +from datetime import datetime, timedelta +from datetime import time as dt_time +from datetime import tzinfo from six import PY2 from io import BytesIO, StringIO -import unittest - -import sys -import base64 -import copy +import unittest + +import sys +import base64 +import copy import gc import weakref - -from functools import partial - -IS_WIN = sys.platform.startswith('win') - -import pytest - -# dateutil imports -from dateutil.relativedelta import relativedelta, SU, TH -from dateutil.parser import parse -from dateutil import tz as tz -from dateutil import zoneinfo - -try: - from dateutil import tzwin -except ImportError as e: - if IS_WIN: - raise e - else: - pass - -MISSING_TARBALL = ("This test fails if you don't have the dateutil " - "timezone file installed. Please read the README") - -TZFILE_EST5EDT = b""" -VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAADrAAAABAAAABCeph5wn7rrYKCGAHCh -ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e -S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 -YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg -yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db -wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW -8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b -YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g -BGD9cAVQ4GAGQN9wBzDCYAeNGXAJEKRgCa2U8ArwhmAL4IVwDNmi4A3AZ3AOuYTgD6mD8BCZZuAR -iWXwEnlI4BNpR/AUWSrgFUkp8BY5DOAXKQvwGCIpYBkI7fAaAgtgGvIKcBvh7WAc0exwHcHPYB6x -znAfobFgIHYA8CGBk2AiVeLwI2qv4CQ1xPAlSpHgJhWm8Ccqc+An/sNwKQpV4CnepXAq6jfgK76H -cCzTVGAtnmlwLrM2YC9+S3AwkxhgMWdn8DJy+mAzR0nwNFLcYDUnK/A2Mr5gNwcN8Dgb2uA45u/w -Ofu84DrG0fA7257gPK/ucD27gOA+j9BwP5ti4EBvsnBBhH9gQk+UcENkYWBEL3ZwRURDYEYPWHBH -JCVgR/h08EkEB2BJ2FbwSuPpYEu4OPBMzQXgTZga8E6s5+BPd/zwUIzJ4FFhGXBSbKvgU0D7cFRM -jeBVIN1wVixv4FcAv3BYFYxgWOChcFn1bmBawINwW9VQYFypn/BdtTJgXomB8F+VFGBgaWPwYX4w -4GJJRfBjXhLgZCkn8GU99OBmEkRwZx3W4GfyJnBo/bjgadIIcGrdmuBrsepwbMa3YG2RzHBupplg -b3GucHCGe2BxWsrwcmZdYHM6rPB0Rj9gdRqO8HYvW+B2+nDweA894HjaUvB57x/gero08HvPAeB8 -o1Fwfa7j4H6DM3B/jsXgAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB -AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB -AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA -AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB -AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU -AEVQVAAAAAABAAAAAQ== -""" - -EUROPE_HELSINKI = b""" -VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAB1AAAABQAAAA2kc28Yy85RYMy/hdAV -I+uQFhPckBcDzZAX876QGOOvkBnToJAaw5GQG7y9EBysrhAdnJ8QHoyQEB98gRAgbHIQIVxjECJM -VBAjPEUQJCw2ECUcJxAmDBgQJwVDkCf1NJAo5SWQKdUWkCrFB5ArtPiQLKTpkC2U2pAuhMuQL3S8 -kDBkrZAxXdkQMnK0EDM9uxA0UpYQNR2dEDYyeBA2/X8QOBuUkDjdYRA5+3aQOr1DEDvbWJA8pl+Q -Pbs6kD6GQZA/mxyQQGYjkEGEORBCRgWQQ2QbEEQl55BFQ/0QRgXJkEcj3xBH7uYQSQPBEEnOyBBK -46MQS66qEEzMv5BNjowQTqyhkE9ubhBQjIOQUVeKkFJsZZBTN2yQVExHkFUXTpBWLCmQVvcwkFgV -RhBY1xKQWfUoEFq29JBb1QoQXKAREF207BBef/MQX5TOEGBf1RBhfeqQYj+3EGNdzJBkH5kQZT2u -kGYItZBnHZCQZ+iXkGj9cpBpyHmQat1UkGuoW5BsxnEQbYg9kG6mUxBvaB+QcIY1EHFRPBByZhcQ -czEeEHRF+RB1EQAQdi8VkHbw4hB4DveQeNDEEHnu2ZB6sKYQe867kHyZwpB9rp2QfnmkkH+Of5AC -AQIDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQD -BAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAME -AwQAABdoAAAAACowAQQAABwgAAkAACowAQQAABwgAAlITVQARUVTVABFRVQAAAAAAQEAAAABAQ== -""" - -NEW_YORK = b""" -VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABcAAADrAAAABAAAABCeph5wn7rrYKCGAHCh -ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e -S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 -YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg -yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db -wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW -8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b -YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g -BGD9cAVQ4GEGQN9yBzDCYgeNGXMJEKRjCa2U9ArwhmQL4IV1DNmi5Q3AZ3YOuYTmD6mD9xCZZucR -iWX4EnlI6BNpR/kUWSrpFUkp+RY5DOoXKQv6GCIpaxkI7fsaAgtsGvIKfBvh7Wwc0ex8HcHPbR6x -zn0fobFtIHYA/SGBk20iVeL+I2qv7iQ1xP4lSpHuJhWm/ycqc+8n/sOAKQpV8CnepYAq6jfxK76H -gSzTVHItnmmCLrM2cy9+S4MwkxhzMWdoBDJy+nQzR0oENFLcdTUnLAU2Mr51NwcOBjgb2vY45vAG -Ofu89jrG0gY72572PK/uhj27gPY+j9CGP5ti9kBvsoZBhH92Qk+UhkNkYXZEL3aHRURDd0XzqQdH -LV/3R9OLB0kNQfdJs20HSu0j90uciYdM1kB3TXxrh062IndPXE2HUJYEd1E8L4dSdeZ3UxwRh1RV -yHdU+/OHVjWqd1blEAdYHsb3WMTyB1n+qPdapNQHW96K91yEtgddvmz3XmSYB1+eTvdgTbSHYYdr -d2ItlodjZ013ZA14h2VHL3dl7VqHZycRd2fNPIdpBvN3aa0eh2rm1XdrljsHbM/x9212HQdur9P3 -b1X/B3CPtfdxNeEHcm+X93MVwwd0T3n3dP7fh3Y4lnd23sGHeBh4d3i+o4d5+Fp3ep6Fh3vYPHd8 -fmeHfbged35eSYd/mAB3AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB -AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB -AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA -AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB -AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU -AEVQVAAEslgAAAAAAQWk7AEAAAACB4YfggAAAAMJZ1MDAAAABAtIhoQAAAAFDSsLhQAAAAYPDD8G -AAAABxDtcocAAAAIEs6mCAAAAAkVn8qJAAAACheA/goAAAALGWIxiwAAAAwdJeoMAAAADSHa5Q0A -AAAOJZ6djgAAAA8nf9EPAAAAECpQ9ZAAAAARLDIpEQAAABIuE1ySAAAAEzDnJBMAAAAUM7hIlAAA -ABU2jBAVAAAAFkO3G5YAAAAXAAAAAQAAAAE= -""" - -TZICAL_EST5EDT = """ -BEGIN:VTIMEZONE -TZID:US-Eastern -LAST-MODIFIED:19870101T000000Z -TZURL:http://zones.stds_r_us.net/tz/US-Eastern -BEGIN:STANDARD -DTSTART:19671029T020000 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZOFFSETFROM:-0400 -TZOFFSETTO:-0500 -TZNAME:EST -END:STANDARD -BEGIN:DAYLIGHT -DTSTART:19870405T020000 -RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 -TZOFFSETFROM:-0500 -TZOFFSETTO:-0400 -TZNAME:EDT -END:DAYLIGHT -END:VTIMEZONE -""" - -TZICAL_PST8PDT = """ -BEGIN:VTIMEZONE -TZID:US-Pacific -LAST-MODIFIED:19870101T000000Z -BEGIN:STANDARD -DTSTART:19671029T020000 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZOFFSETFROM:-0700 -TZOFFSETTO:-0800 -TZNAME:PST -END:STANDARD -BEGIN:DAYLIGHT -DTSTART:19870405T020000 -RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 -TZOFFSETFROM:-0800 -TZOFFSETTO:-0700 -TZNAME:PDT -END:DAYLIGHT -END:VTIMEZONE -""" - -EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0)) -EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1)) - + +from functools import partial + +IS_WIN = sys.platform.startswith('win') + +import pytest + +# dateutil imports +from dateutil.relativedelta import relativedelta, SU, TH +from dateutil.parser import parse +from dateutil import tz as tz +from dateutil import zoneinfo + +try: + from dateutil import tzwin +except ImportError as e: + if IS_WIN: + raise e + else: + pass + +MISSING_TARBALL = ("This test fails if you don't have the dateutil " + "timezone file installed. Please read the README") + +TZFILE_EST5EDT = b""" +VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAADrAAAABAAAABCeph5wn7rrYKCGAHCh +ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e +S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 +YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg +yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db +wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW +8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b +YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g +BGD9cAVQ4GAGQN9wBzDCYAeNGXAJEKRgCa2U8ArwhmAL4IVwDNmi4A3AZ3AOuYTgD6mD8BCZZuAR +iWXwEnlI4BNpR/AUWSrgFUkp8BY5DOAXKQvwGCIpYBkI7fAaAgtgGvIKcBvh7WAc0exwHcHPYB6x +znAfobFgIHYA8CGBk2AiVeLwI2qv4CQ1xPAlSpHgJhWm8Ccqc+An/sNwKQpV4CnepXAq6jfgK76H +cCzTVGAtnmlwLrM2YC9+S3AwkxhgMWdn8DJy+mAzR0nwNFLcYDUnK/A2Mr5gNwcN8Dgb2uA45u/w +Ofu84DrG0fA7257gPK/ucD27gOA+j9BwP5ti4EBvsnBBhH9gQk+UcENkYWBEL3ZwRURDYEYPWHBH +JCVgR/h08EkEB2BJ2FbwSuPpYEu4OPBMzQXgTZga8E6s5+BPd/zwUIzJ4FFhGXBSbKvgU0D7cFRM +jeBVIN1wVixv4FcAv3BYFYxgWOChcFn1bmBawINwW9VQYFypn/BdtTJgXomB8F+VFGBgaWPwYX4w +4GJJRfBjXhLgZCkn8GU99OBmEkRwZx3W4GfyJnBo/bjgadIIcGrdmuBrsepwbMa3YG2RzHBupplg +b3GucHCGe2BxWsrwcmZdYHM6rPB0Rj9gdRqO8HYvW+B2+nDweA894HjaUvB57x/gero08HvPAeB8 +o1Fwfa7j4H6DM3B/jsXgAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB +AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB +AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA +AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB +AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU +AEVQVAAAAAABAAAAAQ== +""" + +EUROPE_HELSINKI = b""" +VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAB1AAAABQAAAA2kc28Yy85RYMy/hdAV +I+uQFhPckBcDzZAX876QGOOvkBnToJAaw5GQG7y9EBysrhAdnJ8QHoyQEB98gRAgbHIQIVxjECJM +VBAjPEUQJCw2ECUcJxAmDBgQJwVDkCf1NJAo5SWQKdUWkCrFB5ArtPiQLKTpkC2U2pAuhMuQL3S8 +kDBkrZAxXdkQMnK0EDM9uxA0UpYQNR2dEDYyeBA2/X8QOBuUkDjdYRA5+3aQOr1DEDvbWJA8pl+Q +Pbs6kD6GQZA/mxyQQGYjkEGEORBCRgWQQ2QbEEQl55BFQ/0QRgXJkEcj3xBH7uYQSQPBEEnOyBBK +46MQS66qEEzMv5BNjowQTqyhkE9ubhBQjIOQUVeKkFJsZZBTN2yQVExHkFUXTpBWLCmQVvcwkFgV +RhBY1xKQWfUoEFq29JBb1QoQXKAREF207BBef/MQX5TOEGBf1RBhfeqQYj+3EGNdzJBkH5kQZT2u +kGYItZBnHZCQZ+iXkGj9cpBpyHmQat1UkGuoW5BsxnEQbYg9kG6mUxBvaB+QcIY1EHFRPBByZhcQ +czEeEHRF+RB1EQAQdi8VkHbw4hB4DveQeNDEEHnu2ZB6sKYQe867kHyZwpB9rp2QfnmkkH+Of5AC +AQIDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQD +BAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAME +AwQAABdoAAAAACowAQQAABwgAAkAACowAQQAABwgAAlITVQARUVTVABFRVQAAAAAAQEAAAABAQ== +""" + +NEW_YORK = b""" +VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABcAAADrAAAABAAAABCeph5wn7rrYKCGAHCh +ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e +S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 +YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg +yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db +wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW +8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b +YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g +BGD9cAVQ4GEGQN9yBzDCYgeNGXMJEKRjCa2U9ArwhmQL4IV1DNmi5Q3AZ3YOuYTmD6mD9xCZZucR +iWX4EnlI6BNpR/kUWSrpFUkp+RY5DOoXKQv6GCIpaxkI7fsaAgtsGvIKfBvh7Wwc0ex8HcHPbR6x +zn0fobFtIHYA/SGBk20iVeL+I2qv7iQ1xP4lSpHuJhWm/ycqc+8n/sOAKQpV8CnepYAq6jfxK76H +gSzTVHItnmmCLrM2cy9+S4MwkxhzMWdoBDJy+nQzR0oENFLcdTUnLAU2Mr51NwcOBjgb2vY45vAG +Ofu89jrG0gY72572PK/uhj27gPY+j9CGP5ti9kBvsoZBhH92Qk+UhkNkYXZEL3aHRURDd0XzqQdH +LV/3R9OLB0kNQfdJs20HSu0j90uciYdM1kB3TXxrh062IndPXE2HUJYEd1E8L4dSdeZ3UxwRh1RV +yHdU+/OHVjWqd1blEAdYHsb3WMTyB1n+qPdapNQHW96K91yEtgddvmz3XmSYB1+eTvdgTbSHYYdr +d2ItlodjZ013ZA14h2VHL3dl7VqHZycRd2fNPIdpBvN3aa0eh2rm1XdrljsHbM/x9212HQdur9P3 +b1X/B3CPtfdxNeEHcm+X93MVwwd0T3n3dP7fh3Y4lnd23sGHeBh4d3i+o4d5+Fp3ep6Fh3vYPHd8 +fmeHfbged35eSYd/mAB3AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB +AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB +AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA +AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB +AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU +AEVQVAAEslgAAAAAAQWk7AEAAAACB4YfggAAAAMJZ1MDAAAABAtIhoQAAAAFDSsLhQAAAAYPDD8G +AAAABxDtcocAAAAIEs6mCAAAAAkVn8qJAAAACheA/goAAAALGWIxiwAAAAwdJeoMAAAADSHa5Q0A +AAAOJZ6djgAAAA8nf9EPAAAAECpQ9ZAAAAARLDIpEQAAABIuE1ySAAAAEzDnJBMAAAAUM7hIlAAA +ABU2jBAVAAAAFkO3G5YAAAAXAAAAAQAAAAE= +""" + +TZICAL_EST5EDT = """ +BEGIN:VTIMEZONE +TZID:US-Eastern +LAST-MODIFIED:19870101T000000Z +TZURL:http://zones.stds_r_us.net/tz/US-Eastern +BEGIN:STANDARD +DTSTART:19671029T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19870405T020000 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +END:DAYLIGHT +END:VTIMEZONE +""" + +TZICAL_PST8PDT = """ +BEGIN:VTIMEZONE +TZID:US-Pacific +LAST-MODIFIED:19870101T000000Z +BEGIN:STANDARD +DTSTART:19671029T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19870405T020000 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +""" + +EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0)) +EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1)) + SUPPORTS_SUB_MINUTE_OFFSETS = sys.version_info >= (3, 6) - - -### -# Helper functions -def get_timezone_tuple(dt): - """Retrieve a (tzname, utcoffset, dst) tuple for a given DST""" - return dt.tzname(), dt.utcoffset(), dt.dst() - - -### -# Mix-ins -class context_passthrough(object): - def __init__(*args, **kwargs): - pass - - def __enter__(*args, **kwargs): - pass - - def __exit__(*args, **kwargs): - pass - - -class TzFoldMixin(object): - """ Mix-in class for testing ambiguous times """ - def gettz(self, tzname): - raise NotImplementedError - - def _get_tzname(self, tzname): - return tzname - - def _gettz_context(self, tzname): - return context_passthrough() - - def testFoldPositiveUTCOffset(self): - # Test that we can resolve ambiguous times - tzname = self._get_tzname('Australia/Sydney') - - with self._gettz_context(tzname): - SYD = self.gettz(tzname) - + + +### +# Helper functions +def get_timezone_tuple(dt): + """Retrieve a (tzname, utcoffset, dst) tuple for a given DST""" + return dt.tzname(), dt.utcoffset(), dt.dst() + + +### +# Mix-ins +class context_passthrough(object): + def __init__(*args, **kwargs): + pass + + def __enter__(*args, **kwargs): + pass + + def __exit__(*args, **kwargs): + pass + + +class TzFoldMixin(object): + """ Mix-in class for testing ambiguous times """ + def gettz(self, tzname): + raise NotImplementedError + + def _get_tzname(self, tzname): + return tzname + + def _gettz_context(self, tzname): + return context_passthrough() + + def testFoldPositiveUTCOffset(self): + # Test that we can resolve ambiguous times + tzname = self._get_tzname('Australia/Sydney') + + with self._gettz_context(tzname): + SYD = self.gettz(tzname) + t0_u = datetime(2012, 3, 31, 15, 30, tzinfo=tz.UTC) # AEST t1_u = datetime(2012, 3, 31, 16, 30, tzinfo=tz.UTC) # AEDT - - t0_syd0 = t0_u.astimezone(SYD) - t1_syd1 = t1_u.astimezone(SYD) - - self.assertEqual(t0_syd0.replace(tzinfo=None), - datetime(2012, 4, 1, 2, 30)) - - self.assertEqual(t1_syd1.replace(tzinfo=None), - datetime(2012, 4, 1, 2, 30)) - - self.assertEqual(t0_syd0.utcoffset(), timedelta(hours=11)) - self.assertEqual(t1_syd1.utcoffset(), timedelta(hours=10)) - - def testGapPositiveUTCOffset(self): - # Test that we don't have a problem around gaps. - tzname = self._get_tzname('Australia/Sydney') - - with self._gettz_context(tzname): - SYD = self.gettz(tzname) - + + t0_syd0 = t0_u.astimezone(SYD) + t1_syd1 = t1_u.astimezone(SYD) + + self.assertEqual(t0_syd0.replace(tzinfo=None), + datetime(2012, 4, 1, 2, 30)) + + self.assertEqual(t1_syd1.replace(tzinfo=None), + datetime(2012, 4, 1, 2, 30)) + + self.assertEqual(t0_syd0.utcoffset(), timedelta(hours=11)) + self.assertEqual(t1_syd1.utcoffset(), timedelta(hours=10)) + + def testGapPositiveUTCOffset(self): + # Test that we don't have a problem around gaps. + tzname = self._get_tzname('Australia/Sydney') + + with self._gettz_context(tzname): + SYD = self.gettz(tzname) + t0_u = datetime(2012, 10, 6, 15, 30, tzinfo=tz.UTC) # AEST t1_u = datetime(2012, 10, 6, 16, 30, tzinfo=tz.UTC) # AEDT - - t0 = t0_u.astimezone(SYD) - t1 = t1_u.astimezone(SYD) - - self.assertEqual(t0.replace(tzinfo=None), - datetime(2012, 10, 7, 1, 30)) - - self.assertEqual(t1.replace(tzinfo=None), - datetime(2012, 10, 7, 3, 30)) - - self.assertEqual(t0.utcoffset(), timedelta(hours=10)) - self.assertEqual(t1.utcoffset(), timedelta(hours=11)) - - def testFoldNegativeUTCOffset(self): - # Test that we can resolve ambiguous times - tzname = self._get_tzname('America/Toronto') - - with self._gettz_context(tzname): - TOR = self.gettz(tzname) - + + t0 = t0_u.astimezone(SYD) + t1 = t1_u.astimezone(SYD) + + self.assertEqual(t0.replace(tzinfo=None), + datetime(2012, 10, 7, 1, 30)) + + self.assertEqual(t1.replace(tzinfo=None), + datetime(2012, 10, 7, 3, 30)) + + self.assertEqual(t0.utcoffset(), timedelta(hours=10)) + self.assertEqual(t1.utcoffset(), timedelta(hours=11)) + + def testFoldNegativeUTCOffset(self): + # Test that we can resolve ambiguous times + tzname = self._get_tzname('America/Toronto') + + with self._gettz_context(tzname): + TOR = self.gettz(tzname) + t0_u = datetime(2011, 11, 6, 5, 30, tzinfo=tz.UTC) t1_u = datetime(2011, 11, 6, 6, 30, tzinfo=tz.UTC) - - t0_tor = t0_u.astimezone(TOR) - t1_tor = t1_u.astimezone(TOR) - - self.assertEqual(t0_tor.replace(tzinfo=None), - datetime(2011, 11, 6, 1, 30)) - - self.assertEqual(t1_tor.replace(tzinfo=None), - datetime(2011, 11, 6, 1, 30)) - - self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname()) - self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0)) - self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0)) - - def testGapNegativeUTCOffset(self): - # Test that we don't have a problem around gaps. - tzname = self._get_tzname('America/Toronto') - - with self._gettz_context(tzname): - TOR = self.gettz(tzname) - + + t0_tor = t0_u.astimezone(TOR) + t1_tor = t1_u.astimezone(TOR) + + self.assertEqual(t0_tor.replace(tzinfo=None), + datetime(2011, 11, 6, 1, 30)) + + self.assertEqual(t1_tor.replace(tzinfo=None), + datetime(2011, 11, 6, 1, 30)) + + self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname()) + self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0)) + self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0)) + + def testGapNegativeUTCOffset(self): + # Test that we don't have a problem around gaps. + tzname = self._get_tzname('America/Toronto') + + with self._gettz_context(tzname): + TOR = self.gettz(tzname) + t0_u = datetime(2011, 3, 13, 6, 30, tzinfo=tz.UTC) t1_u = datetime(2011, 3, 13, 7, 30, tzinfo=tz.UTC) - - t0 = t0_u.astimezone(TOR) - t1 = t1_u.astimezone(TOR) - - self.assertEqual(t0.replace(tzinfo=None), - datetime(2011, 3, 13, 1, 30)) - - self.assertEqual(t1.replace(tzinfo=None), - datetime(2011, 3, 13, 3, 30)) - - self.assertNotEqual(t0, t1) - self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0)) - self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0)) - - def testFoldLondon(self): - tzname = self._get_tzname('Europe/London') - - with self._gettz_context(tzname): - LON = self.gettz(tzname) + + t0 = t0_u.astimezone(TOR) + t1 = t1_u.astimezone(TOR) + + self.assertEqual(t0.replace(tzinfo=None), + datetime(2011, 3, 13, 1, 30)) + + self.assertEqual(t1.replace(tzinfo=None), + datetime(2011, 3, 13, 3, 30)) + + self.assertNotEqual(t0, t1) + self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0)) + self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0)) + + def testFoldLondon(self): + tzname = self._get_tzname('Europe/London') + + with self._gettz_context(tzname): + LON = self.gettz(tzname) UTC = tz.UTC - - t0_u = datetime(2013, 10, 27, 0, 30, tzinfo=UTC) # BST - t1_u = datetime(2013, 10, 27, 1, 30, tzinfo=UTC) # GMT - - t0 = t0_u.astimezone(LON) - t1 = t1_u.astimezone(LON) - - self.assertEqual(t0.replace(tzinfo=None), - datetime(2013, 10, 27, 1, 30)) - - self.assertEqual(t1.replace(tzinfo=None), - datetime(2013, 10, 27, 1, 30)) - - self.assertEqual(t0.utcoffset(), timedelta(hours=1)) - self.assertEqual(t1.utcoffset(), timedelta(hours=0)) - - def testFoldIndependence(self): - tzname = self._get_tzname('America/New_York') - - with self._gettz_context(tzname): - NYC = self.gettz(tzname) + + t0_u = datetime(2013, 10, 27, 0, 30, tzinfo=UTC) # BST + t1_u = datetime(2013, 10, 27, 1, 30, tzinfo=UTC) # GMT + + t0 = t0_u.astimezone(LON) + t1 = t1_u.astimezone(LON) + + self.assertEqual(t0.replace(tzinfo=None), + datetime(2013, 10, 27, 1, 30)) + + self.assertEqual(t1.replace(tzinfo=None), + datetime(2013, 10, 27, 1, 30)) + + self.assertEqual(t0.utcoffset(), timedelta(hours=1)) + self.assertEqual(t1.utcoffset(), timedelta(hours=0)) + + def testFoldIndependence(self): + tzname = self._get_tzname('America/New_York') + + with self._gettz_context(tzname): + NYC = self.gettz(tzname) UTC = tz.UTC - hour = timedelta(hours=1) - - # Firmly 2015-11-01 0:30 EDT-4 - pre_dst = datetime(2015, 11, 1, 0, 30, tzinfo=NYC) - - # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5 - in_dst = pre_dst + hour - in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT - - # Doing the arithmetic in UTC creates a date that is unambiguously - # 2015-11-01 1:30 EDT-5 - in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC) - - # Make sure the dates are actually ambiguous - self.assertEqual(in_dst, in_dst_via_utc) - - # Make sure we got the right folding behavior - self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0) - - # Now check to make sure in_dst's tzname hasn't changed - self.assertEqual(in_dst_tzname_0, in_dst.tzname()) - - def testInZoneFoldEquality(self): - # Two datetimes in the same zone are considered to be equal if their - # wall times are equal, even if they have different absolute times. - - tzname = self._get_tzname('America/New_York') - - with self._gettz_context(tzname): - NYC = self.gettz(tzname) + hour = timedelta(hours=1) + + # Firmly 2015-11-01 0:30 EDT-4 + pre_dst = datetime(2015, 11, 1, 0, 30, tzinfo=NYC) + + # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5 + in_dst = pre_dst + hour + in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT + + # Doing the arithmetic in UTC creates a date that is unambiguously + # 2015-11-01 1:30 EDT-5 + in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC) + + # Make sure the dates are actually ambiguous + self.assertEqual(in_dst, in_dst_via_utc) + + # Make sure we got the right folding behavior + self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0) + + # Now check to make sure in_dst's tzname hasn't changed + self.assertEqual(in_dst_tzname_0, in_dst.tzname()) + + def testInZoneFoldEquality(self): + # Two datetimes in the same zone are considered to be equal if their + # wall times are equal, even if they have different absolute times. + + tzname = self._get_tzname('America/New_York') + + with self._gettz_context(tzname): + NYC = self.gettz(tzname) UTC = tz.UTC - - dt0 = datetime(2011, 11, 6, 1, 30, tzinfo=NYC) - dt1 = tz.enfold(dt0, fold=1) - - # Make sure these actually represent different times - self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC)) - - # Test that they compare equal - self.assertEqual(dt0, dt1) - - def _test_ambiguous_time(self, dt, tzid, ambiguous): - # This is a test to check that the individual is_ambiguous values - # on the _tzinfo subclasses work. - tzname = self._get_tzname(tzid) - - with self._gettz_context(tzname): - tzi = self.gettz(tzname) - - self.assertEqual(tz.datetime_ambiguous(dt, tz=tzi), ambiguous) - - def testAmbiguousNegativeUTCOffset(self): - self._test_ambiguous_time(datetime(2015, 11, 1, 1, 30), - 'America/New_York', True) - - def testAmbiguousPositiveUTCOffset(self): - self._test_ambiguous_time(datetime(2012, 4, 1, 2, 30), - 'Australia/Sydney', True) - - def testUnambiguousNegativeUTCOffset(self): - self._test_ambiguous_time(datetime(2015, 11, 1, 2, 30), - 'America/New_York', False) - - def testUnambiguousPositiveUTCOffset(self): - self._test_ambiguous_time(datetime(2012, 4, 1, 3, 30), - 'Australia/Sydney', False) - - def testUnambiguousGapNegativeUTCOffset(self): - # Imaginary time - self._test_ambiguous_time(datetime(2011, 3, 13, 2, 30), - 'America/New_York', False) - - def testUnambiguousGapPositiveUTCOffset(self): - # Imaginary time - self._test_ambiguous_time(datetime(2012, 10, 7, 2, 30), - 'Australia/Sydney', False) - - def _test_imaginary_time(self, dt, tzid, exists): - tzname = self._get_tzname(tzid) - with self._gettz_context(tzname): - tzi = self.gettz(tzname) - - self.assertEqual(tz.datetime_exists(dt, tz=tzi), exists) - - def testImaginaryNegativeUTCOffset(self): - self._test_imaginary_time(datetime(2011, 3, 13, 2, 30), - 'America/New_York', False) - - def testNotImaginaryNegativeUTCOffset(self): - self._test_imaginary_time(datetime(2011, 3, 13, 1, 30), - 'America/New_York', True) - - def testImaginaryPositiveUTCOffset(self): - self._test_imaginary_time(datetime(2012, 10, 7, 2, 30), - 'Australia/Sydney', False) - - def testNotImaginaryPositiveUTCOffset(self): - self._test_imaginary_time(datetime(2012, 10, 7, 1, 30), - 'Australia/Sydney', True) - - def testNotImaginaryFoldNegativeUTCOffset(self): - self._test_imaginary_time(datetime(2015, 11, 1, 1, 30), - 'America/New_York', True) - - def testNotImaginaryFoldPositiveUTCOffset(self): - self._test_imaginary_time(datetime(2012, 4, 1, 3, 30), - 'Australia/Sydney', True) - - @unittest.skip("Known failure in Python 3.6.") - def testEqualAmbiguousComparison(self): - tzname = self._get_tzname('Australia/Sydney') - - with self._gettz_context(tzname): - SYD0 = self.gettz(tzname) - SYD1 = self.gettz(tzname) - + + dt0 = datetime(2011, 11, 6, 1, 30, tzinfo=NYC) + dt1 = tz.enfold(dt0, fold=1) + + # Make sure these actually represent different times + self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC)) + + # Test that they compare equal + self.assertEqual(dt0, dt1) + + def _test_ambiguous_time(self, dt, tzid, ambiguous): + # This is a test to check that the individual is_ambiguous values + # on the _tzinfo subclasses work. + tzname = self._get_tzname(tzid) + + with self._gettz_context(tzname): + tzi = self.gettz(tzname) + + self.assertEqual(tz.datetime_ambiguous(dt, tz=tzi), ambiguous) + + def testAmbiguousNegativeUTCOffset(self): + self._test_ambiguous_time(datetime(2015, 11, 1, 1, 30), + 'America/New_York', True) + + def testAmbiguousPositiveUTCOffset(self): + self._test_ambiguous_time(datetime(2012, 4, 1, 2, 30), + 'Australia/Sydney', True) + + def testUnambiguousNegativeUTCOffset(self): + self._test_ambiguous_time(datetime(2015, 11, 1, 2, 30), + 'America/New_York', False) + + def testUnambiguousPositiveUTCOffset(self): + self._test_ambiguous_time(datetime(2012, 4, 1, 3, 30), + 'Australia/Sydney', False) + + def testUnambiguousGapNegativeUTCOffset(self): + # Imaginary time + self._test_ambiguous_time(datetime(2011, 3, 13, 2, 30), + 'America/New_York', False) + + def testUnambiguousGapPositiveUTCOffset(self): + # Imaginary time + self._test_ambiguous_time(datetime(2012, 10, 7, 2, 30), + 'Australia/Sydney', False) + + def _test_imaginary_time(self, dt, tzid, exists): + tzname = self._get_tzname(tzid) + with self._gettz_context(tzname): + tzi = self.gettz(tzname) + + self.assertEqual(tz.datetime_exists(dt, tz=tzi), exists) + + def testImaginaryNegativeUTCOffset(self): + self._test_imaginary_time(datetime(2011, 3, 13, 2, 30), + 'America/New_York', False) + + def testNotImaginaryNegativeUTCOffset(self): + self._test_imaginary_time(datetime(2011, 3, 13, 1, 30), + 'America/New_York', True) + + def testImaginaryPositiveUTCOffset(self): + self._test_imaginary_time(datetime(2012, 10, 7, 2, 30), + 'Australia/Sydney', False) + + def testNotImaginaryPositiveUTCOffset(self): + self._test_imaginary_time(datetime(2012, 10, 7, 1, 30), + 'Australia/Sydney', True) + + def testNotImaginaryFoldNegativeUTCOffset(self): + self._test_imaginary_time(datetime(2015, 11, 1, 1, 30), + 'America/New_York', True) + + def testNotImaginaryFoldPositiveUTCOffset(self): + self._test_imaginary_time(datetime(2012, 4, 1, 3, 30), + 'Australia/Sydney', True) + + @unittest.skip("Known failure in Python 3.6.") + def testEqualAmbiguousComparison(self): + tzname = self._get_tzname('Australia/Sydney') + + with self._gettz_context(tzname): + SYD0 = self.gettz(tzname) + SYD1 = self.gettz(tzname) + t0_u = datetime(2012, 3, 31, 14, 30, tzinfo=tz.UTC) # AEST - - t0_syd0 = t0_u.astimezone(SYD0) - t0_syd1 = t0_u.astimezone(SYD1) - - # This is considered an "inter-zone comparison" because it's an - # ambiguous datetime. - self.assertEqual(t0_syd0, t0_syd1) - - -class TzWinFoldMixin(object): - def get_args(self, tzname): - return (tzname, ) - - class context(object): - def __init__(*args, **kwargs): - pass - - def __enter__(*args, **kwargs): - pass - - def __exit__(*args, **kwargs): - pass - - def get_utc_transitions(self, tzi, year, gap): - dston, dstoff = tzi.transitions(year) - if gap: - t_n = dston - timedelta(minutes=30) - + + t0_syd0 = t0_u.astimezone(SYD0) + t0_syd1 = t0_u.astimezone(SYD1) + + # This is considered an "inter-zone comparison" because it's an + # ambiguous datetime. + self.assertEqual(t0_syd0, t0_syd1) + + +class TzWinFoldMixin(object): + def get_args(self, tzname): + return (tzname, ) + + class context(object): + def __init__(*args, **kwargs): + pass + + def __enter__(*args, **kwargs): + pass + + def __exit__(*args, **kwargs): + pass + + def get_utc_transitions(self, tzi, year, gap): + dston, dstoff = tzi.transitions(year) + if gap: + t_n = dston - timedelta(minutes=30) + t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.UTC) - t1_u = t0_u + timedelta(hours=1) - else: - # Get 1 hour before the first ambiguous date - t_n = dstoff - timedelta(minutes=30) - + t1_u = t0_u + timedelta(hours=1) + else: + # Get 1 hour before the first ambiguous date + t_n = dstoff - timedelta(minutes=30) + t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.UTC) - t_n += timedelta(hours=1) # Naive ambiguous date - t0_u = t0_u + timedelta(hours=1) # First ambiguous date - t1_u = t0_u + timedelta(hours=1) # Second ambiguous date - - return t_n, t0_u, t1_u - - def testFoldPositiveUTCOffset(self): - # Test that we can resolve ambiguous times - tzname = 'AUS Eastern Standard Time' - args = self.get_args(tzname) - - with self.context(tzname): - # Calling fromutc() alters the tzfile object - SYD = self.tzclass(*args) - - # Get the transition time in UTC from the object, because - # Windows doesn't store historical info - t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, False) - - # Using fresh tzfiles - t0_syd = t0_u.astimezone(SYD) - t1_syd = t1_u.astimezone(SYD) - - self.assertEqual(t0_syd.replace(tzinfo=None), t_n) - - self.assertEqual(t1_syd.replace(tzinfo=None), t_n) - - self.assertEqual(t0_syd.utcoffset(), timedelta(hours=11)) - self.assertEqual(t1_syd.utcoffset(), timedelta(hours=10)) - self.assertNotEqual(t0_syd.tzname(), t1_syd.tzname()) - - def testGapPositiveUTCOffset(self): - # Test that we don't have a problem around gaps. - tzname = 'AUS Eastern Standard Time' - args = self.get_args(tzname) - - with self.context(tzname): - SYD = self.tzclass(*args) - - t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, True) - - t0 = t0_u.astimezone(SYD) - t1 = t1_u.astimezone(SYD) - - self.assertEqual(t0.replace(tzinfo=None), t_n) - - self.assertEqual(t1.replace(tzinfo=None), t_n + timedelta(hours=2)) - - self.assertEqual(t0.utcoffset(), timedelta(hours=10)) - self.assertEqual(t1.utcoffset(), timedelta(hours=11)) - - def testFoldNegativeUTCOffset(self): - # Test that we can resolve ambiguous times - tzname = 'Eastern Standard Time' - args = self.get_args(tzname) - - with self.context(tzname): - TOR = self.tzclass(*args) - - t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, False) - - t0_tor = t0_u.astimezone(TOR) - t1_tor = t1_u.astimezone(TOR) - - self.assertEqual(t0_tor.replace(tzinfo=None), t_n) - self.assertEqual(t1_tor.replace(tzinfo=None), t_n) - - self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname()) - self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0)) - self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0)) - - def testGapNegativeUTCOffset(self): - # Test that we don't have a problem around gaps. - tzname = 'Eastern Standard Time' - args = self.get_args(tzname) - - with self.context(tzname): - TOR = self.tzclass(*args) - - t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, True) - - t0 = t0_u.astimezone(TOR) - t1 = t1_u.astimezone(TOR) - - self.assertEqual(t0.replace(tzinfo=None), - t_n) - - self.assertEqual(t1.replace(tzinfo=None), - t_n + timedelta(hours=2)) - - self.assertNotEqual(t0.tzname(), t1.tzname()) - self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0)) - self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0)) - - def testFoldIndependence(self): - tzname = 'Eastern Standard Time' - args = self.get_args(tzname) - - with self.context(tzname): - NYC = self.tzclass(*args) + t_n += timedelta(hours=1) # Naive ambiguous date + t0_u = t0_u + timedelta(hours=1) # First ambiguous date + t1_u = t0_u + timedelta(hours=1) # Second ambiguous date + + return t_n, t0_u, t1_u + + def testFoldPositiveUTCOffset(self): + # Test that we can resolve ambiguous times + tzname = 'AUS Eastern Standard Time' + args = self.get_args(tzname) + + with self.context(tzname): + # Calling fromutc() alters the tzfile object + SYD = self.tzclass(*args) + + # Get the transition time in UTC from the object, because + # Windows doesn't store historical info + t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, False) + + # Using fresh tzfiles + t0_syd = t0_u.astimezone(SYD) + t1_syd = t1_u.astimezone(SYD) + + self.assertEqual(t0_syd.replace(tzinfo=None), t_n) + + self.assertEqual(t1_syd.replace(tzinfo=None), t_n) + + self.assertEqual(t0_syd.utcoffset(), timedelta(hours=11)) + self.assertEqual(t1_syd.utcoffset(), timedelta(hours=10)) + self.assertNotEqual(t0_syd.tzname(), t1_syd.tzname()) + + def testGapPositiveUTCOffset(self): + # Test that we don't have a problem around gaps. + tzname = 'AUS Eastern Standard Time' + args = self.get_args(tzname) + + with self.context(tzname): + SYD = self.tzclass(*args) + + t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, True) + + t0 = t0_u.astimezone(SYD) + t1 = t1_u.astimezone(SYD) + + self.assertEqual(t0.replace(tzinfo=None), t_n) + + self.assertEqual(t1.replace(tzinfo=None), t_n + timedelta(hours=2)) + + self.assertEqual(t0.utcoffset(), timedelta(hours=10)) + self.assertEqual(t1.utcoffset(), timedelta(hours=11)) + + def testFoldNegativeUTCOffset(self): + # Test that we can resolve ambiguous times + tzname = 'Eastern Standard Time' + args = self.get_args(tzname) + + with self.context(tzname): + TOR = self.tzclass(*args) + + t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, False) + + t0_tor = t0_u.astimezone(TOR) + t1_tor = t1_u.astimezone(TOR) + + self.assertEqual(t0_tor.replace(tzinfo=None), t_n) + self.assertEqual(t1_tor.replace(tzinfo=None), t_n) + + self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname()) + self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0)) + self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0)) + + def testGapNegativeUTCOffset(self): + # Test that we don't have a problem around gaps. + tzname = 'Eastern Standard Time' + args = self.get_args(tzname) + + with self.context(tzname): + TOR = self.tzclass(*args) + + t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, True) + + t0 = t0_u.astimezone(TOR) + t1 = t1_u.astimezone(TOR) + + self.assertEqual(t0.replace(tzinfo=None), + t_n) + + self.assertEqual(t1.replace(tzinfo=None), + t_n + timedelta(hours=2)) + + self.assertNotEqual(t0.tzname(), t1.tzname()) + self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0)) + self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0)) + + def testFoldIndependence(self): + tzname = 'Eastern Standard Time' + args = self.get_args(tzname) + + with self.context(tzname): + NYC = self.tzclass(*args) UTC = tz.UTC - hour = timedelta(hours=1) - - # Firmly 2015-11-01 0:30 EDT-4 - t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2015, False) - - pre_dst = (t_n - hour).replace(tzinfo=NYC) - - # Currently, there's no way around the fact that this resolves to an - # ambiguous date, which defaults to EST. I'm not hard-coding in the - # answer, though, because the preferred behavior would be that this - # results in a time on the EDT side. - - # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5 - in_dst = pre_dst + hour - in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT - - # Doing the arithmetic in UTC creates a date that is unambiguously - # 2015-11-01 1:30 EDT-5 - in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC) - - # Make sure we got the right folding behavior - self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0) - - # Now check to make sure in_dst's tzname hasn't changed - self.assertEqual(in_dst_tzname_0, in_dst.tzname()) - - def testInZoneFoldEquality(self): - # Two datetimes in the same zone are considered to be equal if their - # wall times are equal, even if they have different absolute times. - tzname = 'Eastern Standard Time' - args = self.get_args(tzname) - - with self.context(tzname): - NYC = self.tzclass(*args) + hour = timedelta(hours=1) + + # Firmly 2015-11-01 0:30 EDT-4 + t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2015, False) + + pre_dst = (t_n - hour).replace(tzinfo=NYC) + + # Currently, there's no way around the fact that this resolves to an + # ambiguous date, which defaults to EST. I'm not hard-coding in the + # answer, though, because the preferred behavior would be that this + # results in a time on the EDT side. + + # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5 + in_dst = pre_dst + hour + in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT + + # Doing the arithmetic in UTC creates a date that is unambiguously + # 2015-11-01 1:30 EDT-5 + in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC) + + # Make sure we got the right folding behavior + self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0) + + # Now check to make sure in_dst's tzname hasn't changed + self.assertEqual(in_dst_tzname_0, in_dst.tzname()) + + def testInZoneFoldEquality(self): + # Two datetimes in the same zone are considered to be equal if their + # wall times are equal, even if they have different absolute times. + tzname = 'Eastern Standard Time' + args = self.get_args(tzname) + + with self.context(tzname): + NYC = self.tzclass(*args) UTC = tz.UTC - - t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2011, False) - - dt0 = t_n.replace(tzinfo=NYC) - dt1 = tz.enfold(dt0, fold=1) - - # Make sure these actually represent different times - self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC)) - - # Test that they compare equal - self.assertEqual(dt0, dt1) - -### -# Test Cases -class TzUTCTest(unittest.TestCase): - def testSingleton(self): - UTC_0 = tz.tzutc() - UTC_1 = tz.tzutc() - - self.assertIs(UTC_0, UTC_1) - - def testOffset(self): - ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) - - self.assertEqual(ct.utcoffset(), timedelta(seconds=0)) - - def testDst(self): - ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) - - self.assertEqual(ct.dst(), timedelta(seconds=0)) - - def testTzName(self): - ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) - self.assertEqual(ct.tzname(), 'UTC') - - def testEquality(self): - UTC0 = tz.tzutc() - UTC1 = tz.tzutc() - - self.assertEqual(UTC0, UTC1) - - def testInequality(self): - UTC = tz.tzutc() - UTCp4 = tz.tzoffset('UTC+4', 14400) - - self.assertNotEqual(UTC, UTCp4) - - def testInequalityInteger(self): - self.assertFalse(tz.tzutc() == 7) - self.assertNotEqual(tz.tzutc(), 7) - - def testInequalityUnsupported(self): - self.assertEqual(tz.tzutc(), ComparesEqual) - - def testRepr(self): - UTC = tz.tzutc() - self.assertEqual(repr(UTC), 'tzutc()') - - def testTimeOnlyUTC(self): - # https://github.com/dateutil/dateutil/issues/132 - # tzutc doesn't care - tz_utc = tz.tzutc() - self.assertEqual(dt_time(13, 20, tzinfo=tz_utc).utcoffset(), - timedelta(0)) - - def testAmbiguity(self): - # Pick an arbitrary datetime, this should always return False. - dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzutc()) - - self.assertFalse(tz.datetime_ambiguous(dt)) - - -@pytest.mark.tzoffset -class TzOffsetTest(unittest.TestCase): - def testTimedeltaOffset(self): - est = tz.tzoffset('EST', timedelta(hours=-5)) - est_s = tz.tzoffset('EST', -18000) - - self.assertEqual(est, est_s) - - def testTzNameNone(self): - gmt5 = tz.tzoffset(None, -18000) # -5:00 - self.assertIs(datetime(2003, 10, 26, 0, 0, tzinfo=gmt5).tzname(), - None) - - def testTimeOnlyOffset(self): - # tzoffset doesn't care - tz_offset = tz.tzoffset('+3', 3600) - self.assertEqual(dt_time(13, 20, tzinfo=tz_offset).utcoffset(), - timedelta(seconds=3600)) - - def testTzOffsetRepr(self): - tname = 'EST' - tzo = tz.tzoffset(tname, -5 * 3600) - self.assertEqual(repr(tzo), "tzoffset(" + repr(tname) + ", -18000)") - - def testEquality(self): - utc = tz.tzoffset('UTC', 0) - gmt = tz.tzoffset('GMT', 0) - - self.assertEqual(utc, gmt) - - def testUTCEquality(self): + + t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2011, False) + + dt0 = t_n.replace(tzinfo=NYC) + dt1 = tz.enfold(dt0, fold=1) + + # Make sure these actually represent different times + self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC)) + + # Test that they compare equal + self.assertEqual(dt0, dt1) + +### +# Test Cases +class TzUTCTest(unittest.TestCase): + def testSingleton(self): + UTC_0 = tz.tzutc() + UTC_1 = tz.tzutc() + + self.assertIs(UTC_0, UTC_1) + + def testOffset(self): + ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) + + self.assertEqual(ct.utcoffset(), timedelta(seconds=0)) + + def testDst(self): + ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) + + self.assertEqual(ct.dst(), timedelta(seconds=0)) + + def testTzName(self): + ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) + self.assertEqual(ct.tzname(), 'UTC') + + def testEquality(self): + UTC0 = tz.tzutc() + UTC1 = tz.tzutc() + + self.assertEqual(UTC0, UTC1) + + def testInequality(self): + UTC = tz.tzutc() + UTCp4 = tz.tzoffset('UTC+4', 14400) + + self.assertNotEqual(UTC, UTCp4) + + def testInequalityInteger(self): + self.assertFalse(tz.tzutc() == 7) + self.assertNotEqual(tz.tzutc(), 7) + + def testInequalityUnsupported(self): + self.assertEqual(tz.tzutc(), ComparesEqual) + + def testRepr(self): + UTC = tz.tzutc() + self.assertEqual(repr(UTC), 'tzutc()') + + def testTimeOnlyUTC(self): + # https://github.com/dateutil/dateutil/issues/132 + # tzutc doesn't care + tz_utc = tz.tzutc() + self.assertEqual(dt_time(13, 20, tzinfo=tz_utc).utcoffset(), + timedelta(0)) + + def testAmbiguity(self): + # Pick an arbitrary datetime, this should always return False. + dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzutc()) + + self.assertFalse(tz.datetime_ambiguous(dt)) + + +@pytest.mark.tzoffset +class TzOffsetTest(unittest.TestCase): + def testTimedeltaOffset(self): + est = tz.tzoffset('EST', timedelta(hours=-5)) + est_s = tz.tzoffset('EST', -18000) + + self.assertEqual(est, est_s) + + def testTzNameNone(self): + gmt5 = tz.tzoffset(None, -18000) # -5:00 + self.assertIs(datetime(2003, 10, 26, 0, 0, tzinfo=gmt5).tzname(), + None) + + def testTimeOnlyOffset(self): + # tzoffset doesn't care + tz_offset = tz.tzoffset('+3', 3600) + self.assertEqual(dt_time(13, 20, tzinfo=tz_offset).utcoffset(), + timedelta(seconds=3600)) + + def testTzOffsetRepr(self): + tname = 'EST' + tzo = tz.tzoffset(tname, -5 * 3600) + self.assertEqual(repr(tzo), "tzoffset(" + repr(tname) + ", -18000)") + + def testEquality(self): + utc = tz.tzoffset('UTC', 0) + gmt = tz.tzoffset('GMT', 0) + + self.assertEqual(utc, gmt) + + def testUTCEquality(self): utc = tz.UTC - o_utc = tz.tzoffset('UTC', 0) - - self.assertEqual(utc, o_utc) - self.assertEqual(o_utc, utc) - - def testInequalityInvalid(self): - tzo = tz.tzoffset('-3', -3 * 3600) - self.assertFalse(tzo == -3) - self.assertNotEqual(tzo, -3) - - def testInequalityUnsupported(self): - tzo = tz.tzoffset('-5', -5 * 3600) - - self.assertTrue(tzo == ComparesEqual) - self.assertFalse(tzo != ComparesEqual) - self.assertEqual(tzo, ComparesEqual) - - def testAmbiguity(self): - # Pick an arbitrary datetime, this should always return False. - dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzoffset("EST", -5 * 3600)) - - self.assertFalse(tz.datetime_ambiguous(dt)) - - def testTzOffsetInstance(self): - tz1 = tz.tzoffset.instance('EST', timedelta(hours=-5)) - tz2 = tz.tzoffset.instance('EST', timedelta(hours=-5)) - - assert tz1 is not tz2 - - def testTzOffsetSingletonDifferent(self): - tz1 = tz.tzoffset('EST', timedelta(hours=-5)) - tz2 = tz.tzoffset('EST', -18000) - - assert tz1 is tz2 - + o_utc = tz.tzoffset('UTC', 0) + + self.assertEqual(utc, o_utc) + self.assertEqual(o_utc, utc) + + def testInequalityInvalid(self): + tzo = tz.tzoffset('-3', -3 * 3600) + self.assertFalse(tzo == -3) + self.assertNotEqual(tzo, -3) + + def testInequalityUnsupported(self): + tzo = tz.tzoffset('-5', -5 * 3600) + + self.assertTrue(tzo == ComparesEqual) + self.assertFalse(tzo != ComparesEqual) + self.assertEqual(tzo, ComparesEqual) + + def testAmbiguity(self): + # Pick an arbitrary datetime, this should always return False. + dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzoffset("EST", -5 * 3600)) + + self.assertFalse(tz.datetime_ambiguous(dt)) + + def testTzOffsetInstance(self): + tz1 = tz.tzoffset.instance('EST', timedelta(hours=-5)) + tz2 = tz.tzoffset.instance('EST', timedelta(hours=-5)) + + assert tz1 is not tz2 + + def testTzOffsetSingletonDifferent(self): + tz1 = tz.tzoffset('EST', timedelta(hours=-5)) + tz2 = tz.tzoffset('EST', -18000) + + assert tz1 is tz2 + @pytest.mark.smoke -@pytest.mark.tzoffset +@pytest.mark.tzoffset def test_tzoffset_weakref(): UTC1 = tz.tzoffset('UTC', 0) UTC_ref = weakref.ref(tz.tzoffset('UTC', 0)) @@ -758,18 +758,18 @@ def test_tzoffset_weakref(): @pytest.mark.tzoffset -@pytest.mark.parametrize('args', [ - ('UTC', 0), - ('EST', -18000), - ('EST', timedelta(hours=-5)), - (None, timedelta(hours=3)), -]) -def test_tzoffset_singleton(args): - tz1 = tz.tzoffset(*args) - tz2 = tz.tzoffset(*args) - - assert tz1 is tz2 - +@pytest.mark.parametrize('args', [ + ('UTC', 0), + ('EST', -18000), + ('EST', timedelta(hours=-5)), + (None, timedelta(hours=3)), +]) +def test_tzoffset_singleton(args): + tz1 = tz.tzoffset(*args) + tz2 = tz.tzoffset(*args) + + assert tz1 is tz2 + @pytest.mark.tzoffset @pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS, @@ -789,297 +789,297 @@ def test_tzoffset_sub_minute_rounding(): assert test_date.utcoffset() == timedelta(hours=12, minutes=1) -@pytest.mark.tzlocal -class TzLocalTest(unittest.TestCase): - def testEquality(self): - tz1 = tz.tzlocal() - tz2 = tz.tzlocal() - - # Explicitly calling == and != here to ensure the operators work - self.assertTrue(tz1 == tz2) - self.assertFalse(tz1 != tz2) - - def testInequalityFixedOffset(self): - tzl = tz.tzlocal() - tzos = tz.tzoffset('LST', tzl._std_offset.total_seconds()) - tzod = tz.tzoffset('LDT', tzl._std_offset.total_seconds()) - - self.assertFalse(tzl == tzos) - self.assertFalse(tzl == tzod) - self.assertTrue(tzl != tzos) - self.assertTrue(tzl != tzod) - - def testInequalityInvalid(self): - tzl = tz.tzlocal() - - self.assertTrue(tzl != 1) - self.assertFalse(tzl == 1) - - # TODO: Use some sort of universal local mocking so that it's clear - # that we're expecting tzlocal to *not* be Pacific/Kiritimati - LINT = tz.gettz('Pacific/Kiritimati') - self.assertTrue(tzl != LINT) - self.assertFalse(tzl == LINT) - - def testInequalityUnsupported(self): - tzl = tz.tzlocal() - - self.assertTrue(tzl == ComparesEqual) - self.assertFalse(tzl != ComparesEqual) - - def testRepr(self): - tzl = tz.tzlocal() - - self.assertEqual(repr(tzl), 'tzlocal()') - - -@pytest.mark.parametrize('args,kwargs', [ - (('EST', -18000), {}), - (('EST', timedelta(hours=-5)), {}), - (('EST',), {'offset': -18000}), - (('EST',), {'offset': timedelta(hours=-5)}), - (tuple(), {'name': 'EST', 'offset': -18000}) -]) -def test_tzoffset_is(args, kwargs): - tz_ref = tz.tzoffset('EST', -18000) - assert tz.tzoffset(*args, **kwargs) is tz_ref - - -def test_tzoffset_is_not(): - assert tz.tzoffset('EDT', -14400) is not tz.tzoffset('EST', -18000) - - -@pytest.mark.tzlocal -@unittest.skipIf(IS_WIN, "requires Unix") -class TzLocalNixTest(unittest.TestCase, TzFoldMixin): - # This is a set of tests for `tzlocal()` on *nix systems - - # POSIX string indicating change to summer time on the 2nd Sunday in March - # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007) - TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2' - - # POSIX string for AEST/AEDT (valid >= 2008) - TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3' - - # POSIX string for BST/GMT - TZ_LON = 'GMT0BST,M3.5.0,M10.5.0' - - # POSIX string for UTC - UTC = 'UTC' - - def gettz(self, tzname): - # Actual time zone changes are handled by the _gettz_context function - return tz.tzlocal() - - def _gettz_context(self, tzname): - tzname_map = {'Australia/Sydney': self.TZ_AEST, - 'America/Toronto': self.TZ_EST, - 'America/New_York': self.TZ_EST, - 'Europe/London': self.TZ_LON} - - return TZEnvContext(tzname_map.get(tzname, tzname)) - - def _testTzFunc(self, tzval, func, std_val, dst_val): - """ - This generates tests about how the behavior of a function ``func`` - changes between STD and DST (e.g. utcoffset, tzname, dst). - - It assume that DST starts the 2nd Sunday in March and ends the 1st - Sunday in November - """ - with TZEnvContext(tzval): - dt1 = datetime(2015, 2, 1, 12, 0, tzinfo=tz.tzlocal()) # STD - dt2 = datetime(2015, 5, 1, 12, 0, tzinfo=tz.tzlocal()) # DST - - self.assertEqual(func(dt1), std_val) - self.assertEqual(func(dt2), dst_val) - - def _testTzName(self, tzval, std_name, dst_name): - func = datetime.tzname - - self._testTzFunc(tzval, func, std_name, dst_name) - - def testTzNameDST(self): - # Test tzname in a zone with DST - self._testTzName(self.TZ_EST, 'EST', 'EDT') - - def testTzNameUTC(self): - # Test tzname in a zone without DST - self._testTzName(self.UTC, 'UTC', 'UTC') - - def _testOffset(self, tzval, std_off, dst_off): - func = datetime.utcoffset - - self._testTzFunc(tzval, func, std_off, dst_off) - - def testOffsetDST(self): - self._testOffset(self.TZ_EST, timedelta(hours=-5), timedelta(hours=-4)) - - def testOffsetUTC(self): - self._testOffset(self.UTC, timedelta(0), timedelta(0)) - - def _testDST(self, tzval, dst_dst): - func = datetime.dst - std_dst = timedelta(0) - - self._testTzFunc(tzval, func, std_dst, dst_dst) - - def testDSTDST(self): - self._testDST(self.TZ_EST, timedelta(hours=1)) - - def testDSTUTC(self): - self._testDST(self.UTC, timedelta(0)) - - def testTimeOnlyOffsetLocalUTC(self): - with TZEnvContext(self.UTC): - self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(), - timedelta(0)) - - def testTimeOnlyOffsetLocalDST(self): - with TZEnvContext(self.TZ_EST): - self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(), - None) - - def testTimeOnlyDSTLocalUTC(self): - with TZEnvContext(self.UTC): - self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(), - timedelta(0)) - - def testTimeOnlyDSTLocalDST(self): - with TZEnvContext(self.TZ_EST): - self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(), - None) - - def testUTCEquality(self): - with TZEnvContext(self.UTC): +@pytest.mark.tzlocal +class TzLocalTest(unittest.TestCase): + def testEquality(self): + tz1 = tz.tzlocal() + tz2 = tz.tzlocal() + + # Explicitly calling == and != here to ensure the operators work + self.assertTrue(tz1 == tz2) + self.assertFalse(tz1 != tz2) + + def testInequalityFixedOffset(self): + tzl = tz.tzlocal() + tzos = tz.tzoffset('LST', tzl._std_offset.total_seconds()) + tzod = tz.tzoffset('LDT', tzl._std_offset.total_seconds()) + + self.assertFalse(tzl == tzos) + self.assertFalse(tzl == tzod) + self.assertTrue(tzl != tzos) + self.assertTrue(tzl != tzod) + + def testInequalityInvalid(self): + tzl = tz.tzlocal() + + self.assertTrue(tzl != 1) + self.assertFalse(tzl == 1) + + # TODO: Use some sort of universal local mocking so that it's clear + # that we're expecting tzlocal to *not* be Pacific/Kiritimati + LINT = tz.gettz('Pacific/Kiritimati') + self.assertTrue(tzl != LINT) + self.assertFalse(tzl == LINT) + + def testInequalityUnsupported(self): + tzl = tz.tzlocal() + + self.assertTrue(tzl == ComparesEqual) + self.assertFalse(tzl != ComparesEqual) + + def testRepr(self): + tzl = tz.tzlocal() + + self.assertEqual(repr(tzl), 'tzlocal()') + + +@pytest.mark.parametrize('args,kwargs', [ + (('EST', -18000), {}), + (('EST', timedelta(hours=-5)), {}), + (('EST',), {'offset': -18000}), + (('EST',), {'offset': timedelta(hours=-5)}), + (tuple(), {'name': 'EST', 'offset': -18000}) +]) +def test_tzoffset_is(args, kwargs): + tz_ref = tz.tzoffset('EST', -18000) + assert tz.tzoffset(*args, **kwargs) is tz_ref + + +def test_tzoffset_is_not(): + assert tz.tzoffset('EDT', -14400) is not tz.tzoffset('EST', -18000) + + +@pytest.mark.tzlocal +@unittest.skipIf(IS_WIN, "requires Unix") +class TzLocalNixTest(unittest.TestCase, TzFoldMixin): + # This is a set of tests for `tzlocal()` on *nix systems + + # POSIX string indicating change to summer time on the 2nd Sunday in March + # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007) + TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2' + + # POSIX string for AEST/AEDT (valid >= 2008) + TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3' + + # POSIX string for BST/GMT + TZ_LON = 'GMT0BST,M3.5.0,M10.5.0' + + # POSIX string for UTC + UTC = 'UTC' + + def gettz(self, tzname): + # Actual time zone changes are handled by the _gettz_context function + return tz.tzlocal() + + def _gettz_context(self, tzname): + tzname_map = {'Australia/Sydney': self.TZ_AEST, + 'America/Toronto': self.TZ_EST, + 'America/New_York': self.TZ_EST, + 'Europe/London': self.TZ_LON} + + return TZEnvContext(tzname_map.get(tzname, tzname)) + + def _testTzFunc(self, tzval, func, std_val, dst_val): + """ + This generates tests about how the behavior of a function ``func`` + changes between STD and DST (e.g. utcoffset, tzname, dst). + + It assume that DST starts the 2nd Sunday in March and ends the 1st + Sunday in November + """ + with TZEnvContext(tzval): + dt1 = datetime(2015, 2, 1, 12, 0, tzinfo=tz.tzlocal()) # STD + dt2 = datetime(2015, 5, 1, 12, 0, tzinfo=tz.tzlocal()) # DST + + self.assertEqual(func(dt1), std_val) + self.assertEqual(func(dt2), dst_val) + + def _testTzName(self, tzval, std_name, dst_name): + func = datetime.tzname + + self._testTzFunc(tzval, func, std_name, dst_name) + + def testTzNameDST(self): + # Test tzname in a zone with DST + self._testTzName(self.TZ_EST, 'EST', 'EDT') + + def testTzNameUTC(self): + # Test tzname in a zone without DST + self._testTzName(self.UTC, 'UTC', 'UTC') + + def _testOffset(self, tzval, std_off, dst_off): + func = datetime.utcoffset + + self._testTzFunc(tzval, func, std_off, dst_off) + + def testOffsetDST(self): + self._testOffset(self.TZ_EST, timedelta(hours=-5), timedelta(hours=-4)) + + def testOffsetUTC(self): + self._testOffset(self.UTC, timedelta(0), timedelta(0)) + + def _testDST(self, tzval, dst_dst): + func = datetime.dst + std_dst = timedelta(0) + + self._testTzFunc(tzval, func, std_dst, dst_dst) + + def testDSTDST(self): + self._testDST(self.TZ_EST, timedelta(hours=1)) + + def testDSTUTC(self): + self._testDST(self.UTC, timedelta(0)) + + def testTimeOnlyOffsetLocalUTC(self): + with TZEnvContext(self.UTC): + self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(), + timedelta(0)) + + def testTimeOnlyOffsetLocalDST(self): + with TZEnvContext(self.TZ_EST): + self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(), + None) + + def testTimeOnlyDSTLocalUTC(self): + with TZEnvContext(self.UTC): + self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(), + timedelta(0)) + + def testTimeOnlyDSTLocalDST(self): + with TZEnvContext(self.TZ_EST): + self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(), + None) + + def testUTCEquality(self): + with TZEnvContext(self.UTC): assert tz.tzlocal() == tz.UTC - - -# TODO: Maybe a better hack than this? -def mark_tzlocal_nix(f): - marks = [ - pytest.mark.tzlocal, - pytest.mark.skipif(IS_WIN, reason='requires Unix'), - ] - - for mark in reversed(marks): - f = mark(f) - - return f - - -@mark_tzlocal_nix -@pytest.mark.parametrize('tzvar', ['UTC', 'GMT0', 'UTC0']) -def test_tzlocal_utc_equal(tzvar): - with TZEnvContext(tzvar): - assert tz.tzlocal() == tz.UTC - - -@mark_tzlocal_nix -@pytest.mark.parametrize('tzvar', [ - 'Europe/London', 'America/New_York', - 'GMT0BST', 'EST5EDT']) -def test_tzlocal_utc_unequal(tzvar): - with TZEnvContext(tzvar): - assert tz.tzlocal() != tz.UTC - - -@mark_tzlocal_nix -def test_tzlocal_local_time_trim_colon(): - with TZEnvContext(':/etc/localtime'): - assert tz.gettz() is not None - - -@mark_tzlocal_nix -@pytest.mark.parametrize('tzvar, tzoff', [ - ('EST5', tz.tzoffset('EST', -18000)), + + +# TODO: Maybe a better hack than this? +def mark_tzlocal_nix(f): + marks = [ + pytest.mark.tzlocal, + pytest.mark.skipif(IS_WIN, reason='requires Unix'), + ] + + for mark in reversed(marks): + f = mark(f) + + return f + + +@mark_tzlocal_nix +@pytest.mark.parametrize('tzvar', ['UTC', 'GMT0', 'UTC0']) +def test_tzlocal_utc_equal(tzvar): + with TZEnvContext(tzvar): + assert tz.tzlocal() == tz.UTC + + +@mark_tzlocal_nix +@pytest.mark.parametrize('tzvar', [ + 'Europe/London', 'America/New_York', + 'GMT0BST', 'EST5EDT']) +def test_tzlocal_utc_unequal(tzvar): + with TZEnvContext(tzvar): + assert tz.tzlocal() != tz.UTC + + +@mark_tzlocal_nix +def test_tzlocal_local_time_trim_colon(): + with TZEnvContext(':/etc/localtime'): + assert tz.gettz() is not None + + +@mark_tzlocal_nix +@pytest.mark.parametrize('tzvar, tzoff', [ + ('EST5', tz.tzoffset('EST', -18000)), ('GMT0', tz.tzoffset('GMT', 0)), - ('YAKT-9', tz.tzoffset('YAKT', timedelta(hours=9))), - ('JST-9', tz.tzoffset('JST', timedelta(hours=9))), -]) -def test_tzlocal_offset_equal(tzvar, tzoff): - with TZEnvContext(tzvar): - # Including both to test both __eq__ and __ne__ - assert tz.tzlocal() == tzoff - assert not (tz.tzlocal() != tzoff) - - -@mark_tzlocal_nix -@pytest.mark.parametrize('tzvar, tzoff', [ - ('EST5EDT', tz.tzoffset('EST', -18000)), - ('GMT0BST', tz.tzoffset('GMT', 0)), - ('EST5', tz.tzoffset('EST', -14400)), - ('YAKT-9', tz.tzoffset('JST', timedelta(hours=9))), - ('JST-9', tz.tzoffset('YAKT', timedelta(hours=9))), -]) -def test_tzlocal_offset_unequal(tzvar, tzoff): - with TZEnvContext(tzvar): - # Including both to test both __eq__ and __ne__ - assert tz.tzlocal() != tzoff - assert not (tz.tzlocal() == tzoff) - - -@pytest.mark.gettz -class GettzTest(unittest.TestCase, TzFoldMixin): - gettz = staticmethod(tz.gettz) - - def testGettz(self): - # bug 892569 - str(self.gettz('UTC')) - - def testGetTzEquality(self): - self.assertEqual(self.gettz('UTC'), self.gettz('UTC')) - - def testTimeOnlyGettz(self): - # gettz returns None - tz_get = self.gettz('Europe/Minsk') - self.assertIs(dt_time(13, 20, tzinfo=tz_get).utcoffset(), None) - - def testTimeOnlyGettzDST(self): - # gettz returns None - tz_get = self.gettz('Europe/Minsk') - self.assertIs(dt_time(13, 20, tzinfo=tz_get).dst(), None) - - def testTimeOnlyGettzTzName(self): - tz_get = self.gettz('Europe/Minsk') - self.assertIs(dt_time(13, 20, tzinfo=tz_get).tzname(), None) - - def testTimeOnlyFormatZ(self): - tz_get = self.gettz('Europe/Minsk') - t = dt_time(13, 20, tzinfo=tz_get) - - self.assertEqual(t.strftime('%H%M%Z'), '1320') - - def testPortugalDST(self): - # In 1996, Portugal changed from CET to WET - PORTUGAL = self.gettz('Portugal') - - t_cet = datetime(1996, 3, 31, 1, 59, tzinfo=PORTUGAL) - - self.assertEqual(t_cet.tzname(), 'CET') - self.assertEqual(t_cet.utcoffset(), timedelta(hours=1)) - self.assertEqual(t_cet.dst(), timedelta(0)) - - t_west = datetime(1996, 3, 31, 2, 1, tzinfo=PORTUGAL) - - self.assertEqual(t_west.tzname(), 'WEST') - self.assertEqual(t_west.utcoffset(), timedelta(hours=1)) - self.assertEqual(t_west.dst(), timedelta(hours=1)) - - def testGettzCacheTzFile(self): - NYC1 = tz.gettz('America/New_York') - NYC2 = tz.gettz('America/New_York') - - assert NYC1 is NYC2 - - def testGettzCacheTzLocal(self): - local1 = tz.gettz() - local2 = tz.gettz() - - assert local1 is not local2 - - -@pytest.mark.gettz + ('YAKT-9', tz.tzoffset('YAKT', timedelta(hours=9))), + ('JST-9', tz.tzoffset('JST', timedelta(hours=9))), +]) +def test_tzlocal_offset_equal(tzvar, tzoff): + with TZEnvContext(tzvar): + # Including both to test both __eq__ and __ne__ + assert tz.tzlocal() == tzoff + assert not (tz.tzlocal() != tzoff) + + +@mark_tzlocal_nix +@pytest.mark.parametrize('tzvar, tzoff', [ + ('EST5EDT', tz.tzoffset('EST', -18000)), + ('GMT0BST', tz.tzoffset('GMT', 0)), + ('EST5', tz.tzoffset('EST', -14400)), + ('YAKT-9', tz.tzoffset('JST', timedelta(hours=9))), + ('JST-9', tz.tzoffset('YAKT', timedelta(hours=9))), +]) +def test_tzlocal_offset_unequal(tzvar, tzoff): + with TZEnvContext(tzvar): + # Including both to test both __eq__ and __ne__ + assert tz.tzlocal() != tzoff + assert not (tz.tzlocal() == tzoff) + + +@pytest.mark.gettz +class GettzTest(unittest.TestCase, TzFoldMixin): + gettz = staticmethod(tz.gettz) + + def testGettz(self): + # bug 892569 + str(self.gettz('UTC')) + + def testGetTzEquality(self): + self.assertEqual(self.gettz('UTC'), self.gettz('UTC')) + + def testTimeOnlyGettz(self): + # gettz returns None + tz_get = self.gettz('Europe/Minsk') + self.assertIs(dt_time(13, 20, tzinfo=tz_get).utcoffset(), None) + + def testTimeOnlyGettzDST(self): + # gettz returns None + tz_get = self.gettz('Europe/Minsk') + self.assertIs(dt_time(13, 20, tzinfo=tz_get).dst(), None) + + def testTimeOnlyGettzTzName(self): + tz_get = self.gettz('Europe/Minsk') + self.assertIs(dt_time(13, 20, tzinfo=tz_get).tzname(), None) + + def testTimeOnlyFormatZ(self): + tz_get = self.gettz('Europe/Minsk') + t = dt_time(13, 20, tzinfo=tz_get) + + self.assertEqual(t.strftime('%H%M%Z'), '1320') + + def testPortugalDST(self): + # In 1996, Portugal changed from CET to WET + PORTUGAL = self.gettz('Portugal') + + t_cet = datetime(1996, 3, 31, 1, 59, tzinfo=PORTUGAL) + + self.assertEqual(t_cet.tzname(), 'CET') + self.assertEqual(t_cet.utcoffset(), timedelta(hours=1)) + self.assertEqual(t_cet.dst(), timedelta(0)) + + t_west = datetime(1996, 3, 31, 2, 1, tzinfo=PORTUGAL) + + self.assertEqual(t_west.tzname(), 'WEST') + self.assertEqual(t_west.utcoffset(), timedelta(hours=1)) + self.assertEqual(t_west.dst(), timedelta(hours=1)) + + def testGettzCacheTzFile(self): + NYC1 = tz.gettz('America/New_York') + NYC2 = tz.gettz('America/New_York') + + assert NYC1 is NYC2 + + def testGettzCacheTzLocal(self): + local1 = tz.gettz() + local2 = tz.gettz() + + assert local1 is not local2 + + +@pytest.mark.gettz def test_gettz_same_result_for_none_and_empty_string(): local_from_none = tz.gettz() local_from_empty_string = tz.gettz("") @@ -1137,21 +1137,21 @@ def test_gettz_zone_wrong_type(badzone, exc_reason): @pytest.mark.gettz -@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached') -def test_gettz_cache_clear(): - NYC1 = tz.gettz('America/New_York') - tz.gettz.cache_clear() - - NYC2 = tz.gettz('America/New_York') - - assert NYC1 is not NYC2 - +@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached') +def test_gettz_cache_clear(): + NYC1 = tz.gettz('America/New_York') + tz.gettz.cache_clear() + + NYC2 = tz.gettz('America/New_York') + + assert NYC1 is not NYC2 + @pytest.mark.gettz @pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached') def test_gettz_set_cache_size(): tz.gettz.cache_clear() tz.gettz.set_cache_size(3) - + MONACO_ref = weakref.ref(tz.gettz('Europe/Monaco')) EASTER_ref = weakref.ref(tz.gettz('Pacific/Easter')) CURRIE_ref = weakref.ref(tz.gettz('Australia/Currie')) @@ -1194,335 +1194,335 @@ def test_gettz_weakref(): assert tz.gettz('America/New_York') is not NYC_ref() class ZoneInfoGettzTest(GettzTest): - def gettz(self, name): - zoneinfo_file = zoneinfo.get_zonefile_instance() - return zoneinfo_file.get(name) - - def testZoneInfoFileStart1(self): - tz = self.gettz("EST5EDT") - self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST", - MISSING_TARBALL) - self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT") - - def testZoneInfoFileEnd1(self): - tzc = self.gettz("EST5EDT") - self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(), - "EDT", MISSING_TARBALL) - - end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc), fold=1) - self.assertEqual(end_est.tzname(), "EST") - - def testZoneInfoOffsetSignal(self): - utc = self.gettz("UTC") - nyc = self.gettz("America/New_York") - self.assertNotEqual(utc, None, MISSING_TARBALL) - self.assertNotEqual(nyc, None) - t0 = datetime(2007, 11, 4, 0, 30, tzinfo=nyc) - t1 = t0.astimezone(utc) - t2 = t1.astimezone(nyc) - self.assertEqual(t0, t2) - self.assertEqual(nyc.dst(t0), timedelta(hours=1)) - - def testZoneInfoCopy(self): - # copy.copy() called on a ZoneInfo file was returning the same instance - CHI = self.gettz('America/Chicago') - CHI_COPY = copy.copy(CHI) - - self.assertIsNot(CHI, CHI_COPY) - self.assertEqual(CHI, CHI_COPY) - - def testZoneInfoDeepCopy(self): - CHI = self.gettz('America/Chicago') - CHI_COPY = copy.deepcopy(CHI) - - self.assertIsNot(CHI, CHI_COPY) - self.assertEqual(CHI, CHI_COPY) - - def testZoneInfoInstanceCaching(self): - zif_0 = zoneinfo.get_zonefile_instance() - zif_1 = zoneinfo.get_zonefile_instance() - - self.assertIs(zif_0, zif_1) - - def testZoneInfoNewInstance(self): - zif_0 = zoneinfo.get_zonefile_instance() - zif_1 = zoneinfo.get_zonefile_instance(new_instance=True) - zif_2 = zoneinfo.get_zonefile_instance() - - self.assertIsNot(zif_0, zif_1) - self.assertIs(zif_1, zif_2) - - def testZoneInfoDeprecated(self): + def gettz(self, name): + zoneinfo_file = zoneinfo.get_zonefile_instance() + return zoneinfo_file.get(name) + + def testZoneInfoFileStart1(self): + tz = self.gettz("EST5EDT") + self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST", + MISSING_TARBALL) + self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT") + + def testZoneInfoFileEnd1(self): + tzc = self.gettz("EST5EDT") + self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(), + "EDT", MISSING_TARBALL) + + end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc), fold=1) + self.assertEqual(end_est.tzname(), "EST") + + def testZoneInfoOffsetSignal(self): + utc = self.gettz("UTC") + nyc = self.gettz("America/New_York") + self.assertNotEqual(utc, None, MISSING_TARBALL) + self.assertNotEqual(nyc, None) + t0 = datetime(2007, 11, 4, 0, 30, tzinfo=nyc) + t1 = t0.astimezone(utc) + t2 = t1.astimezone(nyc) + self.assertEqual(t0, t2) + self.assertEqual(nyc.dst(t0), timedelta(hours=1)) + + def testZoneInfoCopy(self): + # copy.copy() called on a ZoneInfo file was returning the same instance + CHI = self.gettz('America/Chicago') + CHI_COPY = copy.copy(CHI) + + self.assertIsNot(CHI, CHI_COPY) + self.assertEqual(CHI, CHI_COPY) + + def testZoneInfoDeepCopy(self): + CHI = self.gettz('America/Chicago') + CHI_COPY = copy.deepcopy(CHI) + + self.assertIsNot(CHI, CHI_COPY) + self.assertEqual(CHI, CHI_COPY) + + def testZoneInfoInstanceCaching(self): + zif_0 = zoneinfo.get_zonefile_instance() + zif_1 = zoneinfo.get_zonefile_instance() + + self.assertIs(zif_0, zif_1) + + def testZoneInfoNewInstance(self): + zif_0 = zoneinfo.get_zonefile_instance() + zif_1 = zoneinfo.get_zonefile_instance(new_instance=True) + zif_2 = zoneinfo.get_zonefile_instance() + + self.assertIsNot(zif_0, zif_1) + self.assertIs(zif_1, zif_2) + + def testZoneInfoDeprecated(self): with pytest.warns(DeprecationWarning): - zoneinfo.gettz('US/Eastern') - - def testZoneInfoMetadataDeprecated(self): + zoneinfo.gettz('US/Eastern') + + def testZoneInfoMetadataDeprecated(self): with pytest.warns(DeprecationWarning): - zoneinfo.gettz_db_metadata() - - -class TZRangeTest(unittest.TestCase, TzFoldMixin): - TZ_EST = tz.tzrange('EST', timedelta(hours=-5), - 'EDT', timedelta(hours=-4), - start=relativedelta(month=3, day=1, hour=2, - weekday=SU(+2)), - end=relativedelta(month=11, day=1, hour=1, - weekday=SU(+1))) - - TZ_AEST = tz.tzrange('AEST', timedelta(hours=10), - 'AEDT', timedelta(hours=11), - start=relativedelta(month=10, day=1, hour=2, - weekday=SU(+1)), - end=relativedelta(month=4, day=1, hour=2, - weekday=SU(+1))) - - TZ_LON = tz.tzrange('GMT', timedelta(hours=0), - 'BST', timedelta(hours=1), - start=relativedelta(month=3, day=31, weekday=SU(-1), - hours=2), - end=relativedelta(month=10, day=31, weekday=SU(-1), - hours=1)) - # POSIX string for UTC - UTC = 'UTC' - - def gettz(self, tzname): - tzname_map = {'Australia/Sydney': self.TZ_AEST, - 'America/Toronto': self.TZ_EST, - 'America/New_York': self.TZ_EST, - 'Europe/London': self.TZ_LON} - - return tzname_map[tzname] - - def testRangeCmp1(self): - self.assertEqual(tz.tzstr("EST5EDT"), - tz.tzrange("EST", -18000, "EDT", -14400, - relativedelta(hours=+2, - month=4, day=1, - weekday=SU(+1)), - relativedelta(hours=+1, - month=10, day=31, - weekday=SU(-1)))) - - def testRangeCmp2(self): - self.assertEqual(tz.tzstr("EST5EDT"), - tz.tzrange("EST", -18000, "EDT")) - - def testRangeOffsets(self): - TZR = tz.tzrange('EST', -18000, 'EDT', -14400, - start=relativedelta(hours=2, month=4, day=1, - weekday=SU(+2)), - end=relativedelta(hours=1, month=10, day=31, - weekday=SU(-1))) - - dt_std = datetime(2014, 4, 11, 12, 0, tzinfo=TZR) # STD - dt_dst = datetime(2016, 4, 11, 12, 0, tzinfo=TZR) # DST - - dst_zero = timedelta(0) - dst_hour = timedelta(hours=1) - - std_offset = timedelta(hours=-5) - dst_offset = timedelta(hours=-4) - - # Check dst() - self.assertEqual(dt_std.dst(), dst_zero) - self.assertEqual(dt_dst.dst(), dst_hour) - - # Check utcoffset() - self.assertEqual(dt_std.utcoffset(), std_offset) - self.assertEqual(dt_dst.utcoffset(), dst_offset) - - # Check tzname - self.assertEqual(dt_std.tzname(), 'EST') - self.assertEqual(dt_dst.tzname(), 'EDT') - - def testTimeOnlyRangeFixed(self): - # This is a fixed-offset zone, so tzrange allows this - tz_range = tz.tzrange('dflt', stdoffset=timedelta(hours=-3)) - self.assertEqual(dt_time(13, 20, tzinfo=tz_range).utcoffset(), - timedelta(hours=-3)) - - def testTimeOnlyRange(self): - # tzrange returns None because this zone has DST - tz_range = tz.tzrange('EST', timedelta(hours=-5), - 'EDT', timedelta(hours=-4)) - self.assertIs(dt_time(13, 20, tzinfo=tz_range).utcoffset(), None) - - def testBrokenIsDstHandling(self): - # tzrange._isdst() was using a date() rather than a datetime(). - # Issue reported by Lennart Regebro. + zoneinfo.gettz_db_metadata() + + +class TZRangeTest(unittest.TestCase, TzFoldMixin): + TZ_EST = tz.tzrange('EST', timedelta(hours=-5), + 'EDT', timedelta(hours=-4), + start=relativedelta(month=3, day=1, hour=2, + weekday=SU(+2)), + end=relativedelta(month=11, day=1, hour=1, + weekday=SU(+1))) + + TZ_AEST = tz.tzrange('AEST', timedelta(hours=10), + 'AEDT', timedelta(hours=11), + start=relativedelta(month=10, day=1, hour=2, + weekday=SU(+1)), + end=relativedelta(month=4, day=1, hour=2, + weekday=SU(+1))) + + TZ_LON = tz.tzrange('GMT', timedelta(hours=0), + 'BST', timedelta(hours=1), + start=relativedelta(month=3, day=31, weekday=SU(-1), + hours=2), + end=relativedelta(month=10, day=31, weekday=SU(-1), + hours=1)) + # POSIX string for UTC + UTC = 'UTC' + + def gettz(self, tzname): + tzname_map = {'Australia/Sydney': self.TZ_AEST, + 'America/Toronto': self.TZ_EST, + 'America/New_York': self.TZ_EST, + 'Europe/London': self.TZ_LON} + + return tzname_map[tzname] + + def testRangeCmp1(self): + self.assertEqual(tz.tzstr("EST5EDT"), + tz.tzrange("EST", -18000, "EDT", -14400, + relativedelta(hours=+2, + month=4, day=1, + weekday=SU(+1)), + relativedelta(hours=+1, + month=10, day=31, + weekday=SU(-1)))) + + def testRangeCmp2(self): + self.assertEqual(tz.tzstr("EST5EDT"), + tz.tzrange("EST", -18000, "EDT")) + + def testRangeOffsets(self): + TZR = tz.tzrange('EST', -18000, 'EDT', -14400, + start=relativedelta(hours=2, month=4, day=1, + weekday=SU(+2)), + end=relativedelta(hours=1, month=10, day=31, + weekday=SU(-1))) + + dt_std = datetime(2014, 4, 11, 12, 0, tzinfo=TZR) # STD + dt_dst = datetime(2016, 4, 11, 12, 0, tzinfo=TZR) # DST + + dst_zero = timedelta(0) + dst_hour = timedelta(hours=1) + + std_offset = timedelta(hours=-5) + dst_offset = timedelta(hours=-4) + + # Check dst() + self.assertEqual(dt_std.dst(), dst_zero) + self.assertEqual(dt_dst.dst(), dst_hour) + + # Check utcoffset() + self.assertEqual(dt_std.utcoffset(), std_offset) + self.assertEqual(dt_dst.utcoffset(), dst_offset) + + # Check tzname + self.assertEqual(dt_std.tzname(), 'EST') + self.assertEqual(dt_dst.tzname(), 'EDT') + + def testTimeOnlyRangeFixed(self): + # This is a fixed-offset zone, so tzrange allows this + tz_range = tz.tzrange('dflt', stdoffset=timedelta(hours=-3)) + self.assertEqual(dt_time(13, 20, tzinfo=tz_range).utcoffset(), + timedelta(hours=-3)) + + def testTimeOnlyRange(self): + # tzrange returns None because this zone has DST + tz_range = tz.tzrange('EST', timedelta(hours=-5), + 'EDT', timedelta(hours=-4)) + self.assertIs(dt_time(13, 20, tzinfo=tz_range).utcoffset(), None) + + def testBrokenIsDstHandling(self): + # tzrange._isdst() was using a date() rather than a datetime(). + # Issue reported by Lennart Regebro. dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.UTC) - self.assertEqual(dt.astimezone(tz=tz.gettz("GMT+2")), - datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2"))) - - def testRangeTimeDelta(self): - # Test that tzrange can be specified with a timedelta instead of an int. - EST5EDT_td = tz.tzrange('EST', timedelta(hours=-5), - 'EDT', timedelta(hours=-4)) - - EST5EDT_sec = tz.tzrange('EST', -18000, - 'EDT', -14400) - - self.assertEqual(EST5EDT_td, EST5EDT_sec) - - def testRangeEquality(self): - TZR1 = tz.tzrange('EST', -18000, 'EDT', -14400) - - # Standard abbreviation different - TZR2 = tz.tzrange('ET', -18000, 'EDT', -14400) - self.assertNotEqual(TZR1, TZR2) - - # DST abbreviation different - TZR3 = tz.tzrange('EST', -18000, 'EMT', -14400) - self.assertNotEqual(TZR1, TZR3) - - # STD offset different - TZR4 = tz.tzrange('EST', -14000, 'EDT', -14400) - self.assertNotEqual(TZR1, TZR4) - - # DST offset different - TZR5 = tz.tzrange('EST', -18000, 'EDT', -18000) - self.assertNotEqual(TZR1, TZR5) - - # Start delta different - TZR6 = tz.tzrange('EST', -18000, 'EDT', -14400, - start=relativedelta(hours=+1, month=3, - day=1, weekday=SU(+2))) - self.assertNotEqual(TZR1, TZR6) - - # End delta different - TZR7 = tz.tzrange('EST', -18000, 'EDT', -14400, - end=relativedelta(hours=+1, month=11, - day=1, weekday=SU(+2))) - self.assertNotEqual(TZR1, TZR7) - - def testRangeInequalityUnsupported(self): - TZR = tz.tzrange('EST', -18000, 'EDT', -14400) - - self.assertFalse(TZR == 4) - self.assertTrue(TZR == ComparesEqual) - self.assertFalse(TZR != ComparesEqual) - - -@pytest.mark.tzstr -class TZStrTest(unittest.TestCase, TzFoldMixin): - # POSIX string indicating change to summer time on the 2nd Sunday in March - # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007) - TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2' - - # POSIX string for AEST/AEDT (valid >= 2008) - TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3' - - # POSIX string for GMT/BST - TZ_LON = 'GMT0BST,M3.5.0,M10.5.0' - - def gettz(self, tzname): - # Actual time zone changes are handled by the _gettz_context function - tzname_map = {'Australia/Sydney': self.TZ_AEST, - 'America/Toronto': self.TZ_EST, - 'America/New_York': self.TZ_EST, - 'Europe/London': self.TZ_LON} - - return tz.tzstr(tzname_map[tzname]) - - def testStrStr(self): - # Test that tz.tzstr() won't throw an error if given a str instead - # of a unicode literal. - self.assertEqual(datetime(2003, 4, 6, 1, 59, - tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EST") - self.assertEqual(datetime(2003, 4, 6, 2, 00, - tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EDT") - - def testStrInequality(self): - TZS1 = tz.tzstr('EST5EDT4') - - # Standard abbreviation different - TZS2 = tz.tzstr('ET5EDT4') - self.assertNotEqual(TZS1, TZS2) - - # DST abbreviation different - TZS3 = tz.tzstr('EST5EMT') - self.assertNotEqual(TZS1, TZS3) - - # STD offset different - TZS4 = tz.tzstr('EST4EDT4') - self.assertNotEqual(TZS1, TZS4) - - # DST offset different - TZS5 = tz.tzstr('EST5EDT3') - self.assertNotEqual(TZS1, TZS5) - - def testStrInequalityStartEnd(self): - TZS1 = tz.tzstr('EST5EDT4') - - # Start delta different - TZS2 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M10-5-0/02:00') - self.assertNotEqual(TZS1, TZS2) - - # End delta different - TZS3 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M11-5-0/02:00') - self.assertNotEqual(TZS1, TZS3) - - def testPosixOffset(self): - TZ1 = tz.tzstr('UTC-3') - self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ1).utcoffset(), - timedelta(hours=-3)) - - TZ2 = tz.tzstr('UTC-3', posix_offset=True) - self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ2).utcoffset(), - timedelta(hours=+3)) - - def testStrInequalityUnsupported(self): - TZS = tz.tzstr('EST5EDT') - - self.assertFalse(TZS == 4) - self.assertTrue(TZS == ComparesEqual) - self.assertFalse(TZS != ComparesEqual) - - def testTzStrRepr(self): - TZS1 = tz.tzstr('EST5EDT4') - TZS2 = tz.tzstr('EST') - - self.assertEqual(repr(TZS1), "tzstr(" + repr('EST5EDT4') + ")") - self.assertEqual(repr(TZS2), "tzstr(" + repr('EST') + ")") - - def testTzStrFailure(self): - with self.assertRaises(ValueError): - tz.tzstr('InvalidString;439999') - - def testTzStrSingleton(self): - tz1 = tz.tzstr('EST5EDT') - tz2 = tz.tzstr('CST4CST') - tz3 = tz.tzstr('EST5EDT') - - self.assertIsNot(tz1, tz2) - self.assertIs(tz1, tz3) - - def testTzStrSingletonPosix(self): - tz_t1 = tz.tzstr('GMT+3', posix_offset=True) - tz_f1 = tz.tzstr('GMT+3', posix_offset=False) - - tz_t2 = tz.tzstr('GMT+3', posix_offset=True) - tz_f2 = tz.tzstr('GMT+3', posix_offset=False) - - self.assertIs(tz_t1, tz_t2) - self.assertIsNot(tz_t1, tz_f1) - - self.assertIs(tz_f1, tz_f2) - - def testTzStrInstance(self): - tz1 = tz.tzstr('EST5EDT') - tz2 = tz.tzstr.instance('EST5EDT') - tz3 = tz.tzstr.instance('EST5EDT') - - assert tz1 is not tz2 - assert tz2 is not tz3 - - # Ensure that these still are all the same zone - assert tz1 == tz2 == tz3 - + self.assertEqual(dt.astimezone(tz=tz.gettz("GMT+2")), + datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2"))) + + def testRangeTimeDelta(self): + # Test that tzrange can be specified with a timedelta instead of an int. + EST5EDT_td = tz.tzrange('EST', timedelta(hours=-5), + 'EDT', timedelta(hours=-4)) + + EST5EDT_sec = tz.tzrange('EST', -18000, + 'EDT', -14400) + + self.assertEqual(EST5EDT_td, EST5EDT_sec) + + def testRangeEquality(self): + TZR1 = tz.tzrange('EST', -18000, 'EDT', -14400) + + # Standard abbreviation different + TZR2 = tz.tzrange('ET', -18000, 'EDT', -14400) + self.assertNotEqual(TZR1, TZR2) + + # DST abbreviation different + TZR3 = tz.tzrange('EST', -18000, 'EMT', -14400) + self.assertNotEqual(TZR1, TZR3) + + # STD offset different + TZR4 = tz.tzrange('EST', -14000, 'EDT', -14400) + self.assertNotEqual(TZR1, TZR4) + + # DST offset different + TZR5 = tz.tzrange('EST', -18000, 'EDT', -18000) + self.assertNotEqual(TZR1, TZR5) + + # Start delta different + TZR6 = tz.tzrange('EST', -18000, 'EDT', -14400, + start=relativedelta(hours=+1, month=3, + day=1, weekday=SU(+2))) + self.assertNotEqual(TZR1, TZR6) + + # End delta different + TZR7 = tz.tzrange('EST', -18000, 'EDT', -14400, + end=relativedelta(hours=+1, month=11, + day=1, weekday=SU(+2))) + self.assertNotEqual(TZR1, TZR7) + + def testRangeInequalityUnsupported(self): + TZR = tz.tzrange('EST', -18000, 'EDT', -14400) + + self.assertFalse(TZR == 4) + self.assertTrue(TZR == ComparesEqual) + self.assertFalse(TZR != ComparesEqual) + + +@pytest.mark.tzstr +class TZStrTest(unittest.TestCase, TzFoldMixin): + # POSIX string indicating change to summer time on the 2nd Sunday in March + # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007) + TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2' + + # POSIX string for AEST/AEDT (valid >= 2008) + TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3' + + # POSIX string for GMT/BST + TZ_LON = 'GMT0BST,M3.5.0,M10.5.0' + + def gettz(self, tzname): + # Actual time zone changes are handled by the _gettz_context function + tzname_map = {'Australia/Sydney': self.TZ_AEST, + 'America/Toronto': self.TZ_EST, + 'America/New_York': self.TZ_EST, + 'Europe/London': self.TZ_LON} + + return tz.tzstr(tzname_map[tzname]) + + def testStrStr(self): + # Test that tz.tzstr() won't throw an error if given a str instead + # of a unicode literal. + self.assertEqual(datetime(2003, 4, 6, 1, 59, + tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EST") + self.assertEqual(datetime(2003, 4, 6, 2, 00, + tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EDT") + + def testStrInequality(self): + TZS1 = tz.tzstr('EST5EDT4') + + # Standard abbreviation different + TZS2 = tz.tzstr('ET5EDT4') + self.assertNotEqual(TZS1, TZS2) + + # DST abbreviation different + TZS3 = tz.tzstr('EST5EMT') + self.assertNotEqual(TZS1, TZS3) + + # STD offset different + TZS4 = tz.tzstr('EST4EDT4') + self.assertNotEqual(TZS1, TZS4) + + # DST offset different + TZS5 = tz.tzstr('EST5EDT3') + self.assertNotEqual(TZS1, TZS5) + + def testStrInequalityStartEnd(self): + TZS1 = tz.tzstr('EST5EDT4') + + # Start delta different + TZS2 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M10-5-0/02:00') + self.assertNotEqual(TZS1, TZS2) + + # End delta different + TZS3 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M11-5-0/02:00') + self.assertNotEqual(TZS1, TZS3) + + def testPosixOffset(self): + TZ1 = tz.tzstr('UTC-3') + self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ1).utcoffset(), + timedelta(hours=-3)) + + TZ2 = tz.tzstr('UTC-3', posix_offset=True) + self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ2).utcoffset(), + timedelta(hours=+3)) + + def testStrInequalityUnsupported(self): + TZS = tz.tzstr('EST5EDT') + + self.assertFalse(TZS == 4) + self.assertTrue(TZS == ComparesEqual) + self.assertFalse(TZS != ComparesEqual) + + def testTzStrRepr(self): + TZS1 = tz.tzstr('EST5EDT4') + TZS2 = tz.tzstr('EST') + + self.assertEqual(repr(TZS1), "tzstr(" + repr('EST5EDT4') + ")") + self.assertEqual(repr(TZS2), "tzstr(" + repr('EST') + ")") + + def testTzStrFailure(self): + with self.assertRaises(ValueError): + tz.tzstr('InvalidString;439999') + + def testTzStrSingleton(self): + tz1 = tz.tzstr('EST5EDT') + tz2 = tz.tzstr('CST4CST') + tz3 = tz.tzstr('EST5EDT') + + self.assertIsNot(tz1, tz2) + self.assertIs(tz1, tz3) + + def testTzStrSingletonPosix(self): + tz_t1 = tz.tzstr('GMT+3', posix_offset=True) + tz_f1 = tz.tzstr('GMT+3', posix_offset=False) + + tz_t2 = tz.tzstr('GMT+3', posix_offset=True) + tz_f2 = tz.tzstr('GMT+3', posix_offset=False) + + self.assertIs(tz_t1, tz_t2) + self.assertIsNot(tz_t1, tz_f1) + + self.assertIs(tz_f1, tz_f2) + + def testTzStrInstance(self): + tz1 = tz.tzstr('EST5EDT') + tz2 = tz.tzstr.instance('EST5EDT') + tz3 = tz.tzstr.instance('EST5EDT') + + assert tz1 is not tz2 + assert tz2 is not tz3 + + # Ensure that these still are all the same zone + assert tz1 == tz2 == tz3 + @pytest.mark.smoke -@pytest.mark.tzstr +@pytest.mark.tzstr def test_tzstr_weakref(): tz_t1 = tz.tzstr('EST5EDT') tz_t2_ref = weakref.ref(tz.tzstr('EST5EDT')) @@ -1543,547 +1543,547 @@ def test_tzstr_weakref(): @pytest.mark.tzstr -@pytest.mark.parametrize('tz_str,expected', [ - # From https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html - ('', tz.tzrange(None)), # TODO: Should change this so tz.tzrange('') works - ('EST+5EDT,M3.2.0/2,M11.1.0/12', - tz.tzrange('EST', -18000, 'EDT', -14400, - start=relativedelta(month=3, day=1, weekday=SU(2), hours=2), - end=relativedelta(month=11, day=1, weekday=SU(1), hours=11))), - ('WART4WARST,J1/0,J365/25', # This is DST all year, Western Argentina Summer Time - tz.tzrange('WART', timedelta(hours=-4), 'WARST', - start=relativedelta(month=1, day=1, hours=0), - end=relativedelta(month=12, day=31, days=1))), - ('IST-2IDT,M3.4.4/26,M10.5.0', # Israel Standard / Daylight Time - tz.tzrange('IST', timedelta(hours=2), 'IDT', - start=relativedelta(month=3, day=1, weekday=TH(4), days=1, hours=2), - end=relativedelta(month=10, day=31, weekday=SU(-1), hours=1))), - ('WGT3WGST,M3.5.0/2,M10.5.0/1', - tz.tzrange('WGT', timedelta(hours=-3), 'WGST', - start=relativedelta(month=3, day=31, weekday=SU(-1), hours=2), - end=relativedelta(month=10, day=31, weekday=SU(-1), hours=0))), - - # Different offset specifications - ('WGT0300WGST', - tz.tzrange('WGT', timedelta(hours=-3), 'WGST')), - ('WGT03:00WGST', - tz.tzrange('WGT', timedelta(hours=-3), 'WGST')), - ('AEST-1100AEDT', - tz.tzrange('AEST', timedelta(hours=11), 'AEDT')), - ('AEST-11:00AEDT', - tz.tzrange('AEST', timedelta(hours=11), 'AEDT')), - - # Different time formats - ('EST5EDT,M3.2.0/4:00,M11.1.0/3:00', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', - start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), - end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), - ('EST5EDT,M3.2.0/04:00,M11.1.0/03:00', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', - start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), - end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), - ('EST5EDT,M3.2.0/0400,M11.1.0/0300', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', - start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), - end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), -]) -def test_valid_GNU_tzstr(tz_str, expected): - tzi = tz.tzstr(tz_str) - - assert tzi == expected - - -@pytest.mark.tzstr -@pytest.mark.parametrize('tz_str, expected', [ - ('EST5EDT,5,4,0,7200,11,3,0,7200', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', - start=relativedelta(month=5, day=1, weekday=SU(+4), hours=+2), - end=relativedelta(month=11, day=1, weekday=SU(+3), hours=+1))), - ('EST5EDT,5,-4,0,7200,11,3,0,7200', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', - start=relativedelta(hours=+2, month=5, day=31, weekday=SU(-4)), - end=relativedelta(hours=+1, month=11, day=1, weekday=SU(+3)))), - ('EST5EDT,5,4,0,7200,11,-3,0,7200', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', - start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), - end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), - ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', - start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), - end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), - ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', - start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), - end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), - ('EST5EDT,5,4,0,7200,11,-3,0,7200,-3600', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-6), - start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), - end=relativedelta(hours=+3, month=11, day=31, weekday=SU(-3)))), - ('EST5EDT,5,4,0,7200,11,-3,0,7200,+7200', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-3), - start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), - end=relativedelta(hours=0, month=11, day=31, weekday=SU(-3)))), - ('EST5EDT,5,4,0,7200,11,-3,0,7200,+3600', - tz.tzrange('EST', timedelta(hours=-5), 'EDT', - start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), - end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), -]) -def test_valid_dateutil_format(tz_str, expected): - # This tests the dateutil-specific format that is used widely in the tests - # and examples. It is unclear where this format originated from. - with pytest.warns(tz.DeprecatedTzFormatWarning): - tzi = tz.tzstr.instance(tz_str) - - assert tzi == expected - - -@pytest.mark.tzstr -@pytest.mark.parametrize('tz_str', [ - 'hdfiughdfuig,dfughdfuigpu87ñ::', - ',dfughdfuigpu87ñ::', - '-1:WART4WARST,J1,J365/25', - 'WART4WARST,J1,J365/-25', - 'IST-2IDT,M3.4.-1/26,M10.5.0', - 'IST-2IDT,M3,2000,1/26,M10,5,0' -]) -def test_invalid_GNU_tzstr(tz_str): - with pytest.raises(ValueError): - tz.tzstr(tz_str) - - -# Different representations of the same default rule set -DEFAULT_TZSTR_RULES_EQUIV_2003 = [ - 'EST5EDT', - 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00', - 'EST5EDT4,95/02:00:00,298/02:00', - 'EST5EDT4,J96/02:00:00,J299/02:00', - 'EST5EDT4,J96/02:00:00,J299/02' -] - - -@pytest.mark.tzstr -@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003) -def test_tzstr_default_start(tz_str): - tzi = tz.tzstr(tz_str) - dt_std = datetime(2003, 4, 6, 1, 59, tzinfo=tzi) - dt_dst = datetime(2003, 4, 6, 2, 00, tzinfo=tzi) - - assert get_timezone_tuple(dt_std) == EST_TUPLE - assert get_timezone_tuple(dt_dst) == EDT_TUPLE - - -@pytest.mark.tzstr -@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003) -def test_tzstr_default_end(tz_str): - tzi = tz.tzstr(tz_str) - dt_dst = datetime(2003, 10, 26, 0, 59, tzinfo=tzi) - dt_dst_ambig = datetime(2003, 10, 26, 1, 00, tzinfo=tzi) - dt_std_ambig = tz.enfold(dt_dst_ambig, fold=1) - dt_std = datetime(2003, 10, 26, 2, 00, tzinfo=tzi) - - assert get_timezone_tuple(dt_dst) == EDT_TUPLE - assert get_timezone_tuple(dt_dst_ambig) == EDT_TUPLE - assert get_timezone_tuple(dt_std_ambig) == EST_TUPLE - assert get_timezone_tuple(dt_std) == EST_TUPLE - - -@pytest.mark.tzstr -@pytest.mark.parametrize('tzstr_1', ['EST5EDT', - 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00']) -@pytest.mark.parametrize('tzstr_2', ['EST5EDT', - 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00']) -def test_tzstr_default_cmp(tzstr_1, tzstr_2): - tz1 = tz.tzstr(tzstr_1) - tz2 = tz.tzstr(tzstr_2) - - assert tz1 == tz2 - -class TZICalTest(unittest.TestCase, TzFoldMixin): - def _gettz_str_tuple(self, tzname): - TZ_EST = ( - 'BEGIN:VTIMEZONE', - 'TZID:US-Eastern', - 'BEGIN:STANDARD', - 'DTSTART:19971029T020000', - 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11', - 'TZOFFSETFROM:-0400', - 'TZOFFSETTO:-0500', - 'TZNAME:EST', - 'END:STANDARD', - 'BEGIN:DAYLIGHT', - 'DTSTART:19980301T020000', - 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03', - 'TZOFFSETFROM:-0500', - 'TZOFFSETTO:-0400', - 'TZNAME:EDT', - 'END:DAYLIGHT', - 'END:VTIMEZONE' - ) - - TZ_PST = ( - 'BEGIN:VTIMEZONE', - 'TZID:US-Pacific', - 'BEGIN:STANDARD', - 'DTSTART:19971029T020000', - 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11', - 'TZOFFSETFROM:-0700', - 'TZOFFSETTO:-0800', - 'TZNAME:PST', - 'END:STANDARD', - 'BEGIN:DAYLIGHT', - 'DTSTART:19980301T020000', - 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03', - 'TZOFFSETFROM:-0800', - 'TZOFFSETTO:-0700', - 'TZNAME:PDT', - 'END:DAYLIGHT', - 'END:VTIMEZONE' - ) - - TZ_AEST = ( - 'BEGIN:VTIMEZONE', - 'TZID:Australia-Sydney', - 'BEGIN:STANDARD', - 'DTSTART:19980301T030000', - 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=04', - 'TZOFFSETFROM:+1100', - 'TZOFFSETTO:+1000', - 'TZNAME:AEST', - 'END:STANDARD', - 'BEGIN:DAYLIGHT', - 'DTSTART:19971029T020000', - 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=10', - 'TZOFFSETFROM:+1000', - 'TZOFFSETTO:+1100', - 'TZNAME:AEDT', - 'END:DAYLIGHT', - 'END:VTIMEZONE' - ) - - TZ_LON = ( - 'BEGIN:VTIMEZONE', - 'TZID:Europe-London', - 'BEGIN:STANDARD', - 'DTSTART:19810301T030000', - 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10;BYHOUR=02', - 'TZOFFSETFROM:+0100', - 'TZOFFSETTO:+0000', - 'TZNAME:GMT', - 'END:STANDARD', - 'BEGIN:DAYLIGHT', - 'DTSTART:19961001T030000', - 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=03;BYHOUR=01', - 'TZOFFSETFROM:+0000', - 'TZOFFSETTO:+0100', - 'TZNAME:BST', - 'END:DAYLIGHT', - 'END:VTIMEZONE' - ) - - tzname_map = {'Australia/Sydney': TZ_AEST, - 'America/Toronto': TZ_EST, - 'America/New_York': TZ_EST, - 'America/Los_Angeles': TZ_PST, - 'Europe/London': TZ_LON} - - return tzname_map[tzname] - - def _gettz_str(self, tzname): - return '\n'.join(self._gettz_str_tuple(tzname)) - - def _tzstr_dtstart_with_params(self, tzname, param_str): - # Adds parameters to the DTSTART values of a given tzstr - tz_str_tuple = self._gettz_str_tuple(tzname) - - out_tz = [] - for line in tz_str_tuple: - if line.startswith('DTSTART'): - name, value = line.split(':', 1) - line = name + ';' + param_str + ':' + value - - out_tz.append(line) - - return '\n'.join(out_tz) - - def gettz(self, tzname): - tz_str = self._gettz_str(tzname) - - tzc = tz.tzical(StringIO(tz_str)).get() - - return tzc - - def testRepr(self): - instr = StringIO(TZICAL_PST8PDT) - instr.name = 'StringIO(PST8PDT)' - tzc = tz.tzical(instr) - - self.assertEqual(repr(tzc), "tzical(" + repr(instr.name) + ")") - - # Test performance - def _test_us_zone(self, tzc, func, values, start): - if start: - dt1 = datetime(2003, 3, 9, 1, 59) - dt2 = datetime(2003, 3, 9, 2, 00) - fold = [0, 0] - else: - dt1 = datetime(2003, 11, 2, 0, 59) - dt2 = datetime(2003, 11, 2, 1, 00) - fold = [0, 1] - - dts = (tz.enfold(dt.replace(tzinfo=tzc), fold=f) - for dt, f in zip((dt1, dt2), fold)) - - for value, dt in zip(values, dts): - self.assertEqual(func(dt), value) - - def _test_multi_zones(self, tzstrs, tzids, func, values, start): - tzic = tz.tzical(StringIO('\n'.join(tzstrs))) - for tzid, vals in zip(tzids, values): - tzc = tzic.get(tzid) - - self._test_us_zone(tzc, func, vals, start) - - def _prepare_EST(self): - tz_str = self._gettz_str('America/New_York') - return tz.tzical(StringIO(tz_str)).get() - - def _testEST(self, start, test_type, tzc=None): - if tzc is None: - tzc = self._prepare_EST() - - argdict = { - 'name': (datetime.tzname, ('EST', 'EDT')), - 'offset': (datetime.utcoffset, (timedelta(hours=-5), - timedelta(hours=-4))), - 'dst': (datetime.dst, (timedelta(hours=0), - timedelta(hours=1))) - } - - func, values = argdict[test_type] - - if not start: - values = reversed(values) - - self._test_us_zone(tzc, func, values, start=start) - - def testESTStartName(self): - self._testEST(start=True, test_type='name') - - def testESTEndName(self): - self._testEST(start=False, test_type='name') - - def testESTStartOffset(self): - self._testEST(start=True, test_type='offset') - - def testESTEndOffset(self): - self._testEST(start=False, test_type='offset') - - def testESTStartDST(self): - self._testEST(start=True, test_type='dst') - - def testESTEndDST(self): - self._testEST(start=False, test_type='dst') - - def testESTValueDatetime(self): - # Violating one-test-per-test rule because we're not set up to do - # parameterized tests and the manual proliferation is getting a bit - # out of hand. - tz_str = self._tzstr_dtstart_with_params('America/New_York', - 'VALUE=DATE-TIME') - - tzc = tz.tzical(StringIO(tz_str)).get() - - for start in (True, False): - for test_type in ('name', 'offset', 'dst'): - self._testEST(start=start, test_type=test_type, tzc=tzc) - - def _testMultizone(self, start, test_type): - tzstrs = (self._gettz_str('America/New_York'), - self._gettz_str('America/Los_Angeles')) - tzids = ('US-Eastern', 'US-Pacific') - - argdict = { - 'name': (datetime.tzname, (('EST', 'EDT'), - ('PST', 'PDT'))), - 'offset': (datetime.utcoffset, ((timedelta(hours=-5), - timedelta(hours=-4)), - (timedelta(hours=-8), - timedelta(hours=-7)))), - 'dst': (datetime.dst, ((timedelta(hours=0), - timedelta(hours=1)), - (timedelta(hours=0), - timedelta(hours=1)))) - } - - func, values = argdict[test_type] - - if not start: - values = map(reversed, values) - - self._test_multi_zones(tzstrs, tzids, func, values, start) - - def testMultiZoneStartName(self): - self._testMultizone(start=True, test_type='name') - - def testMultiZoneEndName(self): - self._testMultizone(start=False, test_type='name') - - def testMultiZoneStartOffset(self): - self._testMultizone(start=True, test_type='offset') - - def testMultiZoneEndOffset(self): - self._testMultizone(start=False, test_type='offset') - - def testMultiZoneStartDST(self): - self._testMultizone(start=True, test_type='dst') - - def testMultiZoneEndDST(self): - self._testMultizone(start=False, test_type='dst') - - def testMultiZoneKeys(self): - est_str = self._gettz_str('America/New_York') - pst_str = self._gettz_str('America/Los_Angeles') - tzic = tz.tzical(StringIO('\n'.join((est_str, pst_str)))) - - # Sort keys because they are in a random order, being dictionary keys - keys = sorted(tzic.keys()) - - self.assertEqual(keys, ['US-Eastern', 'US-Pacific']) - - # Test error conditions - def testEmptyString(self): - with self.assertRaises(ValueError): - tz.tzical(StringIO("")) - - def testMultiZoneGet(self): - tzic = tz.tzical(StringIO(TZICAL_EST5EDT + TZICAL_PST8PDT)) - - with self.assertRaises(ValueError): - tzic.get() - - def testDtstartDate(self): - tz_str = self._tzstr_dtstart_with_params('America/New_York', - 'VALUE=DATE') - with self.assertRaises(ValueError): - tz.tzical(StringIO(tz_str)) - - def testDtstartTzid(self): - tz_str = self._tzstr_dtstart_with_params('America/New_York', - 'TZID=UTC') - with self.assertRaises(ValueError): - tz.tzical(StringIO(tz_str)) - - def testDtstartBadParam(self): - tz_str = self._tzstr_dtstart_with_params('America/New_York', - 'FOO=BAR') - with self.assertRaises(ValueError): - tz.tzical(StringIO(tz_str)) - - # Test Parsing - def testGap(self): - tzic = tz.tzical(StringIO('\n'.join((TZICAL_EST5EDT, TZICAL_PST8PDT)))) - - keys = sorted(tzic.keys()) - self.assertEqual(keys, ['US-Eastern', 'US-Pacific']) - - -class TZTest(unittest.TestCase): - def testFileStart1(self): - tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) - self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzc).tzname(), "EST") - self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzc).tzname(), "EDT") - - def testFileEnd1(self): - tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) - self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(), - "EDT") - end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc)) - self.assertEqual(end_est.tzname(), "EST") - - def testFileLastTransition(self): - # After the last transition, it goes to standard time in perpetuity - tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) - self.assertEqual(datetime(2037, 10, 25, 0, 59, tzinfo=tzc).tzname(), - "EDT") - - last_date = tz.enfold(datetime(2037, 10, 25, 1, 00, tzinfo=tzc), fold=1) - self.assertEqual(last_date.tzname(), - "EST") - - self.assertEqual(datetime(2038, 5, 25, 12, 0, tzinfo=tzc).tzname(), - "EST") - - def testInvalidFile(self): - # Should throw a ValueError if an invalid file is passed - with self.assertRaises(ValueError): - tz.tzfile(BytesIO(b'BadFile')) - - def testFilestreamWithNameRepr(self): - # If fileobj is a filestream with a "name" attribute this name should - # be reflected in the tz object's repr - fileobj = BytesIO(base64.b64decode(TZFILE_EST5EDT)) - fileobj.name = 'foo' - tzc = tz.tzfile(fileobj) - self.assertEqual(repr(tzc), 'tzfile(' + repr('foo') + ')') - - def testLeapCountDecodesProperly(self): - # This timezone has leapcnt, and failed to decode until - # Eugene Oden notified about the issue. - - # As leap information is currently unused (and unstored) by tzfile() we - # can only indirectly test this: Take advantage of tzfile() not closing - # the input file if handed in as an opened file and assert that the - # full file content has been read by tzfile(). Note: For this test to - # work NEW_YORK must be in TZif version 1 format i.e. no more data - # after TZif v1 header + data has been read - fileobj = BytesIO(base64.b64decode(NEW_YORK)) - tz.tzfile(fileobj) - # we expect no remaining file content now, i.e. zero-length; if there's - # still data we haven't read the file format correctly - remaining_tzfile_content = fileobj.read() - self.assertEqual(len(remaining_tzfile_content), 0) - - def testIsStd(self): - # NEW_YORK tzfile contains this isstd information: - isstd_expected = (0, 0, 0, 1) - tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK))) - # gather the actual information as parsed by the tzfile class - isstd = [] - for ttinfo in tzc._ttinfo_list: - # ttinfo objects contain boolean values - isstd.append(int(ttinfo.isstd)) - # ttinfo list may contain more entries than isstd file content - isstd = tuple(isstd[:len(isstd_expected)]) - self.assertEqual( - isstd_expected, isstd, - "isstd UTC/local indicators parsed: %s != tzfile contents: %s" - % (isstd, isstd_expected)) - - def testGMTHasNoDaylight(self): - # tz.tzstr("GMT+2") improperly considered daylight saving time. - # Issue reported by Lennart Regebro. - dt = datetime(2007, 8, 6, 4, 10) - self.assertEqual(tz.gettz("GMT+2").dst(dt), timedelta(0)) - - def testGMTOffset(self): - # GMT and UTC offsets have inverted signal when compared to the - # usual TZ variable handling. +@pytest.mark.parametrize('tz_str,expected', [ + # From https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + ('', tz.tzrange(None)), # TODO: Should change this so tz.tzrange('') works + ('EST+5EDT,M3.2.0/2,M11.1.0/12', + tz.tzrange('EST', -18000, 'EDT', -14400, + start=relativedelta(month=3, day=1, weekday=SU(2), hours=2), + end=relativedelta(month=11, day=1, weekday=SU(1), hours=11))), + ('WART4WARST,J1/0,J365/25', # This is DST all year, Western Argentina Summer Time + tz.tzrange('WART', timedelta(hours=-4), 'WARST', + start=relativedelta(month=1, day=1, hours=0), + end=relativedelta(month=12, day=31, days=1))), + ('IST-2IDT,M3.4.4/26,M10.5.0', # Israel Standard / Daylight Time + tz.tzrange('IST', timedelta(hours=2), 'IDT', + start=relativedelta(month=3, day=1, weekday=TH(4), days=1, hours=2), + end=relativedelta(month=10, day=31, weekday=SU(-1), hours=1))), + ('WGT3WGST,M3.5.0/2,M10.5.0/1', + tz.tzrange('WGT', timedelta(hours=-3), 'WGST', + start=relativedelta(month=3, day=31, weekday=SU(-1), hours=2), + end=relativedelta(month=10, day=31, weekday=SU(-1), hours=0))), + + # Different offset specifications + ('WGT0300WGST', + tz.tzrange('WGT', timedelta(hours=-3), 'WGST')), + ('WGT03:00WGST', + tz.tzrange('WGT', timedelta(hours=-3), 'WGST')), + ('AEST-1100AEDT', + tz.tzrange('AEST', timedelta(hours=11), 'AEDT')), + ('AEST-11:00AEDT', + tz.tzrange('AEST', timedelta(hours=11), 'AEDT')), + + # Different time formats + ('EST5EDT,M3.2.0/4:00,M11.1.0/3:00', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', + start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), + end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), + ('EST5EDT,M3.2.0/04:00,M11.1.0/03:00', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', + start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), + end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), + ('EST5EDT,M3.2.0/0400,M11.1.0/0300', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', + start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), + end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), +]) +def test_valid_GNU_tzstr(tz_str, expected): + tzi = tz.tzstr(tz_str) + + assert tzi == expected + + +@pytest.mark.tzstr +@pytest.mark.parametrize('tz_str, expected', [ + ('EST5EDT,5,4,0,7200,11,3,0,7200', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', + start=relativedelta(month=5, day=1, weekday=SU(+4), hours=+2), + end=relativedelta(month=11, day=1, weekday=SU(+3), hours=+1))), + ('EST5EDT,5,-4,0,7200,11,3,0,7200', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', + start=relativedelta(hours=+2, month=5, day=31, weekday=SU(-4)), + end=relativedelta(hours=+1, month=11, day=1, weekday=SU(+3)))), + ('EST5EDT,5,4,0,7200,11,-3,0,7200', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', + start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), + end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), + ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', + start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), + end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), + ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', + start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), + end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), + ('EST5EDT,5,4,0,7200,11,-3,0,7200,-3600', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-6), + start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), + end=relativedelta(hours=+3, month=11, day=31, weekday=SU(-3)))), + ('EST5EDT,5,4,0,7200,11,-3,0,7200,+7200', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-3), + start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), + end=relativedelta(hours=0, month=11, day=31, weekday=SU(-3)))), + ('EST5EDT,5,4,0,7200,11,-3,0,7200,+3600', + tz.tzrange('EST', timedelta(hours=-5), 'EDT', + start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), + end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), +]) +def test_valid_dateutil_format(tz_str, expected): + # This tests the dateutil-specific format that is used widely in the tests + # and examples. It is unclear where this format originated from. + with pytest.warns(tz.DeprecatedTzFormatWarning): + tzi = tz.tzstr.instance(tz_str) + + assert tzi == expected + + +@pytest.mark.tzstr +@pytest.mark.parametrize('tz_str', [ + 'hdfiughdfuig,dfughdfuigpu87ñ::', + ',dfughdfuigpu87ñ::', + '-1:WART4WARST,J1,J365/25', + 'WART4WARST,J1,J365/-25', + 'IST-2IDT,M3.4.-1/26,M10.5.0', + 'IST-2IDT,M3,2000,1/26,M10,5,0' +]) +def test_invalid_GNU_tzstr(tz_str): + with pytest.raises(ValueError): + tz.tzstr(tz_str) + + +# Different representations of the same default rule set +DEFAULT_TZSTR_RULES_EQUIV_2003 = [ + 'EST5EDT', + 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00', + 'EST5EDT4,95/02:00:00,298/02:00', + 'EST5EDT4,J96/02:00:00,J299/02:00', + 'EST5EDT4,J96/02:00:00,J299/02' +] + + +@pytest.mark.tzstr +@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003) +def test_tzstr_default_start(tz_str): + tzi = tz.tzstr(tz_str) + dt_std = datetime(2003, 4, 6, 1, 59, tzinfo=tzi) + dt_dst = datetime(2003, 4, 6, 2, 00, tzinfo=tzi) + + assert get_timezone_tuple(dt_std) == EST_TUPLE + assert get_timezone_tuple(dt_dst) == EDT_TUPLE + + +@pytest.mark.tzstr +@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003) +def test_tzstr_default_end(tz_str): + tzi = tz.tzstr(tz_str) + dt_dst = datetime(2003, 10, 26, 0, 59, tzinfo=tzi) + dt_dst_ambig = datetime(2003, 10, 26, 1, 00, tzinfo=tzi) + dt_std_ambig = tz.enfold(dt_dst_ambig, fold=1) + dt_std = datetime(2003, 10, 26, 2, 00, tzinfo=tzi) + + assert get_timezone_tuple(dt_dst) == EDT_TUPLE + assert get_timezone_tuple(dt_dst_ambig) == EDT_TUPLE + assert get_timezone_tuple(dt_std_ambig) == EST_TUPLE + assert get_timezone_tuple(dt_std) == EST_TUPLE + + +@pytest.mark.tzstr +@pytest.mark.parametrize('tzstr_1', ['EST5EDT', + 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00']) +@pytest.mark.parametrize('tzstr_2', ['EST5EDT', + 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00']) +def test_tzstr_default_cmp(tzstr_1, tzstr_2): + tz1 = tz.tzstr(tzstr_1) + tz2 = tz.tzstr(tzstr_2) + + assert tz1 == tz2 + +class TZICalTest(unittest.TestCase, TzFoldMixin): + def _gettz_str_tuple(self, tzname): + TZ_EST = ( + 'BEGIN:VTIMEZONE', + 'TZID:US-Eastern', + 'BEGIN:STANDARD', + 'DTSTART:19971029T020000', + 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11', + 'TZOFFSETFROM:-0400', + 'TZOFFSETTO:-0500', + 'TZNAME:EST', + 'END:STANDARD', + 'BEGIN:DAYLIGHT', + 'DTSTART:19980301T020000', + 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03', + 'TZOFFSETFROM:-0500', + 'TZOFFSETTO:-0400', + 'TZNAME:EDT', + 'END:DAYLIGHT', + 'END:VTIMEZONE' + ) + + TZ_PST = ( + 'BEGIN:VTIMEZONE', + 'TZID:US-Pacific', + 'BEGIN:STANDARD', + 'DTSTART:19971029T020000', + 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11', + 'TZOFFSETFROM:-0700', + 'TZOFFSETTO:-0800', + 'TZNAME:PST', + 'END:STANDARD', + 'BEGIN:DAYLIGHT', + 'DTSTART:19980301T020000', + 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03', + 'TZOFFSETFROM:-0800', + 'TZOFFSETTO:-0700', + 'TZNAME:PDT', + 'END:DAYLIGHT', + 'END:VTIMEZONE' + ) + + TZ_AEST = ( + 'BEGIN:VTIMEZONE', + 'TZID:Australia-Sydney', + 'BEGIN:STANDARD', + 'DTSTART:19980301T030000', + 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=04', + 'TZOFFSETFROM:+1100', + 'TZOFFSETTO:+1000', + 'TZNAME:AEST', + 'END:STANDARD', + 'BEGIN:DAYLIGHT', + 'DTSTART:19971029T020000', + 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=10', + 'TZOFFSETFROM:+1000', + 'TZOFFSETTO:+1100', + 'TZNAME:AEDT', + 'END:DAYLIGHT', + 'END:VTIMEZONE' + ) + + TZ_LON = ( + 'BEGIN:VTIMEZONE', + 'TZID:Europe-London', + 'BEGIN:STANDARD', + 'DTSTART:19810301T030000', + 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10;BYHOUR=02', + 'TZOFFSETFROM:+0100', + 'TZOFFSETTO:+0000', + 'TZNAME:GMT', + 'END:STANDARD', + 'BEGIN:DAYLIGHT', + 'DTSTART:19961001T030000', + 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=03;BYHOUR=01', + 'TZOFFSETFROM:+0000', + 'TZOFFSETTO:+0100', + 'TZNAME:BST', + 'END:DAYLIGHT', + 'END:VTIMEZONE' + ) + + tzname_map = {'Australia/Sydney': TZ_AEST, + 'America/Toronto': TZ_EST, + 'America/New_York': TZ_EST, + 'America/Los_Angeles': TZ_PST, + 'Europe/London': TZ_LON} + + return tzname_map[tzname] + + def _gettz_str(self, tzname): + return '\n'.join(self._gettz_str_tuple(tzname)) + + def _tzstr_dtstart_with_params(self, tzname, param_str): + # Adds parameters to the DTSTART values of a given tzstr + tz_str_tuple = self._gettz_str_tuple(tzname) + + out_tz = [] + for line in tz_str_tuple: + if line.startswith('DTSTART'): + name, value = line.split(':', 1) + line = name + ';' + param_str + ':' + value + + out_tz.append(line) + + return '\n'.join(out_tz) + + def gettz(self, tzname): + tz_str = self._gettz_str(tzname) + + tzc = tz.tzical(StringIO(tz_str)).get() + + return tzc + + def testRepr(self): + instr = StringIO(TZICAL_PST8PDT) + instr.name = 'StringIO(PST8PDT)' + tzc = tz.tzical(instr) + + self.assertEqual(repr(tzc), "tzical(" + repr(instr.name) + ")") + + # Test performance + def _test_us_zone(self, tzc, func, values, start): + if start: + dt1 = datetime(2003, 3, 9, 1, 59) + dt2 = datetime(2003, 3, 9, 2, 00) + fold = [0, 0] + else: + dt1 = datetime(2003, 11, 2, 0, 59) + dt2 = datetime(2003, 11, 2, 1, 00) + fold = [0, 1] + + dts = (tz.enfold(dt.replace(tzinfo=tzc), fold=f) + for dt, f in zip((dt1, dt2), fold)) + + for value, dt in zip(values, dts): + self.assertEqual(func(dt), value) + + def _test_multi_zones(self, tzstrs, tzids, func, values, start): + tzic = tz.tzical(StringIO('\n'.join(tzstrs))) + for tzid, vals in zip(tzids, values): + tzc = tzic.get(tzid) + + self._test_us_zone(tzc, func, vals, start) + + def _prepare_EST(self): + tz_str = self._gettz_str('America/New_York') + return tz.tzical(StringIO(tz_str)).get() + + def _testEST(self, start, test_type, tzc=None): + if tzc is None: + tzc = self._prepare_EST() + + argdict = { + 'name': (datetime.tzname, ('EST', 'EDT')), + 'offset': (datetime.utcoffset, (timedelta(hours=-5), + timedelta(hours=-4))), + 'dst': (datetime.dst, (timedelta(hours=0), + timedelta(hours=1))) + } + + func, values = argdict[test_type] + + if not start: + values = reversed(values) + + self._test_us_zone(tzc, func, values, start=start) + + def testESTStartName(self): + self._testEST(start=True, test_type='name') + + def testESTEndName(self): + self._testEST(start=False, test_type='name') + + def testESTStartOffset(self): + self._testEST(start=True, test_type='offset') + + def testESTEndOffset(self): + self._testEST(start=False, test_type='offset') + + def testESTStartDST(self): + self._testEST(start=True, test_type='dst') + + def testESTEndDST(self): + self._testEST(start=False, test_type='dst') + + def testESTValueDatetime(self): + # Violating one-test-per-test rule because we're not set up to do + # parameterized tests and the manual proliferation is getting a bit + # out of hand. + tz_str = self._tzstr_dtstart_with_params('America/New_York', + 'VALUE=DATE-TIME') + + tzc = tz.tzical(StringIO(tz_str)).get() + + for start in (True, False): + for test_type in ('name', 'offset', 'dst'): + self._testEST(start=start, test_type=test_type, tzc=tzc) + + def _testMultizone(self, start, test_type): + tzstrs = (self._gettz_str('America/New_York'), + self._gettz_str('America/Los_Angeles')) + tzids = ('US-Eastern', 'US-Pacific') + + argdict = { + 'name': (datetime.tzname, (('EST', 'EDT'), + ('PST', 'PDT'))), + 'offset': (datetime.utcoffset, ((timedelta(hours=-5), + timedelta(hours=-4)), + (timedelta(hours=-8), + timedelta(hours=-7)))), + 'dst': (datetime.dst, ((timedelta(hours=0), + timedelta(hours=1)), + (timedelta(hours=0), + timedelta(hours=1)))) + } + + func, values = argdict[test_type] + + if not start: + values = map(reversed, values) + + self._test_multi_zones(tzstrs, tzids, func, values, start) + + def testMultiZoneStartName(self): + self._testMultizone(start=True, test_type='name') + + def testMultiZoneEndName(self): + self._testMultizone(start=False, test_type='name') + + def testMultiZoneStartOffset(self): + self._testMultizone(start=True, test_type='offset') + + def testMultiZoneEndOffset(self): + self._testMultizone(start=False, test_type='offset') + + def testMultiZoneStartDST(self): + self._testMultizone(start=True, test_type='dst') + + def testMultiZoneEndDST(self): + self._testMultizone(start=False, test_type='dst') + + def testMultiZoneKeys(self): + est_str = self._gettz_str('America/New_York') + pst_str = self._gettz_str('America/Los_Angeles') + tzic = tz.tzical(StringIO('\n'.join((est_str, pst_str)))) + + # Sort keys because they are in a random order, being dictionary keys + keys = sorted(tzic.keys()) + + self.assertEqual(keys, ['US-Eastern', 'US-Pacific']) + + # Test error conditions + def testEmptyString(self): + with self.assertRaises(ValueError): + tz.tzical(StringIO("")) + + def testMultiZoneGet(self): + tzic = tz.tzical(StringIO(TZICAL_EST5EDT + TZICAL_PST8PDT)) + + with self.assertRaises(ValueError): + tzic.get() + + def testDtstartDate(self): + tz_str = self._tzstr_dtstart_with_params('America/New_York', + 'VALUE=DATE') + with self.assertRaises(ValueError): + tz.tzical(StringIO(tz_str)) + + def testDtstartTzid(self): + tz_str = self._tzstr_dtstart_with_params('America/New_York', + 'TZID=UTC') + with self.assertRaises(ValueError): + tz.tzical(StringIO(tz_str)) + + def testDtstartBadParam(self): + tz_str = self._tzstr_dtstart_with_params('America/New_York', + 'FOO=BAR') + with self.assertRaises(ValueError): + tz.tzical(StringIO(tz_str)) + + # Test Parsing + def testGap(self): + tzic = tz.tzical(StringIO('\n'.join((TZICAL_EST5EDT, TZICAL_PST8PDT)))) + + keys = sorted(tzic.keys()) + self.assertEqual(keys, ['US-Eastern', 'US-Pacific']) + + +class TZTest(unittest.TestCase): + def testFileStart1(self): + tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) + self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzc).tzname(), "EST") + self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzc).tzname(), "EDT") + + def testFileEnd1(self): + tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) + self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(), + "EDT") + end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc)) + self.assertEqual(end_est.tzname(), "EST") + + def testFileLastTransition(self): + # After the last transition, it goes to standard time in perpetuity + tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) + self.assertEqual(datetime(2037, 10, 25, 0, 59, tzinfo=tzc).tzname(), + "EDT") + + last_date = tz.enfold(datetime(2037, 10, 25, 1, 00, tzinfo=tzc), fold=1) + self.assertEqual(last_date.tzname(), + "EST") + + self.assertEqual(datetime(2038, 5, 25, 12, 0, tzinfo=tzc).tzname(), + "EST") + + def testInvalidFile(self): + # Should throw a ValueError if an invalid file is passed + with self.assertRaises(ValueError): + tz.tzfile(BytesIO(b'BadFile')) + + def testFilestreamWithNameRepr(self): + # If fileobj is a filestream with a "name" attribute this name should + # be reflected in the tz object's repr + fileobj = BytesIO(base64.b64decode(TZFILE_EST5EDT)) + fileobj.name = 'foo' + tzc = tz.tzfile(fileobj) + self.assertEqual(repr(tzc), 'tzfile(' + repr('foo') + ')') + + def testLeapCountDecodesProperly(self): + # This timezone has leapcnt, and failed to decode until + # Eugene Oden notified about the issue. + + # As leap information is currently unused (and unstored) by tzfile() we + # can only indirectly test this: Take advantage of tzfile() not closing + # the input file if handed in as an opened file and assert that the + # full file content has been read by tzfile(). Note: For this test to + # work NEW_YORK must be in TZif version 1 format i.e. no more data + # after TZif v1 header + data has been read + fileobj = BytesIO(base64.b64decode(NEW_YORK)) + tz.tzfile(fileobj) + # we expect no remaining file content now, i.e. zero-length; if there's + # still data we haven't read the file format correctly + remaining_tzfile_content = fileobj.read() + self.assertEqual(len(remaining_tzfile_content), 0) + + def testIsStd(self): + # NEW_YORK tzfile contains this isstd information: + isstd_expected = (0, 0, 0, 1) + tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK))) + # gather the actual information as parsed by the tzfile class + isstd = [] + for ttinfo in tzc._ttinfo_list: + # ttinfo objects contain boolean values + isstd.append(int(ttinfo.isstd)) + # ttinfo list may contain more entries than isstd file content + isstd = tuple(isstd[:len(isstd_expected)]) + self.assertEqual( + isstd_expected, isstd, + "isstd UTC/local indicators parsed: %s != tzfile contents: %s" + % (isstd, isstd_expected)) + + def testGMTHasNoDaylight(self): + # tz.tzstr("GMT+2") improperly considered daylight saving time. + # Issue reported by Lennart Regebro. + dt = datetime(2007, 8, 6, 4, 10) + self.assertEqual(tz.gettz("GMT+2").dst(dt), timedelta(0)) + + def testGMTOffset(self): + # GMT and UTC offsets have inverted signal when compared to the + # usual TZ variable handling. dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.UTC) - self.assertEqual(dt.astimezone(tz=tz.tzstr("GMT+2")), - datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2"))) - self.assertEqual(dt.astimezone(tz=tz.gettz("UTC-2")), - datetime(2007, 8, 6, 2, 10, tzinfo=tz.tzstr("UTC-2"))) - + self.assertEqual(dt.astimezone(tz=tz.tzstr("GMT+2")), + datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2"))) + self.assertEqual(dt.astimezone(tz=tz.gettz("UTC-2")), + datetime(2007, 8, 6, 2, 10, tzinfo=tz.tzstr("UTC-2"))) + @unittest.skipIf(IS_WIN, "requires Unix") - def testTZSetDoesntCorrupt(self): - # if we start in non-UTC then tzset UTC make sure parse doesn't get - # confused - with TZEnvContext('UTC'): - # this should parse to UTC timezone not the original timezone - dt = parse('2014-07-20T12:34:56+00:00') - self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00') - - + def testTZSetDoesntCorrupt(self): + # if we start in non-UTC then tzset UTC make sure parse doesn't get + # confused + with TZEnvContext('UTC'): + # this should parse to UTC timezone not the original timezone + dt = parse('2014-07-20T12:34:56+00:00') + self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00') + + @pytest.mark.tzfile @pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS, reason='Sub-minute offsets not supported') @@ -2119,570 +2119,570 @@ def test_samoa_transition(): @unittest.skipUnless(IS_WIN, "Requires Windows") -class TzWinTest(unittest.TestCase, TzWinFoldMixin): - def setUp(self): - self.tzclass = tzwin.tzwin - - def testTzResLoadName(self): - # This may not work right on non-US locales. - tzr = tzwin.tzres() - self.assertEqual(tzr.load_name(112), "Eastern Standard Time") - - def testTzResNameFromString(self): - tzr = tzwin.tzres() - self.assertEqual(tzr.name_from_string('@tzres.dll,-221'), - 'Alaskan Daylight Time') - - self.assertEqual(tzr.name_from_string('Samoa Daylight Time'), - 'Samoa Daylight Time') - - with self.assertRaises(ValueError): - tzr.name_from_string('@tzres.dll,100') - - def testIsdstZoneWithNoDaylightSaving(self): - tz = tzwin.tzwin("UTC") - dt = parse("2013-03-06 19:08:15") - self.assertFalse(tz._isdst(dt)) - - def testOffset(self): - tz = tzwin.tzwin("Cape Verde Standard Time") - self.assertEqual(tz.utcoffset(datetime(1995, 5, 21, 12, 9, 13)), - timedelta(-1, 82800)) - - def testTzwinName(self): - # https://github.com/dateutil/dateutil/issues/143 - tw = tz.tzwin('Eastern Standard Time') - - # Cover the transitions for at least two years. - ESTs = 'Eastern Standard Time' - EDTs = 'Eastern Daylight Time' - transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs), - (datetime(2015, 3, 8, 3, 1), EDTs), - (datetime(2015, 11, 1, 0, 59), EDTs), - (datetime(2015, 11, 1, 3, 1), ESTs), - (datetime(2016, 3, 13, 0, 59), ESTs), - (datetime(2016, 3, 13, 3, 1), EDTs), - (datetime(2016, 11, 6, 0, 59), EDTs), - (datetime(2016, 11, 6, 3, 1), ESTs)] - - for t_date, expected in transition_dates: - self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected) - - def testTzwinRepr(self): - tw = tz.tzwin('Yakutsk Standard Time') - self.assertEqual(repr(tw), 'tzwin(' + - repr('Yakutsk Standard Time') + ')') - - def testTzWinEquality(self): - # https://github.com/dateutil/dateutil/issues/151 - tzwin_names = ('Eastern Standard Time', - 'West Pacific Standard Time', - 'Yakutsk Standard Time', - 'Iran Standard Time', - 'UTC') - - for tzwin_name in tzwin_names: - # Get two different instances to compare - tw1 = tz.tzwin(tzwin_name) - tw2 = tz.tzwin(tzwin_name) - - self.assertEqual(tw1, tw2) - - def testTzWinInequality(self): - # https://github.com/dateutil/dateutil/issues/151 - # Note these last two currently differ only in their name. - tzwin_names = (('Eastern Standard Time', 'Yakutsk Standard Time'), - ('Greenwich Standard Time', 'GMT Standard Time'), - ('GMT Standard Time', 'UTC'), - ('E. South America Standard Time', - 'Argentina Standard Time')) - - for tzwn1, tzwn2 in tzwin_names: - # Get two different instances to compare - tw1 = tz.tzwin(tzwn1) - tw2 = tz.tzwin(tzwn2) - - self.assertNotEqual(tw1, tw2) - - def testTzWinEqualityInvalid(self): - # Compare to objects that do not implement comparison with this - # (should default to False) +class TzWinTest(unittest.TestCase, TzWinFoldMixin): + def setUp(self): + self.tzclass = tzwin.tzwin + + def testTzResLoadName(self): + # This may not work right on non-US locales. + tzr = tzwin.tzres() + self.assertEqual(tzr.load_name(112), "Eastern Standard Time") + + def testTzResNameFromString(self): + tzr = tzwin.tzres() + self.assertEqual(tzr.name_from_string('@tzres.dll,-221'), + 'Alaskan Daylight Time') + + self.assertEqual(tzr.name_from_string('Samoa Daylight Time'), + 'Samoa Daylight Time') + + with self.assertRaises(ValueError): + tzr.name_from_string('@tzres.dll,100') + + def testIsdstZoneWithNoDaylightSaving(self): + tz = tzwin.tzwin("UTC") + dt = parse("2013-03-06 19:08:15") + self.assertFalse(tz._isdst(dt)) + + def testOffset(self): + tz = tzwin.tzwin("Cape Verde Standard Time") + self.assertEqual(tz.utcoffset(datetime(1995, 5, 21, 12, 9, 13)), + timedelta(-1, 82800)) + + def testTzwinName(self): + # https://github.com/dateutil/dateutil/issues/143 + tw = tz.tzwin('Eastern Standard Time') + + # Cover the transitions for at least two years. + ESTs = 'Eastern Standard Time' + EDTs = 'Eastern Daylight Time' + transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs), + (datetime(2015, 3, 8, 3, 1), EDTs), + (datetime(2015, 11, 1, 0, 59), EDTs), + (datetime(2015, 11, 1, 3, 1), ESTs), + (datetime(2016, 3, 13, 0, 59), ESTs), + (datetime(2016, 3, 13, 3, 1), EDTs), + (datetime(2016, 11, 6, 0, 59), EDTs), + (datetime(2016, 11, 6, 3, 1), ESTs)] + + for t_date, expected in transition_dates: + self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected) + + def testTzwinRepr(self): + tw = tz.tzwin('Yakutsk Standard Time') + self.assertEqual(repr(tw), 'tzwin(' + + repr('Yakutsk Standard Time') + ')') + + def testTzWinEquality(self): + # https://github.com/dateutil/dateutil/issues/151 + tzwin_names = ('Eastern Standard Time', + 'West Pacific Standard Time', + 'Yakutsk Standard Time', + 'Iran Standard Time', + 'UTC') + + for tzwin_name in tzwin_names: + # Get two different instances to compare + tw1 = tz.tzwin(tzwin_name) + tw2 = tz.tzwin(tzwin_name) + + self.assertEqual(tw1, tw2) + + def testTzWinInequality(self): + # https://github.com/dateutil/dateutil/issues/151 + # Note these last two currently differ only in their name. + tzwin_names = (('Eastern Standard Time', 'Yakutsk Standard Time'), + ('Greenwich Standard Time', 'GMT Standard Time'), + ('GMT Standard Time', 'UTC'), + ('E. South America Standard Time', + 'Argentina Standard Time')) + + for tzwn1, tzwn2 in tzwin_names: + # Get two different instances to compare + tw1 = tz.tzwin(tzwn1) + tw2 = tz.tzwin(tzwn2) + + self.assertNotEqual(tw1, tw2) + + def testTzWinEqualityInvalid(self): + # Compare to objects that do not implement comparison with this + # (should default to False) UTC = tz.UTC - EST = tz.tzwin('Eastern Standard Time') - - self.assertFalse(EST == UTC) - self.assertFalse(EST == 1) - self.assertFalse(UTC == EST) - - self.assertTrue(EST != UTC) - self.assertTrue(EST != 1) - - def testTzWinInequalityUnsupported(self): - # Compare it to an object that is promiscuous about equality, but for - # which tzwin does not implement an equality operator. - EST = tz.tzwin('Eastern Standard Time') - self.assertTrue(EST == ComparesEqual) - self.assertFalse(EST != ComparesEqual) - - def testTzwinTimeOnlyDST(self): - # For zones with DST, .dst() should return None - tw_est = tz.tzwin('Eastern Standard Time') - self.assertIs(dt_time(14, 10, tzinfo=tw_est).dst(), None) - - # This zone has no DST, so .dst() can return 0 - tw_sast = tz.tzwin('South Africa Standard Time') - self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).dst(), - timedelta(0)) - - def testTzwinTimeOnlyUTCOffset(self): - # For zones with DST, .utcoffset() should return None - tw_est = tz.tzwin('Eastern Standard Time') - self.assertIs(dt_time(14, 10, tzinfo=tw_est).utcoffset(), None) - - # This zone has no DST, so .utcoffset() returns standard offset - tw_sast = tz.tzwin('South Africa Standard Time') - self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).utcoffset(), - timedelta(hours=2)) - - def testTzwinTimeOnlyTZName(self): - # For zones with DST, the name defaults to standard time - tw_est = tz.tzwin('Eastern Standard Time') - self.assertEqual(dt_time(14, 10, tzinfo=tw_est).tzname(), - 'Eastern Standard Time') - - # For zones with no DST, this should work normally. - tw_sast = tz.tzwin('South Africa Standard Time') - self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).tzname(), - 'South Africa Standard Time') - - + EST = tz.tzwin('Eastern Standard Time') + + self.assertFalse(EST == UTC) + self.assertFalse(EST == 1) + self.assertFalse(UTC == EST) + + self.assertTrue(EST != UTC) + self.assertTrue(EST != 1) + + def testTzWinInequalityUnsupported(self): + # Compare it to an object that is promiscuous about equality, but for + # which tzwin does not implement an equality operator. + EST = tz.tzwin('Eastern Standard Time') + self.assertTrue(EST == ComparesEqual) + self.assertFalse(EST != ComparesEqual) + + def testTzwinTimeOnlyDST(self): + # For zones with DST, .dst() should return None + tw_est = tz.tzwin('Eastern Standard Time') + self.assertIs(dt_time(14, 10, tzinfo=tw_est).dst(), None) + + # This zone has no DST, so .dst() can return 0 + tw_sast = tz.tzwin('South Africa Standard Time') + self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).dst(), + timedelta(0)) + + def testTzwinTimeOnlyUTCOffset(self): + # For zones with DST, .utcoffset() should return None + tw_est = tz.tzwin('Eastern Standard Time') + self.assertIs(dt_time(14, 10, tzinfo=tw_est).utcoffset(), None) + + # This zone has no DST, so .utcoffset() returns standard offset + tw_sast = tz.tzwin('South Africa Standard Time') + self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).utcoffset(), + timedelta(hours=2)) + + def testTzwinTimeOnlyTZName(self): + # For zones with DST, the name defaults to standard time + tw_est = tz.tzwin('Eastern Standard Time') + self.assertEqual(dt_time(14, 10, tzinfo=tw_est).tzname(), + 'Eastern Standard Time') + + # For zones with no DST, this should work normally. + tw_sast = tz.tzwin('South Africa Standard Time') + self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).tzname(), + 'South Africa Standard Time') + + @unittest.skipUnless(IS_WIN, "Requires Windows") -class TzWinLocalTest(unittest.TestCase, TzWinFoldMixin): - - def setUp(self): - self.tzclass = tzwin.tzwinlocal - self.context = TZWinContext - - def get_args(self, tzname): - return () - - def testLocal(self): - # Not sure how to pin a local time zone, so for now we're just going - # to run this and make sure it doesn't raise an error +class TzWinLocalTest(unittest.TestCase, TzWinFoldMixin): + + def setUp(self): + self.tzclass = tzwin.tzwinlocal + self.context = TZWinContext + + def get_args(self, tzname): + return () + + def testLocal(self): + # Not sure how to pin a local time zone, so for now we're just going + # to run this and make sure it doesn't raise an error # See GitHub Issue #135: https://github.com/dateutil/dateutil/issues/135 - datetime.now(tzwin.tzwinlocal()) - - def testTzwinLocalUTCOffset(self): - with TZWinContext('Eastern Standard Time'): - tzwl = tzwin.tzwinlocal() - self.assertEqual(datetime(2014, 3, 11, tzinfo=tzwl).utcoffset(), - timedelta(hours=-4)) - - def testTzwinLocalName(self): - # https://github.com/dateutil/dateutil/issues/143 - ESTs = 'Eastern Standard Time' - EDTs = 'Eastern Daylight Time' - transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs), - (datetime(2015, 3, 8, 3, 1), EDTs), - (datetime(2015, 11, 1, 0, 59), EDTs), - (datetime(2015, 11, 1, 3, 1), ESTs), - (datetime(2016, 3, 13, 0, 59), ESTs), - (datetime(2016, 3, 13, 3, 1), EDTs), - (datetime(2016, 11, 6, 0, 59), EDTs), - (datetime(2016, 11, 6, 3, 1), ESTs)] - - with TZWinContext('Eastern Standard Time'): - tw = tz.tzwinlocal() - - for t_date, expected in transition_dates: - self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected) - - def testTzWinLocalRepr(self): - tw = tz.tzwinlocal() - self.assertEqual(repr(tw), 'tzwinlocal()') - - def testTzwinLocalRepr(self): - # https://github.com/dateutil/dateutil/issues/143 - with TZWinContext('Eastern Standard Time'): - tw = tz.tzwinlocal() - - self.assertEqual(str(tw), 'tzwinlocal(' + - repr('Eastern Standard Time') + ')') - - with TZWinContext('Pacific Standard Time'): - tw = tz.tzwinlocal() - - self.assertEqual(str(tw), 'tzwinlocal(' + - repr('Pacific Standard Time') + ')') - - def testTzwinLocalEquality(self): - tw_est = tz.tzwin('Eastern Standard Time') - tw_pst = tz.tzwin('Pacific Standard Time') - - with TZWinContext('Eastern Standard Time'): - twl1 = tz.tzwinlocal() - twl2 = tz.tzwinlocal() - - self.assertEqual(twl1, twl2) - self.assertEqual(twl1, tw_est) - self.assertNotEqual(twl1, tw_pst) - - with TZWinContext('Pacific Standard Time'): - twl1 = tz.tzwinlocal() - twl2 = tz.tzwinlocal() - tw = tz.tzwin('Pacific Standard Time') - - self.assertEqual(twl1, twl2) - self.assertEqual(twl1, tw) - self.assertEqual(twl1, tw_pst) - self.assertNotEqual(twl1, tw_est) - - def testTzwinLocalTimeOnlyDST(self): - # For zones with DST, .dst() should return None - with TZWinContext('Eastern Standard Time'): - twl = tz.tzwinlocal() - self.assertIs(dt_time(14, 10, tzinfo=twl).dst(), None) - - # This zone has no DST, so .dst() can return 0 - with TZWinContext('South Africa Standard Time'): - twl = tz.tzwinlocal() - self.assertEqual(dt_time(14, 10, tzinfo=twl).dst(), timedelta(0)) - - def testTzwinLocalTimeOnlyUTCOffset(self): - # For zones with DST, .utcoffset() should return None - with TZWinContext('Eastern Standard Time'): - twl = tz.tzwinlocal() - self.assertIs(dt_time(14, 10, tzinfo=twl).utcoffset(), None) - - # This zone has no DST, so .utcoffset() returns standard offset - with TZWinContext('South Africa Standard Time'): - twl = tz.tzwinlocal() - self.assertEqual(dt_time(14, 10, tzinfo=twl).utcoffset(), - timedelta(hours=2)) - - def testTzwinLocalTimeOnlyTZName(self): - # For zones with DST, the name defaults to standard time - with TZWinContext('Eastern Standard Time'): - twl = tz.tzwinlocal() - self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(), - 'Eastern Standard Time') - - # For zones with no DST, this should work normally. - with TZWinContext('South Africa Standard Time'): - twl = tz.tzwinlocal() - self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(), - 'South Africa Standard Time') - - -class TzPickleTest(PicklableMixin, unittest.TestCase): - _asfile = False - - def setUp(self): - self.assertPicklable = partial(self.assertPicklable, - asfile=self._asfile) - - def testPickleTzUTC(self): - self.assertPicklable(tz.tzutc(), singleton=True) - - def testPickleTzOffsetZero(self): - self.assertPicklable(tz.tzoffset('UTC', 0), singleton=True) - - def testPickleTzOffsetPos(self): - self.assertPicklable(tz.tzoffset('UTC+1', 3600), singleton=True) - - def testPickleTzOffsetNeg(self): - self.assertPicklable(tz.tzoffset('UTC-1', -3600), singleton=True) - - @pytest.mark.tzlocal - def testPickleTzLocal(self): - self.assertPicklable(tz.tzlocal()) - - def testPickleTzFileEST5EDT(self): - tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) - self.assertPicklable(tzc) - - def testPickleTzFileEurope_Helsinki(self): - tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI))) - self.assertPicklable(tzc) - - def testPickleTzFileNew_York(self): - tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK))) - self.assertPicklable(tzc) - - @unittest.skip("Known failure") - def testPickleTzICal(self): - tzc = tz.tzical(StringIO(TZICAL_EST5EDT)).get() - self.assertPicklable(tzc) - - def testPickleTzGettz(self): - self.assertPicklable(tz.gettz('America/New_York')) - - def testPickleZoneFileGettz(self): - zoneinfo_file = zoneinfo.get_zonefile_instance() - tzi = zoneinfo_file.get('America/New_York') - self.assertIsNot(tzi, None) - self.assertPicklable(tzi) - - -class TzPickleFileTest(TzPickleTest): - """ Run all the TzPickleTest tests, using a temporary file """ - _asfile = True - - -class DatetimeAmbiguousTest(unittest.TestCase): - """ Test the datetime_exists / datetime_ambiguous functions """ - - def testNoTzSpecified(self): - with self.assertRaises(ValueError): - tz.datetime_ambiguous(datetime(2016, 4, 1, 2, 9)) - - def _get_no_support_tzinfo_class(self, dt_start, dt_end, dst_only=False): - # Generates a class of tzinfo with no support for is_ambiguous - # where dates between dt_start and dt_end are ambiguous. - - class FoldingTzInfo(tzinfo): - def utcoffset(self, dt): - if not dst_only: - dt_n = dt.replace(tzinfo=None) - - if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0): - return timedelta(hours=-1) - - return timedelta(hours=0) - - def dst(self, dt): - dt_n = dt.replace(tzinfo=None) - - if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0): - return timedelta(hours=1) - else: - return timedelta(0) - - return FoldingTzInfo - - def _get_no_support_tzinfo(self, dt_start, dt_end, dst_only=False): - return self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)() - - def testNoSupportAmbiguityFoldNaive(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_no_support_tzinfo(dt_start, dt_end) - - self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), - tz=tzi)) - - def testNoSupportAmbiguityFoldAware(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_no_support_tzinfo(dt_start, dt_end) - - self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30, - tzinfo=tzi))) - - def testNoSupportAmbiguityUnambiguousNaive(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_no_support_tzinfo(dt_start, dt_end) - - self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), - tz=tzi)) - - def testNoSupportAmbiguityUnambiguousAware(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_no_support_tzinfo(dt_start, dt_end) - - self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30, - tzinfo=tzi))) - - def testNoSupportAmbiguityFoldDSTOnly(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True) - - self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), - tz=tzi)) - - def testNoSupportAmbiguityUnambiguousDSTOnly(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True) - - self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), - tz=tzi)) - - def testSupportAmbiguityFoldNaive(self): - tzi = tz.gettz('US/Eastern') - - dt = datetime(2011, 11, 6, 1, 30) - - self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) - - def testSupportAmbiguityFoldAware(self): - tzi = tz.gettz('US/Eastern') - - dt = datetime(2011, 11, 6, 1, 30, tzinfo=tzi) - - self.assertTrue(tz.datetime_ambiguous(dt)) - - def testSupportAmbiguityUnambiguousAware(self): - tzi = tz.gettz('US/Eastern') - - dt = datetime(2011, 11, 6, 4, 30) - - self.assertFalse(tz.datetime_ambiguous(dt, tz=tzi)) - - def testSupportAmbiguityUnambiguousNaive(self): - tzi = tz.gettz('US/Eastern') - - dt = datetime(2011, 11, 6, 4, 30, tzinfo=tzi) - - self.assertFalse(tz.datetime_ambiguous(dt)) - - def _get_ambig_error_tzinfo(self, dt_start, dt_end, dst_only=False): - cTzInfo = self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only) - - # Takes the wrong number of arguments and raises an error anyway. - class FoldTzInfoRaises(cTzInfo): - def is_ambiguous(self, dt, other_arg): - raise NotImplementedError('This is not implemented') - - return FoldTzInfoRaises() - - def testIncompatibleAmbiguityFoldNaive(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) - - self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), - tz=tzi)) - - def testIncompatibleAmbiguityFoldAware(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) - - self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30, - tzinfo=tzi))) - - def testIncompatibleAmbiguityUnambiguousNaive(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) - - self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), - tz=tzi)) - - def testIncompatibleAmbiguityUnambiguousAware(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) - - self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30, - tzinfo=tzi))) - - def testIncompatibleAmbiguityFoldDSTOnly(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True) - - self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), - tz=tzi)) - - def testIncompatibleAmbiguityUnambiguousDSTOnly(self): - dt_start = datetime(2018, 9, 1, 1, 0) - dt_end = datetime(2018, 9, 1, 2, 0) - - tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True) - - self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), - tz=tzi)) - - def testSpecifiedTzOverridesAttached(self): - # If a tz is specified, the datetime will be treated as naive. - - # This is not ambiguous in the local zone - dt = datetime(2011, 11, 6, 1, 30, tzinfo=tz.gettz('Australia/Sydney')) - - self.assertFalse(tz.datetime_ambiguous(dt)) - - tzi = tz.gettz('US/Eastern') - self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) - - -class DatetimeExistsTest(unittest.TestCase): - def testNoTzSpecified(self): - with self.assertRaises(ValueError): - tz.datetime_exists(datetime(2016, 4, 1, 2, 9)) - - def testInGapNaive(self): - tzi = tz.gettz('Australia/Sydney') - - dt = datetime(2012, 10, 7, 2, 30) - - self.assertFalse(tz.datetime_exists(dt, tz=tzi)) - - def testInGapAware(self): - tzi = tz.gettz('Australia/Sydney') - - dt = datetime(2012, 10, 7, 2, 30, tzinfo=tzi) - - self.assertFalse(tz.datetime_exists(dt)) - - def testExistsNaive(self): - tzi = tz.gettz('Australia/Sydney') - - dt = datetime(2012, 10, 7, 10, 30) - - self.assertTrue(tz.datetime_exists(dt, tz=tzi)) - - def testExistsAware(self): - tzi = tz.gettz('Australia/Sydney') - - dt = datetime(2012, 10, 7, 10, 30, tzinfo=tzi) - - self.assertTrue(tz.datetime_exists(dt)) - - def testSpecifiedTzOverridesAttached(self): - EST = tz.gettz('US/Eastern') - AEST = tz.gettz('Australia/Sydney') - - dt = datetime(2012, 10, 7, 2, 30, tzinfo=EST) # This time exists - - self.assertFalse(tz.datetime_exists(dt, tz=AEST)) - - + datetime.now(tzwin.tzwinlocal()) + + def testTzwinLocalUTCOffset(self): + with TZWinContext('Eastern Standard Time'): + tzwl = tzwin.tzwinlocal() + self.assertEqual(datetime(2014, 3, 11, tzinfo=tzwl).utcoffset(), + timedelta(hours=-4)) + + def testTzwinLocalName(self): + # https://github.com/dateutil/dateutil/issues/143 + ESTs = 'Eastern Standard Time' + EDTs = 'Eastern Daylight Time' + transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs), + (datetime(2015, 3, 8, 3, 1), EDTs), + (datetime(2015, 11, 1, 0, 59), EDTs), + (datetime(2015, 11, 1, 3, 1), ESTs), + (datetime(2016, 3, 13, 0, 59), ESTs), + (datetime(2016, 3, 13, 3, 1), EDTs), + (datetime(2016, 11, 6, 0, 59), EDTs), + (datetime(2016, 11, 6, 3, 1), ESTs)] + + with TZWinContext('Eastern Standard Time'): + tw = tz.tzwinlocal() + + for t_date, expected in transition_dates: + self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected) + + def testTzWinLocalRepr(self): + tw = tz.tzwinlocal() + self.assertEqual(repr(tw), 'tzwinlocal()') + + def testTzwinLocalRepr(self): + # https://github.com/dateutil/dateutil/issues/143 + with TZWinContext('Eastern Standard Time'): + tw = tz.tzwinlocal() + + self.assertEqual(str(tw), 'tzwinlocal(' + + repr('Eastern Standard Time') + ')') + + with TZWinContext('Pacific Standard Time'): + tw = tz.tzwinlocal() + + self.assertEqual(str(tw), 'tzwinlocal(' + + repr('Pacific Standard Time') + ')') + + def testTzwinLocalEquality(self): + tw_est = tz.tzwin('Eastern Standard Time') + tw_pst = tz.tzwin('Pacific Standard Time') + + with TZWinContext('Eastern Standard Time'): + twl1 = tz.tzwinlocal() + twl2 = tz.tzwinlocal() + + self.assertEqual(twl1, twl2) + self.assertEqual(twl1, tw_est) + self.assertNotEqual(twl1, tw_pst) + + with TZWinContext('Pacific Standard Time'): + twl1 = tz.tzwinlocal() + twl2 = tz.tzwinlocal() + tw = tz.tzwin('Pacific Standard Time') + + self.assertEqual(twl1, twl2) + self.assertEqual(twl1, tw) + self.assertEqual(twl1, tw_pst) + self.assertNotEqual(twl1, tw_est) + + def testTzwinLocalTimeOnlyDST(self): + # For zones with DST, .dst() should return None + with TZWinContext('Eastern Standard Time'): + twl = tz.tzwinlocal() + self.assertIs(dt_time(14, 10, tzinfo=twl).dst(), None) + + # This zone has no DST, so .dst() can return 0 + with TZWinContext('South Africa Standard Time'): + twl = tz.tzwinlocal() + self.assertEqual(dt_time(14, 10, tzinfo=twl).dst(), timedelta(0)) + + def testTzwinLocalTimeOnlyUTCOffset(self): + # For zones with DST, .utcoffset() should return None + with TZWinContext('Eastern Standard Time'): + twl = tz.tzwinlocal() + self.assertIs(dt_time(14, 10, tzinfo=twl).utcoffset(), None) + + # This zone has no DST, so .utcoffset() returns standard offset + with TZWinContext('South Africa Standard Time'): + twl = tz.tzwinlocal() + self.assertEqual(dt_time(14, 10, tzinfo=twl).utcoffset(), + timedelta(hours=2)) + + def testTzwinLocalTimeOnlyTZName(self): + # For zones with DST, the name defaults to standard time + with TZWinContext('Eastern Standard Time'): + twl = tz.tzwinlocal() + self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(), + 'Eastern Standard Time') + + # For zones with no DST, this should work normally. + with TZWinContext('South Africa Standard Time'): + twl = tz.tzwinlocal() + self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(), + 'South Africa Standard Time') + + +class TzPickleTest(PicklableMixin, unittest.TestCase): + _asfile = False + + def setUp(self): + self.assertPicklable = partial(self.assertPicklable, + asfile=self._asfile) + + def testPickleTzUTC(self): + self.assertPicklable(tz.tzutc(), singleton=True) + + def testPickleTzOffsetZero(self): + self.assertPicklable(tz.tzoffset('UTC', 0), singleton=True) + + def testPickleTzOffsetPos(self): + self.assertPicklable(tz.tzoffset('UTC+1', 3600), singleton=True) + + def testPickleTzOffsetNeg(self): + self.assertPicklable(tz.tzoffset('UTC-1', -3600), singleton=True) + + @pytest.mark.tzlocal + def testPickleTzLocal(self): + self.assertPicklable(tz.tzlocal()) + + def testPickleTzFileEST5EDT(self): + tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) + self.assertPicklable(tzc) + + def testPickleTzFileEurope_Helsinki(self): + tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI))) + self.assertPicklable(tzc) + + def testPickleTzFileNew_York(self): + tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK))) + self.assertPicklable(tzc) + + @unittest.skip("Known failure") + def testPickleTzICal(self): + tzc = tz.tzical(StringIO(TZICAL_EST5EDT)).get() + self.assertPicklable(tzc) + + def testPickleTzGettz(self): + self.assertPicklable(tz.gettz('America/New_York')) + + def testPickleZoneFileGettz(self): + zoneinfo_file = zoneinfo.get_zonefile_instance() + tzi = zoneinfo_file.get('America/New_York') + self.assertIsNot(tzi, None) + self.assertPicklable(tzi) + + +class TzPickleFileTest(TzPickleTest): + """ Run all the TzPickleTest tests, using a temporary file """ + _asfile = True + + +class DatetimeAmbiguousTest(unittest.TestCase): + """ Test the datetime_exists / datetime_ambiguous functions """ + + def testNoTzSpecified(self): + with self.assertRaises(ValueError): + tz.datetime_ambiguous(datetime(2016, 4, 1, 2, 9)) + + def _get_no_support_tzinfo_class(self, dt_start, dt_end, dst_only=False): + # Generates a class of tzinfo with no support for is_ambiguous + # where dates between dt_start and dt_end are ambiguous. + + class FoldingTzInfo(tzinfo): + def utcoffset(self, dt): + if not dst_only: + dt_n = dt.replace(tzinfo=None) + + if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0): + return timedelta(hours=-1) + + return timedelta(hours=0) + + def dst(self, dt): + dt_n = dt.replace(tzinfo=None) + + if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0): + return timedelta(hours=1) + else: + return timedelta(0) + + return FoldingTzInfo + + def _get_no_support_tzinfo(self, dt_start, dt_end, dst_only=False): + return self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)() + + def testNoSupportAmbiguityFoldNaive(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_no_support_tzinfo(dt_start, dt_end) + + self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), + tz=tzi)) + + def testNoSupportAmbiguityFoldAware(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_no_support_tzinfo(dt_start, dt_end) + + self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30, + tzinfo=tzi))) + + def testNoSupportAmbiguityUnambiguousNaive(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_no_support_tzinfo(dt_start, dt_end) + + self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), + tz=tzi)) + + def testNoSupportAmbiguityUnambiguousAware(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_no_support_tzinfo(dt_start, dt_end) + + self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30, + tzinfo=tzi))) + + def testNoSupportAmbiguityFoldDSTOnly(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True) + + self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), + tz=tzi)) + + def testNoSupportAmbiguityUnambiguousDSTOnly(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True) + + self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), + tz=tzi)) + + def testSupportAmbiguityFoldNaive(self): + tzi = tz.gettz('US/Eastern') + + dt = datetime(2011, 11, 6, 1, 30) + + self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) + + def testSupportAmbiguityFoldAware(self): + tzi = tz.gettz('US/Eastern') + + dt = datetime(2011, 11, 6, 1, 30, tzinfo=tzi) + + self.assertTrue(tz.datetime_ambiguous(dt)) + + def testSupportAmbiguityUnambiguousAware(self): + tzi = tz.gettz('US/Eastern') + + dt = datetime(2011, 11, 6, 4, 30) + + self.assertFalse(tz.datetime_ambiguous(dt, tz=tzi)) + + def testSupportAmbiguityUnambiguousNaive(self): + tzi = tz.gettz('US/Eastern') + + dt = datetime(2011, 11, 6, 4, 30, tzinfo=tzi) + + self.assertFalse(tz.datetime_ambiguous(dt)) + + def _get_ambig_error_tzinfo(self, dt_start, dt_end, dst_only=False): + cTzInfo = self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only) + + # Takes the wrong number of arguments and raises an error anyway. + class FoldTzInfoRaises(cTzInfo): + def is_ambiguous(self, dt, other_arg): + raise NotImplementedError('This is not implemented') + + return FoldTzInfoRaises() + + def testIncompatibleAmbiguityFoldNaive(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) + + self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), + tz=tzi)) + + def testIncompatibleAmbiguityFoldAware(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) + + self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30, + tzinfo=tzi))) + + def testIncompatibleAmbiguityUnambiguousNaive(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) + + self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), + tz=tzi)) + + def testIncompatibleAmbiguityUnambiguousAware(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) + + self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30, + tzinfo=tzi))) + + def testIncompatibleAmbiguityFoldDSTOnly(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True) + + self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), + tz=tzi)) + + def testIncompatibleAmbiguityUnambiguousDSTOnly(self): + dt_start = datetime(2018, 9, 1, 1, 0) + dt_end = datetime(2018, 9, 1, 2, 0) + + tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True) + + self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), + tz=tzi)) + + def testSpecifiedTzOverridesAttached(self): + # If a tz is specified, the datetime will be treated as naive. + + # This is not ambiguous in the local zone + dt = datetime(2011, 11, 6, 1, 30, tzinfo=tz.gettz('Australia/Sydney')) + + self.assertFalse(tz.datetime_ambiguous(dt)) + + tzi = tz.gettz('US/Eastern') + self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) + + +class DatetimeExistsTest(unittest.TestCase): + def testNoTzSpecified(self): + with self.assertRaises(ValueError): + tz.datetime_exists(datetime(2016, 4, 1, 2, 9)) + + def testInGapNaive(self): + tzi = tz.gettz('Australia/Sydney') + + dt = datetime(2012, 10, 7, 2, 30) + + self.assertFalse(tz.datetime_exists(dt, tz=tzi)) + + def testInGapAware(self): + tzi = tz.gettz('Australia/Sydney') + + dt = datetime(2012, 10, 7, 2, 30, tzinfo=tzi) + + self.assertFalse(tz.datetime_exists(dt)) + + def testExistsNaive(self): + tzi = tz.gettz('Australia/Sydney') + + dt = datetime(2012, 10, 7, 10, 30) + + self.assertTrue(tz.datetime_exists(dt, tz=tzi)) + + def testExistsAware(self): + tzi = tz.gettz('Australia/Sydney') + + dt = datetime(2012, 10, 7, 10, 30, tzinfo=tzi) + + self.assertTrue(tz.datetime_exists(dt)) + + def testSpecifiedTzOverridesAttached(self): + EST = tz.gettz('US/Eastern') + AEST = tz.gettz('Australia/Sydney') + + dt = datetime(2012, 10, 7, 2, 30, tzinfo=EST) # This time exists + + self.assertFalse(tz.datetime_exists(dt, tz=AEST)) + + class TestEnfold: def test_enter_fold_default(self): - dt = tz.enfold(datetime(2020, 1, 19, 3, 32)) - + dt = tz.enfold(datetime(2020, 1, 19, 3, 32)) + assert dt.fold == 1 - + def test_enter_fold(self): - dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1) - + dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1) + assert dt.fold == 1 - + def test_exit_fold(self): - dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=0) - - # Before Python 3.6, dt.fold won't exist if fold is 0. + dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=0) + + # Before Python 3.6, dt.fold won't exist if fold is 0. assert getattr(dt, 'fold', 0) == 0 - + def test_defold(self): dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1) - + dt2 = tz.enfold(dt, fold=0) assert getattr(dt2, 'fold', 0) == 0 @@ -2703,109 +2703,109 @@ class TestEnfold: dt.replace(1950, year=2000) -@pytest.mark.tz_resolve_imaginary -class ImaginaryDateTest(unittest.TestCase): - def testCanberraForward(self): - tzi = tz.gettz('Australia/Canberra') - dt = datetime(2018, 10, 7, 2, 30, tzinfo=tzi) - dt_act = tz.resolve_imaginary(dt) - dt_exp = datetime(2018, 10, 7, 3, 30, tzinfo=tzi) - self.assertEqual(dt_act, dt_exp) - - def testLondonForward(self): - tzi = tz.gettz('Europe/London') - dt = datetime(2018, 3, 25, 1, 30, tzinfo=tzi) - dt_act = tz.resolve_imaginary(dt) - dt_exp = datetime(2018, 3, 25, 2, 30, tzinfo=tzi) - self.assertEqual(dt_act, dt_exp) - - def testKeivForward(self): - tzi = tz.gettz('Europe/Kiev') - dt = datetime(2018, 3, 25, 3, 30, tzinfo=tzi) - dt_act = tz.resolve_imaginary(dt) - dt_exp = datetime(2018, 3, 25, 4, 30, tzinfo=tzi) - self.assertEqual(dt_act, dt_exp) - - -@pytest.mark.tz_resolve_imaginary -@pytest.mark.parametrize('dt', [ - datetime(2017, 11, 5, 1, 30, tzinfo=tz.gettz('America/New_York')), - datetime(2018, 10, 28, 1, 30, tzinfo=tz.gettz('Europe/London')), - datetime(2017, 4, 2, 2, 30, tzinfo=tz.gettz('Australia/Sydney')), -]) -def test_resolve_imaginary_ambiguous(dt): - assert tz.resolve_imaginary(dt) is dt - - dt_f = tz.enfold(dt) - assert dt is not dt_f - assert tz.resolve_imaginary(dt_f) is dt_f - - -@pytest.mark.tz_resolve_imaginary -@pytest.mark.parametrize('dt', [ - datetime(2017, 6, 2, 12, 30, tzinfo=tz.gettz('America/New_York')), - datetime(2018, 4, 2, 9, 30, tzinfo=tz.gettz('Europe/London')), - datetime(2017, 2, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')), - datetime(2017, 12, 2, 12, 30, tzinfo=tz.gettz('America/New_York')), - datetime(2018, 12, 2, 9, 30, tzinfo=tz.gettz('Europe/London')), - datetime(2017, 6, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')), +@pytest.mark.tz_resolve_imaginary +class ImaginaryDateTest(unittest.TestCase): + def testCanberraForward(self): + tzi = tz.gettz('Australia/Canberra') + dt = datetime(2018, 10, 7, 2, 30, tzinfo=tzi) + dt_act = tz.resolve_imaginary(dt) + dt_exp = datetime(2018, 10, 7, 3, 30, tzinfo=tzi) + self.assertEqual(dt_act, dt_exp) + + def testLondonForward(self): + tzi = tz.gettz('Europe/London') + dt = datetime(2018, 3, 25, 1, 30, tzinfo=tzi) + dt_act = tz.resolve_imaginary(dt) + dt_exp = datetime(2018, 3, 25, 2, 30, tzinfo=tzi) + self.assertEqual(dt_act, dt_exp) + + def testKeivForward(self): + tzi = tz.gettz('Europe/Kiev') + dt = datetime(2018, 3, 25, 3, 30, tzinfo=tzi) + dt_act = tz.resolve_imaginary(dt) + dt_exp = datetime(2018, 3, 25, 4, 30, tzinfo=tzi) + self.assertEqual(dt_act, dt_exp) + + +@pytest.mark.tz_resolve_imaginary +@pytest.mark.parametrize('dt', [ + datetime(2017, 11, 5, 1, 30, tzinfo=tz.gettz('America/New_York')), + datetime(2018, 10, 28, 1, 30, tzinfo=tz.gettz('Europe/London')), + datetime(2017, 4, 2, 2, 30, tzinfo=tz.gettz('Australia/Sydney')), +]) +def test_resolve_imaginary_ambiguous(dt): + assert tz.resolve_imaginary(dt) is dt + + dt_f = tz.enfold(dt) + assert dt is not dt_f + assert tz.resolve_imaginary(dt_f) is dt_f + + +@pytest.mark.tz_resolve_imaginary +@pytest.mark.parametrize('dt', [ + datetime(2017, 6, 2, 12, 30, tzinfo=tz.gettz('America/New_York')), + datetime(2018, 4, 2, 9, 30, tzinfo=tz.gettz('Europe/London')), + datetime(2017, 2, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')), + datetime(2017, 12, 2, 12, 30, tzinfo=tz.gettz('America/New_York')), + datetime(2018, 12, 2, 9, 30, tzinfo=tz.gettz('Europe/London')), + datetime(2017, 6, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')), datetime(2025, 9, 25, 1, 17, tzinfo=tz.UTC), - datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzoffset('EST', -18000)), - datetime(2019, 3, 4, tzinfo=None) -]) -def test_resolve_imaginary_existing(dt): - assert tz.resolve_imaginary(dt) is dt - - -def __get_kiritimati_resolve_imaginary_test(): - # In the 2018d release of the IANA database, the Kiritimati "imaginary day" - # data was corrected, so if the system zoneinfo is older than 2018d, the - # Kiritimati test will fail. - - tzi = tz.gettz('Pacific/Kiritimati') - new_version = False - if not tz.datetime_exists(datetime(1995, 1, 1, 12, 30), tzi): - zif = zoneinfo.get_zonefile_instance() - if zif.metadata is not None: - new_version = zif.metadata['tzversion'] >= '2018d' - - if new_version: - tzi = zif.get('Pacific/Kiritimati') - else: - new_version = True - - if new_version: - dates = (datetime(1994, 12, 31, 12, 30), datetime(1995, 1, 1, 12, 30)) - else: - dates = (datetime(1995, 1, 1, 12, 30), datetime(1995, 1, 2, 12, 30)) - - return (tzi, ) + dates - - + datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzoffset('EST', -18000)), + datetime(2019, 3, 4, tzinfo=None) +]) +def test_resolve_imaginary_existing(dt): + assert tz.resolve_imaginary(dt) is dt + + +def __get_kiritimati_resolve_imaginary_test(): + # In the 2018d release of the IANA database, the Kiritimati "imaginary day" + # data was corrected, so if the system zoneinfo is older than 2018d, the + # Kiritimati test will fail. + + tzi = tz.gettz('Pacific/Kiritimati') + new_version = False + if not tz.datetime_exists(datetime(1995, 1, 1, 12, 30), tzi): + zif = zoneinfo.get_zonefile_instance() + if zif.metadata is not None: + new_version = zif.metadata['tzversion'] >= '2018d' + + if new_version: + tzi = zif.get('Pacific/Kiritimati') + else: + new_version = True + + if new_version: + dates = (datetime(1994, 12, 31, 12, 30), datetime(1995, 1, 1, 12, 30)) + else: + dates = (datetime(1995, 1, 1, 12, 30), datetime(1995, 1, 2, 12, 30)) + + return (tzi, ) + dates + + resolve_imaginary_tests = [ - (tz.gettz('Europe/London'), - datetime(2018, 3, 25, 1, 30), datetime(2018, 3, 25, 2, 30)), - (tz.gettz('America/New_York'), - datetime(2017, 3, 12, 2, 30), datetime(2017, 3, 12, 3, 30)), - (tz.gettz('Australia/Sydney'), - datetime(2014, 10, 5, 2, 0), datetime(2014, 10, 5, 3, 0)), - __get_kiritimati_resolve_imaginary_test(), + (tz.gettz('Europe/London'), + datetime(2018, 3, 25, 1, 30), datetime(2018, 3, 25, 2, 30)), + (tz.gettz('America/New_York'), + datetime(2017, 3, 12, 2, 30), datetime(2017, 3, 12, 3, 30)), + (tz.gettz('Australia/Sydney'), + datetime(2014, 10, 5, 2, 0), datetime(2014, 10, 5, 3, 0)), + __get_kiritimati_resolve_imaginary_test(), ] - - + + if SUPPORTS_SUB_MINUTE_OFFSETS: resolve_imaginary_tests.append( (tz.gettz('Africa/Monrovia'), datetime(1972, 1, 7, 0, 30), datetime(1972, 1, 7, 1, 14, 30))) + - -@pytest.mark.tz_resolve_imaginary +@pytest.mark.tz_resolve_imaginary @pytest.mark.parametrize('tzi, dt, dt_exp', resolve_imaginary_tests) def test_resolve_imaginary(tzi, dt, dt_exp): dt = dt.replace(tzinfo=tzi) dt_exp = dt_exp.replace(tzinfo=tzi) - - dt_r = tz.resolve_imaginary(dt) - assert dt_r == dt_exp - assert dt_r.tzname() == dt_exp.tzname() - assert dt_r.utcoffset() == dt_exp.utcoffset() + + dt_r = tz.resolve_imaginary(dt) + assert dt_r == dt_exp + assert dt_r.tzname() == dt_exp.tzname() + assert dt_r.utcoffset() == dt_exp.utcoffset() diff --git a/contrib/python/dateutil/dateutil/test/test_utils.py b/contrib/python/dateutil/dateutil/test/test_utils.py index fe1bfdcb84..2b41c7ecfe 100644 --- a/contrib/python/dateutil/dateutil/test/test_utils.py +++ b/contrib/python/dateutil/dateutil/test/test_utils.py @@ -1,37 +1,37 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from datetime import timedelta, datetime - -from dateutil import tz -from dateutil import utils +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from datetime import timedelta, datetime + +from dateutil import tz +from dateutil import utils from dateutil.tz import UTC -from dateutil.utils import within_delta - -from freezegun import freeze_time - -NYC = tz.gettz("America/New_York") - - +from dateutil.utils import within_delta + +from freezegun import freeze_time + +NYC = tz.gettz("America/New_York") + + @freeze_time(datetime(2014, 12, 15, 1, 21, 33, 4003)) def test_utils_today(): assert utils.today() == datetime(2014, 12, 15, 0, 0, 0) - - + + @freeze_time(datetime(2014, 12, 15, 12), tz_offset=5) def test_utils_today_tz_info(): assert utils.today(NYC) == datetime(2014, 12, 15, 0, 0, 0, tzinfo=NYC) - - + + @freeze_time(datetime(2014, 12, 15, 23), tz_offset=5) def test_utils_today_tz_info_different_day(): assert utils.today(UTC) == datetime(2014, 12, 16, 0, 0, 0, tzinfo=UTC) - - + + def test_utils_default_tz_info_naive(): dt = datetime(2014, 9, 14, 9, 30) assert utils.default_tzinfo(dt, NYC).tzinfo is NYC - - + + def test_utils_default_tz_info_aware(): dt = datetime(2014, 9, 14, 9, 30, tzinfo=UTC) assert utils.default_tzinfo(dt, NYC).tzinfo is UTC diff --git a/contrib/python/dateutil/dateutil/tz/__init__.py b/contrib/python/dateutil/dateutil/tz/__init__.py index af1352c472..98d1589dfb 100644 --- a/contrib/python/dateutil/dateutil/tz/__init__.py +++ b/contrib/python/dateutil/dateutil/tz/__init__.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- from .tz import * -from .tz import __doc__ +from .tz import __doc__ __all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz", - "enfold", "datetime_ambiguous", "datetime_exists", - "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"] - - -class DeprecatedTzFormatWarning(Warning): - """Warning raised when time zones are parsed from deprecated formats.""" + "enfold", "datetime_ambiguous", "datetime_exists", + "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"] + + +class DeprecatedTzFormatWarning(Warning): + """Warning raised when time zones are parsed from deprecated formats.""" diff --git a/contrib/python/dateutil/dateutil/tz/_common.py b/contrib/python/dateutil/dateutil/tz/_common.py index e6ac118315..55cbd79170 100644 --- a/contrib/python/dateutil/dateutil/tz/_common.py +++ b/contrib/python/dateutil/dateutil/tz/_common.py @@ -65,36 +65,36 @@ else: """ __slots__ = () - def replace(self, *args, **kwargs): - """ - Return a datetime with the same attributes, except for those - attributes given new values by whichever keyword arguments are - specified. Note that tzinfo=None can be specified to create a naive - datetime from an aware datetime with no conversion of date and time - data. - - This is reimplemented in ``_DatetimeWithFold`` because pypy3 will - return a ``datetime.datetime`` even if ``fold`` is unchanged. - """ - argnames = ( - 'year', 'month', 'day', 'hour', 'minute', 'second', - 'microsecond', 'tzinfo' - ) - - for arg, argname in zip(args, argnames): - if argname in kwargs: - raise TypeError('Duplicate argument: {}'.format(argname)) - - kwargs[argname] = arg - - for argname in argnames: - if argname not in kwargs: - kwargs[argname] = getattr(self, argname) - - dt_class = self.__class__ if kwargs.get('fold', 1) else datetime - - return dt_class(**kwargs) - + def replace(self, *args, **kwargs): + """ + Return a datetime with the same attributes, except for those + attributes given new values by whichever keyword arguments are + specified. Note that tzinfo=None can be specified to create a naive + datetime from an aware datetime with no conversion of date and time + data. + + This is reimplemented in ``_DatetimeWithFold`` because pypy3 will + return a ``datetime.datetime`` even if ``fold`` is unchanged. + """ + argnames = ( + 'year', 'month', 'day', 'hour', 'minute', 'second', + 'microsecond', 'tzinfo' + ) + + for arg, argname in zip(args, argnames): + if argname in kwargs: + raise TypeError('Duplicate argument: {}'.format(argname)) + + kwargs[argname] = arg + + for argname in argnames: + if argname not in kwargs: + kwargs[argname] = getattr(self, argname) + + dt_class = self.__class__ if kwargs.get('fold', 1) else datetime + + return dt_class(**kwargs) + @property def fold(self): return 1 diff --git a/contrib/python/dateutil/dateutil/tz/_factories.py b/contrib/python/dateutil/dateutil/tz/_factories.py index f8a65891a0..fe43054379 100644 --- a/contrib/python/dateutil/dateutil/tz/_factories.py +++ b/contrib/python/dateutil/dateutil/tz/_factories.py @@ -1,41 +1,41 @@ -from datetime import timedelta +from datetime import timedelta import weakref from collections import OrderedDict - + from six.moves import _thread - - -class _TzSingleton(type): - def __init__(cls, *args, **kwargs): - cls.__instance = None - super(_TzSingleton, cls).__init__(*args, **kwargs) - - def __call__(cls): - if cls.__instance is None: - cls.__instance = super(_TzSingleton, cls).__call__() - return cls.__instance - - -class _TzFactory(type): - def instance(cls, *args, **kwargs): - """Alternate constructor that returns a fresh instance""" - return type.__call__(cls, *args, **kwargs) - - -class _TzOffsetFactory(_TzFactory): - def __init__(cls, *args, **kwargs): + + +class _TzSingleton(type): + def __init__(cls, *args, **kwargs): + cls.__instance = None + super(_TzSingleton, cls).__init__(*args, **kwargs) + + def __call__(cls): + if cls.__instance is None: + cls.__instance = super(_TzSingleton, cls).__call__() + return cls.__instance + + +class _TzFactory(type): + def instance(cls, *args, **kwargs): + """Alternate constructor that returns a fresh instance""" + return type.__call__(cls, *args, **kwargs) + + +class _TzOffsetFactory(_TzFactory): + def __init__(cls, *args, **kwargs): cls.__instances = weakref.WeakValueDictionary() cls.__strong_cache = OrderedDict() cls.__strong_cache_size = 8 cls._cache_lock = _thread.allocate_lock() - - def __call__(cls, name, offset): + + def __call__(cls, name, offset): if isinstance(offset, timedelta): key = (name, offset.total_seconds()) else: key = (name, offset) - + instance = cls.__instances.get(key, None) if instance is None: instance = cls.__instances.setdefault(key, @@ -49,21 +49,21 @@ class _TzOffsetFactory(_TzFactory): if len(cls.__strong_cache) > cls.__strong_cache_size: cls.__strong_cache.popitem(last=False) - return instance - - -class _TzStrFactory(_TzFactory): - def __init__(cls, *args, **kwargs): + return instance + + +class _TzStrFactory(_TzFactory): + def __init__(cls, *args, **kwargs): cls.__instances = weakref.WeakValueDictionary() cls.__strong_cache = OrderedDict() cls.__strong_cache_size = 8 - + cls.__cache_lock = _thread.allocate_lock() - def __call__(cls, s, posix_offset=False): + def __call__(cls, s, posix_offset=False): key = (s, posix_offset) instance = cls.__instances.get(key, None) - + if instance is None: instance = cls.__instances.setdefault(key, cls.instance(s, posix_offset)) @@ -76,5 +76,5 @@ class _TzStrFactory(_TzFactory): if len(cls.__strong_cache) > cls.__strong_cache_size: cls.__strong_cache.popitem(last=False) - return instance + return instance diff --git a/contrib/python/dateutil/dateutil/tz/tz.py b/contrib/python/dateutil/dateutil/tz/tz.py index c67f56d465..246500e2ce 100644 --- a/contrib/python/dateutil/dateutil/tz/tz.py +++ b/contrib/python/dateutil/dateutil/tz/tz.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- """ This module offers timezone implementations subclassing the abstract -:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format -files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, -etc), TZ environment string (in all known formats), given ranges (with help -from relative deltas), local machine timezone, fixed offset timezone, and UTC +:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format +files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, +etc), TZ environment string (in all known formats), given ranges (with help +from relative deltas), local machine timezone, fixed offset timezone, and UTC timezone. """ import datetime @@ -16,15 +16,15 @@ import bisect import weakref from collections import OrderedDict -import six +import six from six import string_types -from six.moves import _thread -from ._common import tzname_in_python2, _tzinfo +from six.moves import _thread +from ._common import tzname_in_python2, _tzinfo from ._common import tzrangebase, enfold from ._common import _validate_fromutc_inputs -from ._factories import _TzSingleton, _TzOffsetFactory -from ._factories import _TzStrFactory +from ._factories import _TzSingleton, _TzOffsetFactory +from ._factories import _TzStrFactory try: from .win import tzwin, tzwinlocal except ImportError: @@ -38,38 +38,38 @@ EPOCH = datetime.datetime.utcfromtimestamp(0) EPOCHORDINAL = EPOCH.toordinal() -@six.add_metaclass(_TzSingleton) +@six.add_metaclass(_TzSingleton) class tzutc(datetime.tzinfo): """ This is a tzinfo object that represents the UTC time zone. - - **Examples:** - - .. doctest:: - - >>> from datetime import * - >>> from dateutil.tz import * - - >>> datetime.now() - datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) - - >>> datetime.now(tzutc()) - datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) - - >>> datetime.now(tzutc()).tzname() - 'UTC' - - .. versionchanged:: 2.7.0 - ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will - always return the same object. - - .. doctest:: - - >>> from dateutil.tz import tzutc, UTC - >>> tzutc() is tzutc() - True - >>> tzutc() is UTC - True + + **Examples:** + + .. doctest:: + + >>> from datetime import * + >>> from dateutil.tz import * + + >>> datetime.now() + datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) + + >>> datetime.now(tzutc()) + datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) + + >>> datetime.now(tzutc()).tzname() + 'UTC' + + .. versionchanged:: 2.7.0 + ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will + always return the same object. + + .. doctest:: + + >>> from dateutil.tz import tzutc, UTC + >>> tzutc() is tzutc() + True + >>> tzutc() is UTC + True """ def utcoffset(self, dt): return ZERO @@ -129,7 +129,7 @@ class tzutc(datetime.tzinfo): UTC = tzutc() -@six.add_metaclass(_TzOffsetFactory) +@six.add_metaclass(_TzOffsetFactory) class tzoffset(datetime.tzinfo): """ A simple class for representing a fixed offset from UTC. @@ -138,14 +138,14 @@ class tzoffset(datetime.tzinfo): The timezone name, to be returned when ``tzname()`` is called. :param offset: The time zone offset in seconds, or (since version 2.6.0, represented - as a :py:class:`datetime.timedelta` object). + as a :py:class:`datetime.timedelta` object). """ def __init__(self, name, offset): self._name = name try: # Allow a timedelta - offset = offset.total_seconds() + offset = offset.total_seconds() except (TypeError, AttributeError): pass @@ -193,7 +193,7 @@ class tzoffset(datetime.tzinfo): def __repr__(self): return "%s(%s, %s)" % (self.__class__.__name__, repr(self._name), - int(self._offset.total_seconds())) + int(self._offset.total_seconds())) __reduce__ = object.__reduce__ @@ -213,7 +213,7 @@ class tzlocal(_tzinfo): self._dst_saved = self._dst_offset - self._std_offset self._hasdst = bool(self._dst_saved) - self._tznames = tuple(time.tzname) + self._tznames = tuple(time.tzname) def utcoffset(self, dt): if dt is None and self._hasdst: @@ -235,7 +235,7 @@ class tzlocal(_tzinfo): @tzname_in_python2 def tzname(self, dt): - return self._tznames[self._isdst(dt)] + return self._tznames[self._isdst(dt)] def is_ambiguous(self, dt): """ @@ -300,18 +300,18 @@ class tzlocal(_tzinfo): return dstval def __eq__(self, other): - if isinstance(other, tzlocal): - return (self._std_offset == other._std_offset and - self._dst_offset == other._dst_offset) - elif isinstance(other, tzutc): - return (not self._hasdst and - self._tznames[0] in {'UTC', 'GMT'} and - self._std_offset == ZERO) - elif isinstance(other, tzoffset): - return (not self._hasdst and - self._tznames[0] == other._name and - self._std_offset == other._offset) - else: + if isinstance(other, tzlocal): + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset) + elif isinstance(other, tzutc): + return (not self._hasdst and + self._tznames[0] in {'UTC', 'GMT'} and + self._std_offset == ZERO) + elif isinstance(other, tzoffset): + return (not self._hasdst and + self._tznames[0] == other._name and + self._std_offset == other._offset) + else: return NotImplemented __hash__ = None @@ -399,60 +399,60 @@ class tzfile(_tzinfo): ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``. See `Sources for Time Zone and Daylight Saving Time Data - <https://data.iana.org/time-zones/tz-link.html>`_ for more information. - Time zone files can be compiled from the `IANA Time Zone database files + <https://data.iana.org/time-zones/tz-link.html>`_ for more information. + Time zone files can be compiled from the `IANA Time Zone database files <https://www.iana.org/time-zones>`_ with the `zic time zone compiler <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_ - - .. note:: - - Only construct a ``tzfile`` directly if you have a specific timezone - file on disk that you want to read into a Python ``tzinfo`` object. - If you want to get a ``tzfile`` representing a specific IANA zone, - (e.g. ``'America/New_York'``), you should call - :func:`dateutil.tz.gettz` with the zone identifier. - - - **Examples:** - - Using the US Eastern time zone as an example, we can see that a ``tzfile`` - provides time zone information for the standard Daylight Saving offsets: - - .. testsetup:: tzfile - - from dateutil.tz import gettz - from datetime import datetime - - .. doctest:: tzfile - - >>> NYC = gettz('America/New_York') - >>> NYC - tzfile('/usr/share/zoneinfo/America/New_York') - - >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST - 2016-01-03 00:00:00-05:00 - - >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT - 2016-07-07 00:00:00-04:00 - - - The ``tzfile`` structure contains a fully history of the time zone, - so historical dates will also have the right offsets. For example, before - the adoption of the UTC standards, New York used local solar mean time: - - .. doctest:: tzfile - - >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT - 1901-04-12 00:00:00-04:56 - - And during World War II, New York was on "Eastern War Time", which was a - state of permanent daylight saving time: - - .. doctest:: tzfile - - >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT - 1944-02-07 00:00:00-04:00 - + + .. note:: + + Only construct a ``tzfile`` directly if you have a specific timezone + file on disk that you want to read into a Python ``tzinfo`` object. + If you want to get a ``tzfile`` representing a specific IANA zone, + (e.g. ``'America/New_York'``), you should call + :func:`dateutil.tz.gettz` with the zone identifier. + + + **Examples:** + + Using the US Eastern time zone as an example, we can see that a ``tzfile`` + provides time zone information for the standard Daylight Saving offsets: + + .. testsetup:: tzfile + + from dateutil.tz import gettz + from datetime import datetime + + .. doctest:: tzfile + + >>> NYC = gettz('America/New_York') + >>> NYC + tzfile('/usr/share/zoneinfo/America/New_York') + + >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST + 2016-01-03 00:00:00-05:00 + + >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT + 2016-07-07 00:00:00-04:00 + + + The ``tzfile`` structure contains a fully history of the time zone, + so historical dates will also have the right offsets. For example, before + the adoption of the UTC standards, New York used local solar mean time: + + .. doctest:: tzfile + + >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT + 1901-04-12 00:00:00-04:56 + + And during World War II, New York was on "Eastern War Time", which was a + state of permanent daylight saving time: + + .. doctest:: tzfile + + >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT + 1944-02-07 00:00:00-04:00 + """ def __init__(self, fileobj, filename=None): @@ -549,7 +549,7 @@ class tzfile(_tzinfo): if timecnt: out.trans_idx = struct.unpack(">%dB" % timecnt, - fileobj.read(timecnt)) + fileobj.read(timecnt)) else: out.trans_idx = [] @@ -722,7 +722,7 @@ class tzfile(_tzinfo): idx = bisect.bisect_right(trans_list, timestamp) # We want to know when the previous transition was, so subtract off 1 - return idx - 1 + return idx - 1 def _get_ttinfo(self, idx): # For no list or after the last transition, default to _ttinfo_std @@ -906,9 +906,9 @@ class tzrange(tzrangebase): :param start: A :class:`relativedelta.relativedelta` object or equivalent specifying - the time and time of year that daylight savings time starts. To - specify, for example, that DST starts at 2AM on the 2nd Sunday in - March, pass: + the time and time of year that daylight savings time starts. To + specify, for example, that DST starts at 2AM on the 2nd Sunday in + March, pass: ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))`` @@ -916,12 +916,12 @@ class tzrange(tzrangebase): value is 2 AM on the first Sunday in April. :param end: - A :class:`relativedelta.relativedelta` object or equivalent - representing the time and time of year that daylight savings time - ends, with the same specification method as in ``start``. One note is - that this should point to the first time in the *standard* zone, so if - a transition occurs at 2AM in the DST zone and the clocks are set back - 1 hour to 1AM, set the ``hours`` parameter to +1. + A :class:`relativedelta.relativedelta` object or equivalent + representing the time and time of year that daylight savings time + ends, with the same specification method as in ``start``. One note is + that this should point to the first time in the *standard* zone, so if + a transition occurs at 2AM in the DST zone and the clocks are set back + 1 hour to 1AM, set the ``hours`` parameter to +1. **Examples:** @@ -957,12 +957,12 @@ class tzrange(tzrangebase): self._dst_abbr = dstabbr try: - stdoffset = stdoffset.total_seconds() + stdoffset = stdoffset.total_seconds() except (TypeError, AttributeError): pass try: - dstoffset = dstoffset.total_seconds() + dstoffset = dstoffset.total_seconds() except (TypeError, AttributeError): pass @@ -1033,7 +1033,7 @@ class tzrange(tzrangebase): return self._dst_base_offset_ -@six.add_metaclass(_TzStrFactory) +@six.add_metaclass(_TzStrFactory) class tzstr(tzrange): """ ``tzstr`` objects are time zone objects specified by a time-zone string as @@ -1052,38 +1052,38 @@ class tzstr(tzrange): :param s: A time zone string in ``TZ`` variable format. This can be a - :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: - :class:`unicode`) or a stream emitting unicode characters - (e.g. :class:`StringIO`). + :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: + :class:`unicode`) or a stream emitting unicode characters + (e.g. :class:`StringIO`). :param posix_offset: Optional. If set to ``True``, interpret strings such as ``GMT+3`` or ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the POSIX standard. - .. caution:: - - Prior to version 2.7.0, this function also supported time zones - in the format: - - * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600`` - * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600`` - - This format is non-standard and has been deprecated; this function - will raise a :class:`DeprecatedTZFormatWarning` until - support is removed in a future version. - + .. caution:: + + Prior to version 2.7.0, this function also supported time zones + in the format: + + * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600`` + * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600`` + + This format is non-standard and has been deprecated; this function + will raise a :class:`DeprecatedTZFormatWarning` until + support is removed in a future version. + .. _`GNU C Library: TZ Variable`: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html """ def __init__(self, s, posix_offset=False): global parser - from dateutil.parser import _parser as parser + from dateutil.parser import _parser as parser self._s = s res = parser._parsetz(s) - if res is None or res.any_unused_tokens: + if res is None or res.any_unused_tokens: raise ValueError("unknown string format") # Here we break the compatibility with the TZ variable handling. @@ -1172,7 +1172,7 @@ class _tzicalvtz(_tzinfo): self._comps = comps self._cachedate = [] self._cachecomp = [] - self._cache_lock = _thread.allocate_lock() + self._cache_lock = _thread.allocate_lock() def _find_comp(self, dt): if len(self._comps) == 1: @@ -1181,9 +1181,9 @@ class _tzicalvtz(_tzinfo): dt = dt.replace(tzinfo=None) try: - with self._cache_lock: - return self._cachecomp[self._cachedate.index( - (dt, self._fold(dt)))] + with self._cache_lock: + return self._cachecomp[self._cachedate.index( + (dt, self._fold(dt)))] except ValueError: pass @@ -1209,13 +1209,13 @@ class _tzicalvtz(_tzinfo): else: lastcomp = comp[0] - with self._cache_lock: - self._cachedate.insert(0, (dt, self._fold(dt))) - self._cachecomp.insert(0, lastcomp) + with self._cache_lock: + self._cachedate.insert(0, (dt, self._fold(dt))) + self._cachecomp.insert(0, lastcomp) - if len(self._cachedate) > 10: - self._cachedate.pop() - self._cachecomp.pop() + if len(self._cachedate) > 10: + self._cachedate.pop() + self._cachecomp.pop() return lastcomp @@ -1253,13 +1253,13 @@ class _tzicalvtz(_tzinfo): class tzical(object): """ This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure - as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects. + as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects. :param `fileobj`: A file or stream in iCalendar format, which should be UTF-8 encoded with CRLF endings. - .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545 + .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545 """ def __init__(self, fileobj): global rrule @@ -1407,13 +1407,13 @@ class tzical(object): raise ValueError("invalid component end: "+value) elif comptype: if name == "DTSTART": - # DTSTART in VTIMEZONE takes a subset of valid RRULE - # values under RFC 5545. - for parm in parms: - if parm != 'VALUE=DATE-TIME': - msg = ('Unsupported DTSTART param in ' + - 'VTIMEZONE: ' + parm) - raise ValueError(msg) + # DTSTART in VTIMEZONE takes a subset of valid RRULE + # values under RFC 5545. + for parm in parms: + if parm != 'VALUE=DATE-TIME': + msg = ('Unsupported DTSTART param in ' + + 'VTIMEZONE: ' + parm) + raise ValueError(msg) rrulelines.append(line) founddtstart = True elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): @@ -1467,153 +1467,153 @@ else: TZPATHS = [] -def __get_gettz(): - tzlocal_classes = (tzlocal,) - if tzwinlocal is not None: - tzlocal_classes += (tzwinlocal,) - - class GettzFunc(object): - """ - Retrieve a time zone object from a string representation - - This function is intended to retrieve the :py:class:`tzinfo` subclass - that best represents the time zone that would be used if a POSIX - `TZ variable`_ were set to the same value. - - If no argument or an empty string is passed to ``gettz``, local time - is returned: - - .. code-block:: python3 - - >>> gettz() - tzfile('/etc/localtime') - - This function is also the preferred way to map IANA tz database keys - to :class:`tzfile` objects: - - .. code-block:: python3 - - >>> gettz('Pacific/Kiritimati') - tzfile('/usr/share/zoneinfo/Pacific/Kiritimati') - - On Windows, the standard is extended to include the Windows-specific - zone names provided by the operating system: - - .. code-block:: python3 - - >>> gettz('Egypt Standard Time') - tzwin('Egypt Standard Time') - - Passing a GNU ``TZ`` style string time zone specification returns a - :class:`tzstr` object: - - .. code-block:: python3 - - >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') - tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') - - :param name: - A time zone name (IANA, or, on Windows, Windows keys), location of - a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone - specifier. An empty string, no argument or ``None`` is interpreted - as local time. - - :return: - Returns an instance of one of ``dateutil``'s :py:class:`tzinfo` - subclasses. - - .. versionchanged:: 2.7.0 - - After version 2.7.0, any two calls to ``gettz`` using the same - input strings will return the same object: - - .. code-block:: python3 - - >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago') - True - - In addition to improving performance, this ensures that - `"same zone" semantics`_ are used for datetimes in the same zone. - - - .. _`TZ variable`: - https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html - - .. _`"same zone" semantics`: - https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html - """ - def __init__(self): - +def __get_gettz(): + tzlocal_classes = (tzlocal,) + if tzwinlocal is not None: + tzlocal_classes += (tzwinlocal,) + + class GettzFunc(object): + """ + Retrieve a time zone object from a string representation + + This function is intended to retrieve the :py:class:`tzinfo` subclass + that best represents the time zone that would be used if a POSIX + `TZ variable`_ were set to the same value. + + If no argument or an empty string is passed to ``gettz``, local time + is returned: + + .. code-block:: python3 + + >>> gettz() + tzfile('/etc/localtime') + + This function is also the preferred way to map IANA tz database keys + to :class:`tzfile` objects: + + .. code-block:: python3 + + >>> gettz('Pacific/Kiritimati') + tzfile('/usr/share/zoneinfo/Pacific/Kiritimati') + + On Windows, the standard is extended to include the Windows-specific + zone names provided by the operating system: + + .. code-block:: python3 + + >>> gettz('Egypt Standard Time') + tzwin('Egypt Standard Time') + + Passing a GNU ``TZ`` style string time zone specification returns a + :class:`tzstr` object: + + .. code-block:: python3 + + >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + + :param name: + A time zone name (IANA, or, on Windows, Windows keys), location of + a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone + specifier. An empty string, no argument or ``None`` is interpreted + as local time. + + :return: + Returns an instance of one of ``dateutil``'s :py:class:`tzinfo` + subclasses. + + .. versionchanged:: 2.7.0 + + After version 2.7.0, any two calls to ``gettz`` using the same + input strings will return the same object: + + .. code-block:: python3 + + >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago') + True + + In addition to improving performance, this ensures that + `"same zone" semantics`_ are used for datetimes in the same zone. + + + .. _`TZ variable`: + https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + + .. _`"same zone" semantics`: + https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html + """ + def __init__(self): + self.__instances = weakref.WeakValueDictionary() self.__strong_cache_size = 8 self.__strong_cache = OrderedDict() - self._cache_lock = _thread.allocate_lock() - - def __call__(self, name=None): - with self._cache_lock: - rv = self.__instances.get(name, None) - - if rv is None: - rv = self.nocache(name=name) + self._cache_lock = _thread.allocate_lock() + + def __call__(self, name=None): + with self._cache_lock: + rv = self.__instances.get(name, None) + + if rv is None: + rv = self.nocache(name=name) if not (name is None or isinstance(rv, tzlocal_classes) or rv is None): - # tzlocal is slightly more complicated than the other - # time zone providers because it depends on environment - # at construction time, so don't cache that. + # tzlocal is slightly more complicated than the other + # time zone providers because it depends on environment + # at construction time, so don't cache that. # # We also cannot store weak references to None, so we # will also not store that. - self.__instances[name] = rv + self.__instances[name] = rv else: # No need for strong caching, return immediately return rv - + self.__strong_cache[name] = self.__strong_cache.pop(name, rv) if len(self.__strong_cache) > self.__strong_cache_size: self.__strong_cache.popitem(last=False) - return rv - + return rv + def set_cache_size(self, size): with self._cache_lock: self.__strong_cache_size = size while len(self.__strong_cache) > size: self.__strong_cache.popitem(last=False) - def cache_clear(self): - with self._cache_lock: + def cache_clear(self): + with self._cache_lock: self.__instances = weakref.WeakValueDictionary() self.__strong_cache.clear() - - @staticmethod - def nocache(name=None): - """A non-cached version of gettz""" - tz = None - if not name: - try: - name = os.environ["TZ"] - except KeyError: - pass + + @staticmethod + def nocache(name=None): + """A non-cached version of gettz""" + tz = None + if not name: + try: + name = os.environ["TZ"] + except KeyError: + pass if name is None or name in ("", ":"): - 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() + 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: try: if name.startswith(":"): @@ -1624,62 +1624,62 @@ def __get_gettz(): six.raise_from(TypeError(new_msg), e) else: raise - if os.path.isabs(name): - if os.path.isfile(name): - tz = tzfile(name) - else: + 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) + 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 + except (IOError, OSError, ValueError): + pass else: - tz = None - if tzwin is not None: - try: - tz = tzwin(name) + tz = None + if tzwin is not None: + try: + tz = tzwin(name) except (WindowsError, UnicodeEncodeError): # UnicodeEncodeError is for Python 2.7 compat - tz = None - - if not tz: - from dateutil.zoneinfo import get_zonefile_instance - tz = get_zonefile_instance().get(name) - - if not tz: - for c in name: - # name is not a tzstr unless it has at least - # one offset. For short values of "name", an - # explicit for loop seems to be the fastest way - # To determine if a string contains a digit - if c in "0123456789": - try: - tz = tzstr(name) - except ValueError: - pass - break - else: - if name in ("GMT", "UTC"): + tz = None + + if not tz: + from dateutil.zoneinfo import get_zonefile_instance + tz = get_zonefile_instance().get(name) + + if not tz: + for c in name: + # name is not a tzstr unless it has at least + # one offset. For short values of "name", an + # explicit for loop seems to be the fastest way + # To determine if a string contains a digit + if c in "0123456789": + try: + tz = tzstr(name) + except ValueError: + pass + break + else: + if name in ("GMT", "UTC"): tz = UTC - elif name in time.tzname: - tz = tzlocal() - return tz - - return GettzFunc() - - -gettz = __get_gettz() -del __get_gettz - - + elif name in time.tzname: + tz = tzlocal() + return tz + + return GettzFunc() + + +gettz = __get_gettz() +del __get_gettz + + def datetime_exists(dt, tz=None): """ Given a datetime and a time zone, determine whether or not a given datetime @@ -1694,10 +1694,10 @@ def datetime_exists(dt, tz=None): ``None`` or not provided, the datetime's own time zone will be used. :return: - Returns a boolean value whether or not the "wall time" exists in - ``tz``. - - .. versionadded:: 2.7.0 + Returns a boolean value whether or not the "wall time" exists in + ``tz``. + + .. versionadded:: 2.7.0 """ if tz is None: if dt.tzinfo is None: @@ -1745,7 +1745,7 @@ def datetime_ambiguous(dt, tz=None): if is_ambiguous_fn is not None: try: return tz.is_ambiguous(dt) - except Exception: + except Exception: pass # If it doesn't come out and tell us it's ambiguous, we'll just check if @@ -1760,58 +1760,58 @@ def datetime_ambiguous(dt, tz=None): return not (same_offset and same_dst) -def resolve_imaginary(dt): - """ - Given a datetime that may be imaginary, return an existing datetime. - - This function assumes that an imaginary datetime represents what the - wall time would be in a zone had the offset transition not occurred, so - it will always fall forward by the transition's change in offset. - - .. doctest:: - - >>> from dateutil import tz - >>> from datetime import datetime - >>> NYC = tz.gettz('America/New_York') - >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC))) - 2017-03-12 03:30:00-04:00 - - >>> KIR = tz.gettz('Pacific/Kiritimati') - >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR))) - 1995-01-02 12:30:00+14:00 - - As a note, :func:`datetime.astimezone` is guaranteed to produce a valid, - existing datetime, so a round-trip to and from UTC is sufficient to get - an extant datetime, however, this generally "falls back" to an earlier time - rather than falling forward to the STD side (though no guarantees are made - about this behavior). - - :param dt: - A :class:`datetime.datetime` which may or may not exist. - - :return: - Returns an existing :class:`datetime.datetime`. If ``dt`` was not - imaginary, the datetime returned is guaranteed to be the same object - passed to the function. - - .. versionadded:: 2.7.0 - """ - if dt.tzinfo is not None and not datetime_exists(dt): - - curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset() - old_offset = (dt - datetime.timedelta(hours=24)).utcoffset() - - dt += curr_offset - old_offset - - return dt - - +def resolve_imaginary(dt): + """ + Given a datetime that may be imaginary, return an existing datetime. + + This function assumes that an imaginary datetime represents what the + wall time would be in a zone had the offset transition not occurred, so + it will always fall forward by the transition's change in offset. + + .. doctest:: + + >>> from dateutil import tz + >>> from datetime import datetime + >>> NYC = tz.gettz('America/New_York') + >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC))) + 2017-03-12 03:30:00-04:00 + + >>> KIR = tz.gettz('Pacific/Kiritimati') + >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR))) + 1995-01-02 12:30:00+14:00 + + As a note, :func:`datetime.astimezone` is guaranteed to produce a valid, + existing datetime, so a round-trip to and from UTC is sufficient to get + an extant datetime, however, this generally "falls back" to an earlier time + rather than falling forward to the STD side (though no guarantees are made + about this behavior). + + :param dt: + A :class:`datetime.datetime` which may or may not exist. + + :return: + Returns an existing :class:`datetime.datetime`. If ``dt`` was not + imaginary, the datetime returned is guaranteed to be the same object + passed to the function. + + .. versionadded:: 2.7.0 + """ + if dt.tzinfo is not None and not datetime_exists(dt): + + curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset() + old_offset = (dt - datetime.timedelta(hours=24)).utcoffset() + + dt += curr_offset - old_offset + + return dt + + def _datetime_to_timestamp(dt): """ - Convert a :class:`datetime.datetime` object to an epoch timestamp in - seconds since January 1, 1970, ignoring the time zone. + Convert a :class:`datetime.datetime` object to an epoch timestamp in + seconds since January 1, 1970, ignoring the time zone. """ - return (dt.replace(tzinfo=None) - EPOCH).total_seconds() + return (dt.replace(tzinfo=None) - EPOCH).total_seconds() if sys.version_info >= (3, 6): diff --git a/contrib/python/dateutil/dateutil/utils.py b/contrib/python/dateutil/dateutil/utils.py index dd2d245a0b..a6848ba625 100644 --- a/contrib/python/dateutil/dateutil/utils.py +++ b/contrib/python/dateutil/dateutil/utils.py @@ -1,71 +1,71 @@ -# -*- coding: utf-8 -*- -""" -This module offers general convenience and utility functions for dealing with -datetimes. - -.. versionadded:: 2.7.0 -""" -from __future__ import unicode_literals - -from datetime import datetime, time - - -def today(tzinfo=None): - """ - Returns a :py:class:`datetime` representing the current day at midnight - - :param tzinfo: - The time zone to attach (also used to determine the current day). - - :return: - A :py:class:`datetime.datetime` object representing the current day - at midnight. - """ - - dt = datetime.now(tzinfo) - return datetime.combine(dt.date(), time(0, tzinfo=tzinfo)) - - -def default_tzinfo(dt, tzinfo): - """ +# -*- coding: utf-8 -*- +""" +This module offers general convenience and utility functions for dealing with +datetimes. + +.. versionadded:: 2.7.0 +""" +from __future__ import unicode_literals + +from datetime import datetime, time + + +def today(tzinfo=None): + """ + Returns a :py:class:`datetime` representing the current day at midnight + + :param tzinfo: + The time zone to attach (also used to determine the current day). + + :return: + A :py:class:`datetime.datetime` object representing the current day + at midnight. + """ + + dt = datetime.now(tzinfo) + return datetime.combine(dt.date(), time(0, tzinfo=tzinfo)) + + +def default_tzinfo(dt, tzinfo): + """ Sets the ``tzinfo`` parameter on naive datetimes only - - This is useful for example when you are provided a datetime that may have - either an implicit or explicit time zone, such as when parsing a time zone - string. - - .. doctest:: - - >>> from dateutil.tz import tzoffset - >>> from dateutil.parser import parse - >>> from dateutil.utils import default_tzinfo - >>> dflt_tz = tzoffset("EST", -18000) - >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz)) - 2014-01-01 12:30:00+00:00 - >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz)) - 2014-01-01 12:30:00-05:00 - - :param dt: - The datetime on which to replace the time zone - - :param tzinfo: - The :py:class:`datetime.tzinfo` subclass instance to assign to - ``dt`` if (and only if) it is naive. - - :return: - Returns an aware :py:class:`datetime.datetime`. - """ - if dt.tzinfo is not None: - return dt - else: - return dt.replace(tzinfo=tzinfo) - - -def within_delta(dt1, dt2, delta): - """ + + This is useful for example when you are provided a datetime that may have + either an implicit or explicit time zone, such as when parsing a time zone + string. + + .. doctest:: + + >>> from dateutil.tz import tzoffset + >>> from dateutil.parser import parse + >>> from dateutil.utils import default_tzinfo + >>> dflt_tz = tzoffset("EST", -18000) + >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz)) + 2014-01-01 12:30:00+00:00 + >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz)) + 2014-01-01 12:30:00-05:00 + + :param dt: + The datetime on which to replace the time zone + + :param tzinfo: + The :py:class:`datetime.tzinfo` subclass instance to assign to + ``dt`` if (and only if) it is naive. + + :return: + Returns an aware :py:class:`datetime.datetime`. + """ + if dt.tzinfo is not None: + return dt + else: + return dt.replace(tzinfo=tzinfo) + + +def within_delta(dt1, dt2, delta): + """ Useful for comparing two datetimes that may have a negligible difference - to be considered equal. - """ - delta = abs(delta) - difference = dt1 - dt2 - return -delta <= difference <= delta + to be considered equal. + """ + delta = abs(delta) + difference = dt1 - dt2 + return -delta <= difference <= delta diff --git a/contrib/python/dateutil/dateutil/zoneinfo/__init__.py b/contrib/python/dateutil/dateutil/zoneinfo/__init__.py index 34f11ad66c..30d8feff16 100644 --- a/contrib/python/dateutil/dateutil/zoneinfo/__init__.py +++ b/contrib/python/dateutil/dateutil/zoneinfo/__init__.py @@ -6,15 +6,15 @@ from tarfile import TarFile from pkgutil import get_data from io import BytesIO -from dateutil.tz import tzfile as _tzfile +from dateutil.tz import tzfile as _tzfile -__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] +__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] ZONEFILENAME = "dateutil-zoneinfo.tar.gz" METADATA_FN = 'METADATA' -class tzfile(_tzfile): +class tzfile(_tzfile): def __reduce__(self): return (gettz, (self._filename,)) @@ -30,15 +30,15 @@ def getzoneinfofile_stream(): class ZoneInfoFile(object): def __init__(self, zonefile_stream=None): if zonefile_stream is not None: - with TarFile.open(fileobj=zonefile_stream) as tf: - self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name) - for zf in tf.getmembers() - if zf.isfile() and zf.name != METADATA_FN} + with TarFile.open(fileobj=zonefile_stream) as tf: + self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name) + for zf in tf.getmembers() + if zf.isfile() and zf.name != METADATA_FN} # deal with links: They'll point to their parent object. Less # waste of memory - links = {zl.name: self.zones[zl.linkname] - for zl in tf.getmembers() if - zl.islnk() or zl.issym()} + links = {zl.name: self.zones[zl.linkname] + for zl in tf.getmembers() if + zl.islnk() or zl.issym()} self.zones.update(links) try: metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) @@ -48,7 +48,7 @@ class ZoneInfoFile(object): # no metadata in tar file self.metadata = None else: - self.zones = {} + self.zones = {} self.metadata = None def get(self, name, default=None): @@ -74,7 +74,7 @@ class ZoneInfoFile(object): # timezone. Ugly, but adheres to the api. # # TODO: Remove after deprecation period. -_CLASS_ZONE_INSTANCE = [] +_CLASS_ZONE_INSTANCE = [] def get_zonefile_instance(new_instance=False): diff --git a/contrib/python/dateutil/dateutil/zoneinfo/rebuild.py b/contrib/python/dateutil/dateutil/zoneinfo/rebuild.py index 684c6586f0..42089d57a2 100644 --- a/contrib/python/dateutil/dateutil/zoneinfo/rebuild.py +++ b/contrib/python/dateutil/dateutil/zoneinfo/rebuild.py @@ -4,22 +4,22 @@ import tempfile import shutil import json from subprocess import check_call, check_output -from tarfile import TarFile +from tarfile import TarFile -from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME +from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* - filename is the timezone tarball from ``ftp.iana.org/tz``. + filename is the timezone tarball from ``ftp.iana.org/tz``. """ tmpdir = tempfile.mkdtemp() zonedir = os.path.join(tmpdir, "zoneinfo") moduledir = os.path.dirname(__file__) try: - with TarFile.open(filename) as tf: + with TarFile.open(filename) as tf: for name in zonegroups: tf.extract(name, tmpdir) filepaths = [os.path.join(tmpdir, n) for n in zonegroups] @@ -30,7 +30,7 @@ def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): with open(os.path.join(zonedir, METADATA_FN), 'w') as f: json.dump(metadata, f, indent=4, sort_keys=True) target = os.path.join(moduledir, ZONEFILENAME) - with TarFile.open(target, "w:%s" % format) as tf: + with TarFile.open(target, "w:%s" % format) as tf: for entry in os.listdir(zonedir): entrypath = os.path.join(zonedir, entry) tf.add(entrypath, entry) diff --git a/contrib/python/dateutil/tests/ya.make b/contrib/python/dateutil/tests/ya.make index 93c811e26e..11fbc851ad 100644 --- a/contrib/python/dateutil/tests/ya.make +++ b/contrib/python/dateutil/tests/ya.make @@ -1,7 +1,7 @@ PY23_TEST() OWNER(g:python-contrib) - + PEERDIR( contrib/python/dateutil contrib/python/freezegun diff --git a/contrib/python/dateutil/ya.make b/contrib/python/dateutil/ya.make index 0c4102c505..4a94ea5b1b 100644 --- a/contrib/python/dateutil/ya.make +++ b/contrib/python/dateutil/ya.make @@ -5,7 +5,7 @@ PY23_LIBRARY() OWNER(g:python-contrib) VERSION(2.8.2) - + LICENSE(BSD-3-Clause) PEERDIR( @@ -25,18 +25,18 @@ PY_SRCS( dateutil/_common.py dateutil/_version.py dateutil/easter.py - dateutil/parser/__init__.py - dateutil/parser/_parser.py - dateutil/parser/isoparser.py + dateutil/parser/__init__.py + dateutil/parser/_parser.py + dateutil/parser/isoparser.py dateutil/relativedelta.py dateutil/rrule.py dateutil/tz/__init__.py dateutil/tz/_common.py - dateutil/tz/_factories.py + dateutil/tz/_factories.py dateutil/tz/tz.py dateutil/tz/win.py dateutil/tzwin.py - dateutil/utils.py + dateutil/utils.py dateutil/zoneinfo/__init__.py dateutil/zoneinfo/rebuild.py ) @@ -46,8 +46,8 @@ RESOURCE_FILES( .dist-info/METADATA .dist-info/top_level.txt dateutil/zoneinfo/dateutil-zoneinfo.tar.gz -) - +) + END() RECURSE_FOR_TESTS( |