X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;ds=sidebyside;f=roundup%2Fdate.py;h=a8d6ef3b09817ea0963c0663e806a09eb0dfa5f5;hb=c9e46f5700bb319acc389498e7ec904a4ac7ed11;hp=b9fd398c160b56d844192fb778bb890e928838b5;hpb=b108de64948f27da7a44bc538f12e3e0a5e64bf5;p=roundup.git diff --git a/roundup/date.py b/roundup/date.py index b9fd398..a8d6ef3 100644 --- a/roundup/date.py +++ b/roundup/date.py @@ -15,11 +15,11 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: date.py,v 1.57 2003-11-19 22:53:15 jlgijsbers Exp $ +# $Id: date.py,v 1.63 2004-03-24 04:57:25 richard Exp $ -__doc__ = """ -Date, time and time interval handling. +"""Date, time and time interval handling. """ +__docformat__ = 'restructuredtext' import time, re, calendar, types from i18n import _ @@ -51,6 +51,7 @@ class Date: care of these conversions. In the following examples, suppose that yyyy is the current year, mm is the current month, and dd is the current day of the month; and suppose that the user is on Eastern Standard Time. + Examples:: "2000-04-17" means "01-25" means @@ -69,9 +70,8 @@ class Date: separately. For example, when evaluating "2000-06-25 + 1m 10d", we first add one month to get 2000-07-25, then add 10 days to get 2000-08-04 (rather than trying to decide whether 1m 10d means 38 or 40 - or 41 days). + or 41 days). Example usage:: - Example usage: >>> Date(".") >>> _.local(-5) @@ -95,27 +95,37 @@ class Date: 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 - added or subtracted interval. Or a date 9-tuple. - 'offset' is the local time zone offset from GMT in hours. + 'spec' + is a full date or a partial form, with an optional added or + subtracted interval. Or a date 9-tuple. + 'offset' + is the local time zone offset from GMT in hours. """ if type(spec) == type(''): self.set(spec, offset=offset, add_granularity=add_granularity) - else: + return + elif hasattr(spec, 'tuple'): + spec = spec.tuple() + try: y,m,d,H,M,S,x,x,x = spec + frac = S - int(S) 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) + # we lost the fractional part + self.second = self.second + frac + except: + raise ValueError, 'Unknown spec %r'%spec - usagespec='[yyyy]-[mm]-[dd].[H]H:MM[:SS][offset]' + usagespec='[yyyy]-[mm]-[dd].[H]H:MM[:SS.SSS][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?)[/-](?P\d\d?))? # or mm-dd (?P\.)? # . - (((?P\d?\d):(?P\d\d))?(:(?P\d\d))?)? # hh:mm:ss + (((?P\d?\d):(?P\d\d))?(:(?P\d\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) + (\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d(\.\d+)?) ''', re.VERBOSE), add_granularity=0): ''' set the date to the value in spec ''' @@ -123,8 +133,10 @@ class Date: 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]) + g = m.groups() + (self.year, self.month, self.day, self.hour, self.minute) = \ + map(int, g[:5]) + self.second = float(g[5]) return # not serialised data, try usual format @@ -138,7 +150,11 @@ class Date: _add_granularity(info, 'SMHdmyab') # get the current date as our default - y,m,d,H,M,S,x,x,x = time.gmtime(time.time()) + ts = time.time() + frac = ts - int(ts) + y,m,d,H,M,S,x,x,x = time.gmtime(ts) + # gmtime loses the fractional seconds + S = S + frac if info['y'] is not None or info['a'] is not None: if info['y'] is not None: @@ -159,21 +175,26 @@ class Date: H = int(info['H']) - offset M = int(info['M']) S = 0 - if info['S'] is not None: S = int(info['S']) + if info['S'] is not None: + S = float(info['S']) if add_granularity: S = S - 1 # now handle the adjustment of hour + frac = S - int(S) ts = calendar.timegm((y,m,d,H,M,S,0,0,0)) self.year, self.month, self.day, self.hour, self.minute, \ self.second, x, x, x = time.gmtime(ts) + # we lost the fractional part along the way + self.second = self.second + frac if info.get('o', None): try: self.applyInterval(Interval(info['o'], allowdate=0)) except ValueError: - raise ValueError, _('Not a date spec: %s' % self.usagespec) + raise ValueError, _('%r not a date spec (%s)')%(spec, + self.usagespec) def addInterval(self, interval): ''' Add the interval to this date, returning the date tuple @@ -185,18 +206,19 @@ class Date: day = self.day + sign * interval.day hour = self.hour + sign * interval.hour minute = self.minute + sign * interval.minute - second = self.second + sign * interval.second + # Intervals work on whole seconds + second = int(self.second) + sign * interval.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): + hour < 0 or hour > 23): 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 + elif hour > 23: day += 1; hour -= 24 # fix up the month so we're within range while month < 1 or month > 12: @@ -204,13 +226,13 @@ class Date: if month > 12: year += 1; month -= 12 # now do the days, now that we know what month we're in - def get_mdays(year,month): + 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): + + while month < 1 or month > 12 or day < 1 or day > get_mdays(year,month): # now to day under/over - if day < 0: + if day < 1: # When going backwards, decrement month, then increment days month -= 1 day += get_mdays(year,month) @@ -265,7 +287,8 @@ class Date: 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 + # intervals work in whole seconds + diff = int(a - b) if diff > 0: sign = 1 else: @@ -277,21 +300,29 @@ class Date: d = diff/(24*60*60) return Interval((0, 0, d, H, M, S), sign=sign) - def __cmp__(self, other): + def __cmp__(self, other, int_seconds=0): """Compare this date to another date.""" if other is None: return 1 - for attr in ('year', 'month', 'day', 'hour', 'minute', 'second'): + for attr in ('year', 'month', 'day', 'hour', 'minute'): if not hasattr(other, attr): return 1 r = cmp(getattr(self, attr), getattr(other, attr)) if r: return r - return 0 + if not hasattr(other, 'second'): + return 1 + if int_seconds: + return cmp(int(self.second), int(other.second)) + return cmp(self.second, other.second) def __str__(self): """Return this date as a string in the yyyy-mm-dd.hh:mm:ss format.""" - return '%4d-%02d-%02d.%02d:%02d:%02d'%(self.year, self.month, self.day, - self.hour, self.minute, self.second) + return self.formal() + + def formal(self, sep='.', sec='%02d'): + f = '%%4d-%%02d-%%02d%s%%02d:%%02d:%s'%(sep, sec) + return f%(self.year, self.month, self.day, self.hour, self.minute, + self.second) def pretty(self, format='%d %B %Y'): ''' print up the date date using a pretty format... @@ -307,7 +338,7 @@ class Date: return str def __repr__(self): - return ''%self.__str__() + return ''%self.formal(sec='%f') def local(self, offset): """ Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone. @@ -323,6 +354,14 @@ class Date: return '%4d%02d%02d%02d%02d%02d'%(self.year, self.month, self.day, self.hour, self.minute, self.second) + def timestamp(self): + ''' return a UNIX timestamp for this date ''' + frac = self.second - int(self.second) + ts = calendar.timegm((self.year, self.month, self.day, self.hour, + self.minute, self.second, 0, 0, 0)) + # we lose the fractional part + return ts + frac + class Interval: ''' Date intervals are specified using the suffixes "y", "m", and "d". The @@ -383,11 +422,13 @@ class Interval: if len(spec) == 7: self.sign, self.year, self.month, self.day, self.hour, \ self.minute, self.second = spec + self.second = int(self.second) else: # old, buggy spec form self.sign = sign self.year, self.month, self.day, self.hour, self.minute, \ self.second = spec + self.second = int(self.second) def set(self, spec, allowdate=1, interval_re=re.compile(''' \s*(?P[-+])? # + or - @@ -655,20 +696,24 @@ def fixTimeOverflow(time): return (sign, y, m, d, H, M, S) class Range: - """ - Represents range between two values + """Represents range between two values Ranges can be created using one of theese two alternative syntaxes: - 1. Native english syntax: + 1. Native english syntax:: + [[From] ][ To ] - Keywords "From" and "To" are case insensitive. Keyword "From" is optional. - 2. "Geek" syntax: - [][; ] + Keywords "From" and "To" are case insensitive. Keyword "From" is + optional. + + 2. "Geek" syntax:: + + [][; ] Either first or second can be omitted in both syntaxes. - Examples (consider local time is Sat Mar 8 22:07:48 EET 2003): + Examples (consider local time is Sat Mar 8 22:07:48 EET 2003):: + >>> Range("from 2-12 to 4-2")