Skip to content

Commit 6ff2e1d

Browse files
author
Anthony Baxter
committed
backport of Date arithmetic fixes from trunk
1 parent 7a19883 commit 6ff2e1d

File tree

3 files changed

+118
-28
lines changed

3 files changed

+118
-28
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Fixed:
88
- added script to help migrating queries from pre-0.6 trackers
99
- Fixed "documentation" of getnodeids in roundup.hyperdb
1010
- added flush() to DevNull (sf bug #835365)
11+
- Date arithmetic was utterly broken, and has been for a long time.
12+
Date +/- Interval now works, and Date - Date also works (produces
13+
an Interval.
1114

1215

1316
2003-09-29 0.6.2

roundup/date.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: date.py,v 1.54 2003-04-23 11:48:05 richard Exp $
18+
# $Id: date.py,v 1.54.2.1 2003-11-04 12:39:54 anthonybaxter Exp $
1919

2020
__doc__ = """
2121
Date, time and time interval handling.
@@ -204,24 +204,31 @@ def addInterval(self, interval):
204204
if month > 12: year += 1; month -= 12
205205

206206
# now do the days, now that we know what month we're in
207-
mdays = calendar.mdays
208-
if month == 2 and calendar.isleap(year): month_days = 29
209-
else: month_days = mdays[month]
210-
while month < 1 or month > 12 or day < 0 or day > month_days:
207+
def get_mdays(year,month):
208+
if month == 2 and calendar.isleap(year): return 29
209+
else: return calendar.mdays[month]
210+
211+
while month < 1 or month > 12 or day < 0 or day > get_mdays(year,month):
211212
# now to day under/over
212-
if day < 0: month -= 1; day += month_days
213-
elif day > month_days: month += 1; day -= month_days
213+
if day < 0:
214+
# When going backwards, decrement month, then increment days
215+
month -= 1
216+
day += get_mdays(year,month)
217+
elif day > get_mdays(year,month):
218+
# When going forwards, decrement days, then increment month
219+
day -= get_mdays(year,month)
220+
month += 1
214221

215222
# possibly fix up the month so we're within range
216223
while month < 1 or month > 12:
217-
if month < 1: year -= 1; month += 12
224+
if month < 1: year -= 1; month += 12 ; day += 31
218225
if month > 12: year += 1; month -= 12
219226

220-
# re-figure the number of days for this month
221-
if month == 2 and calendar.isleap(year): month_days = 29
222-
else: month_days = mdays[month]
223227
return (year, month, day, hour, minute, second, 0, 0, 0)
224228

229+
def differenceDate(self, other):
230+
"Return the difference between this date and another date"
231+
225232
def applyInterval(self, interval):
226233
''' Apply the interval to this date
227234
'''
@@ -246,29 +253,29 @@ def __sub__(self, other):
246253

247254
assert isinstance(other, Date), 'May only subtract Dates or Intervals'
248255

249-
# TODO this code will fall over laughing if the dates cross
250-
# leap years, phases of the moon, ....
256+
return self.dateDelta(other)
257+
258+
def dateDelta(self, other):
259+
""" Produce an Interval of the difference between this date
260+
and another date. Only returns days:hours:minutes:seconds.
261+
"""
262+
# Returning intervals larger than a day is almost
263+
# impossible - months, years, weeks, are all so imprecise.
251264
a = calendar.timegm((self.year, self.month, self.day, self.hour,
252265
self.minute, self.second, 0, 0, 0))
253266
b = calendar.timegm((other.year, other.month, other.day,
254267
other.hour, other.minute, other.second, 0, 0, 0))
255268
diff = a - b
256-
if diff < 0:
269+
if diff > 0:
257270
sign = 1
258-
diff = -diff
259271
else:
260272
sign = -1
273+
diff = -diff
261274
S = diff%60
262275
M = (diff/60)%60
263-
H = (diff/(60*60))%60
264-
if H>1: S = 0
265-
d = (diff/(24*60*60))%30
266-
if d>1: H = S = M = 0
267-
m = (diff/(30*24*60*60))%12
268-
if m>1: H = S = M = 0
269-
y = (diff/(365*24*60*60))
270-
if y>1: d = H = S = M = 0
271-
return Interval((y, m, d, H, M, S), sign=sign)
276+
H = (diff/(60*60))%24
277+
d = diff/(24*60*60)
278+
return Interval((0, 0, d, H, M, S), sign=sign)
272279

273280
def __cmp__(self, other):
274281
"""Compare this date to another date."""

test/test_dates.py

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: test_dates.py,v 1.24 2003-04-22 20:53:54 kedder Exp $
18+
# $Id: test_dates.py,v 1.24.2.1 2003-11-04 12:39:54 anthonybaxter Exp $
1919

2020
import unittest, time
2121

@@ -173,9 +173,89 @@ def testIntervalInitDate(self):
173173
now = Date('.')
174174
now.hour = now.minute = now.second = 0
175175
then = now + Interval('2d')
176-
ae(str(Interval(str(then))), '+ 2d')
176+
ae((Interval(str(then))), Interval('- 2d'))
177177
then = now - Interval('2d')
178-
ae(str(Interval(str(then))), '- 2d')
178+
ae(Interval(str(then)), Interval('+ 2d'))
179+
180+
def testIntervalAddMonthBoundary(self):
181+
# force the transition over a month boundary
182+
now = Date('2003-10-30.00:00:00')
183+
then = now + Interval('2d')
184+
self.assertEqual(str(then), '2003-11-01.00:00:00')
185+
now = Date('2004-02-28.00:00:00')
186+
then = now + Interval('1d')
187+
self.assertEqual(str(then), '2004-02-29.00:00:00')
188+
now = Date('2003-02-28.00:00:00')
189+
then = now + Interval('1d')
190+
self.assertEqual(str(then), '2003-03-01.00:00:00')
191+
now = Date('2003-01-01.00:00:00')
192+
then = now + Interval('59d')
193+
self.assertEqual(str(then), '2003-03-01.00:00:00')
194+
now = Date('2004-01-01.00:00:00')
195+
then = now + Interval('59d')
196+
self.assertEqual(str(then), '2004-02-29.00:00:00')
197+
198+
def testIntervalSubtractMonthBoundary(self):
199+
# force the transition over a month boundary
200+
now = Date('2003-11-01.00:00:00')
201+
then = now - Interval('2d')
202+
self.assertEqual(str(then), '2003-10-30.00:00:00')
203+
now = Date('2004-02-29.00:00:00')
204+
then = now - Interval('1d')
205+
self.assertEqual(str(then), '2004-02-28.00:00:00')
206+
now = Date('2003-03-01.00:00:00')
207+
then = now - Interval('1d')
208+
self.assertEqual(str(then), '2003-02-28.00:00:00')
209+
now = Date('2003-03-01.00:00:00')
210+
then = now - Interval('59d')
211+
self.assertEqual(str(then), '2003-01-01.00:00:00')
212+
now = Date('2004-02-29.00:00:00')
213+
then = now - Interval('59d')
214+
self.assertEqual(str(then), '2004-01-01.00:00:00')
215+
216+
def testIntervalAddYearBoundary(self):
217+
# force the transition over a year boundary
218+
now = Date('2003-12-30.00:00:00')
219+
then = now + Interval('2d')
220+
self.assertEqual(str(then), '2004-01-01.00:00:00')
221+
now = Date('2003-01-01.00:00:00')
222+
then = now + Interval('365d')
223+
self.assertEqual(str(then), '2004-01-01.00:00:00')
224+
now = Date('2004-01-01.00:00:00')
225+
then = now + Interval('366d')
226+
self.assertEqual(str(then), '2005-01-01.00:00:00')
227+
228+
def testIntervalSubtractYearBoundary(self):
229+
# force the transition over a year boundary
230+
now = Date('2003-01-01.00:00:00')
231+
then = now - Interval('2d')
232+
self.assertEqual(str(then), '2002-12-30.00:00:00')
233+
now = Date('2004-02-01.00:00:00')
234+
then = now - Interval('365d')
235+
self.assertEqual(str(then), '2003-02-01.00:00:00')
236+
now = Date('2005-02-01.00:00:00')
237+
then = now - Interval('365d')
238+
self.assertEqual(str(then), '2004-02-02.00:00:00')
239+
240+
def testDateSubtract(self):
241+
# These are thoroughly broken right now.
242+
i = Date('2003-03-15.00:00:00') - Date('2003-03-10.00:00:00')
243+
self.assertEqual(i, Interval('5d'))
244+
i = Date('2003-02-01.00:00:00') - Date('2003-03-01.00:00:00')
245+
self.assertEqual(i, Interval('-28d'))
246+
i = Date('2003-03-01.00:00:00') - Date('2003-02-01.00:00:00')
247+
self.assertEqual(i, Interval('28d'))
248+
i = Date('2003-03-03.00:00:00') - Date('2003-02-01.00:00:00')
249+
self.assertEqual(i, Interval('30d'))
250+
i = Date('2003-03-03.00:00:00') - Date('2002-02-01.00:00:00')
251+
self.assertEqual(i, Interval('395d'))
252+
i = Date('2003-03-03.00:00:00') - Date('2003-04-01.00:00:00')
253+
self.assertEqual(i, Interval('-29d'))
254+
i = Date('2003-03-01.00:00:00') - Date('2003-02-01.00:00:00')
255+
self.assertEqual(i, Interval('28d'))
256+
# force the transition over a year boundary
257+
i = Date('2003-01-01.00:00:00') - Date('2002-01-01.00:00:00')
258+
self.assertEqual(i, Interval('365d'))
179259

180260
def testIntervalAdd(self):
181261
ae = self.assertEqual
@@ -257,7 +337,7 @@ def testGranularity(self):
257337
ae(str(Interval('-2m 3w', add_granularity=1)), '- 2m 14d')
258338

259339
def suite():
260-
return unittest.makeSuite(DateTestCase, 'test')
340+
return unittest.makeSuite(DateTestCase, 'test')
261341

262342

263343
# vim: set filetype=python ts=4 sw=4 et si

0 commit comments

Comments
 (0)