Skip to content

Commit f1770aa

Browse files
author
Ralf Schlatterbeck
committed
Fix arbitrary limit on dates.
Dates can now be in the year-range 1-9999 for all backends except metakit which is still limited to 1970-2038.
1 parent 1a8c938 commit f1770aa

File tree

4 files changed

+68
-29
lines changed

4 files changed

+68
-29
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ This file contains the changes to the Roundup system over time. The entries
22
are given with the most recent entry first.
33

44
2007-??-??
5+
Feature:
6+
- Dates can now be in the year-range 1-9999 except for metakit which is
7+
still limited to 1970-2038.
58
Fixed:
69
- Handling of unset Link search in RDBMS backend
710

roundup/date.py

Lines changed: 15 additions & 25 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.89 2006-12-28 22:38:02 richard Exp $
18+
# $Id: date.py,v 1.90 2007-03-09 10:25:09 schlatterbeck Exp $
1919

2020
"""Date, time and time interval handling.
2121
"""
@@ -263,7 +263,6 @@ def __init__(self, spec='.', offset=0, add_granularity=0, translator=i18n):
263263
elif isinstance(spec, datetime.datetime):
264264
# Python 2.3+ datetime object
265265
y,m,d,H,M,S,x,x,x = spec.timetuple()
266-
if y < 1970: raise ValueError, 'year must be > 1970'
267266
S += spec.microsecond/1000000.
268267
spec = (y,m,d,H,M,S,x,x,x)
269268
elif hasattr(spec, 'tuple'):
@@ -272,7 +271,6 @@ def __init__(self, spec='.', offset=0, add_granularity=0, translator=i18n):
272271
spec = spec.get_tuple()
273272
try:
274273
y,m,d,H,M,S,x,x,x = spec
275-
if y < 1970: raise ValueError, 'year must be > 1970'
276274
frac = S - int(S)
277275
self.year, self.month, self.day, self.hour, self.minute, \
278276
self.second = _local_to_utc(y, m, d, H, M, S, offset)
@@ -309,20 +307,16 @@ def set(self, spec, offset=0, date_re=date_re,
309307
_add_granularity(info, 'SMHdmyab')
310308

311309
# get the current date as our default
312-
ts = time.time()
313-
frac = ts - int(ts)
314-
y,m,d,H,M,S,x,x,x = time.gmtime(ts)
315-
# gmtime loses the fractional seconds
316-
S = S + frac
317-
if str(S) == '60.0': S = 59.9
310+
dt = datetime.datetime.utcnow()
311+
y,m,d,H,M,S,x,x,x = dt.timetuple()
312+
S += dt.microsecond/1000000.
318313

319314
# whether we need to convert to UTC
320315
adjust = False
321316

322317
if info['y'] is not None or info['a'] is not None:
323318
if info['y'] is not None:
324319
y = int(info['y'])
325-
if y < 1970: raise ValueError, 'year must be > 1970'
326320
m,d = (1,1)
327321
if info['m'] is not None:
328322
m = int(info['m'])
@@ -344,20 +338,19 @@ def set(self, spec, offset=0, date_re=date_re,
344338
S = float(info['S'])
345339
adjust = True
346340

347-
if add_granularity:
348-
S = S - 1
349341

350342
# now handle the adjustment of hour
351343
frac = S - int(S)
352-
ts = calendar.timegm((y,m,d,H,M,S,0,0,0))
353-
y, m, d, H, M, S, x, x, x = time.gmtime(ts)
344+
dt = datetime.datetime(y,m,d,H,M,int(S), int(frac * 1000000.))
345+
if add_granularity:
346+
dt = dt - datetime.timedelta(seconds=1)
347+
y, m, d, H, M, S, x, x, x = dt.timetuple()
354348
if adjust:
355349
y, m, d, H, M, S = _local_to_utc(y, m, d, H, M, S, offset)
356350
self.year, self.month, self.day, self.hour, self.minute, \
357351
self.second = y, m, d, H, M, S
358352
# we lost the fractional part along the way
359-
self.second = self.second + frac
360-
if str(self.second) == '60.0': self.second = 59.9
353+
self.second += dt.microsecond/1000000.
361354

362355
if info.get('o', None):
363356
try:
@@ -493,7 +486,7 @@ def __str__(self):
493486
return self.formal()
494487

495488
def formal(self, sep='.', sec='%02d'):
496-
f = '%%4d-%%02d-%%02d%s%%02d:%%02d:%s'%(sep, sec)
489+
f = '%%04d-%%02d-%%02d%s%%02d:%%02d:%s'%(sep, sec)
497490
return f%(self.year, self.month, self.day, self.hour, self.minute,
498491
self.second)
499492

@@ -503,13 +496,10 @@ def pretty(self, format='%d %B %Y'):
503496
Note that if the day is zero, and the day appears first in the
504497
format, then the day number will be removed from output.
505498
'''
506-
# Python2.4 strftime() enforces the non-zero-ness of the day-of-year
507-
# component of the time tuple, so we need to figure it out
508-
t = (self.year, self.month, self.day, self.hour, self.minute,
509-
int(self.second), 0, 0, 0)
510-
t = calendar.timegm(t)
511-
t = time.gmtime(t)
512-
str = time.strftime(format, t)
499+
dt = datetime.datetime(self.year, self.month, self.day, self.hour,
500+
self.minute, int(self.second),
501+
(self.second - int (self.second)) * 1000000.)
502+
str = dt.strftime(format)
513503

514504
# handle zero day by removing it
515505
if format.startswith('%d') and str[0] == '0':
@@ -535,7 +525,7 @@ def get_tuple(self):
535525
self.second, 0, 0, 0)
536526

537527
def serialise(self):
538-
return '%4d%02d%02d%02d%02d%06.3f'%(self.year, self.month,
528+
return '%04d%02d%02d%02d%02d%06.3f'%(self.year, self.month,
539529
self.day, self.hour, self.minute, self.second)
540530

541531
def timestamp(self):

test/db_test_base.py

Lines changed: 10 additions & 1 deletion
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: db_test_base.py,v 1.82 2006-11-11 03:21:12 richard Exp $
18+
# $Id: db_test_base.py,v 1.83 2007-03-09 10:25:10 schlatterbeck Exp $
1919

2020
import unittest, os, shutil, errno, imp, sys, time, pprint, sets
2121

@@ -277,6 +277,15 @@ def testDateChange(self):
277277
if commit: self.db.commit()
278278
self.assertNotEqual(a, b)
279279
self.assertNotEqual(b, date.Date('1970-1-1.00:00:00'))
280+
# The 1970 date will fail for metakit -- it is used
281+
# internally for storing NULL. The others would, too
282+
# because metakit tries to convert date.timestamp to an int
283+
# for storing and fails with an overflow.
284+
for d in [date.Date (x) for x in '2038', '1970', '0033', '9999']:
285+
self.db.issue.set(nid, deadline=d)
286+
if commit: self.db.commit()
287+
c = self.db.issue.get(nid, "deadline")
288+
self.assertEqual(c, d)
280289

281290
def testDateUnset(self):
282291
for commit in (0,1):

test/test_dates.py

Lines changed: 40 additions & 3 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.39 2006-05-06 17:21:34 a1s Exp $
18+
# $Id: test_dates.py,v 1.40 2007-03-09 10:25:10 schlatterbeck Exp $
1919
from __future__ import nested_scopes
2020

2121
import unittest, time
@@ -59,11 +59,13 @@ def testDate(self):
5959
ae(str(date), '%s-%02d-%02d.08:47:11'%(y, m, d))
6060
ae(str(Date('2003')), '2003-01-01.00:00:00')
6161
ae(str(Date('2004-06')), '2004-06-01.00:00:00')
62+
ae(str(Date('1900-02-01')), '1900-02-01.00:00:00')
63+
ae(str(Date('1800-07-15')), '1800-07-15.00:00:00')
6264

6365
def testDateError(self):
6466
self.assertRaises(ValueError, Date, "12")
65-
# Date cannot handle dates before UNIX epoch
66-
self.assertRaises(ValueError, Date, (1, 1, 1, 0, 0, 0.0, 0, 1, -1))
67+
# Date cannot handle dates before year 1
68+
self.assertRaises(ValueError, Date, (0, 1, 1, 0, 0, 0.0, 0, 1, -1))
6769
self.assertRaises(ValueError, Date, "1/1/06")
6870

6971
def testOffset(self):
@@ -128,6 +130,8 @@ def testOffsetAdd(self):
128130
ae(str(date), '2000-02-29.00:00:00')
129131
date = Date('2001-02-28.22:58:59') + Interval('00:00:3661')
130132
ae(str(date), '2001-03-01.00:00:00')
133+
date = Date('2001-03-01.00:00:00') + Interval('150y')
134+
ae(str(date), '2151-03-01.00:00:00')
131135

132136
def testOffsetSub(self):
133137
ae = self.assertEqual
@@ -162,6 +166,8 @@ def testOffsetSub(self):
162166
ae(str(date), '2000-02-28.22:58:59')
163167
date = Date('2001-03-01.00:00:00') - Interval('00:00:3661')
164168
ae(str(date), '2001-02-28.22:58:59')
169+
date = Date('2001-03-01.00:00:00') - Interval('150y')
170+
ae(str(date), '1851-03-01.00:00:00')
165171

166172
def testDateLocal(self):
167173
ae = self.assertEqual
@@ -269,6 +275,10 @@ def testDateSubtract(self):
269275
# force the transition over a year boundary
270276
i = Date('2003-01-01.00:00:00') - Date('2002-01-01.00:00:00')
271277
self.assertEqual(i, Interval('365d'))
278+
i = Date('1952-01-01') - Date('1953-01-01')
279+
self.assertEqual(i, Interval('-366d'))
280+
i = Date('1953-01-01') - Date('1952-01-01')
281+
self.assertEqual(i, Interval('366d'))
272282

273283
def testIntervalAdd(self):
274284
ae = self.assertEqual
@@ -389,6 +399,8 @@ def testPyDatetime(self):
389399
return
390400
d = datetime.datetime.now()
391401
Date(d)
402+
toomuch = datetime.MAXYEAR + 1
403+
self.assertRaises(ValueError, Date, (toomuch, 1, 1, 0, 0, 0, 0, 1, -1))
392404

393405
def testSimpleTZ(self):
394406
ae = self.assertEqual
@@ -404,6 +416,31 @@ def testSimpleTZ(self):
404416
date = Date(date, 2)
405417
ae(str(date), '2006-04-04.10:00:00')
406418

419+
def testTimestamp(self):
420+
ae = self.assertEqual
421+
date = Date('2038')
422+
ae(date.timestamp(), 2145916800)
423+
date = Date('1902')
424+
ae(date.timestamp(), -2145916800)
425+
try:
426+
from time import gmtime
427+
except:
428+
return
429+
date = Date(gmtime(0))
430+
ae(date.timestamp(), 0)
431+
ae(str(date), '1970-01-01.00:00:00')
432+
date = Date(gmtime(0x7FFFFFFF))
433+
ae(date.timestamp(), 2147483647)
434+
ae(str(date), '2038-01-19.03:14:07')
435+
date = Date('1901-12-13.20:45:52')
436+
ae(date.timestamp(), -0x80000000L)
437+
ae(str(date), '1901-12-13.20:45:52')
438+
date = Date('9999')
439+
ae (date.timestamp(), 253370764800.0)
440+
date = Date('0033')
441+
ae (date.timestamp(), -61125753600.0)
442+
ae(str(date), '0033-01-01.00:00:00')
443+
407444
class TimezoneTestCase(unittest.TestCase):
408445

409446
def testTZ(self):

0 commit comments

Comments
 (0)