diff --git a/roundup/date.py b/roundup/date.py
index b9fd398c160b56d844192fb778bb890e928838b5..a8d6ef3b09817ea0963c0663e806a09eb0dfa5f5 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.
#
# 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 _
import time, re, calendar, types
from i18n import _
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.
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 <Date 2000-04-17.00:00:00>
"01-25" means <Date yyyy-01-25.00:00:00>
"2000-04-17" means <Date 2000-04-17.00:00:00>
"01-25" means <Date yyyy-01-25.00:00:00>
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
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(".")
<Date 2000-06-26.00:34:02>
>>> _.local(-5)
>>> Date(".")
<Date 2000-06-26.00:34:02>
>>> _.local(-5)
def __init__(self, spec='.', offset=0, add_granularity=0):
"""Construct a date given a specification and a time zone offset.
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)
"""
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
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)
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<y>\d\d\d\d)([/-](?P<m>\d\d?)([/-](?P<d>\d\d?))?)? # yyyy[-mm[-dd]]
|(?P<a>\d\d?)[/-](?P<b>\d\d?))? # or mm-dd
(?P<n>\.)? # .
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<a>\d\d?)[/-](?P<b>\d\d?))? # or mm-dd
(?P<n>\.)? # .
- (((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d))?)? # hh:mm:ss
+ (((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d(\.\d+)?))?)? # hh:mm:ss
(?P<o>.+)? # offset
''', re.VERBOSE), serialised_re=re.compile(r'''
(?P<o>.+)? # 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
'''
''', 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!
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
return
# not serialised data, try usual format
_add_granularity(info, 'SMHdmyab')
# get the current date as our default
_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:
if info['y'] is not None or info['a'] is not None:
if info['y'] is not None:
H = int(info['H']) - offset
M = int(info['M'])
S = 0
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
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)
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:
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
def addInterval(self, interval):
''' Add the interval to this date, returning the date tuple
day = self.day + sign * interval.day
hour = self.hour + sign * interval.hour
minute = self.minute + sign * interval.minute
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
# 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
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:
# fix up the month so we're within range
while month < 1 or month > 12:
if month > 12: year += 1; month -= 12
# now do the days, now that we know what month we're in
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]
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
# 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)
# When going backwards, decrement month, then increment days
month -= 1
day += get_mdays(year,month)
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))
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:
if diff > 0:
sign = 1
else:
d = diff/(24*60*60)
return Interval((0, 0, d, H, M, S), sign=sign)
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
"""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
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."""
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...
def pretty(self, format='%d %B %Y'):
''' print up the date date using a pretty format...
return str
def __repr__(self):
return str
def __repr__(self):
- return '<Date %s>'%self.__str__()
+ return '<Date %s>'%self.formal(sec='%f')
def local(self, offset):
""" Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone.
def local(self, offset):
""" Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone.
return '%4d%02d%02d%02d%02d%02d'%(self.year, self.month,
self.day, self.hour, self.minute, self.second)
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
class Interval:
'''
Date intervals are specified using the suffixes "y", "m", and "d". The
if len(spec) == 7:
self.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
+ 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
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<s>[-+])? # + or -
def set(self, spec, allowdate=1, interval_re=re.compile('''
\s*(?P<s>[-+])? # + or -
return (sign, y, m, d, H, M, S)
class Range:
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:
Ranges can be created using one of theese two alternative syntaxes:
- 1. Native english syntax:
+ 1. Native english syntax::
+
[[From] <value>][ To <value>]
[[From] <value>][ To <value>]
- Keywords "From" and "To" are case insensitive. Keyword "From" is optional.
- 2. "Geek" syntax:
- [<value>][; <value>]
+ Keywords "From" and "To" are case insensitive. Keyword "From" is
+ optional.
+
+ 2. "Geek" syntax::
+
+ [<value>][; <value>]
Either first or second <value> can be omitted in both syntaxes.
Either first or second <value> 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")
<Range from 2003-02-12.00:00:00 to 2003-04-02.00:00:00>
>>> Range("from 2-12 to 4-2")
<Range from 2003-02-12.00:00:00 to 2003-04-02.00:00:00>