🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

python-dateutil

Package Overview
Dependencies
Maintainers
4
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

python-dateutil - pypi Package Compare versions

Comparing version
2.7.5
to
2.8.0
+63
ci_tools/make_zonefile_metadata.py
#!/usr/bin/env python3
import hashlib
ZONEFILE_METADATA_TEMPLATE = """{{
"metadata_version": 2.0,
"releases_url": [],
"tzdata_file": "{tzdata_file}",
"tzdata_file_sha512": "{tzdata_sha512}",
"tzversion": "{tzdata_version}",
"zonegroups": [
"africa",
"antarctica",
"asia",
"australasia",
"europe",
"northamerica",
"southamerica",
"pacificnew",
"etcetera",
"systemv",
"factory",
"backzone",
"backward"
]
}}
"""
def calculate_sha512(fpath):
with open(fpath, 'rb') as f:
sha_hasher = hashlib.sha512()
sha_hasher.update(f.read())
return sha_hasher.hexdigest()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('tzdata', metavar='TZDATA',
help='The name tzdata tarball file')
parser.add_argument('version', metavar='VERSION',
help='The version of the tzdata tarball')
parser.add_argument('out', metavar='OUT', nargs='?',
default='zonefile_metadata.json',
help='Where to write the file')
args = parser.parse_args()
tzdata = args.tzdata
version = args.version
sha512 = calculate_sha512(tzdata)
metadata_file_text = ZONEFILE_METADATA_TEMPLATE.format(
tzdata_file=tzdata,
tzdata_version=version,
tzdata_sha512=sha512,
)
with open(args.out, 'w') as f:
f.write(metadata_file_text)
#!/usr/bin/env bash
###
# Runs the 'tz' tox test environment, which builds the repo against the master
# branch of the upstream tz database project.
set -e
TMP_DIR=${1}
REPO_DIR=${2}
ORIG_DIR=$(pwd)
CITOOLS_DIR=$REPO_DIR/ci_tools
REPO_TARBALL=${REPO_DIR}/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz
TMP_TARBALL=${TMP_DIR}/dateutil-zoneinfo.tar.gz
UPSTREAM_URL="https://github.com/eggert/tz.git"
function cleanup {
# Since this script modifies the original repo, whether or not
# it fails we need to restore the original file so as to not
# overwrite the user's local changes.
echo "Cleaning up."
if [ -f $TMP_TARBALL ]; then
cp -p $TMP_TARBALL $REPO_TARBALL
fi
}
trap cleanup EXIT
# Work in a temporary directory
cd $TMP_DIR
# Clone or update the repo
DIR_EXISTS=false
if [ -d tz ]; then
cd tz
if [[ $(git remote get-url origin) == ${UPSTREAM_URL} ]]; then
git fetch origin master
git reset --hard origin/master
DIR_EXISTS=true
else
cd ..
rm -rf tz
fi
fi
if [ "$DIR_EXISTS" = false ]; then
git clone ${UPSTREAM_URL}
cd tz
fi
# Get the version
make version
VERSION=$(cat version)
TARBALL_NAME=tzdata${VERSION}.tar.gz
# Make the tzdata tarball - deactivate errors because
# I don't know how to make just the .tar.gz and I don't
# care if the others fail
set +e
make traditional_tarballs
set -e
mv $TARBALL_NAME $ORIG_DIR
# Install everything else
make TOPDIR=$TMP_DIR/tzdir install
#
# Make the zoneinfo tarball
#
cd $ORIG_DIR
# Put the latest version of zic on the path
PATH=$TMP_DIR/tzdir/usr/sbin:${PATH}
# Stash the old zoneinfo file in the temporary directory
mv $REPO_TARBALL $TMP_TARBALL
# Make the metadata file
ZONEFILE_METADATA_NAME=zonefile_metadata_master.json
${CITOOLS_DIR}/make_zonefile_metadata.py \
$TARBALL_NAME \
$VERSION \
$ZONEFILE_METADATA_NAME
python ${REPO_DIR}/updatezinfo.py $ZONEFILE_METADATA_NAME
# Run the tests
python -m pytest ${REPO_DIR}/dateutil/test
import os
import pytest
# Configure pytest to ignore xfailing tests
# See: https://stackoverflow.com/a/53198349/467366
def pytest_collection_modifyitems(items):
for item in items:
marker_getter = getattr(item, 'get_closest_marker', None)
# Python 3.3 support
if marker_getter is None:
marker_getter = item.get_marker
marker = marker_getter('xfail')
# Need to query the args because conditional xfail tests still have
# the xfail mark even if they are not expected to fail
if marker and (not marker.args or marker.args[0]):
item.add_marker(pytest.mark.no_cover)
def set_tzpath():
"""
Sets the TZPATH variable if it's specified in an environment variable.
"""
tzpath = os.environ.get('DATEUTIL_TZPATH', None)
if tzpath is None:
return
path_components = tzpath.split(':')
print("Setting TZPATH to {}".format(path_components))
from dateutil import tz
tz.TZPATHS.clear()
tz.TZPATHS.extend(path_components)
set_tzpath()
Exercises
=========
It is often useful to work through some examples in order to understand how a module works; on this page, there are several exercises of varying difficulty that you can use to learn how to use ``dateutil``.
If you are interested in helping improve the documentation of ``dateutil``, it is recommended that you attempt to complete these exercises with no resources *other than dateutil's documentation*. If you find that the documentation is not clear enough to allow you to complete these exercises, open an issue on the `dateutil issue tracker <https://github.com/dateutil/dateutil/issues>`_ to let the developers know what part of the documentation needs improvement.
.. contents:: Table of Contents
:backlinks: top
:local:
.. _mlk-day-exercise:
Martin Luther King Day
--------------------------------
`Martin Luther King, Jr Day <https://en.wikipedia.org/wiki/Martin_Luther_King_Jr._Day>`_ is a US holiday that occurs every year on the third Monday in January?
How would you generate a `recurrence rule <../rrule.html>`_ that generates Martin Luther King Day, starting from its first observance in 1986?
**Test Script**
To solve this exercise, copy-paste this script into a document, change anything between the ``--- YOUR CODE ---`` comment blocks.
.. raw:: html
<details>
.. code-block:: python3
# ------- YOUR CODE -------------#
from dateutil import rrule
MLK_DAY = <<YOUR CODE HERE>>
# -------------------------------#
from datetime import datetime
MLK_TEST_CASES = [
((datetime(1970, 1, 1), datetime(1980, 1, 1)),
[]),
((datetime(1980, 1, 1), datetime(1989, 1, 1)),
[datetime(1986, 1, 20),
datetime(1987, 1, 19),
datetime(1988, 1, 18)]),
((datetime(2017, 2, 1), datetime(2022, 2, 1)),
[datetime(2018, 1, 15, 0, 0),
datetime(2019, 1, 21, 0, 0),
datetime(2020, 1, 20, 0, 0),
datetime(2021, 1, 18, 0, 0),
datetime(2022, 1, 17, 0, 0)]
),
]
def test_mlk_day():
for (between_args, expected) in MLK_TEST_CASES:
assert MLK_DAY.between(*between_args) == expected
if __name__ == "__main__":
test_mlk_day()
print('Success!')
.. raw:: html
</details>
A solution to this problem is provided :doc:`here <solutions/mlk-day-rrule>`.
Next Monday meeting
-------------------
A team has a meeting at 10 AM every Monday and wants a function that tells them, given a ``datetime.datetime`` object, what is the date and time of the *next* Monday meeting? This is probably best accomplished using a `relativedelta <../relativedelta.html>`_.
**Test Script**
To solve this exercise, copy-paste this script into a document, change anything between the ``--- YOUR CODE ---`` comment blocks.
.. raw:: html
<details>
.. code-block:: python3
# --------- YOUR CODE -------------- #
from dateutil import relativedelta
def next_monday(dt):
<<YOUR CODE HERE>>
# ---------------------------------- #
from datetime import datetime
from dateutil import tz
NEXT_MONDAY_CASES = [
(datetime(2018, 4, 11, 14, 30, 15, 123456),
datetime(2018, 4, 16, 10, 0)),
(datetime(2018, 4, 16, 10, 0),
datetime(2018, 4, 16, 10, 0)),
(datetime(2018, 4, 16, 10, 30),
datetime(2018, 4, 23, 10, 0)),
(datetime(2018, 4, 14, 9, 30, tzinfo=tz.gettz('America/New_York')),
datetime(2018, 4, 16, 10, 0, tzinfo=tz.gettz('America/New_York'))),
]
def test_next_monday_1():
for dt_in, dt_out in NEXT_MONDAY_CASES:
assert next_monday(dt_in) == dt_out
if __name__ == "__main__":
test_next_monday_1()
print('Success!')
.. raw:: html
</details>
Parsing a local tzname
----------------------
Three-character time zone abbreviations are *not* unique in that they do not explicitly map to a time zone. A list of time zone abbreviations in use can be found `here <https://www.timeanddate.com/time/zones/>`_. This means that parsing a datetime string such as ``'2018-01-01 12:30:30 CST'`` is ambiguous without context. Using `dateutil.parser <../parser.html>`_ and `dateutil.tz <../tz.html>`_, it is possible to provide a context such that these local names are converted to proper time zones.
Problem 1
*********
Given the context that you will only be parsing dates coming from the continental United States, India and Japan, write a function that parses a datetime string and returns a timezone-aware ``datetime`` with an IANA-style timezone attached.
Note: For the purposes of the experiment, you may ignore the portions of the United States like Arizona and parts of Indiana that do not observe daylight saving time.
**Test Script**
To solve this exercise, copy-paste this script into a document, change anything between the ``--- YOUR CODE ---`` comment blocks.
.. raw:: html
<details>
.. code-block:: python3
# --------- YOUR CODE -------------- #
from dateutil.parser import parse
from dateutil import tz
def parse_func_us_jp_ind():
<<YOUR CODE HERE>>
# ---------------------------------- #
from dateutil import tz
from datetime import datetime
PARSE_TZ_TEST_DATETIMES = [
datetime(2018, 1, 1, 12, 0),
datetime(2018, 3, 20, 2, 0),
datetime(2018, 5, 12, 3, 30),
datetime(2014, 9, 1, 23)
]
PARSE_TZ_TEST_ZONES = [
tz.gettz('America/New_York'),
tz.gettz('America/Chicago'),
tz.gettz('America/Denver'),
tz.gettz('America/Los_Angeles'),
tz.gettz('Asia/Kolkata'),
tz.gettz('Asia/Tokyo'),
]
def test_parse():
for tzi in PARSE_TZ_TEST_ZONES:
for dt in PARSE_TZ_TEST_DATETIMES:
dt_exp = dt.replace(tzinfo=tzi)
dtstr = dt_exp.strftime('%Y-%m-%d %H:%M:%S %Z')
dt_act = parse_func_us_jp_ind(dtstr)
assert dt_act == dt_exp
assert dt_act.tzinfo is dt_exp.tzinfo
if __name__ == "__main__":
test_parse()
print('Success!')
.. raw:: html
</details>
Problem 2
*********
Given the context that you will *only* be passed dates from India or Ireland, write a function that correctly parses all *unambiguous* time zone strings to aware datetimes localized to the correct IANA zone, and for *ambiguous* time zone strings default to India.
**Test Script**
To solve this exercise, copy-paste this script into a document, change anything between the ``--- YOUR CODE ---`` comment blocks.
.. raw:: html
<details>
.. code-block:: python3
# --------- YOUR CODE -------------- #
from dateutil.parser import parse
from dateutil import tz
def parse_func_ind_ire():
<<YOUR CODE HERE>>
# ---------------------------------- #
ISRAEL = tz.gettz('Asia/Jerusalem')
INDIA = tz.gettz('Asia/Kolkata')
PARSE_IXT_TEST_CASE = [
('2018-02-03 12:00 IST+02:00', datetime(2018, 2, 3, 12, tzinfo=ISRAEL)),
('2018-06-14 12:00 IDT+03:00', datetime(2018, 6, 14, 12, tzinfo=ISRAEL)),
('2018-06-14 12:00 IST', datetime(2018, 6, 14, 12, tzinfo=INDIA)),
('2018-06-14 12:00 IST+05:30', datetime(2018, 6, 14, 12, tzinfo=INDIA)),
('2018-02-03 12:00 IST', datetime(2018, 2, 3, 12, tzinfo=INDIA)),
]
def test_parse_ixt():
for dtstr, dt_exp in PARSE_IXT_TEST_CASE:
dt_act = parse_func_ind_ire(dtstr)
assert dt_act == dt_exp, (dt_act, dt_exp)
assert dt_act.tzinfo is dt_exp.tzinfo, (dt_act, dt_exp)
if __name__ == "__main__":
test_parse_ixt()
print('Success!')
.. raw:: html
</details>
# ------- YOUR CODE -------------#
from dateutil import rrule
from datetime import datetime
MLK_DAY = rrule.rrule(
dtstart=datetime(1986, 1, 20), # First celebration
freq=rrule.YEARLY, # Occurs once per year
bymonth=1, # In January
byweekday=rrule.MO(+3), # On the 3rd Monday
)
# -------------------------------#
from datetime import datetime
MLK_TEST_CASES = [
((datetime(1970, 1, 1), datetime(1980, 1, 1)),
[]),
((datetime(1980, 1, 1), datetime(1989, 1, 1)),
[datetime(1986, 1, 20),
datetime(1987, 1, 19),
datetime(1988, 1, 18)]),
((datetime(2017, 2, 1), datetime(2022, 2, 1)),
[datetime(2018, 1, 15, 0, 0),
datetime(2019, 1, 21, 0, 0),
datetime(2020, 1, 20, 0, 0),
datetime(2021, 1, 18, 0, 0),
datetime(2022, 1, 17, 0, 0)]
),
]
def test_mlk_day():
for (between_args, expected) in MLK_TEST_CASES:
assert MLK_DAY.between(*between_args) == expected
if __name__ == "__main__":
test_mlk_day()
print('Success!')
:orphan:
Martin Luther King Day: Solution
================================
Presented here is a solution to the :ref:`Martin Luther King Day exercises <mlk-day-exercise>`.
.. include:: mlk_day_rrule_solution.py
:code: python3
======
tz.win
======
.. py:currentmodule:: dateutil.tz.win
.. automodule:: dateutil.tz.win
Classes
-------
.. autoclass:: tzres
:members:
.. autoclass:: tzwin
:members: list, display, transitions
:undoc-members:
.. autoclass:: tzwinlocal
:members: display, transitions
:undoc-members:
+10
-1

