From 9002dc62d07911217f522887865325692987c8ac Mon Sep 17 00:00:00 2001 From: Ruben Di Battista Date: Thu, 9 Jun 2016 21:47:22 +0200 Subject: [PATCH 1/2] Add microseconds support for DateTimeField A check is introduced in the DateTimeField to understand if microseconds are provided in the datetime string or not. The return value is then handled in accordance to that check. Tests related to the feature implemented are also introduced. Doctest examples are updated too. --- couchdb/mapping.py | 24 +++++++++++++++++------- couchdb/tests/mapping.py | 10 ++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/couchdb/mapping.py b/couchdb/mapping.py index ef90d2db..626aaedc 100644 --- a/couchdb/mapping.py +++ b/couchdb/mapping.py @@ -474,12 +474,16 @@ def _to_json(self, value): class DateTimeField(Field): """Mapping field for storing date/time values. - + >>> field = DateTimeField() >>> field._to_python('2007-04-01T15:30:00Z') datetime.datetime(2007, 4, 1, 15, 30) - >>> field._to_json(datetime(2007, 4, 1, 15, 30, 0, 9876)) + >>> field._to_python('2007-04-01T15:30:00.009876Z') + datetime.datetime(2007, 4, 1, 15, 30, 0, 9876) + >>> field._to_json(datetime(2007, 4, 1, 15, 30, 0)) '2007-04-01T15:30:00Z' + >>> field._to_json(datetime(2007, 4, 1, 15, 30, 0, 9876)) + '2007-04-01T15:30:00.009876Z' >>> field._to_json(date(2007, 4, 1)) '2007-04-01T00:00:00Z' """ @@ -487,9 +491,15 @@ class DateTimeField(Field): def _to_python(self, value): if isinstance(value, util.strbase): try: - value = value.split('.', 1)[0] # strip out microseconds - value = value.rstrip('Z') # remove timezone separator - value = datetime(*strptime(value, '%Y-%m-%dT%H:%M:%S')[:6]) + split_value = value.split('.') # strip out microseconds + if len(split_value) == 1: # No microseconds provided + value = split_value[0] + value = value.rstrip('Z') #remove timezone separator + value = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S') + else: + value = value.rstrip('Z') + value = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f') + except ValueError: raise ValueError('Invalid ISO date/time %r' % value) return value @@ -499,12 +509,12 @@ def _to_json(self, value): value = datetime.utcfromtimestamp(timegm(value)) elif not isinstance(value, datetime): value = datetime.combine(value, time(0)) - return value.replace(microsecond=0).isoformat() + 'Z' + return value.isoformat() + 'Z' class TimeField(Field): """Mapping field for storing times. - + >>> field = TimeField() >>> field._to_python('15:30:00') datetime.time(15, 30) diff --git a/couchdb/tests/mapping.py b/couchdb/tests/mapping.py index b42ab097..2922aba6 100644 --- a/couchdb/tests/mapping.py +++ b/couchdb/tests/mapping.py @@ -11,6 +11,7 @@ from couchdb import design, mapping from couchdb.tests import testutil +from datetime import datetime class DocumentTestCase(testutil.TempDatabaseMixin, unittest.TestCase): @@ -83,6 +84,15 @@ def test_old_datetime(self): dt = mapping.DateTimeField() assert dt._to_python('1880-01-01T00:00:00Z') + def test_datetime_with_microseconds(self): + dt = mapping.DateTimeField() + assert dt._to_python('2016-06-09T21:21:49.739248') + + def test_datetime_to_json(self): + dt = mapping.DateTimeField() + d = datetime.now() + assert dt._to_json(d) + def test_get_has_default(self): doc = mapping.Document() doc.get('foo') From b3975f4fa299c7325a9f3ae7bb64643adaa46415 Mon Sep 17 00:00:00 2001 From: Ruben Di Battista Date: Thu, 9 Jun 2016 21:47:22 +0200 Subject: [PATCH 2/2] Add microseconds support for DateTimeField A check is introduced in the DateTimeField to understand if microseconds are provided in the datetime string or not. The return value is then handled in accordance to that check. Tests related to the feature implemented are also introduced. Doctest examples are updated too. --- couchdb/mapping.py | 24 +++++++++++++++++------- couchdb/tests/mapping.py | 10 ++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/couchdb/mapping.py b/couchdb/mapping.py index ef90d2db..626aaedc 100644 --- a/couchdb/mapping.py +++ b/couchdb/mapping.py @@ -474,12 +474,16 @@ def _to_json(self, value): class DateTimeField(Field): """Mapping field for storing date/time values. - + >>> field = DateTimeField() >>> field._to_python('2007-04-01T15:30:00Z') datetime.datetime(2007, 4, 1, 15, 30) - >>> field._to_json(datetime(2007, 4, 1, 15, 30, 0, 9876)) + >>> field._to_python('2007-04-01T15:30:00.009876Z') + datetime.datetime(2007, 4, 1, 15, 30, 0, 9876) + >>> field._to_json(datetime(2007, 4, 1, 15, 30, 0)) '2007-04-01T15:30:00Z' + >>> field._to_json(datetime(2007, 4, 1, 15, 30, 0, 9876)) + '2007-04-01T15:30:00.009876Z' >>> field._to_json(date(2007, 4, 1)) '2007-04-01T00:00:00Z' """ @@ -487,9 +491,15 @@ class DateTimeField(Field): def _to_python(self, value): if isinstance(value, util.strbase): try: - value = value.split('.', 1)[0] # strip out microseconds - value = value.rstrip('Z') # remove timezone separator - value = datetime(*strptime(value, '%Y-%m-%dT%H:%M:%S')[:6]) + split_value = value.split('.') # strip out microseconds + if len(split_value) == 1: # No microseconds provided + value = split_value[0] + value = value.rstrip('Z') #remove timezone separator + value = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S') + else: + value = value.rstrip('Z') + value = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f') + except ValueError: raise ValueError('Invalid ISO date/time %r' % value) return value @@ -499,12 +509,12 @@ def _to_json(self, value): value = datetime.utcfromtimestamp(timegm(value)) elif not isinstance(value, datetime): value = datetime.combine(value, time(0)) - return value.replace(microsecond=0).isoformat() + 'Z' + return value.isoformat() + 'Z' class TimeField(Field): """Mapping field for storing times. - + >>> field = TimeField() >>> field._to_python('15:30:00') datetime.time(15, 30) diff --git a/couchdb/tests/mapping.py b/couchdb/tests/mapping.py index b42ab097..7f49b690 100644 --- a/couchdb/tests/mapping.py +++ b/couchdb/tests/mapping.py @@ -11,6 +11,7 @@ from couchdb import design, mapping from couchdb.tests import testutil +from datetime import datetime class DocumentTestCase(testutil.TempDatabaseMixin, unittest.TestCase): @@ -83,6 +84,15 @@ def test_old_datetime(self): dt = mapping.DateTimeField() assert dt._to_python('1880-01-01T00:00:00Z') + def test_datetime_with_microseconds(self): + dt = mapping.DateTimeField() + assert dt._to_python('2016-06-09T21:21:49.739248Z') + + def test_datetime_to_json(self): + dt = mapping.DateTimeField() + d = datetime.now() + assert dt._to_json(d) + def test_get_has_default(self): doc = mapping.Document() doc.get('foo')