Merge pull request #911 from jimmyshen/complexdatetime-microsecond-bug

Fixed microsecond-level ordering/filtering bug with ComplexDateTimeField
This commit is contained in:
Omer Katz 2015-04-19 12:40:01 +03:00
commit 7e1a5ce445
3 changed files with 47 additions and 30 deletions

View File

@ -10,6 +10,7 @@ Changes in 0.9.X - DEV
- Support += and *= for ListField #595
- Use sets for populating dbrefs to dereference
- Fixed unpickled documents replacing the global field's list. #888
- Fixed storage of microseconds in ComplexDateTimeField and unused separator option. #910
Changes in 0.9.0
================

View File

@ -458,37 +458,22 @@ class ComplexDateTimeField(StringField):
"""
def __init__(self, separator=',', **kwargs):
self.names = ['year', 'month', 'day', 'hour', 'minute', 'second',
'microsecond']
self.separtor = separator
self.names = ['year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond']
self.separator = separator
self.format = separator.join(['%Y', '%m', '%d', '%H', '%M', '%S', '%f'])
super(ComplexDateTimeField, self).__init__(**kwargs)
def _leading_zero(self, number):
"""
Converts the given number to a string.
If it has only one digit, a leading zero so as it has always at least
two digits.
"""
if int(number) < 10:
return "0%s" % number
else:
return str(number)
def _convert_from_datetime(self, val):
"""
Convert a `datetime` object to a string representation (which will be
stored in MongoDB). This is the reverse function of
`_convert_from_string`.
>>> a = datetime(2011, 6, 8, 20, 26, 24, 192284)
>>> RealDateTimeField()._convert_from_datetime(a)
'2011,06,08,20,26,24,192284'
>>> a = datetime(2011, 6, 8, 20, 26, 24, 92284)
>>> ComplexDateTimeField()._convert_from_datetime(a)
'2011,06,08,20,26,24,092284'
"""
data = []
for name in self.names:
data.append(self._leading_zero(getattr(val, name)))
return ','.join(data)
return val.strftime(self.format)
def _convert_from_string(self, data):
"""
@ -496,16 +481,12 @@ class ComplexDateTimeField(StringField):
will manipulate). This is the reverse function of
`_convert_from_datetime`.
>>> a = '2011,06,08,20,26,24,192284'
>>> a = '2011,06,08,20,26,24,092284'
>>> ComplexDateTimeField()._convert_from_string(a)
datetime.datetime(2011, 6, 8, 20, 26, 24, 192284)
datetime.datetime(2011, 6, 8, 20, 26, 24, 92284)
"""
data = data.split(',')
data = map(int, data)
values = {}
for i in range(7):
values[self.names[i]] = data[i]
return datetime.datetime(**values)
values = map(int, data.split(self.separator))
return datetime.datetime(*values)
def __get__(self, instance, owner):
data = super(ComplexDateTimeField, self).__get__(instance, owner)

View File

@ -5,6 +5,9 @@ sys.path[0:0] = [""]
import datetime
import unittest
import uuid
import math
import itertools
import re
try:
import dateutil
@ -689,6 +692,7 @@ class FieldTest(unittest.TestCase):
"""
class LogEntry(Document):
date = ComplexDateTimeField()
date_with_dots = ComplexDateTimeField(separator='.')
LogEntry.drop_collection()
@ -729,6 +733,18 @@ class FieldTest(unittest.TestCase):
log1 = LogEntry.objects.get(date=d1)
self.assertEqual(log, log1)
# Test string padding
microsecond = map(int, [math.pow(10, x) for x in xrange(6)])
mm = dd = hh = ii = ss = [1, 10]
for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond):
stored = LogEntry(date=datetime.datetime(*values)).to_mongo()['date']
self.assertTrue(re.match('^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$', stored) is not None)
# Test separator
stored = LogEntry(date_with_dots=datetime.datetime(2014, 1, 1)).to_mongo()['date_with_dots']
self.assertTrue(re.match('^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$', stored) is not None)
LogEntry.drop_collection()
def test_complexdatetime_usage(self):
@ -787,6 +803,25 @@ class FieldTest(unittest.TestCase):
LogEntry.drop_collection()
# Test microsecond-level ordering/filtering
for microsecond in (99, 999, 9999, 10000):
LogEntry(date=datetime.datetime(2015, 1, 1, 0, 0, 0, microsecond)).save()
logs = list(LogEntry.objects.order_by('date'))
for next_idx, log in enumerate(logs[:-1], start=1):
next_log = logs[next_idx]
self.assertTrue(log.date < next_log.date)
logs = list(LogEntry.objects.order_by('-date'))
for next_idx, log in enumerate(logs[:-1], start=1):
next_log = logs[next_idx]
self.assertTrue(log.date > next_log.date)
logs = LogEntry.objects.filter(date__lte=datetime.datetime(2015, 1, 1, 0, 0, 0, 10000))
self.assertEqual(logs.count(), 4)
LogEntry.drop_collection()
def test_list_validation(self):
"""Ensure that a list field only accepts lists with valid elements.
"""