# Copyright (C) 2001, 2003 Andrew T. Csillag <drew_csillag@geocities.com>,
# Jacob Smullyan <smulloni@smullyan.org>
#
# You may distribute under the terms of either the GNU General
# Public License or the SkunkWeb License, as specified in the
# README file.
"""miscellaneous time routines. This module is an update of
Date.TimeUtil for Python 2.2 or later. mx.DateTime is no longer
required, but if available it will correctly process DateTime
objects. dateutil relativedelta objects are also accepted. The
facilities of dateutil and mx.DateTime are used, if available, in
that order of preference, and implementions solely in terms of the
time module are also available."""
try:
from mx import DateTime
_have_mx=1
except ImportError:
_have_mx=0
try:
import datetime
_have_datetime=1
except ImportError:
_have_datetime=0
_have_dateutil=0
else:
try:
import dateutil
from dateutil.relativedelta import relativedelta
from dateutil.parser import parse
_have_dateutil=1
except ImportError:
_have_dateutil=0
try:
basestring
except NameError:
_have_basestring=0
else:
_have_basestring=1
def _is_stringish(s):
if _have_basestring:
return isinstance(s, basestring)
else:
return isinstance(s, str) or isinstance(s, unicode)
import sys
import time
import re
class TimeException(Exception):
pass
class PastTimeException(TimeException):
pass
def _is_mx_DateTime(x):
return _have_mx and isinstance(x, DateTime.DateTimeType)
def _is_mx_RelativeDateTime(x):
return _have_mx and isinstance(x, DateTime.RelativeDateTime)
def _is_dateutil_relativedelta(x):
return _have_dateutil and isinstance(x, relativedelta)
def _is_datetime_datetime(x):
return _have_datetime and isinstance(x, datetime.datetime)
def _to_ticks(x):
if x is None:
return time.time()
if isinstance(x, int) or isinstance(x, float):
return x
if _is_mx_DateTime(x):
return x.ticks()
if _is_datetime_datetime(x):
return time.mktime(x.timetuple())
raise ValueError, "unrecognized type: %s" % x
def convertDuration(duration, curdate=None):
"""
>>> import time; t=time.time()
>>> convertDuration("1h", t)==t+3600
True
"""
return timeConv(duration) + _to_ticks(curdate)
def convertUntil(until, curdate=None):
curdate=_to_ticks(curdate)
# if until is a DateTime object, consider it to be in local server
# time, and return the ticks.
if _is_mx_DateTime(until):
result = until.ticks()
# elif a RelativeDateTime, add it to the current date
elif _is_mx_RelativeDateTime(until):
result = (DateTime.DateTimeFromTicks(curdate)+until).ticks()
# elif a dateutil relativedelta, add to current date
elif _have_dateutil and isinstance(until, relativedelta):
result = time.mktime((until+datetime.datetime.fromtimestamp(curdate)).timetuple())
# elif until is a single string, let untilString handle it.
elif _is_stringish(until):
result = untilString(until, curdate)
# elif a list or tuple of items, run convertUntil on each, take
# the minimum value in the result list, and return that.
elif isinstance(until, list) or isinstance(until, tuple):
lowres = None
for i in until:
thisone = convertUntil(i, curdate)
if lowres is None or thisone < lowres:
lowres = thisone
result = lowres
else:
raise TimeException, until
if result < curdate:
raise PastTimeException, until
return result
# helper re's for untilString
_min_after_hour = re.compile(r'^:\d{2}$')
_timespec = re.compile(r'^\d{2}:\d{2}(:\d{2})?$')
_old_datespec = re.compile(r'^\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}$')
_nice_datespec = re.compile(r'^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$')
_digits = '0123456789'
def _until_timespec_dateutil(curticks, hr, min, sec):
"""
>>> t=time.strptime('200212312359', '%Y%m%d%H%M')
>>> u=_until_timespec_dateutil(time.mktime(t), 4, 20, 32)
>>> time.ctime(u)
'Wed Jan 1 04:20:32 2003'
"""
reldate=relativedelta(hour=hr, minute=min, second=sec)
curdate=datetime.datetime.fromtimestamp(curticks)
huh=curdate+reldate
if huh <= curdate:
huh=huh+relativedelta(days=1)
return time.mktime(huh.timetuple())
def _until_timespec_mx(curticks, hr, min, sec):
"""
>>> t=time.strptime('200212312359', '%Y%m%d%H%M')
>>> u=_until_timespec_mx(time.mktime(t), 4, 20, 32)
>>> time.ctime(u)
'Wed Jan 1 04:20:32 2003'
"""
reldate=DateTime.RelativeDateTime(hour=hr, minute=min, second=sec)
curdate=DateTime.DateTimeFromTicks(curticks)
huh=curdate+reldate
if huh <= curdate:
huh=huh+1
return huh.ticks()
def _until_timespec_vanilla(curticks, hr, min, sec):
"""
>>> t=time.strptime('200212312359', '%Y%m%d%H%M')
>>> u=_until_timespec_vanilla(time.mktime(t), 4, 20, 32)
>>> time.ctime(u)
'Wed Jan 1 04:20:32 2003'
"""
ttuple=time.localtime(curticks)
ntuple=ttuple[:3] + (hr, min, sec) + ttuple[-3:]
nt=time.mktime(ntuple)
if nt > curticks:
return nt
else:
# add a day, but the length of the day
# depends on whether we are on the cusp
# of a change in DST status.
dst1=ntuple[-1]
# 24 hours later
nextday=nt+86400
nexttuple=time.localtime(nextday)
dst2=nexttuple[-1]
if dst1==dst2:
# no change in dst
return nextday
elif dst1<dst2:
# 23 hours
return nt+82800
else:
# 25 hours
return nt+90000
def _until_timespec(curticks, hr, min, sec):
if _have_dateutil:
return _until_timespec_dateutil(curticks, hr, min, sec)
elif _have_mx:
return _until_timespec_mx(curticks, hr, min, sec)
else:
return _until_timespec_mx(curticks, hr, min, sec)
def untilString(until, curdate=None):
"""\
handles all cases where until argument is a string
"""
curdate=_to_ticks(curdate)
# if it looks like a "minutes after hour", return current date
# slammed to next minutes.
if until[0]== ':' and until[1] in _digits and until[2] in _digits:
mins = int(until[1:])
# we may need to roll over the hour, and I want to avoid using
# mx or dateutil, hence the following arithmetic. Leap
# seconds be damned.
curmin=time.localtime(curdate)[4]
secs_to_add=((mins-curmin) % 60)* 60
return curdate+secs_to_add
# elif it looks like just a timespec, return current date with
# time set to that.
elif _timespec.search(until):
parts = until.split(':')
if len(parts) == 2:
parts.append('00')
hr, min, sec = map(int, parts)
return _until_timespec(curdate, hr, min, sec)
# elif it looks like an old-format datespec, just pass it to the
# ugly function.
elif _old_datespec.search(until):
return datestrToSeconds(until)
# elif it looks like a nice datespec/timespec, make a date.
elif _nice_datespec.search(until):
try:
return _parse_ISO(until)
except ValueError:
raise TimeException, until
# else boom
else:
raise TimeException, until
def timeConv(s):
"""convert a time like 9d20h10m15s to seconds"""
tot=0
lastind=0
for i in range(len(s)):
c=s[i]
if c=='d':
tot=tot+int(s[lastind:i])*86400
lastind=i+1
elif c=='h':
tot=tot+int(s[lastind:i])*3600
lastind=i+1
elif c=='m':
tot=tot+int(s[lastind:i])*60
lastind=i+1
elif c=='s':
tot=tot+int(s[lastind:i])
lastind=i+1
elif c not in _digits:
raise TimeException, s
return tot
def currentDate():
"""get current date -- duh!"""
tt=time.localtime(time.time())
return tuple(tt[:3])
def isDST():
"""are we in daylight saving time?"""
return time.localtime(time.time())[-1]
def datestrToSeconds(timestr):
"""converts date strings like 10/20/1999 10:30:15 to a seconds since
epoch value
unfortunately, since there is no mktime for UCT, we have to fuck around
with localtime and dealing with the timezone offset and DST"""
#pull apart by /'s
datestuff=timestr.split('/')
if len(datestuff) == 1: #if no /'s use todays date
y,mo,d=currentDate()
timespec=timestr
elif len(datestuff) != 3: #if some weird number of /'s barf
raise TimeException, timestr
else:
parts=datestuff[2].split(' ') #isolate the year from the time
mo=int(datestuff[0])
d=int(datestuff[1])
y=int(parts[0])
#else, we might not be y2k compliant, all years must be 4 digits
if y<100:
raise TimeException, timestr
timespec=parts[-1]
#pull apart at :'s
timestuff=timespec.split(':')
if len(timestuff) != 3: #some weird number of colons, barf
raise TimeException, timestr
h,m,s=map(int, timestuff)
secs=time.mktime((y,mo,d,h,m,s,0,0,0)) #get seconds in local timezone
return secs
def _parse_ISO_mx(datestr):
"""
>>> _parse_ISO_mx('2003-10-21 15:30')
1066764600.0
>>> _parse_ISO_mx('2003-10-21')
1066708800.0
"""
return DateTime.ISO.ParseDateTime(datestr).ticks()
def _parse_ISO_dateutil(datestr):
"""
>>> _parse_ISO_dateutil('2003-10-21 15:30')
1066764600.0
>>> _parse_ISO_dateutil('2003-10-21')
1066708800.0
"""
return time.mktime(_parse_dateutil(datestr).timetuple())
def _parse_ISO_vanilla(datestr):
"""
>>> _parse_ISO_vanilla('2003-10-21 15:30')
1066764600.0
>>> _parse_ISO_vanilla('2003-10-21')
1066708800.0
"""
formats=['%Y-%m-%d %H:%M:%S',
'%Y-%m-%d %H:%M',
'%Y-%m-%d',
'%Y-%m']
for f in formats:
try:
return time.mktime(time.strptime(datestr, f))
except ValueError:
continue
raise ValueError, datestr
def _parse_ISO(datestr):
if _have_dateutil:
return _parse_ISO_dateutil(datestr)
elif _have_mx:
return _parse_ISO_mx(datestr)
else:
return _parse_ISO_vanilla(datestr)
def _arpa_mx(timet):
return DateTime.ARPA.str(DateTime.DateTimeFromTicks(timet))
def _arpa_vanilla(timet):
time.strftime('%a, %d, %b %Y %H;%m;%S ' +time.strftime('%z'),
time.localtime(timet))
def arpa(timet):
if _have_mx:
return _arpa_mx(timet)
else:
return _arpa_vanilla(timet)
def _old_test():
print "testing old functions..."
print timeConv('1h20m3s')
print time.asctime(time.gmtime(datestrToSeconds('10:20:03')))
print time.asctime(time.gmtime(datestrToSeconds('2/11/1999 10:20:03')))
print currentDate()
print "testing new functions..."
cur = DateTime.now()
print cur.ticks(), cur
for test in [
':15',
(':15', ':30', ':45', ':00', '06:00'),
'00:15',
'23:59',
'23:59:59',
'2007-05-06',
'2007-05-06 23:59',
'2007-05-06 23:59:59',
cur+0.5,
DateTime.RelativeDateTime(minute=15),
DateTime.RelativeDateTime(minute=00),
]:
scs = None
try:
scs = convertUntil(test)
status = 'good'
except PastTimeException:
status = 'time is past'
except TimeException:
status = 'bad argument'
print test
print '-->', scs,
if scs is not None:
print DateTime.DateTimeFrom(scs),
else:
print "None",
print status
def _doctest():
import doctest, timeutil
return doctest.testmod(timeutil)
|