X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fdate.py;h=c170fb6b3ac3c61c5f2921ce8fc984cf47b572aa;hb=0bf3521deec7a0467f67293527f3cdaf38e7f5f7;hp=4bdf11125a19b23fab63dc34adf88ea956408ade;hpb=c853df7b1fb5c3deee55fd7c8396f56259b060f6;p=roundup.git diff --git a/roundup/date.py b/roundup/date.py index 4bdf111..c170fb6 100644 --- a/roundup/date.py +++ b/roundup/date.py @@ -4,7 +4,7 @@ # under the same terms as Python, so long as this copyright message and # disclaimer are retained in their original form. # -# IN NO EVENT SHALL THE BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR +# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. @@ -15,9 +15,14 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: date.py,v 1.9 2001-08-07 00:15:51 richard Exp $ +# $Id: date.py,v 1.20 2002-02-21 23:34:51 richard Exp $ + +__doc__ = """ +Date, time and time interval handling. +""" import time, re, calendar +from i18n import _ class Date: ''' @@ -71,8 +76,6 @@ class Date: >>> Date("14:25", -5) ''' - isDate = 1 - def __init__(self, spec='.', offset=0): """Construct a date given a specification and a time zone offset. @@ -101,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 += 24 + elif hour > 59: day += 1; hour -= 24 + + # 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): @@ -116,40 +157,40 @@ class Date: 1. an interval from this date to produce another date. 2. a date from this date to produce an interval. """ - if other.isDate: - # TODO this code will fall over laughing if the dates cross - # 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)) - diff = a - b - if diff < 0: - sign = -1 - diff = -diff - else: - sign = 1 - S = diff%60 - M = (diff/60)%60 - H = (diff/(60*60))%60 - if H>1: S = 0 - d = (diff/(24*60*60))%30 - if d>1: H = S = M = 0 - m = (diff/(30*24*60*60))%12 - if m>1: H = S = M = 0 - 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))) + if isinstance(other, Interval): + other = Interval(other.get_tuple(), sign=-other.sign) + return self.__add__(other) + + assert isinstance(other, Date), 'May only subtract Dates or Intervals' + + # TODO this code will fall over laughing if the dates cross + # 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)) + diff = a - b + if diff < 0: + sign = 1 + diff = -diff + else: + sign = -1 + S = diff%60 + M = (diff/60)%60 + H = (diff/(60*60))%60 + if H>1: S = 0 + d = (diff/(24*60*60))%30 + if d>1: H = S = M = 0 + m = (diff/(30*24*60*60))%12 + if m>1: H = S = M = 0 + y = (diff/(365*24*60*60)) + if y>1: d = H = S = M = 0 + return Interval((y, m, d, H, M, S), sign=sign) def __cmp__(self, other): """Compare this date to another date.""" + if other is None: + return 1 for attr in ('year', 'month', 'day', 'hour', 'minute', 'second'): r = cmp(getattr(self, attr), getattr(other, attr)) if r: return r @@ -163,11 +204,13 @@ class Date: def pretty(self): ''' print up the date date using a pretty format... ''' - return time.strftime('%e %B %Y', (self.year, self.month, + str = time.strftime('%d %B %Y', (self.year, self.month, self.day, self.hour, self.minute, self.second, 0, 0, 0)) + if str[0] == '0': return ' ' + str[1:] + return str def set(self, spec, offset=0, date_re=re.compile(r''' - (((?P\d\d\d\d)-)?((?P\d\d)-(?P\d\d))?)? # yyyy-mm-dd + (((?P\d\d\d\d)-)?((?P\d\d?)-(?P\d\d?))?)? # yyyy-mm-dd (?P\.)? # . (((?P\d?\d):(?P\d\d))?(:(?P\d\d))?)? # hh:mm:ss (?P.+)? # offset @@ -176,7 +219,8 @@ class Date: ''' m = date_re.match(spec) if not m: - raise ValueError, 'Not a date spec: [[yyyy-]mm-dd].[[h]h:mm[:ss]] [offset]' + raise ValueError, _('Not a date spec: [[yyyy-]mm-dd].[[h]h:mm[:ss]]' + '[offset]') info = m.groupdict() # get the current date/time using the offset @@ -236,11 +280,18 @@ class Interval: Example usage: >>> Interval(" 3w 1 d 2:00") - >>> Date(". + 2d") - Interval("3w") + >>> Date(". + 2d") + Interval("- 3w") - ''' - isInterval = 1 + 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.""" if type(spec) == type(''): @@ -252,6 +303,8 @@ class Interval: def __cmp__(self, other): """Compare this interval to another interval.""" + if other is None: + return 1 for attr in ('year', 'month', 'day', 'hour', 'minute', 'second'): r = cmp(getattr(self, attr), getattr(other, attr)) if r: return r @@ -282,7 +335,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 @@ -292,7 +345,8 @@ class Interval: self.sign = 1 m = interval_re.match(spec) if not m: - raise ValueError, 'Not an interval spec: [+-] [#y] [#m] [#w] [#d] [[[H]H:MM]:SS]' + raise ValueError, _('Not an interval spec: [+-] [#y] [#m] [#w] ' + '[#d] [[[H]H:MM]:SS]') info = m.groupdict() for group, attr in {'y':'year', 'm':'month', 'w':'week', 'd':'day', @@ -309,49 +363,52 @@ class Interval: def __repr__(self): return ''%self.__str__() - def pretty(self, threshold=('d', 5)): + def pretty(self): ''' print up the date date using one of these nice formats.. - < 1 minute - < 15 minutes - < 30 minutes - < 1 hour - < 12 hours - < 1 day - otherwise, return None (so a full date may be displayed) ''' if self.year or self.month > 2: return None - if self.month: + elif self.month or self.day > 13: days = (self.month * 30) + self.day if days > 28: - return '%s months'%int(days/30) + if int(days/30) > 1: + s = _('%(number)s months')%{'number': int(days/30)} + else: + s = _('1 month') else: - return '%s weeks'%int(days/7) - if self.day > 7: - return '%s weeks'%self.day - if self.day > 1: - return '%s days'%self.day - if self.day == 1 or self.hour > 12: - return 'yesterday' - if self.hour > 1: - return '%s hours'%self.hour - if self.hour == 1: + 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 _('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 %s/4 hours'%quart - if self.minute < 1: - return 'just now' - if self.minute == 1: - return '1 minute' - if self.minute < 15: - return '%s minutes'%self.minute - quart = int(self.minute/15) - if quart == 2: - return '1/2 an hour' - return '%s/4 hour'%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, @@ -380,6 +437,45 @@ if __name__ == '__main__': # # $Log: not supported by cvs2svn $ +# Revision 1.19 2002/02/21 23:11:45 richard +# . 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. +# +# 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 +# +# Revision 1.16 2002/01/08 11:56:24 richard +# missed an import _ +# +# Revision 1.15 2002/01/05 02:27:00 richard +# I18N'ification +# +# Revision 1.14 2001/11/22 15:46:42 jhermann +# Added module docstrings to all modules. +# +# Revision 1.13 2001/09/18 22:58:37 richard +# +# Added some more help to roundu-admin +# +# Revision 1.12 2001/08/17 03:08:11 richard +# fixed prettification of intervals of 1 week +# +# Revision 1.11 2001/08/15 23:43:18 richard +# Fixed some isFooTypes that I missed. +# Refactored some code in the CGI code. +# +# Revision 1.10 2001/08/07 00:24:42 richard +# stupid typo +# +# Revision 1.9 2001/08/07 00:15:51 richard +# Added the copyright/license notice to (nearly) all files at request of +# Bizar Software. +# # Revision 1.8 2001/08/05 07:46:12 richard # Changed date.Date to use regular string formatting instead of strftime - # win32 seems to have problems with %T and no hour... or something...