Mercurial > p > roundup > code
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 |