@@ -21,2 +21,9 @@ sudo: false

env: TOXENV=docs
- python: 3.6
env: TOXENV=tz
- python: 3.7
# This is required until Travis has a default image that
# can run Python 3.7
dist: xenial
sudo: required
allow_failures:

@@ -27,3 +34,5 @@ - python: "nightly"

- pip install -U six && pip install -U tox
- ./ci_tools/retry.sh python updatezinfo.py
- if [[ $TRAVIS_PYTHON_VERSION == "3.3" ]]; then pip install 'virtualenv<16.0'; fi
- if [[ $TRAVIS_PYTHON_VERSION == "3.3" ]]; then pip install 'setuptools<40.0'; fi
- if [[ $TOXENV == "py" ]]; then ./ci_tools/retry.sh python updatezinfo.py; fi

@@ -30,0 +39,0 @@ script:

+28
-12

@@ -20,6 +20,9 @@ This is a (possibly incomplete) list of all the contributors to python-dateutil,

- Alex Verdyan <verdyan@MASKED>
- Alex Willmer <alex@MASKED> (gh: @moreati)
- Alex Willmer <alex@moreati.org.uk> (gh: @moreati) **R**
- Alexander Brugh <alexander.brugh@MASKED> (gh: @abrugh)
- Alistair McMaster <alistair@MASKED> (gh: @alimcmaster1 ) **D**
- Andrew Bennett (gh: @andrewcbennett) **D**
- Andrew Murray <radarhere@MASKED>
- Bernat Gabor <bgabor8@bloomberg.net> (gh: @gaborbernat) **D**
- Bradlee Speice <bradlee@speice.io> (gh: @bspeice) **D**
- Brandon W Maister <quodlibetor@MASKED>

@@ -29,14 +32,19 @@ - Brock Mendel <jbrockmendel@MASKED> (gh: @jbrockmendel) **R**

- Carlos <carlosxl@MASKED>
- Cheuk Ting Ho <cheukting.ho@gmail.com> (gh: @cheukting) **D**
- Chris van den Berg (gh: bergvca) **D**
- Christopher Cordero <ccordero@pm.me> (gh: cs-cordero) **D**
- Christopher Corley <cscorley@MASKED>
- Claudio Canepa <ccanepacc@MASKED>
- Corey Girard <corey.r.girard@gmail.com> (gh: @coreygirard)
- Corey Girard <corey.r.girard@gmail.com> (gh: @coreygirard) **D**
- Cosimo Lupo <cosimo@anthrotype.com> (gh: @anthrotype) **D**
- Daniel Lepage <dplepage@MASKED>
- David Lehrian <david@MASKED>
- Dean Allsopp (gh: @daplantagenet) **D**
- Dominik Kozaczko <dominik@MASKED>
- Elliot Hughes <elliot.hughes@gmail.com> (gh: @ElliotJH) **D**
- Elvis Pranskevichus <el@MASKED>
- Florian Rathgeber (gh: @kynan)
- Florian Rathgeber (gh: @kynan) **D**
- Gabriel Bianconi <gabriel@MASKED> (gh: @GabrielBianconi) **D**
- Gabriel Poesia <gabriel.poesia@MASKED>
- Gökçen Nurlu <gnurlu1@bloomberg.net> (gh: @gokcennurlu) **D**
- Gustavo Niemeyer <gustavo@niemeyer.net> (gh: @niemeyer)

@@ -48,10 +56,13 @@ - Holger Joukl <holger.joukl@MASKED> (gh: @hjoukl)

- Jan Studený <jendas1@MASKED>
- Jay Weisskopf <jay@jayschwa.net> (gh: @jayschwa) **D**
- Jitesh <jitesh@MASKED>
- John Purviance <jpurviance@MASKED> (gh @jpurviance) **D**
- Jon Dufresne <jon.dufresne@MASKED> (gh: @jdufresne) **R**
- Jonas Neubert <jonas@MASKED>
- Kevin Nguyen <kvn219@MASKED>
- Jonas Neubert <jonas@MASKED> (gh: @jonemo) **R**
- Kevin Nguyen <kvn219@MASKED> **D**
- Kirit Thadaka <kirit.thadaka@gmail.com> (gh: @kirit93) **D**
- Kubilay Kocak <koobs@MASKED>
- Laszlo Kiss Kollar <kiss.kollar.laszlo@MASKED> (gh: @lkollar) **D**
- Lauren Oldja <oldja@MASKED> (gh: @loldja)
- Lauren Oldja <oldja@MASKED> (gh: @loldja) **D**
- Luca Ferocino <luca.ferox@MASKED> (gh: @lucaferocino) **D**
- Mario Corchero <mcorcherojim@MASKED> (gh: @mariocj89) **R**

@@ -65,9 +76,12 @@ - Mateusz Dziedzic (gh: @m-dz) **D**

- Mike Gilbert <floppym@MASKED>
- Nicholas Herrriot <Nicholas.Herriot@gmail.com> **D**
- Nicolas Évrard (gh: @nicoe) **D**
- Nick Smith <nick.smith@MASKED>
- Orson Adams <orson.network@MASKED> (gh: @parsethis)
- Orson Adams <orson.network@MASKED> (gh: @parsethis) **D**
- Paul Dickson (gh @prdickson) **D**
- Paul Ganssle <paul@ganssle.io> (gh: @pganssle) **R**
- Pascal van Kooten <kootenpv@MASKED>
- Pascal van Kooten <kootenpv@MASKED> (gh: @kootenpv) **R**
- Pavel Ponomarev <comrad.awsum@MASKED>
- Peter Bieringer <pb@MASKED>
- Pierre Gergondet <pierre.gergondet@MASKED> (gh: @gergondet)
- Pierre Gergondet <pierre.gergondet@MASKED> (gh: @gergondet) **D**
- Quentin Pradet <quentin@MASKED>

@@ -78,7 +92,9 @@ - Raymond Cha (gh: @weatherpattern) **D**

- Rustem Saiargaliev (gh: @amureki) **D**
- Satyabrat Bhol <satyabrat35@MASKED> (gh: @Satyabrat35) **D**
- Savraj <savraj@MASKED>
- Sergey Vishnikin <armicron@MASKED>
- Sherry Zhou (gh: @cssherry) **D**
- Stefan Bonchev **D**
- Thierry Bastian <thierryb@MASKED>
- Thomas A Caswell <tcaswell@MASKED> (gh: @tacaswell)
- Thomas A Caswell <tcaswell@MASKED> (gh: @tacaswell) **R**
- Thomas Achtemichuk <tom@MASKED>

@@ -98,6 +114,6 @@ - Thomas Kluyver <takowl@MASKED> (gh: @takluyver)

- gl <gl@MASKED>
- labrys <labrys@MASKED> (gh: @labrys)
- labrys <labrys@MASKED> (gh: @labrys) **R**
- ms-boom <ms-boom@MASKED>
- ryanss <ryanssdev@MASKED> (gh: @ryanss)
- ryanss <ryanssdev@MASKED> (gh: @ryanss) **R**
Unless someone has deliberately given permission to publish their e-mail, I have masked the domain names. If you are not on this list and believe you should be, or you *are* on this list and your information is inaccurate, please e-mail the current maintainer or the mailing list (dateutil@python.org) with your name, e-mail (if desired) and github (if desired / applicable), as you would like them displayed. Additionally, please indicate if you are willing to dual license your old contributions under Apache 2.0.

@@ -31,3 +31,3 @@ # Contributing

