X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fdate.py;h=d4f5f36d8acae0e51fd8d29120e530678631d579;hb=4f9b23d1dd4d91d0a3e5f3dfddb91700837c9a9f;hp=a6430d7504b37bf75ba2d06efec49a29fe343cef;hpb=80edcd877ef7b34ce0a882496b7b09d1bd7a80fe;p=roundup.git diff --git a/roundup/date.py b/roundup/date.py index a6430d7..d4f5f36 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.51 2003-03-22 22:43:21 richard Exp $ +# $Id: date.py,v 1.56 2003-11-04 12:35:47 anthonybaxter Exp $ __doc__ = """ Date, time and time interval handling. @@ -24,6 +24,15 @@ Date, time and time interval handling. import time, re, calendar, types from i18n import _ +def _add_granularity(src, order, value = 1): + '''Increment first non-None value in src dictionary ordered by 'order' + parameter + ''' + for gran in order: + if src[gran]: + src[gran] = int(src[gran]) + value + break + class Date: ''' As strings, date-and-time stamps are specified with the date in @@ -50,6 +59,8 @@ class Date: "11-07.09:32:43" means "14:25" means "8:47:11" means + "2003" means + "2003-06" means "." means "right now" The Date class should understand simple date expressions of the form @@ -80,7 +91,8 @@ class Date: minute, second) is the serialisation format returned by the serialise() method, and is accepted as an argument on instatiation. ''' - def __init__(self, spec='.', offset=0): + + def __init__(self, spec='.', offset=0, add_granularity=0): """Construct a date given a specification and a time zone offset. 'spec' is a full date or a partial form, with an optional @@ -88,23 +100,26 @@ class Date: 'offset' is the local time zone offset from GMT in hours. """ if type(spec) == type(''): - self.set(spec, offset=offset) + self.set(spec, offset=offset, add_granularity=add_granularity) else: y,m,d,H,M,S,x,x,x = spec ts = calendar.timegm((y,m,d,H+offset,M,S,0,0,0)) self.year, self.month, self.day, self.hour, self.minute, \ self.second, x, x, x = time.gmtime(ts) + usagespec='[yyyy]-[mm]-[dd].[H]H:MM[:SS][offset]' 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\d\d?)[/-](?P\d\d?))? # or mm-dd (?P\.)? # . (((?P\d?\d):(?P\d\d))?(:(?P\d\d))?)? # hh:mm:ss (?P.+)? # offset ''', re.VERBOSE), serialised_re=re.compile(r''' (\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d) - ''', re.VERBOSE)): + ''', re.VERBOSE), add_granularity=0): ''' set the date to the value in spec ''' + m = serialised_re.match(spec) if m is not None: # we're serialised - easy! @@ -115,21 +130,27 @@ class Date: # not serialised data, try usual format m = date_re.match(spec) if m is None: - raise ValueError, _('Not a date spec: [[yyyy-]mm-dd].' - '[[h]h:mm[:ss]][offset]') + raise ValueError, _('Not a date spec: %s' % self.usagespec) info = m.groupdict() + if add_granularity: + _add_granularity(info, 'SMHdmyab') + # get the current date as our default y,m,d,H,M,S,x,x,x = time.gmtime(time.time()) - # override year, month, day parts - if info['m'] is not None and info['d'] is not None: - m = int(info['m']) - d = int(info['d']) + if info['y'] is not None or info['a'] is not None: if info['y'] is not None: y = int(info['y']) - # time defaults to 00:00:00 GMT - offset (local midnight) + m,d = (1,1) + if info['m'] is not None: + m = int(info['m']) + if info['d'] is not None: + d = int(info['d']) + if info['a'] is not None: + m = int(info['a']) + d = int(info['b']) H = -offset M = S = 0 @@ -140,6 +161,9 @@ class Date: S = 0 if info['S'] is not None: S = int(info['S']) + if add_granularity: + S = S - 1 + # now handle the adjustment of hour ts = calendar.timegm((y,m,d,H,M,S,0,0,0)) self.year, self.month, self.day, self.hour, self.minute, \ @@ -149,8 +173,7 @@ class Date: try: self.applyInterval(Interval(info['o'], allowdate=0)) except ValueError: - raise ValueError, _('Not a date spec: [[yyyy-]mm-dd].' - '[[h]h:mm[:ss]][offset]') + raise ValueError, _('Not a date spec: %s' % self.usagespec) def addInterval(self, interval): ''' Add the interval to this date, returning the date tuple @@ -181,24 +204,31 @@ class Date: 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: + def get_mdays(year,month): + if month == 2 and calendar.isleap(year): return 29 + else: return calendar.mdays[month] + + while month < 1 or month > 12 or day < 0 or day > get_mdays(year,month): # now to day under/over - if day < 0: month -= 1; day += month_days - elif day > month_days: month += 1; day -= month_days + if day < 0: + # When going backwards, decrement month, then increment days + month -= 1 + day += get_mdays(year,month) + elif day > get_mdays(year,month): + # When going forwards, decrement days, then increment month + day -= get_mdays(year,month) + month += 1 # 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 < 1: year -= 1; month += 12 ; day += 31 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 (year, month, day, hour, minute, second, 0, 0, 0) + def differenceDate(self, other): + "Return the difference between this date and another date" + def applyInterval(self, interval): ''' Apply the interval to this date ''' @@ -223,29 +253,29 @@ class Date: 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, .... + return self.dateDelta(other) + + def dateDelta(self, other): + """ Produce an Interval of the difference between this date + and another date. Only returns days:hours:minutes:seconds. + """ + # Returning intervals larger than a day is almost + # impossible - months, years, weeks, are all so imprecise. 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: + if diff > 0: sign = 1 - diff = -diff else: sign = -1 + diff = -diff 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) + H = (diff/(60*60))%24 + d = diff/(24*60*60) + return Interval((0, 0, d, H, M, S), sign=sign) def __cmp__(self, other): """Compare this date to another date.""" @@ -345,10 +375,10 @@ class Interval: TODO: more examples, showing the order of addition operation ''' - def __init__(self, spec, sign=1, allowdate=1): + def __init__(self, spec, sign=1, allowdate=1, add_granularity=0): """Construct an interval given a specification.""" if type(spec) == type(''): - self.set(spec, allowdate) + self.set(spec, allowdate=allowdate, add_granularity=add_granularity) else: if len(spec) == 7: self.sign, self.year, self.month, self.day, self.hour, \ @@ -372,7 +402,8 @@ class Interval: (\d?\d:\d\d)?(:\d\d)? # hh:mm:ss )?''', re.VERBOSE), serialised_re=re.compile(''' (?P[+-])?1?(?P([ ]{3}\d|\d{4}))(?P\d{2})(?P\d{2}) - (?P\d{2})(?P\d{2})(?P\d{2})''', re.VERBOSE)): + (?P\d{2})(?P\d{2})(?P\d{2})''', re.VERBOSE), + add_granularity=0): ''' set the date to the value in spec ''' self.year = self.month = self.week = self.day = self.hour = \ @@ -389,6 +420,9 @@ class Interval: # pull out all the info specified info = m.groupdict() + if add_granularity: + _add_granularity(info, 'SMHdwmy', (info['s']=='-' and -1 or 1)) + valid = 0 for group, attr in {'y':'year', 'm':'month', 'w':'week', 'd':'day', 'H':'hour', 'M':'minute', 'S':'second'}.items(): @@ -576,7 +610,7 @@ class Interval: if self.sign < 0: s = s + _(' ago') else: - s = _('in') + s + s = _('in ') + s return s def get_tuple(self): @@ -654,7 +688,7 @@ class Range: """ - def __init__(self, spec, Type, **params): + def __init__(self, spec, Type, allow_granularity=1, **params): """Initializes Range of type from given string. Sets two properties - from_value and to_value. None assigned to any of @@ -666,8 +700,8 @@ class Range: """ self.range_type = Type - re_range = r'(?:^|(?:from)?(.+?))(?:to(.+?)$|$)' - re_geek_range = r'(?:^|(.+?))(?:;(.+?)$|$)' + re_range = r'(?:^|from(.+?))(?:to(.+?)$|$)' + re_geek_range = r'(?:^|(.+?));(?:(.+?)$|$)' # Check which syntax to use if spec.find(';') == -1: # Native english @@ -682,7 +716,11 @@ class Range: if self.to_value: self.to_value = Type(self.to_value.strip(), **params) else: - raise ValueError, "Invalid range" + if allow_granularity: + self.from_value = Type(spec, **params) + self.to_value = Type(spec, add_granularity=1, **params) + else: + raise ValueError, "Invalid range" def __str__(self): return "from %s to %s" % (self.from_value, self.to_value) @@ -691,12 +729,17 @@ class Range: return "" % self.__str__() def test_range(): - rspecs = ("from 2-12 to 4-2", "18:00 TO +2m", "12:00", "tO +3d", - "2002-11-10; 2002-12-12", "; 20:00 +1d") + rspecs = ("from 2-12 to 4-2", "from 18:00 TO +2m", "12:00;", "tO +3d", + "2002-11-10; 2002-12-12", "; 20:00 +1d", '2002-10-12') + rispecs = ('from -1w 2d 4:32 to 4d', '-2w 1d') for rspec in rspecs: print '>>> Range("%s")' % rspec print `Range(rspec, Date)` print + for rspec in rispecs: + print '>>> Range("%s")' % rspec + print `Range(rspec, Interval)` + print def test(): intervals = (" 3w 1 d 2:00", " + 2d", "3w") @@ -705,7 +748,7 @@ def test(): print `Interval(interval)` dates = (".", "2000-06-25.19:34:02", ". + 2d", "1997-04-17", "01-25", - "08-13.22:13", "14:25") + "08-13.22:13", "14:25", '2002-12') for date in dates: print '>>> Date("%s")'%date print `Date(date)` @@ -716,6 +759,6 @@ def test(): print `Date(date) + Interval(interval)` if __name__ == '__main__': - test_range() + test() # vim: set filetype=python ts=4 sw=4 et si