Skip to content

Commit

Permalink
[2383] Properly convert microseconds in date_str_to_datetime helper.
Browse files Browse the repository at this point in the history
Stop this helper function from incorrectly parsing a UTC offset "+0100" as
100 microseconds.

Now, if passed UTC offset as "+0100", this function will not parse the
timestamp.  The same behaviour as when it's passed "+01:00" as a UTC
offset.
  • Loading branch information
Ian Murray committed May 24, 2012
1 parent fcb5858 commit d6c9e48
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 6 deletions.
32 changes: 27 additions & 5 deletions ckan/lib/helpers.py
Expand Up @@ -594,12 +594,34 @@ def datetime_to_date_str(datetime_):
return datetime_.isoformat()

def date_str_to_datetime(date_str):
'''Takes an ISO format timestamp and returns the equivalent
datetime.datetime object.
'''Convert ISO-like formatted datestring to datetime object.
This function converts ISO format date- and datetime-strings into
datetime objects. Times may be specified down to the microsecond. UTC
offset or timezone information may **not** be included in the string.
Note - Although originally documented as parsing ISO date(-times), this
function doesn't fully adhere to the format. This function will
throw a ValueError if the string contains UTC offset information.
So in that sense, it is less liberal than ISO format. On the
other hand, it is more liberal of the accepted delimiters between
the values in the string. Also, it allows microsecond precision,
despite that not being part of the ISO format.
'''
# Doing this split is more accepting of input variations than doing
# a strptime. Also avoids problem with Python 2.5 not having %f.
return datetime.datetime(*map(int, re.split('[^\d]', date_str)))

time_tuple = re.split('[^\d]+', date_str, maxsplit=5)

# Extract seconds and microseconds
if len(time_tuple) >= 6:
m = re.match('(?P<seconds>\d{2})(\.(?P<microseconds>\d{6}))?$',
time_tuple[5])
if not m:
raise ValueError
seconds = int(m.groupdict().get('seconds'))
microseconds = int(m.groupdict(0).get('microseconds'))
time_tuple = time_tuple[:5] + [seconds, microseconds]

return datetime.datetime(*map(int, time_tuple))

def parse_rfc_2822_date(date_str, assume_utc=True):
"""
Expand Down
21 changes: 20 additions & 1 deletion ckan/tests/lib/test_helpers.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import time
import datetime
from nose.tools import assert_equal
from nose.tools import assert_equal, assert_raises

from pylons import config

Expand Down Expand Up @@ -38,6 +38,10 @@ def test_datetime_to_date_str(self):
res = h.datetime_to_date_str(datetime.datetime(2008, 4, 13, 20, 40, 20, 123456))
assert_equal(res, '2008-04-13T20:40:20.123456')

def test_date_str_to_datetime_date_only(self):
res = h.date_str_to_datetime('2008-04-13')
assert_equal(res, datetime.datetime(2008, 4, 13))

def test_date_str_to_datetime(self):
res = h.date_str_to_datetime('2008-04-13T20:40:20.123456')
assert_equal(res, datetime.datetime(2008, 4, 13, 20, 40, 20, 123456))
Expand All @@ -47,6 +51,21 @@ def test_date_str_to_datetime_without_microseconds(self):
res = h.date_str_to_datetime('2008-04-13T20:40:20')
assert_equal(res, datetime.datetime(2008, 4, 13, 20, 40, 20))

def test_date_str_to_datetime_with_timezone(self):
assert_raises(ValueError,
h.date_str_to_datetime,
'2008-04-13T20:40:20-01:30')

def test_date_str_to_datetime_with_garbage_on_end(self):
assert_raises(ValueError,
h.date_str_to_datetime,
'2008-04-13T20:40:20foobar')

def test_date_str_to_datetime_with_ambiguous_microseconds(self):
assert_raises(ValueError,
h.date_str_to_datetime,
'2008-04-13T20:40:20.500')

def test_time_ago_in_words_from_str(self):
two_months_ago = datetime.datetime.now() - datetime.timedelta(days=65)
two_months_ago_str = h.datetime_to_date_str(two_months_ago)
Expand Down

0 comments on commit d6c9e48

Please sign in to comment.