#### Changelog
To keep users abreast of the changes to the module and to give proper credit, `dateutil` maintains a changelog, which is managed by [towncrier](https://github.com/hawkowl/towncrier). To add a changelog entry, make a new file called `<issue_no>.<type>.rst`, where `<issue_no>` is the number of the PR you've just made (it's easiest to add the changelog *after* you've created the PR so you'll have this number), and `<type>` is one of the following types:
To keep users abreast of the changes to the module and to give proper credit, `dateutil` maintains a changelog, which is managed by [towncrier](https://github.com/hawkowl/towncrier). To add a changelog entry, make a new file called `<issue_no>.<type>.rst` in the `changelog.d` directory, where `<issue_no>` is the number of the PR you've just made (it's easiest to add the changelog *after* you've created the PR so you'll have this number), and `<type>` is one of the following types:

@@ -34,0 +34,0 @@ - `feature`: A new feature, (e.g. a new function, method, attribute, etc)

# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
version = '2.7.5'
version = '2.8.0'

@@ -43,3 +43,3 @@ # -*- coding: utf-8 -*-

import six
from six import binary_type, integer_types, text_type
from six import integer_types, text_type

@@ -67,3 +67,3 @@ from decimal import Decimal

# a 'decode' function, and we'd be double-decoding
if isinstance(instream, (binary_type, bytearray)):
if isinstance(instream, (bytes, bytearray)):
instream = instream.decode()

@@ -296,3 +296,3 @@ else:

("pm", "p")]
UTCZONE = ["UTC", "GMT", "Z"]
UTCZONE = ["UTC", "GMT", "Z", "z"]
PERTAIN = ["of"]

@@ -394,3 +394,4 @@ TZOFFSET = {}

if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
if ((res.tzoffset == 0 and not res.tzname) or
(res.tzname == 'Z' or res.tzname == 'z')):
res.tzname = "UTC"

@@ -1067,3 +1068,4 @@ res.tzoffset = 0

len(token) <= 5 and
all(x in string.ascii_uppercase for x in token))
(all(x in string.ascii_uppercase for x in token)
or token in self.info.UTCZONE))

@@ -1070,0 +1072,0 @@ def _ampm_valid(self, hour, ampm, fuzzy):

@@ -91,7 +91,9 @@ # -*- coding: utf-8 -*-

- ``hh:mm:ss`` or ``hhmmss``
- ``hh:mm:ss.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits)
- ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits)
Midnight is a special case for `hh`, as the standard supports both
00:00 and 24:00 as a representation.
00:00 and 24:00 as a representation. The decimal separator can be
either a dot or a comma.
.. caution::

@@ -141,2 +143,6 @@

if len(components) > 3 and components[3] == 24:
components[3] = 0
return datetime(*components) + timedelta(days=1)
return datetime(*components)

@@ -172,3 +178,6 @@

"""
return time(*self._parse_isotime(timestr))
components = self._parse_isotime(timestr)
if components[0] == 24:
components[0] = 0
return time(*components)

@@ -196,6 +205,5 @@ @_takes_ascii

# Constants
_MICROSECOND_END_REGEX = re.compile(b'[-+Z]+')
_DATE_SEP = b'-'
_TIME_SEP = b':'
_MICRO_SEP = b'.'
_FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)')

@@ -340,3 +348,3 @@ def _parse_isodate(self, dt_str):

if timestr[pos:pos + 1] in b'-+Z':
if timestr[pos:pos + 1] in b'-+Zz':
# Detect time zone boundary

@@ -356,12 +364,10 @@ components[-1] = self._parse_tzstr(timestr[pos:])

if comp == 3:
# Microsecond
if timestr[pos:pos + 1] != self._MICRO_SEP:
# Fraction of a second
frac = self._FRACTION_REGEX.match(timestr[pos:])
if not frac:
continue
pos += 1
us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6],
1)[0]
us_str = frac.group(1)[:6] # Truncate to microseconds
components[comp] = int(us_str) * 10**(6 - len(us_str))
pos += len(us_str)
pos += len(frac.group())

@@ -375,3 +381,2 @@ if pos < len_str:

raise ValueError('Hour may only be 24 at 24:00:00.000')
components[0] = 0

@@ -381,3 +386,3 @@ return components

def _parse_tzstr(self, tzstr, zero_as_utc=True):
if tzstr == b'Z':
if tzstr == b'Z' or tzstr == b'z':
return tz.tzutc()

@@ -384,0 +389,0 @@

@@ -20,4 +20,8 @@ # -*- coding: utf-8 -*-

"""
The relativedelta type is based on the specification of the excellent
work done by M.-A. Lemburg in his
The relativedelta type is designed to be applied to an existing datetime and
can replace specific components of that datetime, or represents an interval
of time.
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.

@@ -49,9 +53,11 @@ However, notice that this type does *NOT* implement the same algorithm as

weekday:
One of the weekday instances (MO, TU, etc). These
instances may receive a parameter N, specifying the Nth
weekday, which could be positive or negative (like MO(+1)
or MO(-2). Not specifying it is the same as specifying
+1. You can also use an integer, where 0=MO. Notice that
if the calculated date is already Monday, for example,
using MO(1) or MO(-1) won't change the day.
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
(like MO(+1) or MO(-2)). Not specifying it is the same as specifying
+1. You can also use an integer, where 0=MO. This argument is always
relative e.g. if the calculated date is already Monday, using MO(1)
or MO(-1) won't change the day. To effectively make it absolute, use
it in combination with the day argument (e.g. day=1, MO(1) for first
Monday of the month).

@@ -87,5 +93,8 @@ leapdays:

>>> 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))
datetime(2018, 4, 2, 14, 37, 0)
>>> dt + delta
datetime.datetime(2018, 4, 2, 14, 37)

@@ -282,3 +291,3 @@ First, the day is set to 1 (the first of the month), then 25 hours

>>> relativedelta(days=1.5, hours=2).normalized()
relativedelta(days=1, hours=14)
relativedelta(days=+1, hours=+14)

@@ -285,0 +294,0 @@ :return:

@@ -5,3 +5,3 @@ from dateutil.easter import easter

from datetime import date
import unittest
import pytest

@@ -77,21 +77,19 @@ # List of easters between 1990 and 2050

class EasterTest(unittest.TestCase):
def testEasterWestern(self):
for easter_date in western_easter_dates:
self.assertEqual(easter_date,
easter(easter_date.year, EASTER_WESTERN))
@pytest.mark.parametrize("easter_date", western_easter_dates)
def test_easter_western(easter_date):
assert easter_date == easter(easter_date.year, EASTER_WESTERN)
def testEasterOrthodox(self):
for easter_date in orthodox_easter_dates:
self.assertEqual(easter_date,
easter(easter_date.year, EASTER_ORTHODOX))
def testEasterJulian(self):
for easter_date in julian_easter_dates:
self.assertEqual(easter_date,
easter(easter_date.year, EASTER_JULIAN))
@pytest.mark.parametrize("easter_date", orthodox_easter_dates)
def test_easter_orthodox(easter_date):
assert easter_date == easter(easter_date.year, EASTER_ORTHODOX)
def testEasterBadMethod(self):
# Invalid methods raise ValueError
with self.assertRaises(ValueError):
easter(1975, 4)
@pytest.mark.parametrize("easter_date", julian_easter_dates)
def test_easter_julian(easter_date):
assert easter_date == easter(easter_date.year, EASTER_JULIAN)
def test_easter_bad_method():
with pytest.raises(ValueError):
easter(1975, 4)
"""Test for the "import *" functionality.
As imort * can be only done at module level, it has been added in a separate file
As import * can be only done at module level, it has been added in a separate file
"""
import unittest
import pytest

@@ -13,22 +13,22 @@ prev_locals = list(locals())

class ImportStarTest(unittest.TestCase):
""" Test that `from dateutil import *` adds the modules in __all__ locally"""
def testImportedModules(self):
import dateutil.easter
import dateutil.parser
import dateutil.relativedelta
import dateutil.rrule
import dateutil.tz
import dateutil.utils
import dateutil.zoneinfo
@pytest.mark.import_star
def test_imported_modules():
""" Test that `from dateutil import *` adds modules in __all__ locally """
import dateutil.easter
import dateutil.parser
import dateutil.relativedelta
import dateutil.rrule
import dateutil.tz
import dateutil.utils
import dateutil.zoneinfo
self.assertEquals(dateutil.easter, new_locals.pop("easter"))
self.assertEquals(dateutil.parser, new_locals.pop("parser"))
self.assertEquals(dateutil.relativedelta, new_locals.pop("relativedelta"))
self.assertEquals(dateutil.rrule, new_locals.pop("rrule"))
self.assertEquals(dateutil.tz, new_locals.pop("tz"))
self.assertEquals(dateutil.utils, new_locals.pop("utils"))
self.assertEquals(dateutil.zoneinfo, new_locals.pop("zoneinfo"))
assert dateutil.easter == new_locals.pop("easter")
assert dateutil.parser == new_locals.pop("parser")
assert dateutil.relativedelta == new_locals.pop("relativedelta")
assert dateutil.rrule == new_locals.pop("rrule")
assert dateutil.tz == new_locals.pop("tz")
assert dateutil.utils == new_locals.pop("utils")
assert dateutil.zoneinfo == new_locals.pop("zoneinfo")
self.assertFalse(new_locals)
assert not new_locals

@@ -123,3 +123,4 @@ # -*- coding: utf-8 -*-

@pytest.mark.parametrize('date_fmt', YMD_FMTS)
@pytest.mark.parametrize('time_fmt', (x + '.%f' for x in HMS_FMTS))
@pytest.mark.parametrize('time_fmt', (x + sep + '%f' for x in HMS_FMTS
for sep in '.,'))
@pytest.mark.parametrize('tzoffset', TZOFFSETS)

@@ -133,2 +134,11 @@ @pytest.mark.parametrize('precision', list(range(3, 7)))

###
# Truncation of extra digits beyond microsecond precision
@pytest.mark.parametrize('dt_str', [
'2018-07-03T14:07:00.123456000001',
'2018-07-03T14:07:00.123456999999',
])
def test_extra_subsecond_digits(dt_str):
assert isoparse(dt_str) == datetime(2018, 7, 3, 14, 7, 0, 123456)
@pytest.mark.parametrize('tzoffset', FULL_TZOFFSETS)

@@ -144,11 +154,11 @@ def test_full_tzoffsets(tzoffset):

'2014-04-11T00',
'2014-04-11T24',
'2014-04-10T24',
'2014-04-11T00:00',
'2014-04-11T24:00',
'2014-04-10T24:00',
'2014-04-11T00:00:00',
'2014-04-11T24:00:00',
'2014-04-10T24:00:00',
'2014-04-11T00:00:00.000',
'2014-04-11T24:00:00.000',
'2014-04-10T24:00:00.000',
'2014-04-11T00:00:00.000000',
'2014-04-11T24:00:00.000000']
'2014-04-10T24:00:00.000000']
)

@@ -224,2 +234,4 @@ def test_datetime_midnight(dt_str):

