diff --git a/roundup/date.py b/roundup/date.py
index c170fb6b3ac3c61c5f2921ce8fc984cf47b572aa..6299318d0cbbb65a299f61d465e68e01cb885ba6 100644 (file)
--- a/roundup/date.py
+++ b/roundup/date.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: date.py,v 1.20 2002-02-21 23:34:51 richard Exp $
+# $Id: date.py,v 1.35 2002-10-11 01:25:40 richard Exp $
__doc__ = """
Date, time and time interval handling.
<Date 2000-08-14.03:13:00>
>>> Date("14:25", -5)
<Date 2000-06-25.19:25:00>
+
+ The date format 'yyyymmddHHMMSS' (year, month, day, hour,
+ 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):
"""Construct a date given a specification and a time zone offset.
self.year, self.month, self.day, self.hour, self.minute, \
self.second, x, x, x = time.gmtime(ts)
- def applyInterval(self, interval):
- ''' Apply the interval to this date
+ def addInterval(self, interval):
+ ''' Add the interval to this date, returning the date tuple
'''
- t = (self.year + interval.year,
- self.month + interval.month,
- self.day + interval.day,
- self.hour + interval.hour,
- self.minute + interval.minute,
- self.second + interval.second, 0, 0, 0)
- self.year, self.month, self.day, self.hour, self.minute, \
- self.second, x, x, x = time.gmtime(calendar.timegm(t))
-
- def __add__(self, other):
- """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
+ sign = interval.sign
+ year = self.year + sign * interval.year
+ month = self.month + sign * interval.month
+ day = self.day + sign * interval.day
+ hour = self.hour + sign * interval.hour
+ minute = self.minute + sign * interval.minute
+ second = self.second + sign * interval.second
# now cope with under- and over-flow
# first do the time
# 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)
- return Date((year, month, day, hour, minute, second, 0, 0, 0))
+ def applyInterval(self, interval):
+ ''' Apply the interval to this date
+ '''
+ self.year, self.month, self.day, self.hour, self.minute, \
+ self.second, x, x, x = self.addInterval(interval)
- # XXX deviates from spec to allow subtraction of dates as well
+ def __add__(self, interval):
+ """Add an interval to this date to produce another date.
+ """
+ return Date(self.addInterval(interval))
+
+ # deviates from spec to allow subtraction of dates as well
def __sub__(self, other):
""" Subtract:
1. an interval from this date to produce another date.
2. a date from this date to produce an interval.
"""
if isinstance(other, Interval):
- other = Interval(other.get_tuple(), sign=-other.sign)
+ other = Interval(other.get_tuple())
+ other.sign *= -1
return self.__add__(other)
assert isinstance(other, Date), 'May only subtract Dates or Intervals'
if other is None:
return 1
for attr in ('year', 'month', 'day', 'hour', 'minute', 'second'):
+ if not hasattr(other, attr):
+ return 1
r = cmp(getattr(self, attr), getattr(other, attr))
if r: return r
return 0
return str
def set(self, spec, offset=0, date_re=re.compile(r'''
- (((?P<y>\d\d\d\d)-)?((?P<m>\d\d?)-(?P<d>\d\d?))?)? # yyyy-mm-dd
- (?P<n>\.)? # .
- (((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d))?)? # hh:mm:ss
- (?P<o>.+)? # offset
- ''', re.VERBOSE)):
+ (((?P<y>\d\d\d\d)-)?((?P<m>\d\d?)-(?P<d>\d\d?))?)? # yyyy-mm-dd
+ (?P<n>\.)? # .
+ (((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d))?)? # hh:mm:ss
+ (?P<o>.+)? # offset
+ ''', re.VERBOSE), serialised_re=re.compile(r'''
+ (\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)
+ ''', re.VERBOSE)):
''' set the date to the value in spec
'''
+ m = serialised_re.match(spec)
+ if m is not None:
+ # we're serialised - easy!
+ self.year, self.month, self.day, self.hour, self.minute, \
+ self.second = map(int, m.groups()[:6])
+ return
+
+ # not serialised data, try usual format
m = date_re.match(spec)
- if not m:
- raise ValueError, _('Not a date spec: [[yyyy-]mm-dd].[[h]h:mm[:ss]]'
- '[offset]')
+ if m is None:
+ 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
self.year, self.month, self.day, self.hour, self.minute, \
self.second, x, x, x = time.gmtime(ts)
- if info['o']:
+ if info.get('o', None):
self.applyInterval(Interval(info['o']))
def __repr__(self):
return (self.year, self.month, self.day, self.hour, self.minute,
self.second, 0, 0, 0)
+ def serialise(self):
+ return '%4d%02d%02d%02d%02d%02d'%(self.year, self.month,
+ self.day, self.hour, self.minute, self.second)
+
class Interval:
'''
Date intervals are specified using the suffixes "y", "m", and "d". The
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.
+ The interval format 'syyyymmddHHMMSS' (sign, year, month, day, hour,
+ minute, second) is the serialisation format returned by the serialise()
+ method, and is accepted as an argument on instatiation.
+
TODO: more examples, showing the order of addition operation
'''
def __init__(self, spec, sign=1):
if type(spec) == type(''):
self.set(spec)
else:
- self.sign = sign
- self.year, self.month, self.day, self.hour, self.minute, \
- self.second = spec
+ if len(spec) == 7:
+ self.sign, self.year, self.month, self.day, self.hour, \
+ self.minute, self.second = spec
+ else:
+ # old, buggy spec form
+ self.sign = sign
+ self.year, self.month, self.day, self.hour, self.minute, \
+ self.second = spec
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'):
+ if not hasattr(other, attr):
+ return 1
r = cmp(getattr(self, attr), getattr(other, attr))
if r: return r
return 0
-
+
def __str__(self):
"""Return this interval as a string."""
sign = {1:'+', -1:'-'}[self.sign]
l.append('%d:%02d'%(self.hour, self.minute))
return ' '.join(l)
- def set(self, spec, interval_re = re.compile('''
- \s*
- (?P<s>[-+])? # + or -
- \s*
- ((?P<y>\d+\s*)y)? # year
- \s*
- ((?P<m>\d+\s*)m)? # month
- \s*
- ((?P<w>\d+\s*)w)? # week
- \s*
- ((?P<d>\d+\s*)d)? # day
- \s*
- (((?P<H>\d+):(?P<M>\d+))?(:(?P<S>\d+))?)? # time
- \s*
- ''', re.VERBOSE)):
+ def __add__(self, other):
+ if isinstance(other, Date):
+ # the other is a Date - produce a Date
+ return Date(other.addInterval(self))
+ elif isinstance(other, Interval):
+ # add the other Interval to this one
+ a = self.get_tuple()
+ b = other.get_tuple()
+ if b[0] < 0:
+ i = Interval([x-y for x,y in zip(a[1:],b[1:])])
+ else:
+ i = Interval([x+y for x,y in zip(a[1:],b[1:])])
+ return i
+ # nope, no idea what to do with this other...
+ raise TypeError, "Can't add %r"%other
+
+ def set(self, spec, interval_re=re.compile('''
+ \s*(?P<s>[-+])? # + or -
+ \s*((?P<y>\d+\s*)y)? # year
+ \s*((?P<m>\d+\s*)m)? # month
+ \s*((?P<w>\d+\s*)w)? # week
+ \s*((?P<d>\d+\s*)d)? # day
+ \s*(((?P<H>\d+):(?P<M>\d+))?(:(?P<S>\d+))?)? # time
+ \s*''', re.VERBOSE), serialised_re=re.compile('''
+ (?P<s>[+-])?1?(?P<y>([ ]{3}\d|\d{4}))(?P<m>\d{2})(?P<d>\d{2})
+ (?P<H>\d{2})(?P<M>\d{2})(?P<S>\d{2})''', re.VERBOSE)):
''' set the date to the value in spec
'''
self.year = self.month = self.week = self.day = self.hour = \
self.minute = self.second = 0
self.sign = 1
- m = interval_re.match(spec)
+ m = serialised_re.match(spec)
if not m:
- raise ValueError, _('Not an interval spec: [+-] [#y] [#m] [#w] '
- '[#d] [[[H]H:MM]:SS]')
+ m = interval_re.match(spec)
+ if not m:
+ 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',
'H':'hour', 'M':'minute', 'S':'second'}.items():
- if info[group] is not None:
+ if info.get(group, None) is not None:
setattr(self, attr, int(info[group]))
if self.week:
def pretty(self):
''' print up the date date using one of these nice formats..
'''
- if self.year or self.month > 2:
- return None
+ if self.year:
+ if self.year == 1:
+ return _('1 year')
+ else:
+ return _('%(number)s years')%{'number': self.year}
elif self.month or self.day > 13:
days = (self.month * 30) + self.day
if days > 28:
return s
def get_tuple(self):
- return (self.year, self.month, self.day, self.hour, self.minute,
- self.second)
+ return (self.sign, self.year, self.month, self.day, self.hour,
+ self.minute, self.second)
+
+ def serialise(self):
+ sign = self.sign > 0 and '+' or '-'
+ return '%s%04d%02d%02d%02d%02d%02d'%(sign, self.year, self.month,
+ self.day, self.hour, self.minute, self.second)
def test():
if __name__ == '__main__':
test()
-#
-# $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...
-#
-# Revision 1.7 2001/08/02 00:27:04 richard
-# Extended the range of intervals that are pretty-printed before actual dates
-# are displayed.
-#
-# Revision 1.6 2001/07/31 09:54:18 richard
-# Fixed the 2.1-specific gmtime() (no arg) call in roundup.date. (Paul Wright)
-#
-# Revision 1.5 2001/07/29 07:01:39 richard
-# Added vim command to all source so that we don't get no steenkin' tabs :)
-#
-# Revision 1.4 2001/07/25 04:09:34 richard
-# Fixed offset handling (shoulda read the spec a little better)
-#
-# Revision 1.3 2001/07/23 07:56:05 richard
-# Storing only marshallable data in the db - no nasty pickled class references.
-#
-# Revision 1.2 2001/07/22 12:09:32 richard
-# Final commit of Grande Splite
-#
-# Revision 1.1 2001/07/22 11:58:35 richard
-# More Grande Splite
-#
-#
# vim: set filetype=python ts=4 sw=4 et si