Skip to content

Commit

Permalink
Reordered some calculations in _calcIndependentSecondEtc to preserv…
Browse files Browse the repository at this point in the history
…e more floating point precision. Optimized the pickled data, by only storing a tuple of `_micros` and time zone information - this reduces the pickle size from an average of 300 bytes to just 60 bytes.
  • Loading branch information
hannosch committed May 6, 2011
1 parent fff6d04 commit a44f3b5
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 16 deletions.
7 changes: 7 additions & 0 deletions CHANGES.txt
Expand Up @@ -4,6 +4,13 @@ Changelog
2.13.0 (unreleased)
-------------------

- Reordered some calculations in `_calcIndependentSecondEtc` to preserve more
floating point precision.

- Optimized the pickled data, by only storing a tuple of `_micros` and time
zone information - this reduces the pickle size from an average of 300 bytes
to just 60 bytes.

- Optimized un-pickling, by avoiding the creation of an intermediate DateTime
value representing the current time.

Expand Down
22 changes: 17 additions & 5 deletions src/DateTime/DateTime.py
Expand Up @@ -161,9 +161,8 @@ def _calcIndependentSecondEtc(tz, x, ms):
# nearTime is now within an hour of being correct.
# Recalculate t according to DST.
fset = long(_tzoffset(tz, nearTime))
x_adjusted = x - fset + ms
d = x_adjusted / 86400.0
t = x_adjusted - long(EPOCH) + 86400L
d = (x - fset) / 86400.0 + (ms / 86400.0)
t = x - fset - long(EPOCH) + 86400L + ms
micros = (x + 86400 - fset) * 1000000 + \
long(round(ms * 1000000.0)) - long(EPOCH * 1000000.0)
s = d - math.floor(d)
Expand Down Expand Up @@ -349,8 +348,21 @@ def __init__(self, *args, **kw):
except Exception:
raise SyntaxError('Unable to parse %s, %s' % (args, kw))

def __getinitargs__(self):
return (None, )
def __getstate__(self):
state = self.__dict__
return (state['_micros'] / 1000000.0,
state.get('_timezone_naive', False),
state['_tz'])

def __setstate__(self, value):
state = self.__dict__
if isinstance(value, tuple):
self._parse_args(value[0], value[2])
state['_micros'] = long(value[0] * 1000000)
state['_timezone_naive'] = value[1]
else:
state.clear()
state.update(value)

def _parse_args(self, *args, **kw):
"""Return a new date-time object.
Expand Down
43 changes: 32 additions & 11 deletions src/DateTime/tests/testDateTime.py
Expand Up @@ -12,6 +12,7 @@
#
##############################################################################

import cPickle
import math
import os
import time
Expand Down Expand Up @@ -55,18 +56,15 @@ def dst(self, dt):

class DateTimeTests(unittest.TestCase):

def _compare(self, dt1, dt2, ms=1):
def _compare(self, dt1, dt2):
'''Compares the internal representation of dt1 with
the representation in dt2. Allows sub-millisecond variations.
Primarily for testing.'''
if ms:
self.assertEqual(dt1.millis(), dt2.millis())
self.assertEqual(math.floor(dt1._t * 1000.0),
math.floor(dt2._t * 1000.0))
self.assertEqual(math.floor(dt1._d * 86400000.0),
math.floor(dt2._d * 86400000.0))
self.assertEqual(math.floor(dt1.time * 86400000.0),
math.floor(dt2.time * 86400000.0))
self.assertEqual(round(dt1._t, 3), round(dt2._t, 3))
self.assertEqual(round(dt1._d, 9), round(dt2._d, 9))
self.assertEqual(round(dt1.time, 9), round(dt2.time, 9))
self.assertEqual(dt1.millis(), dt2.millis())
self.assertEqual(dt1._micros, dt2._micros)

def testBug1203(self):
# 01:59:60 occurred in old DateTime
Expand Down Expand Up @@ -201,17 +199,40 @@ def testCompareMethods(self):
self.failUnless(not dt.equalTo(dt1))

def test_pickle(self):
import cPickle
dt = DateTime()
data = cPickle.dumps(dt, 1)
new = cPickle.loads(data)
self.assertEqual(dt.__dict__, new.__dict__)

dt = DateTime('2002/5/2 8:00am GMT+0')
def test_pickle_with_tz(self):
dt = DateTime('2002/5/2 8:00am GMT+8')
data = cPickle.dumps(dt, 1)
new = cPickle.loads(data)
self.assertEqual(dt.__dict__, new.__dict__)

def test_pickle_with_micros(self):
dt = DateTime('2002/5/2 8:00:14.123 GMT+8')
data = cPickle.dumps(dt, 1)
new = cPickle.loads(data)
self.assertEqual(dt.__dict__, new.__dict__)

def test_pickle_old(self):
dt = DateTime('2002/5/2 8:00am GMT+0')
data = ('(cDateTime.DateTime\nDateTime\nq\x01Noq\x02}q\x03(U\x05_amonq'
'\x04U\x03Mayq\x05U\x05_adayq\x06U\x03Thuq\x07U\x05_pmonq\x08h'
'\x05U\x05_hourq\tK\x08U\x05_fmonq\nh\x05U\x05_pdayq\x0bU\x04T'
'hu.q\x0cU\x05_fdayq\rU\x08Thursdayq\x0eU\x03_pmq\x0fU\x02amq'
'\x10U\x02_tq\x11GA\xcehy\x00\x00\x00\x00U\x07_minuteq\x12K\x00U'
'\x07_microsq\x13L1020326400000000L\nU\x02_dq\x14G@\xe2\x12j\xaa'
'\xaa\xaa\xabU\x07_secondq\x15G\x00\x00\x00\x00\x00\x00\x00\x00U'
'\x03_tzq\x16U\x05GMT+0q\x17U\x06_monthq\x18K\x05U'
'\x0f_timezone_naiveq\x19I00\nU\x04_dayq\x1aK\x02U\x05_yearq'
'\x1bM\xd2\x07U\x08_nearsecq\x1cG\x00\x00\x00\x00\x00\x00\x00'
'\x00U\x07_pmhourq\x1dK\x08U\n_dayoffsetq\x1eK\x04U\x04timeq'
'\x1fG?\xd5UUUV\x00\x00ub.')
new = cPickle.loads(data)
self.assertEqual(dt.__dict__, new.__dict__)

def testTZ2(self):
# Time zone manipulation test 2
dt = DateTime()
Expand Down

0 comments on commit a44f3b5

Please sign in to comment.