summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: ced9498)
raw | patch | inline | side by side (parent: ced9498)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Thu, 21 Feb 2002 23:11:45 +0000 (23:11 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Thu, 21 Feb 2002 23:11:45 +0000 (23:11 +0000) |
and under-flow). Also, hour/minute/second intervals may now be more than
99 each.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@652 57a73879-2fb5-44c3-a270-3262357dd7e2
99 each.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@652 57a73879-2fb5-44c3-a270-3262357dd7e2
diff --git a/CHANGES.txt b/CHANGES.txt
index ac1f47e599bf086bb4bd475fe5f521a35e37c6e7..af4064ee9debd3a694c6b1844c8021dc53bfa2a0 100644 (file)
--- a/CHANGES.txt
+++ b/CHANGES.txt
. #517906 ] Attribute order in "View customisation"
. #514854 ] History: "User" is always ticket creator
. wasn't handling cvs parser feeding correctly
+ . fixed some problems in date calculations (calendar.py doesn't handle over-
+ and under-flow). Also, hour/minute/second intervals may now be more than
+ 99 each.
2002-01-24 - 0.4.0
diff --git a/roundup/date.py b/roundup/date.py
index d9ac75815bbceb8fbb93962b5698dbdd8366905a..c707e6830d96f408149720c8dbea582fb9159293 100644 (file)
--- a/roundup/date.py
+++ b/roundup/date.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: date.py,v 1.18 2002-01-23 20:00:50 jhermann Exp $
+# $Id: date.py,v 1.19 2002-02-21 23:11:45 richard Exp $
__doc__ = """
Date, time and time interval handling.
self.second, x, x, x = time.gmtime(calendar.timegm(t))
def __add__(self, other):
- """Add an interval to this date to produce another date."""
- t = (self.year + other.sign * other.year,
- self.month + other.sign * other.month,
- self.day + other.sign * other.day,
- self.hour + other.sign * other.hour,
- self.minute + other.sign * other.minute,
- self.second + other.sign * other.second, 0, 0, 0)
- return Date(time.gmtime(calendar.timegm(t)))
+ """Add an interval to this date to produce another date.
+ """
+ # do the basic calc
+ sign = other.sign
+ year = self.year + sign * other.year
+ month = self.month + sign * other.month
+ day = self.day + sign * other.day
+ hour = self.hour + sign * other.hour
+ minute = self.minute + sign * other.minute
+ second = self.second + sign * other.second
+
+ # now cope with under- and over-flow
+ # first do the time
+ while (second < 0 or second > 59 or minute < 0 or minute > 59 or
+ hour < 0 or hour > 59):
+ if second < 0: minute -= 1; second += 60
+ elif second > 59: minute += 1; second -= 60
+ if minute < 0: hour -= 1; minute += 60
+ elif minute > 59: hour += 1; minute -= 60
+ if hour < 0: day -= 1; hour += 60
+ elif hour > 59: day += 1; hour -= 60
+
+ # fix up the month so we're within range
+ while month < 1 or month > 12:
+ if month < 1: year -= 1; month += 12
+ if month > 12: year += 1; month -= 12
+
+ # now do the days, now that we know what month we're in
+ mdays = calendar.mdays
+ if month == 2 and calendar.isleap(year): month_days = 29
+ else: month_days = mdays[month]
+ while month < 1 or month > 12 or day < 0 or day > month_days:
+ # now to day under/over
+ if day < 0: month -= 1; day += month_days
+ elif day > month_days: month += 1; day -= month_days
+
+ # possibly fix up the month so we're within range
+ while month < 1 or month > 12:
+ if month < 1: year -= 1; month += 12
+ if month > 12: year += 1; month -= 12
+
+ # re-figure the number of days for this month
+ if month == 2 and calendar.isleap(year): month_days = 29
+ else: month_days = mdays[month]
+
+ return Date((year, month, day, hour, minute, second, 0, 0, 0))
# XXX deviates from spec to allow subtraction of dates as well
def __sub__(self, other):
# leap years, phases of the moon, ....
a = calendar.timegm((self.year, self.month, self.day, self.hour,
self.minute, self.second, 0, 0, 0))
- b = calendar.timegm((other.year, other.month, other.day, other.hour,
- other.minute, other.second, 0, 0, 0))
+ b = calendar.timegm((other.year, other.month, other.day,
+ other.hour, other.minute, other.second, 0, 0, 0))
diff = a - b
if diff < 0:
- sign = -1
+ sign = 1
diff = -diff
else:
- sign = 1
+ sign = -1
S = diff%60
M = (diff/60)%60
H = (diff/(60*60))%60
y = (diff/(365*24*60*60))
if y>1: d = H = S = M = 0
return Interval((y, m, d, H, M, S), sign=sign)
- t = (self.year - other.sign * other.year,
- self.month - other.sign * other.month,
- self.day - other.sign * other.day,
- self.hour - other.sign * other.hour,
- self.minute - other.sign * other.minute,
- self.second - other.sign * other.second, 0, 0, 0)
- return Date(time.gmtime(calendar.timegm(t)))
+ return self.__add__(other)
def __cmp__(self, other):
"""Compare this date to another date."""
Example usage:
>>> Interval(" 3w 1 d 2:00")
<Interval 22d 2:00>
- >>> Date(". + 2d") - Interval("3w")
+ >>> Date(". + 2d") + Interval("- 3w")
<Date 2000-06-07.00:34:02>
+
+ Intervals are added/subtracted in order of:
+ seconds, minutes, hours, years, months, days
+
+ Calculations involving monts (eg '+2m') have no effect on days - only
+ days (or over/underflow from hours/mins/secs) will do that, and
+ days-per-month and leap years are accounted for. Leap seconds are not.
+
+ TODO: more examples, showing the order of addition operation
'''
def __init__(self, spec, sign=1):
"""Construct an interval given a specification."""
\s*
((?P<d>\d+\s*)d)? # day
\s*
- (((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d))?)? # time
+ (((?P<H>\d+):(?P<M>\d+))?(:(?P<S>\d+))?)? # time
\s*
''', re.VERBOSE)):
''' set the date to the value in spec
'''
if self.year or self.month > 2:
return None
- if self.month or self.day > 13:
+ elif self.month or self.day > 13:
days = (self.month * 30) + self.day
if days > 28:
if int(days/30) > 1:
- return _('%(number)s months')%{'number': int(days/30)}
+ s = _('%(number)s months')%{'number': int(days/30)}
else:
- return _('1 month')
+ s = _('1 month')
+ else:
+ s = _('%(number)s weeks')%{'number': int(days/7)}
+ elif self.day > 7:
+ s = _('1 week')
+ elif self.day > 1:
+ s = _('%(number)s days')%{'number': self.day}
+ elif self.day == 1 or self.hour > 12:
+ if self.sign > 0:
+ return _('tomorrow')
else:
- return _('%(number)s weeks')%{'number': int(days/7)}
- if self.day > 7:
- return _('1 week')
- if self.day > 1:
- return _('%(number)s days')%{'number': self.day}
- if self.day == 1 or self.hour > 12:
- return _('yesterday')
- if self.hour > 1:
- return _('%(number)s hours')%{'number': self.hour}
- if self.hour == 1:
+ return _('yesterday')
+ elif self.hour > 1:
+ s = _('%(number)s hours')%{'number': self.hour}
+ elif self.hour == 1:
if self.minute < 15:
- return _('an hour')
- quart = self.minute/15
- if quart == 2:
- return _('1 1/2 hours')
- return _('1 %(number)s/4 hours')%{'number': quart}
- if self.minute < 1:
- return _('just now')
- if self.minute == 1:
- return _('1 minute')
- if self.minute < 15:
- return _('%(number)s minutes')%{'number': self.minute}
- quart = int(self.minute/15)
- if quart == 2:
- return _('1/2 an hour')
- return _('%(number)s/4 hour')%{'number': quart}
+ s = _('an hour')
+ elif self.minute/15 == 2:
+ s = _('1 1/2 hours')
+ else:
+ s = _('1 %(number)s/4 hours')%{'number': self.minute/15}
+ elif self.minute < 1:
+ if self.sign > 0:
+ return _('in a moment')
+ else:
+ return _('just now')
+ elif self.minute == 1:
+ s = _('1 minute')
+ elif self.minute < 15:
+ s = _('%(number)s minutes')%{'number': self.minute}
+ elif int(self.minute/15) == 2:
+ s = _('1/2 an hour')
+ else:
+ s = _('%(number)s/4 hour')%{'number': int(self.minute/15)}
+ return s
def get_tuple(self):
return (self.year, self.month, self.day, self.hour, self.minute,
#
# $Log: not supported by cvs2svn $
+# Revision 1.18 2002/01/23 20:00:50 jhermann
+# %e is a UNIXism and not documented for Python
+#
# Revision 1.17 2002/01/16 07:02:57 richard
# . lots of date/interval related changes:
# - more relaxed date format for input
index 9e0a0114470377f23495453f480f4d7480146ca1..59f128ba3df9e1ece251e1f8ff9884b95b07c201 100644 (file)
--- a/roundup/htmltemplate.py
+++ b/roundup/htmltemplate.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: htmltemplate.py,v 1.81 2002-02-21 07:21:38 richard Exp $
+# $Id: htmltemplate.py,v 1.82 2002-02-21 23:11:45 richard Exp $
__doc__ = """
Template engine.
return ''
# figure the interval
- interval = value - date.Date('.')
+ interval = date.Date('.') - value
if pretty:
if not self.nodeid:
return _('now')
#
# $Log: not supported by cvs2svn $
+# Revision 1.81 2002/02/21 07:21:38 richard
+# docco
+#
# Revision 1.80 2002/02/21 07:19:08 richard
# ... and label, width and height control for extra flavour!
#
diff --git a/test/test_dates.py b/test/test_dates.py
index cedc2d3d75ac14033e97dc07b422153e23cad2bf..6b04d518508b63eb0ee5d25b9fcc2bf7d54b2880 100644 (file)
--- a/test/test_dates.py
+++ b/test/test_dates.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: test_dates.py,v 1.9 2002-02-21 06:57:39 richard Exp $
+# $Id: test_dates.py,v 1.10 2002-02-21 23:11:45 richard Exp $
import unittest, time
ae(str(date), '%s-%02d-%02d.19:25:00'%(y, m, d))
date = Date("8:47:11", -5)
ae(str(date), '%s-%02d-%02d.13:47:11'%(y, m, d))
- # TODO: assert something
- Date() + Interval('- 2y 2m')
+
+ # now check calculations
+ date = Date('2000-01-01') + Interval('- 2y 2m')
+ ae(str(date), '1997-11-01.00:00:00')
+ date = Date('2000-01-01') + Interval('+ 2m')
+ ae(str(date), '2000-03-01.00:00:00')
+
+ date = Date('2000-01-01') + Interval('60d')
+ ae(str(date), '2000-03-01.00:00:00')
+ date = Date('2001-01-01') + Interval('60d')
+ ae(str(date), '2001-03-02.00:00:00')
+
+ date = Date('2000-02-28.23:59:59') + Interval('00:00:01')
+ ae(str(date), '2000-02-29.00:00:00')
+ date = Date('2001-02-28.23:59:59') + Interval('00:00:01')
+ ae(str(date), '2001-03-01.00:00:00')
+
+ date = Date('2000-02-28.23:58:59') + Interval('00:01:01')
+ ae(str(date), '2000-02-29.00:00:00')
+ date = Date('2001-02-28.23:58:59') + Interval('00:01:01')
+ ae(str(date), '2001-03-01.00:00:00')
+
+ date = Date('2000-02-28.22:58:59') + Interval('01:01:01')
+ ae(str(date), '2000-02-29.00:00:00')
+ date = Date('2001-02-28.22:58:59') + Interval('01:01:01')
+ ae(str(date), '2001-03-01.00:00:00')
+
+ date = Date('2000-02-28.22:58:59') + Interval('00:00:3661')
+ ae(str(date), '2000-02-29.00:00:00')
+ date = Date('2001-02-28.22:58:59') + Interval('00:00:3661')
+ ae(str(date), '2001-03-01.00:00:00')
def testInterval(self):
ae = self.assertEqual
#
# $Log: not supported by cvs2svn $
+# Revision 1.9 2002/02/21 06:57:39 richard
+# . Added popup help for classes using the classhelp html template function.
+# - add <display call="classhelp('priority', 'id,name,description')">
+# to an item page, and it generates a link to a popup window which displays
+# the id, name and description for the priority class. The description
+# field won't exist in most installations, but it will be added to the
+# default templates.
+#
# Revision 1.8 2002/01/16 07:02:57 richard
# . lots of date/interval related changes:
# - more relaxed date format for input
index 29bb6f39d0627a748df97e90b596d44fdb3ef45d..bf016ff572c31a3aa7aaf5727f975cce2b13c5be 100644 (file)
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
-# $Id: test_htmltemplate.py,v 1.10 2002-02-21 06:57:39 richard Exp $
+# $Id: test_htmltemplate.py,v 1.11 2002-02-21 23:11:45 richard Exp $
import unittest, cgi, time
elif attribute == 'filename':
return 'file.foo'
elif attribute == 'date':
- return date.Date() + date.Interval('- 2y 2m')
+ return date.Date('2000-01-01')
+ elif attribute == 'reldate':
+ return date.Date() + date.Interval('- 2y 1m')
elif attribute == 'interval':
return date.Interval('-3d')
elif attribute == 'link':
return {'string': String(), 'date': Date(), 'interval': Interval(),
'link': Link('other'), 'multilink': Multilink('other'),
'password': Password(), 'html': String(), 'key': String(),
- 'novalue': String(), 'filename': String(), 'multiline': String()}
+ 'novalue': String(), 'filename': String(), 'multiline': String(),
+ 'reldate': Date()}
def labelprop(self):
return 'key'
self.assertEqual(self.tf.do_reldate('multilink'), s)
def testReldate_date(self):
- self.assertEqual(self.tf.do_reldate('date'), '- 2y 1m')
- date = self.tf.cl.get('1', 'date')
- self.assertEqual(self.tf.do_reldate('date', pretty=1), date.pretty())
+ self.assertEqual(self.tf.do_reldate('reldate'), '- 2y 1m')
+ date = self.tf.cl.get('1', 'reldate')
+ self.assertEqual(self.tf.do_reldate('reldate', pretty=1), date.pretty())
# def do_download(self, property):
def testDownload_novalue(self):
def testClasshelp(self):
self.assertEqual(self.tf.do_classhelp('theclass', 'prop1,prop2'),
'<a href="javascript:help_window(\'classhelp?classname=theclass'
- '&properties=prop1,prop2\')"><b>(?)</b></a>')
+ '&properties=prop1,prop2\', \'400\', \'400\')"><b>(?)</b></a>')
def suite():
return unittest.makeSuite(NodeCase, 'test')
#
# $Log: not supported by cvs2svn $
+# Revision 1.10 2002/02/21 06:57:39 richard
+# . Added popup help for classes using the classhelp html template function.
+# - add <display call="classhelp('priority', 'id,name,description')">
+# to an item page, and it generates a link to a popup window which displays
+# the id, name and description for the priority class. The description
+# field won't exist in most installations, but it will be added to the
+# default templates.
+#
# Revision 1.9 2002/02/15 07:08:45 richard
# . Alternate email addresses are now available for users. See the MIGRATION
# file for info on how to activate the feature.