diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 6655d8d6..8ee37162 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -130,6 +130,8 @@ automatically installed without any action on your part: utilities * `MarkupSafe `_, for a markup safe string implementation +* `python-dateutil `_, to read + the date metadata If you want the following optional packages, you will need to install them manually via ``pip``: diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index f6f96a1c..9047593f 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -41,6 +41,12 @@ class TestUtils(LoggedTestCase): date = datetime.datetime(year=2012, month=11, day=22) date_hour = datetime.datetime( year=2012, month=11, day=22, hour=22, minute=11) + date_hour_z = datetime.datetime( + year=2012, month=11, day=22, hour=22, minute=11, + tzinfo=pytz.timezone('UTC')) + date_hour_est = datetime.datetime( + year=2012, month=11, day=22, hour=22, minute=11, + tzinfo=pytz.timezone('EST')) date_hour_sec = datetime.datetime( year=2012, month=11, day=22, hour=22, minute=11, second=10) date_hour_sec_z = datetime.datetime( @@ -61,22 +67,42 @@ class TestUtils(LoggedTestCase): '22/11/2012': date, '22.11.2012': date, '22.11.2012 22:11': date_hour, + '2012-11-22T22:11Z': date_hour_z, + '2012-11-22T22:11-0500': date_hour_est, '2012-11-22 22:11:10': date_hour_sec, '2012-11-22T22:11:10Z': date_hour_sec_z, '2012-11-22T22:11:10-0500': date_hour_sec_est, '2012-11-22T22:11:10.123Z': date_hour_sec_frac_z, } + # examples from http://www.w3.org/TR/NOTE-datetime + iso_8601_date = datetime.datetime(year=1997, month=7, day=16) + iso_8601_date_hour_tz = datetime.datetime( + year=1997, month=7, day=16, hour=19, minute=20, + tzinfo=pytz.timezone('CET')) + iso_8601_date_hour_sec_tz = datetime.datetime( + year=1997, month=7, day=16, hour=19, minute=20, second=30, + tzinfo=pytz.timezone('CET')) + iso_8601_date_hour_sec_ms_tz = datetime.datetime( + year=1997, month=7, day=16, hour=19, minute=20, second=30, + microsecond=450000, tzinfo=pytz.timezone('CET')) + iso_8601 = { + '1997-07-16': iso_8601_date, + '1997-07-16T19:20+01:00': iso_8601_date_hour_tz, + '1997-07-16T19:20:30+01:00': iso_8601_date_hour_sec_tz, + '1997-07-16T19:20:30.45+01:00': iso_8601_date_hour_sec_ms_tz, + } + # invalid ones invalid_dates = ['2010-110-12', 'yay'] - if version_info < (3, 2): - dates.pop('2012-11-22T22:11:10-0500') - invalid_dates.append('2012-11-22T22:11:10-0500') for value, expected in dates.items(): self.assertEqual(utils.get_date(value), expected, value) + for value, expected in iso_8601.items(): + self.assertEqual(utils.get_date(value), expected, value) + for item in invalid_dates: self.assertRaises(ValueError, utils.get_date, item) diff --git a/pelican/utils.py b/pelican/utils.py index 822e50e9..c5aacaa3 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -15,7 +15,7 @@ import traceback from collections import Hashable from contextlib import contextmanager -from datetime import datetime +import dateutil.parser from functools import partial from itertools import groupby from jinja2 import Markup @@ -181,39 +181,10 @@ def get_date(string): If no format matches the given date, raise a ValueError. """ string = re.sub(' +', ' ', string) - formats = [ - # ISO 8601 - '%Y', - '%Y-%m', - '%Y-%m-%d', - '%Y-%m-%dT%H:%M%z', - '%Y-%m-%dT%H:%MZ', - '%Y-%m-%dT%H:%M', - '%Y-%m-%dT%H:%M:%S%z', - '%Y-%m-%dT%H:%M:%SZ', - '%Y-%m-%dT%H:%M:%S', - '%Y-%m-%dT%H:%M:%S.%f%z', - '%Y-%m-%dT%H:%M:%S.%fZ', - '%Y-%m-%dT%H:%M:%S.%f', - # end ISO 8601 forms - '%Y-%m-%d %H:%M', - '%Y-%m-%d %H:%M:%S', - '%Y/%m/%d %H:%M', - '%Y/%m/%d', - '%d-%m-%Y', - '%d.%m.%Y %H:%M', - '%d.%m.%Y', - '%d/%m/%Y', - ] - for date_format in formats: - try: - date = datetime.strptime(string, date_format) - except ValueError: - continue - if date_format.endswith('Z'): - date = date.replace(tzinfo=pytz.timezone('UTC')) - return date - raise ValueError('{0!r} is not a valid date'.format(string)) + try: + return dateutil.parser.parse(string) + except (TypeError, ValueError): + raise ValueError('{0!r} is not a valid date'.format(string)) @contextmanager diff --git a/setup.py b/setup.py index f56a7c41..e989d549 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup requires = ['feedgenerator >= 1.6', 'jinja2 >= 2.7', 'pygments', 'docutils', - 'pytz >= 0a', 'blinker', 'unidecode', 'six'] + 'pytz >= 0a', 'blinker', 'unidecode', 'six', 'python-dateutil'] entry_points = { 'console_scripts': [