comparison roundup/date.py @ 640:7dd13fd5d8ea

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.
author Richard Jones <richard@users.sourceforge.net>
date Thu, 21 Feb 2002 23:11:45 +0000
parents c08fb4921eda
children cfa460943d4d
comparison
equal deleted inserted replaced
639:2f3e82a69eb5 640:7dd13fd5d8ea
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" 14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, 15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17 # 17 #
18 # $Id: date.py,v 1.18 2002-01-23 20:00:50 jhermann Exp $ 18 # $Id: date.py,v 1.19 2002-02-21 23:11:45 richard Exp $
19 19
20 __doc__ = """ 20 __doc__ = """
21 Date, time and time interval handling. 21 Date, time and time interval handling.
22 """ 22 """
23 23
102 self.second + interval.second, 0, 0, 0) 102 self.second + interval.second, 0, 0, 0)
103 self.year, self.month, self.day, self.hour, self.minute, \ 103 self.year, self.month, self.day, self.hour, self.minute, \
104 self.second, x, x, x = time.gmtime(calendar.timegm(t)) 104 self.second, x, x, x = time.gmtime(calendar.timegm(t))
105 105
106 def __add__(self, other): 106 def __add__(self, other):
107 """Add an interval to this date to produce another date.""" 107 """Add an interval to this date to produce another date.
108 t = (self.year + other.sign * other.year, 108 """
109 self.month + other.sign * other.month, 109 # do the basic calc
110 self.day + other.sign * other.day, 110 sign = other.sign
111 self.hour + other.sign * other.hour, 111 year = self.year + sign * other.year
112 self.minute + other.sign * other.minute, 112 month = self.month + sign * other.month
113 self.second + other.sign * other.second, 0, 0, 0) 113 day = self.day + sign * other.day
114 return Date(time.gmtime(calendar.timegm(t))) 114 hour = self.hour + sign * other.hour
115 minute = self.minute + sign * other.minute
116 second = self.second + sign * other.second
117
118 # now cope with under- and over-flow
119 # first do the time
120 while (second < 0 or second > 59 or minute < 0 or minute > 59 or
121 hour < 0 or hour > 59):
122 if second < 0: minute -= 1; second += 60
123 elif second > 59: minute += 1; second -= 60
124 if minute < 0: hour -= 1; minute += 60
125 elif minute > 59: hour += 1; minute -= 60
126 if hour < 0: day -= 1; hour += 60
127 elif hour > 59: day += 1; hour -= 60
128
129 # fix up the month so we're within range
130 while month < 1 or month > 12:
131 if month < 1: year -= 1; month += 12
132 if month > 12: year += 1; month -= 12
133
134 # now do the days, now that we know what month we're in
135 mdays = calendar.mdays
136 if month == 2 and calendar.isleap(year): month_days = 29
137 else: month_days = mdays[month]
138 while month < 1 or month > 12 or day < 0 or day > month_days:
139 # now to day under/over
140 if day < 0: month -= 1; day += month_days
141 elif day > month_days: month += 1; day -= month_days
142
143 # possibly fix up the month so we're within range
144 while month < 1 or month > 12:
145 if month < 1: year -= 1; month += 12
146 if month > 12: year += 1; month -= 12
147
148 # re-figure the number of days for this month
149 if month == 2 and calendar.isleap(year): month_days = 29
150 else: month_days = mdays[month]
151
152 return Date((year, month, day, hour, minute, second, 0, 0, 0))
115 153
116 # XXX deviates from spec to allow subtraction of dates as well 154 # XXX deviates from spec to allow subtraction of dates as well
117 def __sub__(self, other): 155 def __sub__(self, other):
118 """ Subtract: 156 """ Subtract:
119 1. an interval from this date to produce another date. 157 1. an interval from this date to produce another date.
122 if isinstance(other, Date): 160 if isinstance(other, Date):
123 # TODO this code will fall over laughing if the dates cross 161 # TODO this code will fall over laughing if the dates cross
124 # leap years, phases of the moon, .... 162 # leap years, phases of the moon, ....
125 a = calendar.timegm((self.year, self.month, self.day, self.hour, 163 a = calendar.timegm((self.year, self.month, self.day, self.hour,
126 self.minute, self.second, 0, 0, 0)) 164 self.minute, self.second, 0, 0, 0))
127 b = calendar.timegm((other.year, other.month, other.day, other.hour, 165 b = calendar.timegm((other.year, other.month, other.day,
128 other.minute, other.second, 0, 0, 0)) 166 other.hour, other.minute, other.second, 0, 0, 0))
129 diff = a - b 167 diff = a - b
130 if diff < 0: 168 if diff < 0:
131 sign = -1 169 sign = 1
132 diff = -diff 170 diff = -diff
133 else: 171 else:
134 sign = 1 172 sign = -1
135 S = diff%60 173 S = diff%60
136 M = (diff/60)%60 174 M = (diff/60)%60
137 H = (diff/(60*60))%60 175 H = (diff/(60*60))%60
138 if H>1: S = 0 176 if H>1: S = 0
139 d = (diff/(24*60*60))%30 177 d = (diff/(24*60*60))%30
141 m = (diff/(30*24*60*60))%12 179 m = (diff/(30*24*60*60))%12
142 if m>1: H = S = M = 0 180 if m>1: H = S = M = 0
143 y = (diff/(365*24*60*60)) 181 y = (diff/(365*24*60*60))
144 if y>1: d = H = S = M = 0 182 if y>1: d = H = S = M = 0
145 return Interval((y, m, d, H, M, S), sign=sign) 183 return Interval((y, m, d, H, M, S), sign=sign)
146 t = (self.year - other.sign * other.year, 184 return self.__add__(other)
147 self.month - other.sign * other.month,
148 self.day - other.sign * other.day,
149 self.hour - other.sign * other.hour,
150 self.minute - other.sign * other.minute,
151 self.second - other.sign * other.second, 0, 0, 0)
152 return Date(time.gmtime(calendar.timegm(t)))
153 185
154 def __cmp__(self, other): 186 def __cmp__(self, other):
155 """Compare this date to another date.""" 187 """Compare this date to another date."""
156 if other is None: 188 if other is None:
157 return 1 189 return 1
242 "0:04:33" means four minutes and 33 seconds 274 "0:04:33" means four minutes and 33 seconds
243 275
244 Example usage: 276 Example usage:
245 >>> Interval(" 3w 1 d 2:00") 277 >>> Interval(" 3w 1 d 2:00")
246 <Interval 22d 2:00> 278 <Interval 22d 2:00>
247 >>> Date(". + 2d") - Interval("3w") 279 >>> Date(". + 2d") + Interval("- 3w")
248 <Date 2000-06-07.00:34:02> 280 <Date 2000-06-07.00:34:02>
281
282 Intervals are added/subtracted in order of:
283 seconds, minutes, hours, years, months, days
284
285 Calculations involving monts (eg '+2m') have no effect on days - only
286 days (or over/underflow from hours/mins/secs) will do that, and
287 days-per-month and leap years are accounted for. Leap seconds are not.
288
289 TODO: more examples, showing the order of addition operation
249 ''' 290 '''
250 def __init__(self, spec, sign=1): 291 def __init__(self, spec, sign=1):
251 """Construct an interval given a specification.""" 292 """Construct an interval given a specification."""
252 if type(spec) == type(''): 293 if type(spec) == type(''):
253 self.set(spec) 294 self.set(spec)
288 \s* 329 \s*
289 ((?P<w>\d+\s*)w)? # week 330 ((?P<w>\d+\s*)w)? # week
290 \s* 331 \s*
291 ((?P<d>\d+\s*)d)? # day 332 ((?P<d>\d+\s*)d)? # day
292 \s* 333 \s*
293 (((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d))?)? # time 334 (((?P<H>\d+):(?P<M>\d+))?(:(?P<S>\d+))?)? # time
294 \s* 335 \s*
295 ''', re.VERBOSE)): 336 ''', re.VERBOSE)):
296 ''' set the date to the value in spec 337 ''' set the date to the value in spec
297 ''' 338 '''
298 self.year = self.month = self.week = self.day = self.hour = \ 339 self.year = self.month = self.week = self.day = self.hour = \
321 def pretty(self): 362 def pretty(self):
322 ''' print up the date date using one of these nice formats.. 363 ''' print up the date date using one of these nice formats..
323 ''' 364 '''
324 if self.year or self.month > 2: 365 if self.year or self.month > 2:
325 return None 366 return None
326 if self.month or self.day > 13: 367 elif self.month or self.day > 13:
327 days = (self.month * 30) + self.day 368 days = (self.month * 30) + self.day
328 if days > 28: 369 if days > 28:
329 if int(days/30) > 1: 370 if int(days/30) > 1:
330 return _('%(number)s months')%{'number': int(days/30)} 371 s = _('%(number)s months')%{'number': int(days/30)}
331 else: 372 else:
332 return _('1 month') 373 s = _('1 month')
333 else: 374 else:
334 return _('%(number)s weeks')%{'number': int(days/7)} 375 s = _('%(number)s weeks')%{'number': int(days/7)}
335 if self.day > 7: 376 elif self.day > 7:
336 return _('1 week') 377 s = _('1 week')
337 if self.day > 1: 378 elif self.day > 1:
338 return _('%(number)s days')%{'number': self.day} 379 s = _('%(number)s days')%{'number': self.day}
339 if self.day == 1 or self.hour > 12: 380 elif self.day == 1 or self.hour > 12:
340 return _('yesterday') 381 if self.sign > 0:
341 if self.hour > 1: 382 return _('tomorrow')
342 return _('%(number)s hours')%{'number': self.hour} 383 else:
343 if self.hour == 1: 384 return _('yesterday')
385 elif self.hour > 1:
386 s = _('%(number)s hours')%{'number': self.hour}
387 elif self.hour == 1:
344 if self.minute < 15: 388 if self.minute < 15:
345 return _('an hour') 389 s = _('an hour')
346 quart = self.minute/15 390 elif self.minute/15 == 2:
347 if quart == 2: 391 s = _('1 1/2 hours')
348 return _('1 1/2 hours') 392 else:
349 return _('1 %(number)s/4 hours')%{'number': quart} 393 s = _('1 %(number)s/4 hours')%{'number': self.minute/15}
350 if self.minute < 1: 394 elif self.minute < 1:
351 return _('just now') 395 if self.sign > 0:
352 if self.minute == 1: 396 return _('in a moment')
353 return _('1 minute') 397 else:
354 if self.minute < 15: 398 return _('just now')
355 return _('%(number)s minutes')%{'number': self.minute} 399 elif self.minute == 1:
356 quart = int(self.minute/15) 400 s = _('1 minute')
357 if quart == 2: 401 elif self.minute < 15:
358 return _('1/2 an hour') 402 s = _('%(number)s minutes')%{'number': self.minute}
359 return _('%(number)s/4 hour')%{'number': quart} 403 elif int(self.minute/15) == 2:
404 s = _('1/2 an hour')
405 else:
406 s = _('%(number)s/4 hour')%{'number': int(self.minute/15)}
407 return s
360 408
361 def get_tuple(self): 409 def get_tuple(self):
362 return (self.year, self.month, self.day, self.hour, self.minute, 410 return (self.year, self.month, self.day, self.hour, self.minute,
363 self.second) 411 self.second)
364 412
383 if __name__ == '__main__': 431 if __name__ == '__main__':
384 test() 432 test()
385 433
386 # 434 #
387 # $Log: not supported by cvs2svn $ 435 # $Log: not supported by cvs2svn $
436 # Revision 1.18 2002/01/23 20:00:50 jhermann
437 # %e is a UNIXism and not documented for Python
438 #
388 # Revision 1.17 2002/01/16 07:02:57 richard 439 # Revision 1.17 2002/01/16 07:02:57 richard
389 # . lots of date/interval related changes: 440 # . lots of date/interval related changes:
390 # - more relaxed date format for input 441 # - more relaxed date format for input
391 # 442 #
392 # Revision 1.16 2002/01/08 11:56:24 richard 443 # Revision 1.16 2002/01/08 11:56:24 richard

Roundup Issue Tracker: http://roundup-tracker.org/