tz.tzutc())),
(b'2014-02-04T12:30:15.224z', datetime(2014, 2, 4, 12, 30, 15, 224000,
tz.tzutc())),
(b'2014-02-04T12:30:15.224+05:00',

@@ -270,8 +282,11 @@ datetime(2014, 2, 4, 12, 30, 15, 224000,

@pytest.mark.parametrize('sep_act,valid_sep', [
('C', 'T'),
('T', 'C')
@pytest.mark.parametrize('sep_act, valid_sep, exception', [
('T', 'C', ValueError),
('C', 'T', ValueError),
])
def test_iso_raises_sep(sep_act, valid_sep):
def test_iso_with_sep_raises(sep_act, valid_sep, exception):
parser = isoparser(sep=valid_sep)
isostr = '2012-04-25' + sep_act + '01:25:00'
with pytest.raises(exception):
parser.isoparse(isostr)

@@ -375,3 +390,3 @@

d_str = d_str.encode('ascii')
elif isinstance(d_str, six.binary_type) and not as_bytes:
elif isinstance(d_str, bytes) and not as_bytes:
d_str = d_str.decode('ascii')

@@ -450,3 +465,3 @@

tstr = tstr.encode('ascii')
elif isinstance(time_val, six.binary_type) and not as_bytes:
elif isinstance(time_val, bytes) and not as_bytes:
tstr = tstr.decode('ascii')

@@ -458,2 +473,18 @@

@pytest.mark.parametrize('isostr', [
'24:00',
'2400',
'24:00:00',
'240000',
'24:00:00.000',
'24:00:00,000',
'24:00:00.000000',
'24:00:00,000000',
])
def test_isotime_midnight(isostr):
iparser = isoparser()
assert iparser.parse_isotime(isostr) == time(0, 0, 0, 0)
@pytest.mark.parametrize('isostr,exception', [

@@ -464,3 +495,2 @@ ('3', ValueError), # ISO string too short

('1430:15', ValueError), # Inconsistent separator use
('14:30:15.3684000309', ValueError), # Too much us precision
('25', ValueError), # Invalid hours

@@ -470,3 +500,3 @@ ('25:15', ValueError), # Invalid hours

('14:59:61', ValueError), # Invalid seconds
('14:30:15.3446830500', ValueError), # No sign in time zone
('14:30:15.34468305:00', ValueError), # No sign in time zone
('14:30:15+', ValueError), # Time zone too short

@@ -477,2 +507,6 @@ ('14:30:15+1234567', ValueError), # Time zone invalid

('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
])

@@ -479,0 +513,0 @@ def test_isotime_raises(isostr, exception):

@@ -17,3 +17,3 @@ # -*- coding: utf-8 -*-

from six import assertRaisesRegex, PY3
from six.moves import StringIO
from io import StringIO

@@ -31,3 +31,127 @@ import pytest

# Parser test cases using no keyword arguments. Format: (parsable_text, expected_datetime, assertion_message)
PARSER_TEST_CASES = [
("Thu Sep 25 10:36:28 2003", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
("Thu Sep 25 2003", datetime(2003, 9, 25), "date command format strip"),
("2003-09-25T10:49:41", datetime(2003, 9, 25, 10, 49, 41), "iso format strip"),
("2003-09-25T10:49", datetime(2003, 9, 25, 10, 49), "iso format strip"),
("2003-09-25T10", datetime(2003, 9, 25, 10), "iso format strip"),
("2003-09-25", datetime(2003, 9, 25), "iso format strip"),
("20030925T104941", datetime(2003, 9, 25, 10, 49, 41), "iso stripped format strip"),
("20030925T1049", datetime(2003, 9, 25, 10, 49, 0), "iso stripped format strip"),
("20030925T10", datetime(2003, 9, 25, 10), "iso stripped format strip"),
("20030925", datetime(2003, 9, 25), "iso stripped format strip"),
("2003-09-25 10:49:41,502", datetime(2003, 9, 25, 10, 49, 41, 502000), "python logger format"),
("199709020908", datetime(1997, 9, 2, 9, 8), "no separator"),
("19970902090807", datetime(1997, 9, 2, 9, 8, 7), "no separator"),
("2003-09-25", datetime(2003, 9, 25), "date with dash"),
("09-25-2003", datetime(2003, 9, 25), "date with dash"),
("25-09-2003", datetime(2003, 9, 25), "date with dash"),
("10-09-2003", datetime(2003, 10, 9), "date with dash"),
("10-09-03", datetime(2003, 10, 9), "date with dash"),
("2003.09.25", datetime(2003, 9, 25), "date with dot"),
("09.25.2003", datetime(2003, 9, 25), "date with dot"),
("25.09.2003", datetime(2003, 9, 25), "date with dot"),
("10.09.2003", datetime(2003, 10, 9), "date with dot"),
("10.09.03", datetime(2003, 10, 9), "date with dot"),
("2003/09/25", datetime(2003, 9, 25), "date with slash"),
("09/25/2003", datetime(2003, 9, 25), "date with slash"),
("25/09/2003", datetime(2003, 9, 25), "date with slash"),
("10/09/2003", datetime(2003, 10, 9), "date with slash"),
("10/09/03", datetime(2003, 10, 9), "date with slash"),
("2003 09 25", datetime(2003, 9, 25), "date with space"),
("09 25 2003", datetime(2003, 9, 25), "date with space"),
("25 09 2003", datetime(2003, 9, 25), "date with space"),
("10 09 2003", datetime(2003, 10, 9), "date with space"),
("10 09 03", datetime(2003, 10, 9), "date with space"),
("25 09 03", datetime(2003, 9, 25), "date with space"),
("03 25 Sep", datetime(2003, 9, 25), "strangely ordered date"),
("25 03 Sep", datetime(2025, 9, 3), "strangely ordered date"),
(" July 4 , 1976 12:01:02 am ", datetime(1976, 7, 4, 0, 1, 2), "extra space"),
("Wed, July 10, '96", datetime(1996, 7, 10, 0, 0), "random format"),
("1996.July.10 AD 12:08 PM", datetime(1996, 7, 10, 12, 8), "random format"),
("July 4, 1976", datetime(1976, 7, 4), "random format"),
("7 4 1976", datetime(1976, 7, 4), "random format"),
("4 jul 1976", datetime(1976, 7, 4), "random format"),
("7-4-76", datetime(1976, 7, 4), "random format"),
("19760704", datetime(1976, 7, 4), "random format"),
("0:01:02 on July 4, 1976", datetime(1976, 7, 4, 0, 1, 2), "random format"),
("0:01:02 on July 4, 1976", datetime(1976, 7, 4, 0, 1, 2), "random format"),
("July 4, 1976 12:01:02 am", datetime(1976, 7, 4, 0, 1, 2), "random format"),
("Mon Jan 2 04:24:27 1995", datetime(1995, 1, 2, 4, 24, 27), "random format"),
("04.04.95 00:22", datetime(1995, 4, 4, 0, 22), "random format"),
("Jan 1 1999 11:23:34.578", datetime(1999, 1, 1, 11, 23, 34, 578000), "random format"),
("950404 122212", datetime(1995, 4, 4, 12, 22, 12), "random format"),
("3rd of May 2001", datetime(2001, 5, 3), "random format"),
("5th of March 2001", datetime(2001, 3, 5), "random format"),
("1st of May 2003", datetime(2003, 5, 1), "random format"),
('0099-01-01T00:00:00', datetime(99, 1, 1, 0, 0), "99 ad"),
('0031-01-01T00:00:00', datetime(31, 1, 1, 0, 0), "31 ad"),
("20080227T21:26:01.123456789", datetime(2008, 2, 27, 21, 26, 1, 123456), "high precision seconds"),
('13NOV2017', datetime(2017, 11, 13), "dBY (See GH360)"),
('0003-03-04', datetime(3, 3, 4), "pre 12 year same month (See GH PR #293)"),
('December.0031.30', datetime(31, 12, 30), "BYd corner case (GH#687)")
]
@pytest.mark.parametrize("parsable_text,expected_datetime,assertion_message", PARSER_TEST_CASES)
def test_parser(parsable_text, expected_datetime, assertion_message):
assert parse(parsable_text) == expected_datetime, assertion_message
# Parser test cases using datetime(2003, 9, 25) as a default.
# Format: (parsable_text, expected_datetime, assertion_message)
PARSER_DEFAULT_TEST_CASES = [
("Thu Sep 25 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
("Thu Sep 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
("Thu 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
("Sep 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
("10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
("10:36", datetime(2003, 9, 25, 10, 36), "date command format strip"),
("Sep 2003", datetime(2003, 9, 25), "date command format strip"),
("Sep", datetime(2003, 9, 25), "date command format strip"),
("2003", datetime(2003, 9, 25), "date command format strip"),
("10h36m28.5s", datetime(2003, 9, 25, 10, 36, 28, 500000), "hour with letters"),
("10h36m28s", datetime(2003, 9, 25, 10, 36, 28), "hour with letters strip"),
("10h36m", datetime(2003, 9, 25, 10, 36), "hour with letters strip"),
("10h", datetime(2003, 9, 25, 10), "hour with letters strip"),
("10 h 36", datetime(2003, 9, 25, 10, 36), "hour with letters strip"),
("10 h 36.5", datetime(2003, 9, 25, 10, 36, 30), "hour with letter strip"),
("36 m 5", datetime(2003, 9, 25, 0, 36, 5), "hour with letters spaces"),
("36 m 5 s", datetime(2003, 9, 25, 0, 36, 5), "minute with letters spaces"),
("36 m 05", datetime(2003, 9, 25, 0, 36, 5), "minute with letters spaces"),
("36 m 05 s", datetime(2003, 9, 25, 0, 36, 5), "minutes with letters spaces"),
("10h am", datetime(2003, 9, 25, 10), "hour am pm"),
("10h pm", datetime(2003, 9, 25, 22), "hour am pm"),
("10am", datetime(2003, 9, 25, 10), "hour am pm"),
("10pm", datetime(2003, 9, 25, 22), "hour am pm"),
("10:00 am", datetime(2003, 9, 25, 10), "hour am pm"),
("10:00 pm", datetime(2003, 9, 25, 22), "hour am pm"),
("10:00am", datetime(2003, 9, 25, 10), "hour am pm"),
("10:00pm", datetime(2003, 9, 25, 22), "hour am pm"),
("10:00a.m", datetime(2003, 9, 25, 10), "hour am pm"),
("10:00p.m", datetime(2003, 9, 25, 22), "hour am pm"),
("10:00a.m.", datetime(2003, 9, 25, 10), "hour am pm"),
("10:00p.m.", datetime(2003, 9, 25, 22), "hour am pm"),
("Wed", datetime(2003, 10, 1), "weekday alone"),
("Wednesday", datetime(2003, 10, 1), "long weekday"),
("October", datetime(2003, 10, 25), "long month"),
("31-Dec-00", datetime(2000, 12, 31), "zero year"),
("0:01:02", datetime(2003, 9, 25, 0, 1, 2), "random format"),
("12h 01m02s am", datetime(2003, 9, 25, 0, 1, 2), "random format"),
("12:08 PM", datetime(2003, 9, 25, 12, 8), "random format"),
("01h02m03", datetime(2003, 9, 25, 1, 2, 3), "random format"),
("01h02", datetime(2003, 9, 25, 1, 2), "random format"),
("01h02s", datetime(2003, 9, 25, 1, 0, 2), "random format"),
("01m02", datetime(2003, 9, 25, 0, 1, 2), "random format"),
("01m02h", datetime(2003, 9, 25, 2, 1), "random format"),
("2004 10 Apr 11h30m", datetime(2004, 4, 10, 11, 30), "random format")
]
@pytest.mark.parametrize("parsable_text,expected_datetime,assertion_message", PARSER_DEFAULT_TEST_CASES)
def test_parser_default(parsable_text, expected_datetime, assertion_message):
assert parse(parsable_text, default=datetime(2003, 9, 25)) == expected_datetime, assertion_message
class TestFormat(unittest.TestCase):

@@ -61,26 +185,16 @@

class ParserTest(unittest.TestCase):
def setUp(self):
self.tzinfos = {"BRST": -10800}
self.brsttz = tzoffset("BRST", -10800)
self.default = datetime(2003, 9, 25)
# Parser should be able to handle bytestring and unicode
self.uni_str = '2014-05-01 08:00:00'
self.str_str = self.uni_str.encode()
def testEmptyString(self):
with self.assertRaises(ValueError):
class TestInputFormats(object):
def test_empty_string_invalid(self):
with pytest.raises(ValueError):
parse('')
def testNone(self):
with self.assertRaises(TypeError):
def test_none_invalid(self):
with pytest.raises(TypeError):
parse(None)
def testInvalidType(self):
with self.assertRaises(TypeError):
def test_int_invalid(self):
with pytest.raises(TypeError):
parse(13)
def testDuckTyping(self):
def test_duck_typing(self):
# We want to support arbitrary classes that implement the stream

@@ -98,21 +212,45 @@ # interface.

self.assertEqual(parse(dstr), datetime(2014, 1, 19))
res = parse(dstr)
expected = datetime(2014, 1, 19)
assert res == expected
def testParseStream(self):
def test_parse_stream(self):
dstr = StringIO('2014 January 19')
self.assertEqual(parse(dstr), datetime(2014, 1, 19))
res = parse(dstr)
expected = datetime(2014, 1, 19)
assert res == expected
def testParseStr(self):
self.assertEqual(parse(self.str_str),
parse(self.uni_str))
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()
def testParseBytes(self):
self.assertEqual(parse(b'2014 January 19'), datetime(2014, 1, 19))
res = parse(bytes_str)
expected = parse(uni_str)
assert res == expected
def testParseBytearray(self):
# GH #417
self.assertEqual(parse(bytearray(b'2014 January 19')),
datetime(2014, 1, 19))
def test_parse_bytes(self):
res = parse(b'2014 January 19')
expected = datetime(2014, 1, 19)
assert res == expected
def test_parse_bytearray(self):
# GH#417
res = parse(bytearray(b'2014 January 19'))
expected = datetime(2014, 1, 19)
assert res == expected
class ParserTest(unittest.TestCase):
def setUp(self):
self.tzinfos = {"BRST": -10800}
self.brsttz = tzoffset("BRST", -10800)
self.default = datetime(2003, 9, 25)
# Parser should be able to handle bytestring and unicode
self.uni_str = '2014-05-01 08:00:00'
self.str_str = self.uni_str.encode()
def testParserParseStr(self):

@@ -177,2 +315,3 @@ from dateutil.parser import parser

tzinfo=self.brsttz))
def testDateCommandFormatIgnoreTz(self):

@@ -183,46 +322,2 @@ self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003",

def testDateCommandFormatStrip1(self):
self.assertEqual(parse("Thu Sep 25 10:36:28 2003"),
datetime(2003, 9, 25, 10, 36, 28))
def testDateCommandFormatStrip2(self):
self.assertEqual(parse("Thu Sep 25 10:36:28", default=self.default),
datetime(2003, 9, 25, 10, 36, 28))
def testDateCommandFormatStrip3(self):
self.assertEqual(parse("Thu Sep 10:36:28", default=self.default),
datetime(2003, 9, 25, 10, 36, 28))
def testDateCommandFormatStrip4(self):
self.assertEqual(parse("Thu 10:36:28", default=self.default),
datetime(2003, 9, 25, 10, 36, 28))
def testDateCommandFormatStrip5(self):
self.assertEqual(parse("Sep 10:36:28", default=self.default),
datetime(2003, 9, 25, 10, 36, 28))
def testDateCommandFormatStrip6(self):
self.assertEqual(parse("10:36:28", default=self.default),
datetime(2003, 9, 25, 10, 36, 28))
def testDateCommandFormatStrip7(self):
self.assertEqual(parse("10:36", default=self.default),
datetime(2003, 9, 25, 10, 36))
def testDateCommandFormatStrip8(self):
self.assertEqual(parse("Thu Sep 25 2003"),
datetime(2003, 9, 25))
def testDateCommandFormatStrip10(self):
self.assertEqual(parse("Sep 2003", default=self.default),
datetime(2003, 9, 25))
def testDateCommandFormatStrip11(self):
self.assertEqual(parse("Sep", default=self.default),
datetime(2003, 9, 25))
def testDateCommandFormatStrip12(self):
self.assertEqual(parse("2003", default=self.default),
datetime(2003, 9, 25))
def testDateRCommandFormat(self):

@@ -244,17 +339,6 @@ self.assertEqual(parse("Thu, 25 Sep 2003 10:49:41 -0300"),

def testISOFormatStrip2(self):
self.assertEqual(parse("2003-09-25T10:49:41"),
datetime(2003, 9, 25, 10, 49, 41))
self.assertEqual(parse("2003-09-25T10:49:41+03:00"),
datetime(2003, 9, 25, 10, 49, 41,
tzinfo=tzoffset(None, 10800)))
def testISOFormatStrip3(self):
self.assertEqual(parse("2003-09-25T10:49"),
datetime(2003, 9, 25, 10, 49))
def testISOFormatStrip4(self):
self.assertEqual(parse("2003-09-25T10"),
datetime(2003, 9, 25, 10))
def testISOFormatStrip5(self):
self.assertEqual(parse("2003-09-25"),
datetime(2003, 9, 25))
def testISOStrippedFormat(self):

@@ -271,41 +355,6 @@ self.assertEqual(parse("20030925T104941.5-0300"),

def testISOStrippedFormatStrip2(self):
self.assertEqual(parse("20030925T104941"),
datetime(2003, 9, 25, 10, 49, 41))
self.assertEqual(parse("20030925T104941+0300"),
datetime(2003, 9, 25, 10, 49, 41,
tzinfo=tzoffset(None, 10800)))
def testISOStrippedFormatStrip3(self):
self.assertEqual(parse("20030925T1049"),
datetime(2003, 9, 25, 10, 49, 0))
def testISOStrippedFormatStrip4(self):
self.assertEqual(parse("20030925T10"),
datetime(2003, 9, 25, 10))
def testISOStrippedFormatStrip5(self):
self.assertEqual(parse("20030925"),
datetime(2003, 9, 25))
def testPythonLoggerFormat(self):
self.assertEqual(parse("2003-09-25 10:49:41,502"),
datetime(2003, 9, 25, 10, 49, 41, 502000))
def testNoSeparator1(self):
self.assertEqual(parse("199709020908"),
datetime(1997, 9, 2, 9, 8))
def testNoSeparator2(self):
self.assertEqual(parse("19970902090807"),
datetime(1997, 9, 2, 9, 8, 7))
def testDateWithDash1(self):
self.assertEqual(parse("2003-09-25"),
datetime(2003, 9, 25))
def testDateWithDash6(self):
self.assertEqual(parse("09-25-2003"),
datetime(2003, 9, 25))
def testDateWithDash7(self):
self.assertEqual(parse("25-09-2003"),
datetime(2003, 9, 25))
def testDateWithDash8(self):

@@ -315,10 +364,2 @@ self.assertEqual(parse("10-09-2003", dayfirst=True),

def testDateWithDash9(self):
self.assertEqual(parse("10-09-2003"),
datetime(2003, 10, 9))
def testDateWithDash10(self):
self.assertEqual(parse("10-09-03"),
datetime(2003, 10, 9))
def testDateWithDash11(self):

@@ -328,14 +369,2 @@ self.assertEqual(parse("10-09-03", yearfirst=True),

def testDateWithDot1(self):
self.assertEqual(parse("2003.09.25"),
datetime(2003, 9, 25))
def testDateWithDot6(self):
self.assertEqual(parse("09.25.2003"),
datetime(2003, 9, 25))
def testDateWithDot7(self):
self.assertEqual(parse("25.09.2003"),
datetime(2003, 9, 25))
def testDateWithDot8(self):

@@ -345,10 +374,2 @@ self.assertEqual(parse("10.09.2003", dayfirst=True),

def testDateWithDot9(self):
self.assertEqual(parse("10.09.2003"),
datetime(2003, 10, 9))
def testDateWithDot10(self):
self.assertEqual(parse("10.09.03"),
datetime(2003, 10, 9))
def testDateWithDot11(self):

@@ -358,14 +379,2 @@ self.assertEqual(parse("10.09.03", yearfirst=True),

def testDateWithSlash1(self):
self.assertEqual(parse("2003/09/25"),
datetime(2003, 9, 25))
def testDateWithSlash6(self):
self.assertEqual(parse("09/25/2003"),
datetime(2003, 9, 25))
def testDateWithSlash7(self):
self.assertEqual(parse("25/09/2003"),
datetime(2003, 9, 25))
def testDateWithSlash8(self):

@@ -375,10 +384,2 @@ self.assertEqual(parse("10/09/2003", dayfirst=True),

def testDateWithSlash9(self):
self.assertEqual(parse("10/09/2003"),
datetime(2003, 10, 9))
def testDateWithSlash10(self):
self.assertEqual(parse("10/09/03"),
datetime(2003, 10, 9))
def testDateWithSlash11(self):

@@ -388,14 +389,2 @@ self.assertEqual(parse("10/09/03", yearfirst=True),

def testDateWithSpace1(self):
self.assertEqual(parse("2003 09 25"),
datetime(2003, 9, 25))
def testDateWithSpace6(self):
self.assertEqual(parse("09 25 2003"),
datetime(2003, 9, 25))
def testDateWithSpace7(self):
self.assertEqual(parse("25 09 2003"),
datetime(2003, 9, 25))
def testDateWithSpace8(self):

@@ -405,10 +394,2 @@ self.assertEqual(parse("10 09 2003", dayfirst=True),

def testDateWithSpace9(self):
self.assertEqual(parse("10 09 2003"),
datetime(2003, 10, 9))
def testDateWithSpace10(self):
self.assertEqual(parse("10 09 03"),
datetime(2003, 10, 9))
def testDateWithSpace11(self):

@@ -418,54 +399,2 @@ self.assertEqual(parse("10 09 03", yearfirst=True),

def testDateWithSpace12(self):
self.assertEqual(parse("25 09 03"),
datetime(2003, 9, 25))
def testStrangelyOrderedDate1(self):
self.assertEqual(parse("03 25 Sep"),
datetime(2003, 9, 25))
def testStrangelyOrderedDate3(self):
self.assertEqual(parse("25 03 Sep"),
datetime(2025, 9, 3))
def testHourWithLetters(self):
self.assertEqual(parse("10h36m28.5s", default=self.default),
datetime(2003, 9, 25, 10, 36, 28, 500000))
def testHourWithLettersStrip1(self):
self.assertEqual(parse("10h36m28s", default=self.default),
datetime(2003, 9, 25, 10, 36, 28))
def testHourWithLettersStrip2(self):
self.assertEqual(parse("10h36m", default=self.default),
datetime(2003, 9, 25, 10, 36))
def testHourWithLettersStrip3(self):
self.assertEqual(parse("10h", default=self.default),
datetime(2003, 9, 25, 10))
def testHourWithLettersStrip4(self):
self.assertEqual(parse("10 h 36", default=self.default),
datetime(2003, 9, 25, 10, 36))
def testHourWithLetterStrip5(self):
self.assertEqual(parse("10 h 36.5", default=self.default),
datetime(2003, 9, 25, 10, 36, 30))
def testMinuteWithLettersSpaces1(self):
self.assertEqual(parse("36 m 5", default=self.default),
datetime(2003, 9, 25, 0, 36, 5))
def testMinuteWithLettersSpaces2(self):
self.assertEqual(parse("36 m 5 s", default=self.default),
datetime(2003, 9, 25, 0, 36, 5))
def testMinuteWithLettersSpaces3(self):
self.assertEqual(parse("36 m 05", default=self.default),
datetime(2003, 9, 25, 0, 36, 5))
def testMinuteWithLettersSpaces4(self):
self.assertEqual(parse("36 m 05 s", default=self.default),
datetime(2003, 9, 25, 0, 36, 5))
def testAMPMNoHour(self):

@@ -478,50 +407,2 @@ with self.assertRaises(ValueError):

def testHourAmPm1(self):
self.assertEqual(parse("10h am", default=self.default),
datetime(2003, 9, 25, 10))
def testHourAmPm2(self):
self.assertEqual(parse("10h pm", default=self.default),
datetime(2003, 9, 25, 22))
def testHourAmPm3(self):
self.assertEqual(parse("10am", default=self.default),
datetime(2003, 9, 25, 10))
def testHourAmPm4(self):
self.assertEqual(parse("10pm", default=self.default),
datetime(2003, 9, 25, 22))
def testHourAmPm5(self):
self.assertEqual(parse("10:00 am", default=self.default),
datetime(2003, 9, 25, 10))
def testHourAmPm6(self):
self.assertEqual(parse("10:00 pm", default=self.default),
datetime(2003, 9, 25, 22))
def testHourAmPm7(self):
self.assertEqual(parse("10:00am", default=self.default),
datetime(2003, 9, 25, 10))
def testHourAmPm8(self):
self.assertEqual(parse("10:00pm", default=self.default),
datetime(2003, 9, 25, 22))
def testHourAmPm9(self):
self.assertEqual(parse("10:00a.m", default=self.default),
datetime(2003, 9, 25, 10))
def testHourAmPm10(self):
self.assertEqual(parse("10:00p.m", default=self.default),
datetime(2003, 9, 25, 22))
def testHourAmPm11(self):
self.assertEqual(parse("10:00a.m.", default=self.default),
datetime(2003, 9, 25, 10))
def testHourAmPm12(self):
self.assertEqual(parse("10:00p.m.", default=self.default),
datetime(2003, 9, 25, 22))
def testAMPMRange(self):

@@ -540,18 +421,2 @@ with self.assertRaises(ValueError):

def testWeekdayAlone(self):
self.assertEqual(parse("Wed", default=self.default),
datetime(2003, 10, 1))
def testLongWeekday(self):
self.assertEqual(parse("Wednesday", default=self.default),
datetime(2003, 10, 1))
def testLongMonth(self):
self.assertEqual(parse("October", default=self.default),
datetime(2003, 10, 25))
def testZeroYear(self):
self.assertEqual(parse("31-Dec-00", default=self.default),
datetime(2000, 12, 31))
def testFuzzy(self):

@@ -599,10 +464,2 @@ s = "Today is 25 of September of 2003, exactly " \

def testExtraSpace(self):
self.assertEqual(parse(" July 4 , 1976 12:01:02 am "),
datetime(1976, 7, 4, 0, 1, 2))
def testRandomFormat1(self):
self.assertEqual(parse("Wed, July 10, '96"),
datetime(1996, 7, 10, 0, 0))
def testRandomFormat2(self):

@@ -613,6 +470,2 @@ self.assertEqual(parse("1996.07.10 AD at 15:08:56 PDT",

def testRandomFormat3(self):
self.assertEqual(parse("1996.July.10 AD 12:08 PM"),
datetime(1996, 7, 10, 12, 8))
def testRandomFormat4(self):

@@ -638,33 +491,2 @@ self.assertEqual(parse("Tuesday, April 12, 1952 AD 3:30:42pm PST",

def testRandomFormat8(self):
self.assertEqual(parse("July 4, 1976"), datetime(1976, 7, 4))
def testRandomFormat9(self):
self.assertEqual(parse("7 4 1976"), datetime(1976, 7, 4))
def testRandomFormat10(self):
self.assertEqual(parse("4 jul 1976"), datetime(1976, 7, 4))
def testRandomFormat11(self):
self.assertEqual(parse("7-4-76"), datetime(1976, 7, 4))
def testRandomFormat12(self):
self.assertEqual(parse("19760704"), datetime(1976, 7, 4))
def testRandomFormat13(self):
self.assertEqual(parse("0:01:02", default=self.default),
datetime(2003, 9, 25, 0, 1, 2))
def testRandomFormat14(self):
self.assertEqual(parse("12h 01m02s am", default=self.default),
datetime(2003, 9, 25, 0, 1, 2))
def testRandomFormat15(self):
self.assertEqual(parse("0:01:02 on July 4, 1976"),
datetime(1976, 7, 4, 0, 1, 2))
def testRandomFormat16(self):
self.assertEqual(parse("0:01:02 on July 4, 1976"),
datetime(1976, 7, 4, 0, 1, 2))
def testRandomFormat17(self):

@@ -675,9 +497,6 @@ self.assertEqual(parse("1976-07-04T00:01:02Z", ignoretz=True),

def testRandomFormat18(self):
self.assertEqual(parse("July 4, 1976 12:01:02 am"),
datetime(1976, 7, 4, 0, 1, 2))
self.assertEqual(parse("1986-07-05T08:15:30z",
ignoretz=True),
datetime(1986, 7, 5, 8, 15, 30))
def testRandomFormat19(self):
self.assertEqual(parse("Mon Jan 2 04:24:27 1995"),
datetime(1995, 1, 2, 4, 24, 27))
def testRandomFormat20(self):

@@ -687,14 +506,2 @@ self.assertEqual(parse("Tue Apr 4 00:22:12 PDT 1995", ignoretz=True),

def testRandomFormat21(self):
self.assertEqual(parse("04.04.95 00:22"),
datetime(1995, 4, 4, 0, 22))
def testRandomFormat22(self):
self.assertEqual(parse("Jan 1 1999 11:23:34.578"),
datetime(1999, 1, 1, 11, 23, 34, 578000))
def testRandomFormat23(self):
self.assertEqual(parse("950404 122212"),
datetime(1995, 4, 4, 12, 22, 12))
def testRandomFormat24(self):

@@ -705,6 +512,2 @@ self.assertEqual(parse("0:00 PM, PST", default=self.default,

def testRandomFormat25(self):
self.assertEqual(parse("12:08 PM", default=self.default),
datetime(2003, 9, 25, 12, 8))
def testRandomFormat26(self):

@@ -716,43 +519,2 @@ with pytest.warns(UnknownTimezoneWarning):

def testRandomFormat27(self):
self.assertEqual(parse("3rd of May 2001"), datetime(2001, 5, 3))
def testRandomFormat28(self):
self.assertEqual(parse("5th of March 2001"), datetime(2001, 3, 5))
def testRandomFormat29(self):
self.assertEqual(parse("1st of May 2003"), datetime(2003, 5, 1))
def testRandomFormat30(self):
self.assertEqual(parse("01h02m03", default=self.default),
datetime(2003, 9, 25, 1, 2, 3))
def testRandomFormat31(self):
self.assertEqual(parse("01h02", default=self.default),
datetime(2003, 9, 25, 1, 2))
def testRandomFormat32(self):
self.assertEqual(parse("01h02s", default=self.default),
datetime(2003, 9, 25, 1, 0, 2))
def testRandomFormat33(self):
self.assertEqual(parse("01m02", default=self.default),
datetime(2003, 9, 25, 0, 1, 2))
def testRandomFormat34(self):
self.assertEqual(parse("01m02h", default=self.default),
datetime(2003, 9, 25, 2, 1))
def testRandomFormat35(self):
self.assertEqual(parse("2004 10 Apr 11h30m", default=self.default),
datetime(2004, 4, 10, 11, 30))
def test_99_ad(self):
self.assertEqual(parse('0099-01-01T00:00:00'),
datetime(99, 1, 1, 0, 0))
def test_31_ad(self):
self.assertEqual(parse('0031-01-01T00:00:00'),
datetime(31, 1, 1, 0, 0))
def testInvalidDay(self):

@@ -830,6 +592,2 @@ with self.assertRaises(ValueError):

def testHighPrecisionSeconds(self):
self.assertEqual(parse("20080227T21:26:01.123456789"),
datetime(2008, 2, 27, 21, 26, 1, 123456))
def testCustomParserInfo(self):

@@ -929,8 +687,2 @@ # Custom parser info wasn't working, as Michael Elsdörfer discovered.

def test_dBY(self):
# See GH360
dtstr = '13NOV2017'
res = parse(dtstr)
self.assertEqual(res, datetime(2017, 11, 13))
def test_hmBY(self):

@@ -953,8 +705,3 @@ # See GH#483

def test_pre_12_year_same_month(self):
# See GH PR #293
dtstr = '0003-03-04'
assert parse(dtstr) == datetime(3, 3, 4)
class TestParseUnimplementedCases(object):

@@ -1067,2 +814,3 @@ @pytest.mark.xfail

@pytest.mark.skipif(IS_WIN, reason='Windows does not use TZ var')

@@ -1138,7 +886,1 @@ def test_parse_unambiguous_nonexistent_local():

parse(value)
def test_BYd_corner_case():
# GH#687
res = parse('December.0031.30')
assert res == datetime(31, 12, 30)

@@ -128,2 +128,10 @@ # -*- coding: utf-8 -*-

def testAddMoreThan12Months(self):
self.assertEqual(date(2003, 12, 1) + relativedelta(months=+13),
date(2005, 1, 1))
def testAddNegativeMonths(self):
self.assertEqual(date(2003, 1, 1) + relativedelta(months=-2),
date(2002, 11, 1))
def test15thISOYearWeek(self):

@@ -354,2 +362,12 @@ self.assertEqual(date(2003, 1, 1) +

def testRelativeDeltaInvalidDatetimeObject(self):
with self.assertRaises(TypeError):
relativedelta(dt1='2018-01-01', dt2='2018-01-02')
with self.assertRaises(TypeError):
relativedelta(dt1=datetime(2018, 1, 1), dt2='2018-01-02')
with self.assertRaises(TypeError):
relativedelta(dt1='2018-01-01', dt2=datetime(2018, 1, 2))
def testRelativeDeltaFractionalAbsolutes(self):

@@ -356,0 +374,0 @@ # Fractional absolute values will soon be unsupported,

@@ -1,2 +0,2 @@

from six import PY3
from six import PY2

@@ -19,10 +19,14 @@ from functools import wraps

"""
def adjust_encoding(*args, **kwargs):
name = namefunc(*args, **kwargs)
if name is not None and not PY3:
name = name.encode()
if PY2:
@wraps(namefunc)
def adjust_encoding(*args, **kwargs):
name = namefunc(*args, **kwargs)
if name is not None:
name = name.encode()
return name
return name
return adjust_encoding
return adjust_encoding
else:
return namefunc

@@ -29,0 +33,0 @@

from datetime import timedelta
import weakref
from collections import OrderedDict

@@ -14,2 +16,3 @@

class _TzFactory(type):

@@ -23,3 +26,5 @@ def instance(cls, *args, **kwargs):

def __init__(cls, *args, **kwargs):
cls.__instances = {}
cls.__instances = weakref.WeakValueDictionary()
cls.__strong_cache = OrderedDict()
cls.__strong_cache_size = 8

@@ -36,2 +41,10 @@ def __call__(cls, name, offset):

cls.instance(name, offset))
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
# Remove an item if the strong cache is overpopulated
# TODO: Maybe this should be under a lock?
if len(cls.__strong_cache) > cls.__strong_cache_size:
cls.__strong_cache.popitem(last=False)
return instance

@@ -42,3 +55,5 @@

def __init__(cls, *args, **kwargs):
cls.__instances = {}
cls.__instances = weakref.WeakValueDictionary()
cls.__strong_cache = OrderedDict()
cls.__strong_cache_size = 8

@@ -52,3 +67,12 @@ def __call__(cls, s, posix_offset=False):

cls.instance(s, posix_offset))
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
# Remove an item if the strong cache is overpopulated
# TODO: Maybe this should be under a lock?
if len(cls.__strong_cache) > cls.__strong_cache_size:
cls.__strong_cache.popitem(last=False)
return instance

@@ -16,2 +16,4 @@ # -*- coding: utf-8 -*-

import bisect
import weakref
from collections import OrderedDict

@@ -32,2 +34,5 @@ import six

# For warning about rounding tzinfo
from warnings import warn
ZERO = datetime.timedelta(0)

@@ -142,4 +147,5 @@ EPOCH = datetime.datetime.utcfromtimestamp(0)

pass
self._offset = datetime.timedelta(seconds=offset)
self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
def utcoffset(self, dt):

@@ -466,3 +472,3 @@ return self._offset

if not file_opened_here:
fileobj = _ContextWrapper(fileobj)
fileobj = _nullcontext(fileobj)

@@ -607,6 +613,3 @@ with fileobj as file_stream:

gmtoff, isdst, abbrind = ttinfo[i]
# Round to full-minutes if that's not the case. Python's
# datetime doesn't accept sub-minute timezones. Check
# http://python.org/sf/1447945 for some information.
gmtoff = 60 * ((gmtoff + 30) // 60)
gmtoff = _get_supported_offset(gmtoff)
tti = _ttinfo()

@@ -663,34 +666,41 @@ tti.offset = gmtoff

# about this.
laststdoffset = None
lastdst = None
lastoffset = None
lastdstoffset = None
lastbaseoffset = None
out.trans_list = []
for i, tti in enumerate(out.trans_idx):
if not tti.isdst:
offset = tti.offset
laststdoffset = offset
else:
if laststdoffset is not None:
# Store the DST offset as well and update it in the list
tti.dstoffset = tti.offset - laststdoffset
out.trans_idx[i] = tti
offset = tti.offset
dstoffset = 0
offset = laststdoffset or 0
if lastdst is not None:
if tti.isdst:
if not lastdst:
dstoffset = offset - lastoffset
out.trans_list.append(out.trans_list_utc[i] + offset)
if not dstoffset and lastdstoffset:
dstoffset = lastdstoffset
# In case we missed any DST offsets on the way in for some reason, make
# a second pass over the list, looking for the /next/ DST offset.
laststdoffset = None
for i in reversed(range(len(out.trans_idx))):
tti = out.trans_idx[i]
if tti.isdst:
if not (tti.dstoffset or laststdoffset is None):
tti.dstoffset = tti.offset - laststdoffset
else:
laststdoffset = tti.offset
tti.dstoffset = datetime.timedelta(seconds=dstoffset)
lastdstoffset = dstoffset
if not isinstance(tti.dstoffset, datetime.timedelta):
tti.dstoffset = datetime.timedelta(seconds=tti.dstoffset)
# If a time zone changes its base offset during a DST transition,
# then you need to adjust by the previous base offset to get the
# transition time in local time. Otherwise you use the current
# base offset. Ideally, I would have some mathematical proof of
# why this is true, but I haven't really thought about it enough.
baseoffset = offset - dstoffset
adjustment = baseoffset
if (lastbaseoffset is not None and baseoffset != lastbaseoffset
and tti.isdst != lastdst):
# The base DST has changed
adjustment = lastbaseoffset
out.trans_idx[i] = tti
lastdst = tti.isdst
lastoffset = offset
lastbaseoffset = baseoffset
out.trans_list.append(out.trans_list_utc[i] + adjustment)
out.trans_idx = tuple(out.trans_idx)

@@ -1264,3 +1274,3 @@ out.trans_list = tuple(out.trans_list)

self._s = getattr(fileobj, 'name', repr(fileobj))
fileobj = _ContextWrapper(fileobj)
fileobj = _nullcontext(fileobj)

@@ -1538,3 +1548,5 @@ self._vtz = {}

self.__instances = {}
self.__instances = weakref.WeakValueDictionary()
self.__strong_cache_size = 8
self.__strong_cache = OrderedDict()
self._cache_lock = _thread.allocate_lock()

@@ -1548,13 +1560,33 @@

rv = self.nocache(name=name)
if not (name is None or isinstance(rv, tzlocal_classes)):
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.
#
# We also cannot store weak references to None, so we
# will also not store that.
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
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:
self.__instances = {}
self.__instances = weakref.WeakValueDictionary()
self.__strong_cache.clear()

@@ -1613,3 +1645,4 @@ @staticmethod

tz = tzwin(name)
except WindowsError:
except (WindowsError, UnicodeEncodeError):
# UnicodeEncodeError is for Python 2.7 compat
tz = None

@@ -1781,16 +1814,34 @@

class _ContextWrapper(object):
"""
Class for wrapping contexts so that they are passed through in a
with statement.
"""
def __init__(self, context):
self.context = context
if sys.version_info >= (3, 6):
def _get_supported_offset(second_offset):
return second_offset
else:
def _get_supported_offset(second_offset):
# For python pre-3.6, round to full-minutes if that's not the case.
# Python's datetime doesn't accept sub-minute timezones. Check
# http://python.org/sf/1447945 or https://bugs.python.org/issue5288
# for some information.
old_offset = second_offset
calculated_offset = 60 * ((second_offset + 30) // 60)
return calculated_offset
def __enter__(self):
return self.context
def __exit__(*args, **kwargs):
pass
try:
# Python 3.7 feature
from contextmanager import nullcontext as _nullcontext
except ImportError:
class _nullcontext(object):
"""
Class for wrapping contexts so that they are passed through in a
with statement.
"""
def __init__(self, context):
self.context = context
def __enter__(self):
return self.context
def __exit__(*args, **kwargs):
pass
# vim:ts=4:sw=4:et

@@ -0,1 +1,9 @@

# -*- coding: utf-8 -*-
"""
This module provides an interface to the native time zone data on Windows,
including :py:class:`datetime.tzinfo` implementations.
Attempting to import this module on a non-Windows platform will raise an
:py:obj:`ImportError`.
"""
# This code was originally contributed by Jeffrey Harris.

@@ -42,3 +50,3 @@ import datetime

"""
Class for accessing `tzres.dll`, which contains timezone name related
Class for accessing ``tzres.dll``, which contains timezone name related
resources.

@@ -76,5 +84,6 @@

..note:
.. note::
Offsets found in the registry are generally of the form
`@tzres.dll,-114`. The offset in this case if 114, not -114.
``@tzres.dll,-114``. The offset in this case is 114, not -114.

@@ -151,2 +160,5 @@ """

def display(self):
"""
Return the display name of the time zone.
"""
return self._display

@@ -194,3 +206,14 @@

class tzwin(tzwinbase):
"""
Time zone object created from the zone info in the Windows registry
These are similar to :py:class:`dateutil.tz.tzrange` objects in that
the time zone data is provided in the format of a single offset rule
for either 0 or 2 time zone transitions per year.
:param: name
The name of a Windows time zone key, e.g. "Eastern Standard Time".
The full list of keys can be retrieved with :func:`tzwin.list`.
"""
def __init__(self, name):

@@ -241,2 +264,18 @@ self._name = name

class tzwinlocal(tzwinbase):
"""
Class representing the local time zone information in the Windows registry
While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time`
module) to retrieve time zone information, ``tzwinlocal`` retrieves the
rules directly from the Windows registry and creates an object like
:class:`dateutil.tz.tzwin`.
Because Windows does not have an equivalent of :func:`time.tzset`, on
Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the
time zone settings *at the time that the process was started*, meaning
changes to the machine's time zone settings during the run of a program
on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`.
Because ``tzwinlocal`` reads the registry directly, it is unaffected by
this issue.
"""
def __init__(self):

@@ -243,0 +282,0 @@ with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:

@@ -189,3 +189,17 @@ #!/usr/bin/env python3

# -- Options for autodoc -------------------------------------------------
autodoc_mock_imports = ['ctypes.wintypes', 'six.moves.winreg']
# Need to mock this out specifically to avoid errors
import ctypes
def pointer_mock(*args, **kwargs):
try:
return ctypes.POINTER(*args, **kwargs)
except Exception:
return None
ctypes.POINTER = pointer_mock
sys.modules['ctypes'] = ctypes
# -- Options for LaTeX output ---------------------------------------------

@@ -192,0 +206,0 @@

@@ -1193,2 +1193,15 @@ dateutil examples

Override parserinfo with a custom parserinfo
.. doctest:: tz
>>> from dateutil.parser import parse, parserinfo
>>> class CustomParserInfo(parserinfo):
... # e.g. edit a property of parserinfo to allow a custom 12 hour format
... AMPM = [("am", "a", "xm"), ("pm", "p")]
>>> parse('2018-06-08 12:06:58 XM', parserinfo=CustomParserInfo())
datetime.datetime(2018, 6, 8, 0, 6, 58)
tzutc examples

@@ -1195,0 +1208,0 @@ --------------

@@ -18,2 +18,4 @@ .. dateutil documentation master file, created by

Changelog <changelog>
Examples <examples>
Exercises <exercises/index>

@@ -28,5 +30,5 @@ .. toctree::

tz
tz.win <tzwin>
utils
zoneinfo
examples

@@ -33,0 +35,0 @@ Indices and tables

@@ -1,2 +0,3 @@

Sphinx>=1.7.3,<2
Sphinx>=1.7.3,!=1.8.0
sphinx_rtd_theme>=0.3.0
readme-renderer>=21.0

@@ -6,5 +6,16 @@ =====

.. automodule:: dateutil.rrule
:members:
:undoc-members:
Classes
-------
.. autoclass:: rrule
.. autoclass:: rruleset
Functions
---------
.. autofunction:: rrulestr
rrule examples

@@ -11,0 +22,0 @@ --------------

@@ -39,2 +39,9 @@ ==

.. autoclass:: tzwinlocal
:members: display, transitions
.. note::
Only available on Windows
.. autoclass:: tzrange

@@ -46,1 +53,8 @@

:members:
.. autoclass:: tzwin
:members: display, transitions, list
.. note::
Only available on Windows

@@ -1,4 +0,4 @@

include LICENSE NEWS zonefile_metadata.json updatezinfo.py
include LICENSE NEWS zonefile_metadata.json updatezinfo.py pyproject.toml
recursive-include dateutil/test *
global-exclude __pycache__
global-exclude *.py[co]
+117
-0

@@ -0,1 +1,118 @@

Version 2.8.0 (2019-02-04)
==========================
Data updates
------------
- Updated tzdata version to to 2018i.
Features
--------
- Added support for ``EXDATE`` parameters when parsing ``rrule`` strings.
Reported by @mlorant (gh issue #410), fixed by @nicoe (gh pr #859).
- Added support for sub-minute time zone offsets in Python 3.6+.
Fixed by @cssherry (gh issue #582, pr #763)
- Switched the ``tzoffset``, ``tzstr`` and ``gettz`` caches over to using weak
references, so that the cache expires when no other references to the
original ``tzinfo`` objects exist. This cache-expiry behavior is not
guaranteed in the public interface and may change in the future. To improve
performance in the case where transient references to the same time zones
are repeatedly created but no strong reference is continuously held, a
smaller "strong value" cache was also added. Weak value cache implemented by
@cs-cordero (gh pr #672, #801), strong cache added by
Gökçen Nurlu (gh issue #691, gh pr #761)
Bugfixes
--------
- Added time zone inference when initializing an ``rrule`` with a specified
``UNTIL`` but without an explicitly specified ``DTSTART``; the time zone
of the generated ``DTSTART`` will now be taken from the ``UNTIL`` rule.
Reported by @href (gh issue #652). Fixed by @absreim (gh pr #693).
- Fixed an issue where ``parser.parse`` would raise ``Decimal``-specific errors
instead of a standard ``ValueError`` if certain malformed values were parsed
(e.g. ``NaN`` or infinite values). Reported and fixed by
@amureki (gh issue #662, gh pr #679).
- Fixed issue in ``parser`` where a ``tzinfos`` call explicitly returning
``None`` would throw a ``ValueError``.
Fixed by @parsethis (gh issue #661, gh pr #681)
- Fixed incorrect parsing of certain dates earlier than 100 AD when repesented
in the form "%B.%Y.%d", e.g. "December.0031.30". (gh issue #687, pr #700)
- Add support for ISO 8601 times with comma as the decimal separator in the
``dateutil.parser.isoparse`` function. (gh pr #721)
- Changed handling of ``T24:00`` to be compliant with the standard. ``T24:00``
now represents midnight on the *following* day.
Fixed by @cheukting (gh issue #658, gh pr #751)
- Fixed an issue where ``isoparser.parse_isotime`` was unable to handle the
``24:00`` variant representation of midnight. (gh pr #773)
- Added support for more than 6 fractional digits in `isoparse`.
Reported and fixed by @jayschwa (gh issue #786, gh pr #787).
- Added 'z' (lower case Z) as valid UTC time zone in isoparser.
Reported by @cjgibson (gh issue #820). Fixed by @Cheukting (gh pr #822)
- Fixed a bug with base offset changes during DST in ``tzfile``, and refactored
the way base offset changes are detected. Originally reported on
StackOverflow by @MartinThoma. (gh issue #812, gh pr #810)
- Fixed error condition in ``tz.gettz`` when a non-ASCII timezone is passed on
Windows in Python 2.7. (gh issue #802, pr #861)
- Improved performance and inspection properties of ``tzname`` methods.
(gh pr #811)
- Removed unnecessary binary_type compatibility shims.
Added by @jdufresne (gh pr #817)
- Changed ``python setup.py test`` to print an error to ``stderr`` and exit
with 1 instead of 0. Reported and fixed by @hroncok (gh pr #814)
- Added a ``pyproject.toml`` file with build requirements and an explicitly
specified build backend. (gh issue #736, gh prs #746, #863)
Documentation changes
---------------------
- Added documentation for the ``rrule.rrulestr`` function.
Fixed by @prdickson (gh issue #623, gh pr #762)
- Added documentation for ``dateutil.tz.gettz``.
Fixed by @weatherpattern (gh issue #647, gh pr #704)
- Add documentation for the ``dateutil.tz.win`` module and mocked out certain
Windows-specific modules so that autodoc can still be run on non-Windows
systems. (gh issue #442, pr #715)
- Added changelog to documentation. (gh issue #692, gh pr #707)
- Changed order of keywords in the ``rrule`` docstring.
Reported and fixed by @rmahajan14 (gh issue #686, gh pr #695).
- Improved documentation on the use of ``until`` and ``count`` parameters in
``rrule``. Fixed by @lucaferocino (gh pr #755).
- Added an example of how to use a custom ``parserinfo`` subclass to parse
non-standard datetime formats in the examples documentation for ``parser``.
Added by @prdickson (gh #753)
- Added doctest examples to ``tzfile`` documentation.
Patch by @weatherpattern (gh pr #671)
- Updated the documentation for ``relativedelta``'s ``weekday`` arguments.
Fixed by @kvn219 @huangy22 and @ElliotJH (gh pr #673)
- Improved explanation of the order that ``relativedelta`` components are
applied in. Fixed by @kvn219 @huangy22 and @ElliotJH (gh pr #673)
- Expanded the description and examples in the ``relativedelta`` class.
Contributed by @andrewcbennett (gh pr #759)
- Improved the contributing documentation to clarify where to put new changelog
files. Contributed by @andrewcbennett (gh pr #757)
- Fixed a broken doctest in the ``relativedelta`` module.
Fixed by @nherriot (gh pr #758).
- Changed the default theme to ``sphinx_rtd_theme``, and changed the sphinx
configuration accordingly. (gh pr #707)
- Reorganized ``dateutil.tz`` documentation and fixed issue with the
``dateutil.tz`` docstring. (gh pr #714)
- Cleaned up malformed RST in the ``tz`` documentation.
(gh issue #702, gh pr #706)
- Corrected link syntax and updated URL to https for ISO year week number
notation in ``relativedelta`` examples. (gh issue #670, pr #711)
Misc
----
- GH #674, GH #688, GH #699, GH #720, GH #723, GH #726, GH #727, GH #740,
GH #750, GH #760, GH #767, GH #772, GH #773, GH #780, GH #784, GH #785,
GH #791, GH #799, GH #813, GH #836, GH #839, GH #857
Version 2.7.5 (2018-10-27)

@@ -2,0 +119,0 @@ ==========================

Metadata-Version: 2.1
Name: python-dateutil
Version: 2.7.5
Version: 2.8.0
Summary: Extensions to the standard Python datetime module

@@ -55,2 +55,8 @@ Home-page: https://dateutil.readthedocs.io

Installation
============
`dateutil` can be installed from PyPI using `pip` (note that the package name is
different from the importable name)::
pip install python-dateutil

@@ -186,4 +192,3 @@ Download

Classifier: Topic :: Software Development :: Libraries
Requires: six
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*
Description-Content-Type: text/x-rst

@@ -0,1 +1,10 @@

[build-system]
requires = [
"setuptools; python_version != '3.3'",
"setuptools<40.0; python_version == '3.3'",
"wheel",
"setuptools_scm"
]
build-backend = "setuptools.build_meta"
[tool.towncrier]

@@ -2,0 +11,0 @@ package = "dateutil"

Metadata-Version: 2.1
Name: python-dateutil
Version: 2.7.5
Version: 2.8.0
Summary: Extensions to the standard Python datetime module

@@ -55,2 +55,8 @@ Home-page: https://dateutil.readthedocs.io

Installation
============
`dateutil` can be installed from PyPI using `pip` (note that the package name is
different from the importable name)::
pip install python-dateutil

@@ -186,4 +192,3 @@ Download

Classifier: Topic :: Software Development :: Libraries
Requires: six
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*
Description-Content-Type: text/x-rst

@@ -24,4 +24,6 @@ .gitattributes

changelog.d/template.rst
ci_tools/make_zonefile_metadata.py
ci_tools/retry.bat
ci_tools/retry.sh
ci_tools/run_tz_master_env.sh
dateutil/__init__.py

@@ -40,2 +42,3 @@ dateutil/_common.py

dateutil/test/_common.py
dateutil/test/conftest.py
dateutil/test/test_easter.py

@@ -73,4 +76,8 @@ dateutil/test/test_import_star.py

docs/tz.rst
docs/tzwin.rst
docs/utils.rst
docs/zoneinfo.rst
docs/exercises/index.rst
docs/exercises/solutions/mlk-day-rrule.rst
docs/exercises/solutions/mlk_day_rrule_solution.py
docs/samples/EST5EDT.ics

@@ -77,0 +84,0 @@ python_dateutil.egg-info/PKG-INFO

@@ -45,2 +45,8 @@ dateutil - powerful extensions to datetime

Installation
============
`dateutil` can be installed from PyPI using `pip` (note that the package name is
different from the importable name)::
pip install python-dateutil

@@ -47,0 +53,0 @@ Download

@@ -8,3 +8,11 @@ [bdist_wheel]

[tool:pytest]
python_files =
test_*.py
*_test.py
*_solution.py
xfail_strict = true
filterwarnings =
error
error::DeprecationWarning
error:PendingDeprecationWarning

@@ -11,0 +19,0 @@ [egg_info]

@@ -13,2 +13,3 @@ #!/usr/bin/python

import io
import sys

@@ -25,5 +26,7 @@ if isfile("MANIFEST"):

def run(self):
print("Running 'test' with setup.py is not supported. "
"Use 'pytest' or 'tox' to run the tests.")
sys.stderr.write("Running 'test' with setup.py is not supported. "
"Use 'pytest' or 'tox' to run the tests.\n")
sys.exit(1)
###

@@ -33,2 +36,3 @@ # Load metadata

def README():

@@ -40,6 +44,4 @@ with io.open('README.rst', encoding='utf-8') as f:

lines_out = []
doctest_line_found = False
for line in readme_lines:
if line.startswith('.. doctest'):
doctest_line_found = True
lines_out.append('.. code-block:: python3\n')

@@ -50,4 +52,5 @@ else:

return ''.join(lines_out)
README = README()
README = README() # NOQA
setup(name="python-dateutil",

@@ -70,5 +73,4 @@ use_scm_version={

zip_safe=True,
requires=["six"],
setup_requires=['setuptools_scm'],
install_requires=["six >=1.5"], # XXX fix when packaging is sane again
install_requires=["six >=1.5"],
classifiers=[

@@ -75,0 +77,0 @@ 'Development Status :: 5 - Production/Stable',

+13
-2

@@ -19,4 +19,3 @@ [tox]

passenv = DATEUTIL_MAY_CHANGE_TZ TOXENV CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* CODECOV_*
commands = python -m pytest -m "not xfail" {posargs: "{toxinidir}/dateutil/test" --cov-config="{toxinidir}/tox.ini" --cov=dateutil}
python -m pytest -m "xfail" {posargs: "{toxinidir}/dateutil/test"}
commands = python -m pytest {posargs: "{toxinidir}/dateutil/test" --cov-config="{toxinidir}/tox.ini" --cov=dateutil}
deps = -rrequirements-dev.txt

@@ -54,2 +53,13 @@

[testenv:tz]
# Warning: This will modify the repository and is only intended to be run
# as part of the CI process, not locally.
description = Run the tests against the master of the tz database
basepython = python3.6
deps = -r {toxinidir}/requirements-dev.txt
setenv = DATEUTIL_TZPATH = {envtmpdir}/tzdir/usr/share/zoneinfo
changedir = {toxworkdir}
commands =
{toxinidir}/ci_tools/run_tz_master_env.sh {envtmpdir} {toxinidir}
[testenv:docs]

@@ -62,1 +72,2 @@ description = invoke sphinx-build to build the HTML docs, check that URIs are valid

sphinx-build -d "{toxworkdir}/docs_doctree" docs "{toxworkdir}/docs_out" {posargs:-W --color -blinkcheck}
python setup.py check -r -s

@@ -15,4 +15,4 @@ #!/usr/bin/env python

def main():
with io.open(METADATA_FILE, 'r') as f:
def main(metadata_file):
with io.open(metadata_file, 'r') as f:
metadata = json.load(f)

@@ -56,2 +56,9 @@

if __name__ == "__main__":
main()
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('metadata', metavar='METADATA_FILE',
default=METADATA_FILE,
nargs='?')
args = parser.parse_args()
main(args.metadata)

@@ -7,5 +7,5 @@ {

],
"tzdata_file": "tzdata2018g.tar.gz",
"tzdata_file_sha512": "92e9bbd61f51be8f2cf7ec9491691e5e2f97803914dbad77b7fb8b6600ed68fc3b98450fc808bb2d4c6c835df5f9eb7bf4529d059d9b1370f2ab4c12e7f1adfa",
"tzversion": "2018g",
"tzdata_file": "tzdata2018i.tar.gz",
"tzdata_file_sha512": "6afcacb377842190648ed26f01abcf3db37aa2e7c63d8c509c29b4bc0078b7ff2d4e5375291b9f53498215b9e2f04936bc6145e2f651ae0be6d8166d8d336f6a",
"tzversion": "2018i",
"zonegroups": [

@@ -12,0 +12,0 @@ "africa",

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet