From 7da602f16ccd98db39b17825242eb30567fd0656 Mon Sep 17 00:00:00 2001 From: richard Date: Thu, 21 Feb 2002 23:11:45 +0000 Subject: [PATCH] . 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. git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@652 57a73879-2fb5-44c3-a270-3262357dd7e2 --- CHANGES.txt | 3 + roundup/date.py | 151 +++++++++++++++++++++++++------------- roundup/htmltemplate.py | 7 +- test/test_dates.py | 43 ++++++++++- test/test_htmltemplate.py | 25 +++++-- 5 files changed, 167 insertions(+), 62 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ac1f47e..af4064e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -37,6 +37,9 @@ Fixed: . #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 d9ac758..c707e68 100644 --- a/roundup/date.py +++ b/roundup/date.py @@ -15,7 +15,7 @@ # 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. @@ -104,14 +104,52 @@ class Date: 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): @@ -124,14 +162,14 @@ class Date: # 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 @@ -143,13 +181,7 @@ class Date: 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.""" @@ -244,8 +276,17 @@ class Interval: Example usage: >>> Interval(" 3w 1 d 2:00") - >>> Date(". + 2d") - Interval("3w") + >>> Date(". + 2d") + Interval("- 3w") + + 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.""" @@ -290,7 +331,7 @@ class Interval: \s* ((?P\d+\s*)d)? # day \s* - (((?P\d?\d):(?P\d\d))?(:(?P\d\d))?)? # time + (((?P\d+):(?P\d+))?(:(?P\d+))?)? # time \s* ''', re.VERBOSE)): ''' set the date to the value in spec @@ -323,40 +364,47 @@ class Interval: ''' 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, @@ -385,6 +433,9 @@ if __name__ == '__main__': # # $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 diff --git a/roundup/htmltemplate.py b/roundup/htmltemplate.py index 9e0a011..59f128b 100644 --- a/roundup/htmltemplate.py +++ b/roundup/htmltemplate.py @@ -15,7 +15,7 @@ # 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. @@ -400,7 +400,7 @@ class TemplateFunctions: return '' # figure the interval - interval = value - date.Date('.') + interval = date.Date('.') - value if pretty: if not self.nodeid: return _('now') @@ -1091,6 +1091,9 @@ class NewItemTemplate(TemplateFunctions): # # $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 cedc2d3..6b04d51 100644 --- a/test/test_dates.py +++ b/test/test_dates.py @@ -15,7 +15,7 @@ # 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 @@ -70,8 +70,37 @@ class DateTestCase(unittest.TestCase): 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 @@ -89,6 +118,14 @@ def suite(): # # $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 +# 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 diff --git a/test/test_htmltemplate.py b/test/test_htmltemplate.py index 29bb6f3..bf016ff 100644 --- a/test/test_htmltemplate.py +++ b/test/test_htmltemplate.py @@ -8,7 +8,7 @@ # 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 @@ -24,7 +24,9 @@ class Class: 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': @@ -45,7 +47,8 @@ class Class: 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' @@ -257,9 +260,9 @@ class NodeCase(unittest.TestCase): 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): @@ -333,7 +336,7 @@ the key2:''') def testClasshelp(self): self.assertEqual(self.tf.do_classhelp('theclass', 'prop1,prop2'), '(?)') + '&properties=prop1,prop2\', \'400\', \'400\')">(?)') def suite(): return unittest.makeSuite(NodeCase, 'test') @@ -341,6 +344,14 @@ def suite(): # # $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 +# 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. -- 2.30.2