mirror of https://github.com/Mikaela/Limnoria.git
Changing to a different date parsing module.
This commit is contained in:
parent
6878510d7e
commit
27fe6554c7
|
@ -0,0 +1,8 @@
|
||||||
|
"""
|
||||||
|
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
|
||||||
|
|
||||||
|
This module offers extensions to the standard python 2.3+
|
||||||
|
datetime module.
|
||||||
|
"""
|
||||||
|
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
|
||||||
|
__license__ = "PSF License"
|
|
@ -0,0 +1,867 @@
|
||||||
|
# -*- coding:iso-8859-1 -*-
|
||||||
|
"""
|
||||||
|
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
|
||||||
|
|
||||||
|
This module offers extensions to the standard python 2.3+
|
||||||
|
datetime module.
|
||||||
|
"""
|
||||||
|
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
|
||||||
|
__license__ = "PSF License"
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import relativedelta
|
||||||
|
import tz
|
||||||
|
|
||||||
|
__all__ = ["parse", "parserinfo"]
|
||||||
|
|
||||||
|
# Some pointers:
|
||||||
|
#
|
||||||
|
# http://www.cl.cam.ac.uk/~mgk25/iso-time.html
|
||||||
|
# http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html
|
||||||
|
# http://www.w3.org/TR/NOTE-datetime
|
||||||
|
# http://ringmaster.arc.nasa.gov/tools/time_formats.html
|
||||||
|
# http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.pm
|
||||||
|
# http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
class _timelex:
|
||||||
|
def __init__(self, instream):
|
||||||
|
if isinstance(instream, basestring):
|
||||||
|
instream = StringIO(instream)
|
||||||
|
self.instream = instream
|
||||||
|
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
||||||
|
'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
|
||||||
|
'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
|
||||||
|
self.numchars = '0123456789'
|
||||||
|
self.whitespace = ' \t\r\n'
|
||||||
|
self.charstack = []
|
||||||
|
self.tokenstack = []
|
||||||
|
self.eof = False
|
||||||
|
|
||||||
|
def get_token(self):
|
||||||
|
if self.tokenstack:
|
||||||
|
return self.tokenstack.pop(0)
|
||||||
|
seenletters = False
|
||||||
|
token = None
|
||||||
|
state = None
|
||||||
|
wordchars = self.wordchars
|
||||||
|
numchars = self.numchars
|
||||||
|
whitespace = self.whitespace
|
||||||
|
while not self.eof:
|
||||||
|
if self.charstack:
|
||||||
|
nextchar = self.charstack.pop(0)
|
||||||
|
else:
|
||||||
|
nextchar = self.instream.read(1)
|
||||||
|
if not nextchar:
|
||||||
|
self.eof = True
|
||||||
|
break
|
||||||
|
elif not state:
|
||||||
|
token = nextchar
|
||||||
|
if nextchar in wordchars:
|
||||||
|
state = 'a'
|
||||||
|
elif nextchar in numchars:
|
||||||
|
state = '0'
|
||||||
|
elif nextchar in whitespace:
|
||||||
|
token = ' '
|
||||||
|
break # emit token
|
||||||
|
else:
|
||||||
|
break # emit token
|
||||||
|
elif state == 'a':
|
||||||
|
seenletters = True
|
||||||
|
if nextchar in wordchars:
|
||||||
|
token += nextchar
|
||||||
|
elif nextchar == '.':
|
||||||
|
token += nextchar
|
||||||
|
state = 'a.'
|
||||||
|
else:
|
||||||
|
self.charstack.append(nextchar)
|
||||||
|
break # emit token
|
||||||
|
elif state == '0':
|
||||||
|
if nextchar in numchars:
|
||||||
|
token += nextchar
|
||||||
|
elif nextchar == '.':
|
||||||
|
token += nextchar
|
||||||
|
state = '0.'
|
||||||
|
else:
|
||||||
|
self.charstack.append(nextchar)
|
||||||
|
break # emit token
|
||||||
|
elif state == 'a.':
|
||||||
|
seenletters = True
|
||||||
|
if nextchar == '.' or nextchar in wordchars:
|
||||||
|
token += nextchar
|
||||||
|
elif nextchar in numchars and token[-1] == '.':
|
||||||
|
token += nextchar
|
||||||
|
state = '0.'
|
||||||
|
else:
|
||||||
|
self.charstack.append(nextchar)
|
||||||
|
break # emit token
|
||||||
|
elif state == '0.':
|
||||||
|
if nextchar == '.' or nextchar in numchars:
|
||||||
|
token += nextchar
|
||||||
|
elif nextchar in wordchars and token[-1] == '.':
|
||||||
|
token += nextchar
|
||||||
|
state = 'a.'
|
||||||
|
else:
|
||||||
|
self.charstack.append(nextchar)
|
||||||
|
break # emit token
|
||||||
|
if (state in ('a.', '0.') and
|
||||||
|
(seenletters or token.count('.') > 1 or token[-1] == '.')):
|
||||||
|
l = token.split('.')
|
||||||
|
token = l[0]
|
||||||
|
for tok in l[1:]:
|
||||||
|
self.tokenstack.append('.')
|
||||||
|
if tok:
|
||||||
|
self.tokenstack.append(tok)
|
||||||
|
return token
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
token = self.get_token()
|
||||||
|
if token is None:
|
||||||
|
raise StopIteration
|
||||||
|
return token
|
||||||
|
|
||||||
|
def split(cls, s):
|
||||||
|
return list(cls(s))
|
||||||
|
split = classmethod(split)
|
||||||
|
|
||||||
|
class _resultbase(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
for attr in self.__slots__:
|
||||||
|
setattr(self, attr, None)
|
||||||
|
|
||||||
|
def _repr(self, classname):
|
||||||
|
l = []
|
||||||
|
for attr in self.__slots__:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value is not None:
|
||||||
|
l.append("%s=%s" % (attr, `value`))
|
||||||
|
return "%s(%s)" % (classname, ", ".join(l))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self._repr(self.__class__.__name__)
|
||||||
|
|
||||||
|
class parserinfo:
|
||||||
|
|
||||||
|
# m from a.m/p.m, t from ISO T separator
|
||||||
|
JUMP = [" ", ".", ",", ";", "-", "/", "'",
|
||||||
|
"at", "on", "and", "ad", "m", "t", "of",
|
||||||
|
"st", "nd", "rd", "th"]
|
||||||
|
|
||||||
|
WEEKDAYS = [("Mon", "Monday"),
|
||||||
|
("Tue", "Tuesday"),
|
||||||
|
("Wed", "Wednesday"),
|
||||||
|
("Thu", "Thursday"),
|
||||||
|
("Fri", "Friday"),
|
||||||
|
("Sat", "Saturday"),
|
||||||
|
("Sun", "Sunday")]
|
||||||
|
MONTHS = [("Jan", "January"),
|
||||||
|
("Feb", "February"),
|
||||||
|
("Mar", "March"),
|
||||||
|
("Apr", "April"),
|
||||||
|
("May", "May"),
|
||||||
|
("Jun", "June"),
|
||||||
|
("Jul", "July"),
|
||||||
|
("Aug", "August"),
|
||||||
|
("Sep", "September"),
|
||||||
|
("Oct", "October"),
|
||||||
|
("Nov", "November"),
|
||||||
|
("Dec", "December")]
|
||||||
|
HMS = [("h", "hour", "hours"),
|
||||||
|
("m", "minute", "minutes"),
|
||||||
|
("s", "second", "seconds")]
|
||||||
|
AMPM = [("am", "a"),
|
||||||
|
("pm", "p")]
|
||||||
|
UTCZONE = ["UTC", "GMT", "Z"]
|
||||||
|
PERTAIN = ["of"]
|
||||||
|
TZOFFSET = {}
|
||||||
|
|
||||||
|
def __init__(self, dayfirst=False, yearfirst=False):
|
||||||
|
self._jump = self._convert(self.JUMP)
|
||||||
|
self._weekdays = self._convert(self.WEEKDAYS)
|
||||||
|
self._months = self._convert(self.MONTHS)
|
||||||
|
self._hms = self._convert(self.HMS)
|
||||||
|
self._ampm = self._convert(self.AMPM)
|
||||||
|
self._utczone = self._convert(self.UTCZONE)
|
||||||
|
self._pertain = self._convert(self.PERTAIN)
|
||||||
|
|
||||||
|
self.dayfirst = dayfirst
|
||||||
|
self.yearfirst = yearfirst
|
||||||
|
|
||||||
|
self._year = time.localtime().tm_year
|
||||||
|
self._century = self._year/100*100
|
||||||
|
|
||||||
|
def _convert(self, lst):
|
||||||
|
dct = {}
|
||||||
|
for i in range(len(lst)):
|
||||||
|
v = lst[i]
|
||||||
|
if isinstance(v, tuple):
|
||||||
|
for v in v:
|
||||||
|
dct[v.lower()] = i
|
||||||
|
else:
|
||||||
|
dct[v.lower()] = i
|
||||||
|
return dct
|
||||||
|
|
||||||
|
def jump(self, name):
|
||||||
|
return name.lower() in self._jump
|
||||||
|
|
||||||
|
def weekday(self, name):
|
||||||
|
if len(name) >= 3:
|
||||||
|
try:
|
||||||
|
return self._weekdays[name.lower()]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def month(self, name):
|
||||||
|
if len(name) >= 3:
|
||||||
|
try:
|
||||||
|
return self._months[name.lower()]+1
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def hms(self, name):
|
||||||
|
try:
|
||||||
|
return self._hms[name.lower()]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def ampm(self, name):
|
||||||
|
try:
|
||||||
|
return self._ampm[name.lower()]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def pertain(self, name):
|
||||||
|
return name.lower() in self._pertain
|
||||||
|
|
||||||
|
def utczone(self, name):
|
||||||
|
return name.lower() in self._utczone
|
||||||
|
|
||||||
|
def tzoffset(self, name):
|
||||||
|
if name in self._utczone:
|
||||||
|
return 0
|
||||||
|
return self.TZOFFSET.get(name)
|
||||||
|
|
||||||
|
def convertyear(self, year):
|
||||||
|
if year < 100:
|
||||||
|
year += self._century
|
||||||
|
if abs(year-self._year) >= 50:
|
||||||
|
if year < self._year:
|
||||||
|
year += 100
|
||||||
|
else:
|
||||||
|
year -= 100
|
||||||
|
return year
|
||||||
|
|
||||||
|
def validate(self, res):
|
||||||
|
# move to info
|
||||||
|
if res.year is not None:
|
||||||
|
res.year = self.convertyear(res.year)
|
||||||
|
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
|
||||||
|
res.tzname = "UTC"
|
||||||
|
res.tzoffset = 0
|
||||||
|
elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
|
||||||
|
res.tzoffset = 0
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class parser:
|
||||||
|
|
||||||
|
def __init__(self, parserinfo=parserinfo):
|
||||||
|
self.info = parserinfo()
|
||||||
|
|
||||||
|
def parse(self, timestr, default=None,
|
||||||
|
ignoretz=False, tzinfos=None,
|
||||||
|
**kwargs):
|
||||||
|
if not default:
|
||||||
|
default = datetime.datetime.now().replace(hour=0, minute=0,
|
||||||
|
second=0, microsecond=0)
|
||||||
|
res = self._parse(timestr, **kwargs)
|
||||||
|
if res is None:
|
||||||
|
raise ValueError, "unknown string format"
|
||||||
|
repl = {}
|
||||||
|
for attr in ["year", "month", "day", "hour",
|
||||||
|
"minute", "second", "microsecond"]:
|
||||||
|
value = getattr(res, attr)
|
||||||
|
if value is not None:
|
||||||
|
repl[attr] = value
|
||||||
|
ret = default.replace(**repl)
|
||||||
|
if res.weekday is not None and not res.day:
|
||||||
|
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
|
||||||
|
if not ignoretz:
|
||||||
|
if callable(tzinfos) or tzinfos and res.tzname in tzinfos:
|
||||||
|
if callable(tzinfos):
|
||||||
|
tzdata = tzinfos(res.tzname, res.tzoffset)
|
||||||
|
else:
|
||||||
|
tzdata = tzinfos.get(res.tzname)
|
||||||
|
if isinstance(tzdata, datetime.tzinfo):
|
||||||
|
tzinfo = tzdata
|
||||||
|
elif isinstance(tzdata, basestring):
|
||||||
|
tzinfo = tz.tzstr(tzdata)
|
||||||
|
elif isinstance(tzdata, int):
|
||||||
|
tzinfo = tz.tzoffset(res.tzname, tzdata)
|
||||||
|
else:
|
||||||
|
raise ValueError, "offset must be tzinfo subclass, " \
|
||||||
|
"tz string, or int offset"
|
||||||
|
ret = ret.replace(tzinfo=tzinfo)
|
||||||
|
elif res.tzname and res.tzname in time.tzname:
|
||||||
|
ret = ret.replace(tzinfo=tz.tzlocal())
|
||||||
|
elif res.tzoffset == 0:
|
||||||
|
ret = ret.replace(tzinfo=tz.tzutc())
|
||||||
|
elif res.tzoffset:
|
||||||
|
ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class _result(_resultbase):
|
||||||
|
__slots__ = ["year", "month", "day", "weekday",
|
||||||
|
"hour", "minute", "second", "microsecond",
|
||||||
|
"tzname", "tzoffset"]
|
||||||
|
|
||||||
|
def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False):
|
||||||
|
info = self.info
|
||||||
|
if dayfirst is None:
|
||||||
|
dayfirst = info.dayfirst
|
||||||
|
if yearfirst is None:
|
||||||
|
yearfirst = info.yearfirst
|
||||||
|
res = self._result()
|
||||||
|
l = _timelex.split(timestr)
|
||||||
|
try:
|
||||||
|
|
||||||
|
# year/month/day list
|
||||||
|
ymd = []
|
||||||
|
|
||||||
|
# Index of the month string in ymd
|
||||||
|
mstridx = -1
|
||||||
|
|
||||||
|
len_l = len(l)
|
||||||
|
i = 0
|
||||||
|
while i < len_l:
|
||||||
|
|
||||||
|
# Check if it's a number
|
||||||
|
try:
|
||||||
|
value = float(l[i])
|
||||||
|
except ValueError:
|
||||||
|
value = None
|
||||||
|
if value is not None:
|
||||||
|
# Token is a number
|
||||||
|
len_li = len(l[i])
|
||||||
|
i += 1
|
||||||
|
if (len(ymd) == 3 and len_li in (2, 4)
|
||||||
|
and (i >= len_l or l[i] != ':')):
|
||||||
|
# 19990101T23[59]
|
||||||
|
s = l[i-1]
|
||||||
|
res.hour = int(s[:2])
|
||||||
|
if len_li == 4:
|
||||||
|
res.minute = int(s[2:])
|
||||||
|
elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
|
||||||
|
# YYMMDD or HHMMSS[.ss]
|
||||||
|
s = l[i-1]
|
||||||
|
if not ymd and l[i-1].find('.') == -1:
|
||||||
|
ymd.append(info.convertyear(int(s[:2])))
|
||||||
|
ymd.append(int(s[2:4]))
|
||||||
|
ymd.append(int(s[4:]))
|
||||||
|
else:
|
||||||
|
# 19990101T235959[.59]
|
||||||
|
res.hour = int(s[:2])
|
||||||
|
res.minute = int(s[2:4])
|
||||||
|
value = float(s[4:])
|
||||||
|
res.second = int(value)
|
||||||
|
if value%1:
|
||||||
|
res.microsecond = int(1000000*(value%1))
|
||||||
|
elif len_li == 8:
|
||||||
|
# YYYYMMDD
|
||||||
|
s = l[i-1]
|
||||||
|
ymd.append(int(s[:4]))
|
||||||
|
ymd.append(int(s[4:6]))
|
||||||
|
ymd.append(int(s[6:]))
|
||||||
|
elif len_li in (12, 14):
|
||||||
|
# YYYYMMDDhhmm[ss]
|
||||||
|
s = l[i-1]
|
||||||
|
ymd.append(int(s[:4]))
|
||||||
|
ymd.append(int(s[4:6]))
|
||||||
|
ymd.append(int(s[6:8]))
|
||||||
|
res.hour = int(s[8:10])
|
||||||
|
res.minute = int(s[10:12])
|
||||||
|
if len_li == 14:
|
||||||
|
res.second = int(s[12:])
|
||||||
|
elif ((i < len_l and info.hms(l[i]) is not None) or
|
||||||
|
(i+1 < len_l and l[i] == ' ' and
|
||||||
|
info.hms(l[i+1]) is not None)):
|
||||||
|
# HH[ ]h or MM[ ]m or SS[.ss][ ]s
|
||||||
|
if l[i] == ' ':
|
||||||
|
i += 1
|
||||||
|
idx = info.hms(l[i])
|
||||||
|
while True:
|
||||||
|
if idx == 0:
|
||||||
|
res.hour = int(value)
|
||||||
|
if value%1:
|
||||||
|
res.minute = int(60*(value%1))
|
||||||
|
elif idx == 1:
|
||||||
|
res.minute = int(value)
|
||||||
|
if value%1:
|
||||||
|
res.second = int(60*(value%1))
|
||||||
|
elif idx == 2:
|
||||||
|
res.second = int(value)
|
||||||
|
if value%1:
|
||||||
|
res.microsecond = int(1000000*(value%1))
|
||||||
|
i += 1
|
||||||
|
if i >= len_l or idx == 2:
|
||||||
|
break
|
||||||
|
# 12h00
|
||||||
|
try:
|
||||||
|
value = float(l[i])
|
||||||
|
except ValueError:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
idx += 1
|
||||||
|
if i < len_l:
|
||||||
|
newidx = info.hms(l[i])
|
||||||
|
if newidx is not None:
|
||||||
|
idx = newidx
|
||||||
|
elif i+1 < len_l and l[i] == ':':
|
||||||
|
# HH:MM[:SS[.ss]]
|
||||||
|
res.hour = int(value)
|
||||||
|
i += 1
|
||||||
|
value = float(l[i])
|
||||||
|
res.minute = int(value)
|
||||||
|
if value%1:
|
||||||
|
res.second = int(60*(value%1))
|
||||||
|
i += 1
|
||||||
|
if i < len_l and l[i] == ':':
|
||||||
|
value = float(l[i+1])
|
||||||
|
res.second = int(value)
|
||||||
|
if value%1:
|
||||||
|
res.microsecond = int(1000000*(value%1))
|
||||||
|
i += 2
|
||||||
|
elif i < len_l and l[i] in ('-', '/', '.'):
|
||||||
|
sep = l[i]
|
||||||
|
ymd.append(int(value))
|
||||||
|
i += 1
|
||||||
|
if i < len_l and not info.jump(l[i]):
|
||||||
|
try:
|
||||||
|
# 01-01[-01]
|
||||||
|
ymd.append(int(l[i]))
|
||||||
|
except ValueError:
|
||||||
|
# 01-Jan[-01]
|
||||||
|
value = info.month(l[i])
|
||||||
|
if value is not None:
|
||||||
|
ymd.append(value)
|
||||||
|
assert mstridx == -1
|
||||||
|
mstridx = len(ymd)-1
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
i += 1
|
||||||
|
if i < len_l and l[i] == sep:
|
||||||
|
# We have three members
|
||||||
|
i += 1
|
||||||
|
value = info.month(l[i])
|
||||||
|
if value is not None:
|
||||||
|
ymd.append(value)
|
||||||
|
mstridx = len(ymd)-1
|
||||||
|
assert mstridx == -1
|
||||||
|
else:
|
||||||
|
ymd.append(int(l[i]))
|
||||||
|
i += 1
|
||||||
|
elif i >= len_l or info.jump(l[i]):
|
||||||
|
if i+1 < len_l and info.ampm(l[i+1]) is not None:
|
||||||
|
# 12 am
|
||||||
|
res.hour = int(value)
|
||||||
|
if res.hour < 12 and info.ampm(l[i+1]) == 1:
|
||||||
|
res.hour += 12
|
||||||
|
elif res.hour == 12 and info.ampm(l[i+1]) == 0:
|
||||||
|
res.hour = 0
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
# Year, month or day
|
||||||
|
ymd.append(int(value))
|
||||||
|
i += 1
|
||||||
|
elif info.ampm(l[i]) is not None:
|
||||||
|
# 12am
|
||||||
|
res.hour = int(value)
|
||||||
|
if res.hour < 12 and info.ampm(l[i]) == 1:
|
||||||
|
res.hour += 12
|
||||||
|
elif res.hour == 12 and info.ampm(l[i]) == 0:
|
||||||
|
res.hour = 0
|
||||||
|
i += 1
|
||||||
|
elif not fuzzy:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check weekday
|
||||||
|
value = info.weekday(l[i])
|
||||||
|
if value is not None:
|
||||||
|
res.weekday = value
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check month name
|
||||||
|
value = info.month(l[i])
|
||||||
|
if value is not None:
|
||||||
|
ymd.append(value)
|
||||||
|
assert mstridx == -1
|
||||||
|
mstridx = len(ymd)-1
|
||||||
|
i += 1
|
||||||
|
if i < len_l:
|
||||||
|
if l[i] in ('-', '/'):
|
||||||
|
# Jan-01[-99]
|
||||||
|
sep = l[i]
|
||||||
|
i += 1
|
||||||
|
ymd.append(int(l[i]))
|
||||||
|
i += 1
|
||||||
|
if i < len_l and l[i] == sep:
|
||||||
|
# Jan-01-99
|
||||||
|
i += 1
|
||||||
|
ymd.append(int(l[i]))
|
||||||
|
i += 1
|
||||||
|
elif (i+3 < len_l and l[i] == l[i+2] == ' '
|
||||||
|
and info.pertain(l[i+1])):
|
||||||
|
# Jan of 01
|
||||||
|
# In this case, 01 is clearly year
|
||||||
|
try:
|
||||||
|
value = int(l[i+3])
|
||||||
|
except ValueError:
|
||||||
|
# Wrong guess
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Convert it here to become unambiguous
|
||||||
|
ymd.append(info.convertyear(value))
|
||||||
|
i += 4
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check am/pm
|
||||||
|
value = info.ampm(l[i])
|
||||||
|
if value is not None:
|
||||||
|
if value == 1 and res.hour < 12:
|
||||||
|
res.hour += 12
|
||||||
|
elif value == 0 and res.hour == 12:
|
||||||
|
res.hour = 0
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check for a timezone name
|
||||||
|
if (res.hour is not None and len(l[i]) <= 5 and
|
||||||
|
res.tzname is None and res.tzoffset is None and
|
||||||
|
not [x for x in l[i] if x not in string.ascii_uppercase]):
|
||||||
|
res.tzname = l[i]
|
||||||
|
res.tzoffset = info.tzoffset(res.tzname)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Check for something like GMT+3, or BRST+3. Notice
|
||||||
|
# that it doesn't mean "I am 3 hours after GMT", but
|
||||||
|
# "my time +3 is GMT". If found, we reverse the
|
||||||
|
# logic so that timezone parsing code will get it
|
||||||
|
# right.
|
||||||
|
if i < len_l and l[i] in ('+', '-'):
|
||||||
|
l[i] = ('+', '-')[l[i] == '+']
|
||||||
|
res.tzoffset = None
|
||||||
|
if info.utczone(res.tzname):
|
||||||
|
# With something like GMT+3, the timezone
|
||||||
|
# is *not* GMT.
|
||||||
|
res.tzname = None
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check for a numbered timezone
|
||||||
|
if res.hour is not None and l[i] in ('+', '-'):
|
||||||
|
signal = (-1,1)[l[i] == '+']
|
||||||
|
i += 1
|
||||||
|
len_li = len(l[i])
|
||||||
|
if len_li == 4:
|
||||||
|
# -0300
|
||||||
|
res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
|
||||||
|
elif i+1 < len_l and l[i+1] == ':':
|
||||||
|
# -03:00
|
||||||
|
res.tzoffset = int(l[i])*3600+int(l[i+2])*60
|
||||||
|
i += 2
|
||||||
|
elif len_li <= 2:
|
||||||
|
# -[0]3
|
||||||
|
res.tzoffset = int(l[i][:2])*3600
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
i += 1
|
||||||
|
res.tzoffset *= signal
|
||||||
|
|
||||||
|
# Look for a timezone name between parenthesis
|
||||||
|
if (i+3 < len_l and
|
||||||
|
info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
|
||||||
|
3 <= len(l[i+2]) <= 5 and
|
||||||
|
not [x for x in l[i+2]
|
||||||
|
if x not in string.ascii_uppercase]):
|
||||||
|
# -0300 (BRST)
|
||||||
|
res.tzname = l[i+2]
|
||||||
|
i += 4
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check jumps
|
||||||
|
if not (info.jump(l[i]) or fuzzy):
|
||||||
|
return None
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Process year/month/day
|
||||||
|
len_ymd = len(ymd)
|
||||||
|
if len_ymd > 3:
|
||||||
|
# More than three members!?
|
||||||
|
return None
|
||||||
|
elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
|
||||||
|
# One member, or two members with a month string
|
||||||
|
if mstridx != -1:
|
||||||
|
res.month = ymd[mstridx]
|
||||||
|
del ymd[mstridx]
|
||||||
|
if len_ymd > 1 or mstridx == -1:
|
||||||
|
if ymd[0] > 31:
|
||||||
|
res.year = ymd[0]
|
||||||
|
else:
|
||||||
|
res.day = ymd[0]
|
||||||
|
elif len_ymd == 2:
|
||||||
|
# Two members with numbers
|
||||||
|
if ymd[0] > 31:
|
||||||
|
# 99-01
|
||||||
|
res.year, res.month = ymd
|
||||||
|
elif ymd[1] > 31:
|
||||||
|
# 01-99
|
||||||
|
res.month, res.year = ymd
|
||||||
|
elif dayfirst and ymd[1] <= 12:
|
||||||
|
# 13-01
|
||||||
|
res.day, res.month = ymd
|
||||||
|
else:
|
||||||
|
# 01-13
|
||||||
|
res.month, res.day = ymd
|
||||||
|
if len_ymd == 3:
|
||||||
|
# Three members
|
||||||
|
if mstridx == 0:
|
||||||
|
res.month, res.day, res.year = ymd
|
||||||
|
elif mstridx == 1:
|
||||||
|
if ymd[0] > 31 or (yearfirst and ymd[2] <= 31):
|
||||||
|
# 99-Jan-01
|
||||||
|
res.year, res.month, res.day = ymd
|
||||||
|
else:
|
||||||
|
# 01-Jan-01
|
||||||
|
# Give precendence to day-first, since
|
||||||
|
# two-digit years is usually hand-written.
|
||||||
|
res.day, res.month, res.year = ymd
|
||||||
|
elif mstridx == 2:
|
||||||
|
# WTF!?
|
||||||
|
if ymd[1] > 31:
|
||||||
|
# 01-99-Jan
|
||||||
|
res.day, res.year, res.month = ymd
|
||||||
|
else:
|
||||||
|
# 99-01-Jan
|
||||||
|
res.year, res.day, res.month = ymd
|
||||||
|
else:
|
||||||
|
if ymd[0] > 31 or \
|
||||||
|
(yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
|
||||||
|
# 99-01-01
|
||||||
|
res.year, res.month, res.day = ymd
|
||||||
|
elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12):
|
||||||
|
# 13-01-01
|
||||||
|
res.day, res.month, res.year = ymd
|
||||||
|
else:
|
||||||
|
# 01-13-01
|
||||||
|
res.month, res.day, res.year = ymd
|
||||||
|
|
||||||
|
except (IndexError, ValueError, AssertionError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not info.validate(res):
|
||||||
|
return None
|
||||||
|
return res
|
||||||
|
|
||||||
|
DEFAULTPARSER = parser()
|
||||||
|
def parse(timestr, parserinfo=None, **kwargs):
|
||||||
|
if parserinfo:
|
||||||
|
return parser(parserinfo).parse(timestr, **kwargs)
|
||||||
|
else:
|
||||||
|
return DEFAULTPARSER.parse(timestr, **kwargs)
|
||||||
|
|
||||||
|
class _tzparser:
|
||||||
|
|
||||||
|
class _result(_resultbase):
|
||||||
|
|
||||||
|
__slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
|
||||||
|
"start", "end"]
|
||||||
|
|
||||||
|
class _attr(_resultbase):
|
||||||
|
__slots__ = ["month", "week", "weekday",
|
||||||
|
"yday", "jyday", "day", "time"]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self._repr("")
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
_resultbase.__init__(self)
|
||||||
|
self.start = self._attr()
|
||||||
|
self.end = self._attr()
|
||||||
|
|
||||||
|
def parse(self, tzstr):
|
||||||
|
res = self._result()
|
||||||
|
l = _timelex.split(tzstr)
|
||||||
|
try:
|
||||||
|
|
||||||
|
len_l = len(l)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < len_l:
|
||||||
|
# BRST+3[BRDT[+2]]
|
||||||
|
j = i
|
||||||
|
while j < len_l and not [x for x in l[j]
|
||||||
|
if x in "0123456789:,-+"]:
|
||||||
|
j += 1
|
||||||
|
if j != i:
|
||||||
|
if not res.stdabbr:
|
||||||
|
offattr = "stdoffset"
|
||||||
|
res.stdabbr = "".join(l[i:j])
|
||||||
|
else:
|
||||||
|
offattr = "dstoffset"
|
||||||
|
res.dstabbr = "".join(l[i:j])
|
||||||
|
i = j
|
||||||
|
if (i < len_l and
|
||||||
|
(l[i] in ('+', '-') or l[i][0] in "0123456789")):
|
||||||
|
if l[i] in ('+', '-'):
|
||||||
|
signal = (1,-1)[l[i] == '+']
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
signal = -1
|
||||||
|
len_li = len(l[i])
|
||||||
|
if len_li == 4:
|
||||||
|
# -0300
|
||||||
|
setattr(res, offattr,
|
||||||
|
(int(l[i][:2])*3600+int(l[i][2:])*60)*signal)
|
||||||
|
elif i+1 < len_l and l[i+1] == ':':
|
||||||
|
# -03:00
|
||||||
|
setattr(res, offattr,
|
||||||
|
(int(l[i])*3600+int(l[i+2])*60)*signal)
|
||||||
|
i += 2
|
||||||
|
elif len_li <= 2:
|
||||||
|
# -[0]3
|
||||||
|
setattr(res, offattr,
|
||||||
|
int(l[i][:2])*3600*signal)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
i += 1
|
||||||
|
if res.dstabbr:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if i < len_l:
|
||||||
|
for j in range(i, len_l):
|
||||||
|
if l[j] == ';': l[j] = ','
|
||||||
|
|
||||||
|
assert l[i] == ','
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if i >= len_l:
|
||||||
|
pass
|
||||||
|
elif (8 <= l.count(',') <= 9 and
|
||||||
|
not [y for x in l[i:] if x != ','
|
||||||
|
for y in x if y not in "0123456789"]):
|
||||||
|
# GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
|
||||||
|
for x in (res.start, res.end):
|
||||||
|
x.month = int(l[i])
|
||||||
|
i += 2
|
||||||
|
if l[i] == '-':
|
||||||
|
value = int(l[i+1])*-1
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
value = int(l[i])
|
||||||
|
i += 2
|
||||||
|
if value:
|
||||||
|
x.week = value
|
||||||
|
x.weekday = (int(l[i])-1)%7
|
||||||
|
else:
|
||||||
|
x.day = int(l[i])
|
||||||
|
i += 2
|
||||||
|
x.time = int(l[i])
|
||||||
|
i += 2
|
||||||
|
if i < len_l:
|
||||||
|
if l[i] in ('-','+'):
|
||||||
|
signal = (-1,1)[l[i] == "+"]
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
signal = 1
|
||||||
|
res.dstoffset = (res.stdoffset+int(l[i]))*signal
|
||||||
|
elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
|
||||||
|
not [y for x in l[i:] if x not in (',','/','J','M',
|
||||||
|
'.','-',':')
|
||||||
|
for y in x if y not in "0123456789"]):
|
||||||
|
for x in (res.start, res.end):
|
||||||
|
if l[i] == 'J':
|
||||||
|
# non-leap year day (1 based)
|
||||||
|
i += 1
|
||||||
|
x.jyday = int(l[i])
|
||||||
|
elif l[i] == 'M':
|
||||||
|
# month[-.]week[-.]weekday
|
||||||
|
i += 1
|
||||||
|
x.month = int(l[i])
|
||||||
|
i += 1
|
||||||
|
assert l[i] in ('-', '.')
|
||||||
|
i += 1
|
||||||
|
x.week = int(l[i])
|
||||||
|
if x.week == 5:
|
||||||
|
x.week = -1
|
||||||
|
i += 1
|
||||||
|
assert l[i] in ('-', '.')
|
||||||
|
i += 1
|
||||||
|
x.weekday = (int(l[i])-1)%7
|
||||||
|
else:
|
||||||
|
# year day (zero based)
|
||||||
|
x.yday = int(l[i])+1
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if i < len_l and l[i] == '/':
|
||||||
|
i += 1
|
||||||
|
# start time
|
||||||
|
len_li = len(l[i])
|
||||||
|
if len_li == 4:
|
||||||
|
# -0300
|
||||||
|
x.time = (int(l[i][:2])*3600+int(l[i][2:])*60)
|
||||||
|
elif i+1 < len_l and l[i+1] == ':':
|
||||||
|
# -03:00
|
||||||
|
x.time = int(l[i])*3600+int(l[i+2])*60
|
||||||
|
i += 2
|
||||||
|
if i+1 < len_l and l[i+1] == ':':
|
||||||
|
i += 2
|
||||||
|
x.time += int(l[i])
|
||||||
|
elif len_li <= 2:
|
||||||
|
# -[0]3
|
||||||
|
x.time = (int(l[i][:2])*3600)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
assert i == len_l or l[i] == ','
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
assert i >= len_l
|
||||||
|
|
||||||
|
except (IndexError, ValueError, AssertionError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
DEFAULTTZPARSER = _tzparser()
|
||||||
|
def _parsetz(tzstr):
|
||||||
|
return DEFAULTTZPARSER.parse(tzstr)
|
||||||
|
|
||||||
|
# vim:ts=4:sw=4:et
|
|
@ -0,0 +1,432 @@
|
||||||
|
"""
|
||||||
|
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
|
||||||
|
|
||||||
|
This module offers extensions to the standard python 2.3+
|
||||||
|
datetime module.
|
||||||
|
"""
|
||||||
|
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
|
||||||
|
__license__ = "PSF License"
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import calendar
|
||||||
|
|
||||||
|
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||||
|
|
||||||
|
class weekday(object):
|
||||||
|
__slots__ = ["weekday", "n"]
|
||||||
|
|
||||||
|
def __init__(self, weekday, n=0):
|
||||||
|
self.weekday = weekday
|
||||||
|
self.n = n
|
||||||
|
|
||||||
|
def __call__(self, n):
|
||||||
|
if n == self.n:
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
return self.__class__(self.weekday, n)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
try:
|
||||||
|
if self.weekday != other.weekday or self.n != other.n:
|
||||||
|
return False
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
||||||
|
if not self.n:
|
||||||
|
return s
|
||||||
|
else:
|
||||||
|
return "%s(%+d)" % (s, self.n)
|
||||||
|
|
||||||
|
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
||||||
|
|
||||||
|
class relativedelta:
|
||||||
|
"""
|
||||||
|
The relativedelta type is based on the specification of the excelent
|
||||||
|
work done by M.-A. Lemburg in his mx.DateTime extension. However,
|
||||||
|
notice that this type does *NOT* implement the same algorithm as
|
||||||
|
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||||
|
|
||||||
|
There's two different ways to build a relativedelta instance. The
|
||||||
|
first one is passing it two date/datetime classes:
|
||||||
|
|
||||||
|
relativedelta(datetime1, datetime2)
|
||||||
|
|
||||||
|
And the other way is to use the following keyword arguments:
|
||||||
|
|
||||||
|
year, month, day, hour, minute, second, microsecond:
|
||||||
|
Absolute information.
|
||||||
|
|
||||||
|
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||||
|
Relative information, may be negative.
|
||||||
|
|
||||||
|
weekday:
|
||||||
|
One of the weekday instances (MO, TU, etc). These instances may
|
||||||
|
receive a parameter N, specifying the Nth weekday, which could
|
||||||
|
be positive or negative (like MO(+1) or MO(-2). Not specifying
|
||||||
|
it is the same as specifying +1. You can also use an integer,
|
||||||
|
where 0=MO.
|
||||||
|
|
||||||
|
leapdays:
|
||||||
|
Will add given days to the date found, if year is a leap
|
||||||
|
year, and the date found is post 28 of february.
|
||||||
|
|
||||||
|
yearday, nlyearday:
|
||||||
|
Set the yearday or the non-leap year day (jump leap days).
|
||||||
|
These are converted to day/month/leapdays information.
|
||||||
|
|
||||||
|
Here is the behavior of operations with relativedelta:
|
||||||
|
|
||||||
|
1) Calculate the absolute year, using the 'year' argument, or the
|
||||||
|
original datetime year, if the argument is not present.
|
||||||
|
|
||||||
|
2) Add the relative 'years' argument to the absolute year.
|
||||||
|
|
||||||
|
3) Do steps 1 and 2 for month/months.
|
||||||
|
|
||||||
|
4) Calculate the absolute day, using the 'day' argument, or the
|
||||||
|
original datetime day, if the argument is not present. Then,
|
||||||
|
subtract from the day until it fits in the year and month
|
||||||
|
found after their operations.
|
||||||
|
|
||||||
|
5) Add the relative 'days' argument to the absolute day. Notice
|
||||||
|
that the 'weeks' argument is multiplied by 7 and added to
|
||||||
|
'days'.
|
||||||
|
|
||||||
|
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
||||||
|
microsecond/microseconds.
|
||||||
|
|
||||||
|
7) If the 'weekday' argument is present, calculate the weekday,
|
||||||
|
with the given (wday, nth) tuple. wday is the index of the
|
||||||
|
weekday (0-6, 0=Mon), and nth is the number of weeks to add
|
||||||
|
forward or backward, depending on its signal. Notice that if
|
||||||
|
the calculated date is already Monday, for example, using
|
||||||
|
(0, 1) or (0, -1) won't change the day.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, dt1=None, dt2=None,
|
||||||
|
years=0, months=0, days=0, leapdays=0, weeks=0,
|
||||||
|
hours=0, minutes=0, seconds=0, microseconds=0,
|
||||||
|
year=None, month=None, day=None, weekday=None,
|
||||||
|
yearday=None, nlyearday=None,
|
||||||
|
hour=None, minute=None, second=None, microsecond=None):
|
||||||
|
if dt1 and dt2:
|
||||||
|
if not isinstance(dt1, datetime.date) or \
|
||||||
|
not isinstance(dt2, datetime.date):
|
||||||
|
raise TypeError, "relativedelta only diffs datetime/date"
|
||||||
|
if type(dt1) is not type(dt2):
|
||||||
|
if not isinstance(dt1, datetime.datetime):
|
||||||
|
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||||
|
elif not isinstance(dt2, datetime.datetime):
|
||||||
|
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
||||||
|
self.years = 0
|
||||||
|
self.months = 0
|
||||||
|
self.days = 0
|
||||||
|
self.leapdays = 0
|
||||||
|
self.hours = 0
|
||||||
|
self.minutes = 0
|
||||||
|
self.seconds = 0
|
||||||
|
self.microseconds = 0
|
||||||
|
self.year = None
|
||||||
|
self.month = None
|
||||||
|
self.day = None
|
||||||
|
self.weekday = None
|
||||||
|
self.hour = None
|
||||||
|
self.minute = None
|
||||||
|
self.second = None
|
||||||
|
self.microsecond = None
|
||||||
|
self._has_time = 0
|
||||||
|
|
||||||
|
months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
|
||||||
|
self._set_months(months)
|
||||||
|
dtm = self.__radd__(dt2)
|
||||||
|
if dt1 < dt2:
|
||||||
|
while dt1 > dtm:
|
||||||
|
months += 1
|
||||||
|
self._set_months(months)
|
||||||
|
dtm = self.__radd__(dt2)
|
||||||
|
else:
|
||||||
|
while dt1 < dtm:
|
||||||
|
months -= 1
|
||||||
|
self._set_months(months)
|
||||||
|
dtm = self.__radd__(dt2)
|
||||||
|
delta = dt1 - dtm
|
||||||
|
self.seconds = delta.seconds+delta.days*86400
|
||||||
|
self.microseconds = delta.microseconds
|
||||||
|
else:
|
||||||
|
self.years = years
|
||||||
|
self.months = months
|
||||||
|
self.days = days+weeks*7
|
||||||
|
self.leapdays = leapdays
|
||||||
|
self.hours = hours
|
||||||
|
self.minutes = minutes
|
||||||
|
self.seconds = seconds
|
||||||
|
self.microseconds = microseconds
|
||||||
|
self.year = year
|
||||||
|
self.month = month
|
||||||
|
self.day = day
|
||||||
|
self.hour = hour
|
||||||
|
self.minute = minute
|
||||||
|
self.second = second
|
||||||
|
self.microsecond = microsecond
|
||||||
|
|
||||||
|
if type(weekday) is int:
|
||||||
|
self.weekday = weekdays[weekday]
|
||||||
|
else:
|
||||||
|
self.weekday = weekday
|
||||||
|
|
||||||
|
yday = 0
|
||||||
|
if nlyearday:
|
||||||
|
yday = nlyearday
|
||||||
|
elif yearday:
|
||||||
|
yday = yearday
|
||||||
|
if yearday > 59:
|
||||||
|
self.leapdays = -1
|
||||||
|
if yday:
|
||||||
|
ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366]
|
||||||
|
for idx, ydays in enumerate(ydayidx):
|
||||||
|
if yday <= ydays:
|
||||||
|
self.month = idx+1
|
||||||
|
if idx == 0:
|
||||||
|
self.day = ydays
|
||||||
|
else:
|
||||||
|
self.day = yday-ydayidx[idx-1]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError, "invalid year day (%d)" % yday
|
||||||
|
|
||||||
|
self._fix()
|
||||||
|
|
||||||
|
def _fix(self):
|
||||||
|
if abs(self.microseconds) > 999999:
|
||||||
|
s = self.microseconds/abs(self.microseconds)
|
||||||
|
div, mod = divmod(self.microseconds*s, 1000000)
|
||||||
|
self.microseconds = mod*s
|
||||||
|
self.seconds += div*s
|
||||||
|
if abs(self.seconds) > 59:
|
||||||
|
s = self.seconds/abs(self.seconds)
|
||||||
|
div, mod = divmod(self.seconds*s, 60)
|
||||||
|
self.seconds = mod*s
|
||||||
|
self.minutes += div*s
|
||||||
|
if abs(self.minutes) > 59:
|
||||||
|
s = self.minutes/abs(self.minutes)
|
||||||
|
div, mod = divmod(self.minutes*s, 60)
|
||||||
|
self.minutes = mod*s
|
||||||
|
self.hours += div*s
|
||||||
|
if abs(self.hours) > 23:
|
||||||
|
s = self.hours/abs(self.hours)
|
||||||
|
div, mod = divmod(self.hours*s, 24)
|
||||||
|
self.hours = mod*s
|
||||||
|
self.days += div*s
|
||||||
|
if abs(self.months) > 11:
|
||||||
|
s = self.months/abs(self.months)
|
||||||
|
div, mod = divmod(self.months*s, 12)
|
||||||
|
self.months = mod*s
|
||||||
|
self.years += div*s
|
||||||
|
if (self.hours or self.minutes or self.seconds or self.microseconds or
|
||||||
|
self.hour is not None or self.minute is not None or
|
||||||
|
self.second is not None or self.microsecond is not None):
|
||||||
|
self._has_time = 1
|
||||||
|
else:
|
||||||
|
self._has_time = 0
|
||||||
|
|
||||||
|
def _set_months(self, months):
|
||||||
|
self.months = months
|
||||||
|
if abs(self.months) > 11:
|
||||||
|
s = self.months/abs(self.months)
|
||||||
|
div, mod = divmod(self.months*s, 12)
|
||||||
|
self.months = mod*s
|
||||||
|
self.years = div*s
|
||||||
|
else:
|
||||||
|
self.years = 0
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
if not isinstance(other, datetime.date):
|
||||||
|
raise TypeError, "unsupported type for add operation"
|
||||||
|
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||||
|
other = datetime.datetime.fromordinal(other.toordinal())
|
||||||
|
year = (self.year or other.year)+self.years
|
||||||
|
month = self.month or other.month
|
||||||
|
if self.months:
|
||||||
|
assert 1 <= abs(self.months) <= 12
|
||||||
|
month += self.months
|
||||||
|
if month > 12:
|
||||||
|
year += 1
|
||||||
|
month -= 12
|
||||||
|
elif month < 1:
|
||||||
|
year -= 1
|
||||||
|
month += 12
|
||||||
|
day = min(calendar.monthrange(year, month)[1],
|
||||||
|
self.day or other.day)
|
||||||
|
repl = {"year": year, "month": month, "day": day}
|
||||||
|
for attr in ["hour", "minute", "second", "microsecond"]:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value is not None:
|
||||||
|
repl[attr] = value
|
||||||
|
days = self.days
|
||||||
|
if self.leapdays and month > 2 and calendar.isleap(year):
|
||||||
|
days += self.leapdays
|
||||||
|
ret = (other.replace(**repl)
|
||||||
|
+ datetime.timedelta(days=days,
|
||||||
|
hours=self.hours,
|
||||||
|
minutes=self.minutes,
|
||||||
|
seconds=self.seconds,
|
||||||
|
microseconds=self.microseconds))
|
||||||
|
if self.weekday:
|
||||||
|
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||||
|
jumpdays = (abs(nth)-1)*7
|
||||||
|
if nth > 0:
|
||||||
|
jumpdays += (7-ret.weekday()+weekday)%7
|
||||||
|
else:
|
||||||
|
jumpdays += (ret.weekday()-weekday)%7
|
||||||
|
jumpdays *= -1
|
||||||
|
ret += datetime.timedelta(days=jumpdays)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __rsub__(self, other):
|
||||||
|
return self.__neg__().__radd__(other)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
if not isinstance(other, relativedelta):
|
||||||
|
raise TypeError, "unsupported type for add operation"
|
||||||
|
return relativedelta(years=other.years+self.years,
|
||||||
|
months=other.months+self.months,
|
||||||
|
days=other.days+self.days,
|
||||||
|
hours=other.hours+self.hours,
|
||||||
|
minutes=other.minutes+self.minutes,
|
||||||
|
seconds=other.seconds+self.seconds,
|
||||||
|
microseconds=other.microseconds+self.microseconds,
|
||||||
|
leapdays=other.leapdays or self.leapdays,
|
||||||
|
year=other.year or self.year,
|
||||||
|
month=other.month or self.month,
|
||||||
|
day=other.day or self.day,
|
||||||
|
weekday=other.weekday or self.weekday,
|
||||||
|
hour=other.hour or self.hour,
|
||||||
|
minute=other.minute or self.minute,
|
||||||
|
second=other.second or self.second,
|
||||||
|
microsecond=other.second or self.microsecond)
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
if not isinstance(other, relativedelta):
|
||||||
|
raise TypeError, "unsupported type for sub operation"
|
||||||
|
return relativedelta(years=other.years-self.years,
|
||||||
|
months=other.months-self.months,
|
||||||
|
days=other.days-self.days,
|
||||||
|
hours=other.hours-self.hours,
|
||||||
|
minutes=other.minutes-self.minutes,
|
||||||
|
seconds=other.seconds-self.seconds,
|
||||||
|
microseconds=other.microseconds-self.microseconds,
|
||||||
|
leapdays=other.leapdays or self.leapdays,
|
||||||
|
year=other.year or self.year,
|
||||||
|
month=other.month or self.month,
|
||||||
|
day=other.day or self.day,
|
||||||
|
weekday=other.weekday or self.weekday,
|
||||||
|
hour=other.hour or self.hour,
|
||||||
|
minute=other.minute or self.minute,
|
||||||
|
second=other.second or self.second,
|
||||||
|
microsecond=other.second or self.microsecond)
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
return relativedelta(years=-self.years,
|
||||||
|
months=-self.months,
|
||||||
|
days=-self.days,
|
||||||
|
hours=-self.hours,
|
||||||
|
minutes=-self.minutes,
|
||||||
|
seconds=-self.seconds,
|
||||||
|
microseconds=-self.microseconds,
|
||||||
|
leapdays=self.leapdays,
|
||||||
|
year=self.year,
|
||||||
|
month=self.month,
|
||||||
|
day=self.day,
|
||||||
|
weekday=self.weekday,
|
||||||
|
hour=self.hour,
|
||||||
|
minute=self.minute,
|
||||||
|
second=self.second,
|
||||||
|
microsecond=self.microsecond)
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
return not (not self.years and
|
||||||
|
not self.months and
|
||||||
|
not self.days and
|
||||||
|
not self.hours and
|
||||||
|
not self.minutes and
|
||||||
|
not self.seconds and
|
||||||
|
not self.microseconds and
|
||||||
|
not self.leapdays and
|
||||||
|
self.year is None and
|
||||||
|
self.month is None and
|
||||||
|
self.day is None and
|
||||||
|
self.weekday is None and
|
||||||
|
self.hour is None and
|
||||||
|
self.minute is None and
|
||||||
|
self.second is None and
|
||||||
|
self.microsecond is None)
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
f = float(other)
|
||||||
|
return relativedelta(years=self.years*f,
|
||||||
|
months=self.months*f,
|
||||||
|
days=self.days*f,
|
||||||
|
hours=self.hours*f,
|
||||||
|
minutes=self.minutes*f,
|
||||||
|
seconds=self.seconds*f,
|
||||||
|
microseconds=self.microseconds*f,
|
||||||
|
leapdays=self.leapdays,
|
||||||
|
year=self.year,
|
||||||
|
month=self.month,
|
||||||
|
day=self.day,
|
||||||
|
weekday=self.weekday,
|
||||||
|
hour=self.hour,
|
||||||
|
minute=self.minute,
|
||||||
|
second=self.second,
|
||||||
|
microsecond=self.microsecond)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, relativedelta):
|
||||||
|
return False
|
||||||
|
if self.weekday or other.weekday:
|
||||||
|
if not self.weekday or not other.weekday:
|
||||||
|
return False
|
||||||
|
if self.weekday.weekday != other.weekday.weekday:
|
||||||
|
return False
|
||||||
|
n1, n2 = self.weekday.n, other.weekday.n
|
||||||
|
if n1 != n2 and not (n1 in (0, 1) and n2 in (0, 1)):
|
||||||
|
return False
|
||||||
|
return (self.years == other.years and
|
||||||
|
self.months == other.months and
|
||||||
|
self.days == other.days and
|
||||||
|
self.hours == other.hours and
|
||||||
|
self.minutes == other.minutes and
|
||||||
|
self.seconds == other.seconds and
|
||||||
|
self.leapdays == other.leapdays and
|
||||||
|
self.year == other.year and
|
||||||
|
self.month == other.month and
|
||||||
|
self.day == other.day and
|
||||||
|
self.hour == other.hour and
|
||||||
|
self.minute == other.minute and
|
||||||
|
self.second == other.second and
|
||||||
|
self.microsecond == other.microsecond)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __div__(self, other):
|
||||||
|
return self.__mul__(1/float(other))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
l = []
|
||||||
|
for attr in ["years", "months", "days", "leapdays",
|
||||||
|
"hours", "minutes", "seconds", "microseconds"]:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value:
|
||||||
|
l.append("%s=%+d" % (attr, value))
|
||||||
|
for attr in ["year", "month", "day", "weekday",
|
||||||
|
"hour", "minute", "second", "microsecond"]:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value is not None:
|
||||||
|
l.append("%s=%s" % (attr, `value`))
|
||||||
|
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
||||||
|
|
||||||
|
# vim:ts=4:sw=4:et
|
|
@ -0,0 +1,883 @@
|
||||||
|
"""
|
||||||
|
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
|
||||||
|
|
||||||
|
This module offers extensions to the standard python 2.3+
|
||||||
|
datetime module.
|
||||||
|
"""
|
||||||
|
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
|
||||||
|
__license__ = "PSF License"
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
|
||||||
|
relativedelta = None
|
||||||
|
parser = None
|
||||||
|
rrule = None
|
||||||
|
|
||||||
|
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile",
|
||||||
|
"tzrange", "tzstr", "tzical", "gettz"]
|
||||||
|
|
||||||
|
ZERO = datetime.timedelta(0)
|
||||||
|
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
||||||
|
|
||||||
|
class tzutc(datetime.tzinfo):
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return "UTC"
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (isinstance(other, tzutc) or
|
||||||
|
(isinstance(other, tzoffset) and other._offset == ZERO))
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s()" % self.__class__.__name__
|
||||||
|
|
||||||
|
class tzoffset(datetime.tzinfo):
|
||||||
|
|
||||||
|
def __init__(self, name, offset):
|
||||||
|
self._name = name
|
||||||
|
self._offset = datetime.timedelta(seconds=offset)
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return self._offset
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (isinstance(other, tzoffset) and
|
||||||
|
self._offset == other._offset)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s, %s)" % (self.__class__.__name__,
|
||||||
|
`self._name`,
|
||||||
|
self._offset.days*86400+self._offset.seconds)
|
||||||
|
|
||||||
|
class tzlocal(datetime.tzinfo):
|
||||||
|
|
||||||
|
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
||||||
|
if time.daylight:
|
||||||
|
_dst_offset = datetime.timedelta(seconds=-time.altzone)
|
||||||
|
else:
|
||||||
|
_dst_offset = _std_offset
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
if self._isdst(dt):
|
||||||
|
return self._dst_offset
|
||||||
|
else:
|
||||||
|
return self._std_offset
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
if self._isdst(dt):
|
||||||
|
return self._dst_offset-self._std_offset
|
||||||
|
else:
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return time.tzname[self._isdst(dt)]
|
||||||
|
|
||||||
|
def _isdst(self, dt):
|
||||||
|
# We can't use mktime here. It is unstable when deciding if
|
||||||
|
# the hour near to a change is DST or not.
|
||||||
|
#
|
||||||
|
# timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
|
||||||
|
# dt.minute, dt.second, dt.weekday(), 0, -1))
|
||||||
|
# return time.localtime(timestamp).tm_isdst
|
||||||
|
#
|
||||||
|
# The code above yields the following result:
|
||||||
|
#
|
||||||
|
#>>> import tz, datetime
|
||||||
|
#>>> t = tz.tzlocal()
|
||||||
|
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||||
|
#'BRDT'
|
||||||
|
#>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
||||||
|
#'BRST'
|
||||||
|
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||||
|
#'BRST'
|
||||||
|
#>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
||||||
|
#'BRDT'
|
||||||
|
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||||
|
#'BRDT'
|
||||||
|
#
|
||||||
|
# Here is a more stable implementation:
|
||||||
|
#
|
||||||
|
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
||||||
|
+ dt.hour * 3600
|
||||||
|
+ dt.minute * 60
|
||||||
|
+ dt.second)
|
||||||
|
return time.localtime(timestamp+time.timezone).tm_isdst
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, tzlocal):
|
||||||
|
return False
|
||||||
|
return (self._std_offset == other._std_offset and
|
||||||
|
self._dst_offset == other._dst_offset)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s()" % self.__class__.__name__
|
||||||
|
|
||||||
|
class _ttinfo(object):
|
||||||
|
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
for attr in self.__slots__:
|
||||||
|
setattr(self, attr, None)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
l = []
|
||||||
|
for attr in self.__slots__:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value is not None:
|
||||||
|
l.append("%s=%s" % (attr, `value`))
|
||||||
|
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, _ttinfo):
|
||||||
|
return False
|
||||||
|
return (self.offset == other.offset and
|
||||||
|
self.delta == other.delta and
|
||||||
|
self.isdst == other.isdst and
|
||||||
|
self.abbr == other.abbr and
|
||||||
|
self.isstd == other.isstd and
|
||||||
|
self.isgmt == other.isgmt)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
class tzfile(datetime.tzinfo):
|
||||||
|
|
||||||
|
# http://www.twinsun.com/tz/tz-link.htm
|
||||||
|
# ftp://elsie.nci.nih.gov/pub/tz*.tar.gz
|
||||||
|
|
||||||
|
def __init__(self, fileobj):
|
||||||
|
if isinstance(fileobj, basestring):
|
||||||
|
self._s = fileobj
|
||||||
|
fileobj = open(fileobj)
|
||||||
|
elif hasattr(fileobj, "name"):
|
||||||
|
self._s = fileobj.name
|
||||||
|
else:
|
||||||
|
self._s = `fileobj`
|
||||||
|
|
||||||
|
# From tzfile(5):
|
||||||
|
#
|
||||||
|
# The time zone information files used by tzset(3)
|
||||||
|
# begin with the magic characters "TZif" to identify
|
||||||
|
# them as time zone information files, followed by
|
||||||
|
# sixteen bytes reserved for future use, followed by
|
||||||
|
# six four-byte values of type long, written in a
|
||||||
|
# ``standard'' byte order (the high-order byte
|
||||||
|
# of the value is written first).
|
||||||
|
|
||||||
|
if fileobj.read(4) != "TZif":
|
||||||
|
raise ValueError, "magic not found"
|
||||||
|
|
||||||
|
fileobj.read(16)
|
||||||
|
|
||||||
|
(
|
||||||
|
# The number of UTC/local indicators stored in the file.
|
||||||
|
ttisgmtcnt,
|
||||||
|
|
||||||
|
# The number of standard/wall indicators stored in the file.
|
||||||
|
ttisstdcnt,
|
||||||
|
|
||||||
|
# The number of leap seconds for which data is
|
||||||
|
# stored in the file.
|
||||||
|
leapcnt,
|
||||||
|
|
||||||
|
# The number of "transition times" for which data
|
||||||
|
# is stored in the file.
|
||||||
|
timecnt,
|
||||||
|
|
||||||
|
# The number of "local time types" for which data
|
||||||
|
# is stored in the file (must not be zero).
|
||||||
|
typecnt,
|
||||||
|
|
||||||
|
# The number of characters of "time zone
|
||||||
|
# abbreviation strings" stored in the file.
|
||||||
|
charcnt,
|
||||||
|
|
||||||
|
) = struct.unpack(">6l", fileobj.read(24))
|
||||||
|
|
||||||
|
# The above header is followed by tzh_timecnt four-byte
|
||||||
|
# values of type long, sorted in ascending order.
|
||||||
|
# These values are written in ``standard'' byte order.
|
||||||
|
# Each is used as a transition time (as returned by
|
||||||
|
# time(2)) at which the rules for computing local time
|
||||||
|
# change.
|
||||||
|
|
||||||
|
if timecnt:
|
||||||
|
self._trans_list = struct.unpack(">%dl" % timecnt,
|
||||||
|
fileobj.read(timecnt*4))
|
||||||
|
else:
|
||||||
|
self._trans_list = []
|
||||||
|
|
||||||
|
# Next come tzh_timecnt one-byte values of type unsigned
|
||||||
|
# char; each one tells which of the different types of
|
||||||
|
# ``local time'' types described in the file is associated
|
||||||
|
# with the same-indexed transition time. These values
|
||||||
|
# serve as indices into an array of ttinfo structures that
|
||||||
|
# appears next in the file.
|
||||||
|
|
||||||
|
if timecnt:
|
||||||
|
self._trans_idx = struct.unpack(">%dB" % timecnt,
|
||||||
|
fileobj.read(timecnt))
|
||||||
|
else:
|
||||||
|
self._trans_idx = []
|
||||||
|
|
||||||
|
# Each ttinfo structure is written as a four-byte value
|
||||||
|
# for tt_gmtoff of type long, in a standard byte
|
||||||
|
# order, followed by a one-byte value for tt_isdst
|
||||||
|
# and a one-byte value for tt_abbrind. In each
|
||||||
|
# structure, tt_gmtoff gives the number of
|
||||||
|
# seconds to be added to UTC, tt_isdst tells whether
|
||||||
|
# tm_isdst should be set by localtime(3), and
|
||||||
|
# tt_abbrind serves as an index into the array of
|
||||||
|
# time zone abbreviation characters that follow the
|
||||||
|
# ttinfo structure(s) in the file.
|
||||||
|
|
||||||
|
ttinfo = []
|
||||||
|
|
||||||
|
for i in range(typecnt):
|
||||||
|
ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
|
||||||
|
|
||||||
|
abbr = fileobj.read(charcnt)
|
||||||
|
|
||||||
|
# Then there are tzh_leapcnt pairs of four-byte
|
||||||
|
# values, written in standard byte order; the
|
||||||
|
# first value of each pair gives the time (as
|
||||||
|
# returned by time(2)) at which a leap second
|
||||||
|
# occurs; the second gives the total number of
|
||||||
|
# leap seconds to be applied after the given time.
|
||||||
|
# The pairs of values are sorted in ascending order
|
||||||
|
# by time.
|
||||||
|
|
||||||
|
# Not used, for now
|
||||||
|
if leapcnt:
|
||||||
|
leap = struct.unpack(">%dl" % leapcnt*2,
|
||||||
|
fileobj.read(leapcnt*8))
|
||||||
|
|
||||||
|
# Then there are tzh_ttisstdcnt standard/wall
|
||||||
|
# indicators, each stored as a one-byte value;
|
||||||
|
# they tell whether the transition times associated
|
||||||
|
# with local time types were specified as standard
|
||||||
|
# time or wall clock time, and are used when
|
||||||
|
# a time zone file is used in handling POSIX-style
|
||||||
|
# time zone environment variables.
|
||||||
|
|
||||||
|
if ttisstdcnt:
|
||||||
|
isstd = struct.unpack(">%db" % ttisstdcnt,
|
||||||
|
fileobj.read(ttisstdcnt))
|
||||||
|
|
||||||
|
# Finally, there are tzh_ttisgmtcnt UTC/local
|
||||||
|
# indicators, each stored as a one-byte value;
|
||||||
|
# they tell whether the transition times associated
|
||||||
|
# with local time types were specified as UTC or
|
||||||
|
# local time, and are used when a time zone file
|
||||||
|
# is used in handling POSIX-style time zone envi-
|
||||||
|
# ronment variables.
|
||||||
|
|
||||||
|
if ttisgmtcnt:
|
||||||
|
isgmt = struct.unpack(">%db" % ttisgmtcnt,
|
||||||
|
fileobj.read(ttisgmtcnt))
|
||||||
|
|
||||||
|
# ** Everything has been read **
|
||||||
|
|
||||||
|
# Build ttinfo list
|
||||||
|
self._ttinfo_list = []
|
||||||
|
for i in range(typecnt):
|
||||||
|
tti = _ttinfo()
|
||||||
|
tti.offset = ttinfo[i][0]
|
||||||
|
tti.delta = datetime.timedelta(seconds=ttinfo[i][0])
|
||||||
|
tti.isdst = ttinfo[i][1]
|
||||||
|
tti.abbr = abbr[ttinfo[i][2]:abbr.find('\x00', ttinfo[i][2])]
|
||||||
|
tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
|
||||||
|
tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
|
||||||
|
self._ttinfo_list.append(tti)
|
||||||
|
|
||||||
|
# Replace ttinfo indexes for ttinfo objects.
|
||||||
|
trans_idx = []
|
||||||
|
for idx in self._trans_idx:
|
||||||
|
trans_idx.append(self._ttinfo_list[idx])
|
||||||
|
self._trans_idx = tuple(trans_idx)
|
||||||
|
|
||||||
|
# Set standard, dst, and before ttinfos. before will be
|
||||||
|
# used when a given time is before any transitions,
|
||||||
|
# and will be set to the first non-dst ttinfo, or to
|
||||||
|
# the first dst, if all of them are dst.
|
||||||
|
self._ttinfo_std = None
|
||||||
|
self._ttinfo_dst = None
|
||||||
|
self._ttinfo_before = None
|
||||||
|
if self._ttinfo_list:
|
||||||
|
if not self._trans_list:
|
||||||
|
self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0]
|
||||||
|
else:
|
||||||
|
for i in range(timecnt-1,-1,-1):
|
||||||
|
tti = self._trans_idx[i]
|
||||||
|
if not self._ttinfo_std and not tti.isdst:
|
||||||
|
self._ttinfo_std = tti
|
||||||
|
elif not self._ttinfo_dst and tti.isdst:
|
||||||
|
self._ttinfo_dst = tti
|
||||||
|
if self._ttinfo_std and self._ttinfo_dst:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if self._ttinfo_dst and not self._ttinfo_std:
|
||||||
|
self._ttinfo_std = self._ttinfo_dst
|
||||||
|
|
||||||
|
for tti in self._ttinfo_list:
|
||||||
|
if not tti.isdst:
|
||||||
|
self._ttinfo_before = tti
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self._ttinfo_before = self._ttinfo_list[0]
|
||||||
|
|
||||||
|
# Now fix transition times to become relative to wall time.
|
||||||
|
#
|
||||||
|
# I'm not sure about this. In my tests, the tz source file
|
||||||
|
# is setup to wall time, and in the binary file isstd and
|
||||||
|
# isgmt are off, so it should be in wall time. OTOH, it's
|
||||||
|
# always in gmt time. Let me know if you have comments
|
||||||
|
# about this.
|
||||||
|
laststdoffset = 0
|
||||||
|
self._trans_list = list(self._trans_list)
|
||||||
|
for i in range(len(self._trans_list)):
|
||||||
|
tti = self._trans_idx[i]
|
||||||
|
if not tti.isdst:
|
||||||
|
# This is std time.
|
||||||
|
self._trans_list[i] += tti.offset
|
||||||
|
laststdoffset = tti.offset
|
||||||
|
else:
|
||||||
|
# This is dst time. Convert to std.
|
||||||
|
self._trans_list[i] += laststdoffset
|
||||||
|
self._trans_list = tuple(self._trans_list)
|
||||||
|
|
||||||
|
def _find_ttinfo(self, dt, laststd=0):
|
||||||
|
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
||||||
|
+ dt.hour * 3600
|
||||||
|
+ dt.minute * 60
|
||||||
|
+ dt.second)
|
||||||
|
idx = 0
|
||||||
|
for trans in self._trans_list:
|
||||||
|
if timestamp < trans:
|
||||||
|
break
|
||||||
|
idx += 1
|
||||||
|
else:
|
||||||
|
return self._ttinfo_std
|
||||||
|
if idx == 0:
|
||||||
|
return self._ttinfo_before
|
||||||
|
if laststd:
|
||||||
|
while idx > 0:
|
||||||
|
tti = self._trans_idx[idx-1]
|
||||||
|
if not tti.isdst:
|
||||||
|
return tti
|
||||||
|
idx -= 1
|
||||||
|
else:
|
||||||
|
return self._ttinfo_std
|
||||||
|
else:
|
||||||
|
return self._trans_idx[idx-1]
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
if not self._ttinfo_std:
|
||||||
|
return ZERO
|
||||||
|
return self._find_ttinfo(dt).delta
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
if not self._ttinfo_dst:
|
||||||
|
return ZERO
|
||||||
|
tti = self._find_ttinfo(dt)
|
||||||
|
if not tti.isdst:
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
# The documentation says that utcoffset()-dst() must
|
||||||
|
# be constant for every dt.
|
||||||
|
return self._find_ttinfo(dt, laststd=1).delta-tti.delta
|
||||||
|
|
||||||
|
# An alternative for that would be:
|
||||||
|
#
|
||||||
|
# return self._ttinfo_dst.offset-self._ttinfo_std.offset
|
||||||
|
#
|
||||||
|
# However, this class stores historical changes in the
|
||||||
|
# dst offset, so I belive that this wouldn't be the right
|
||||||
|
# way to implement this.
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
if not self._ttinfo_std:
|
||||||
|
return None
|
||||||
|
return self._find_ttinfo(dt).abbr
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, tzfile):
|
||||||
|
return False
|
||||||
|
return (self._trans_list == other._trans_list and
|
||||||
|
self._trans_idx == other._trans_idx and
|
||||||
|
self._ttinfo_list == other._ttinfo_list)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||||
|
|
||||||
|
class tzrange(datetime.tzinfo):
|
||||||
|
|
||||||
|
def __init__(self, stdabbr, stdoffset=None,
|
||||||
|
dstabbr=None, dstoffset=None,
|
||||||
|
start=None, end=None):
|
||||||
|
global relativedelta
|
||||||
|
if not relativedelta:
|
||||||
|
from dateutil import relativedelta
|
||||||
|
self._std_abbr = stdabbr
|
||||||
|
self._dst_abbr = dstabbr
|
||||||
|
if stdoffset is not None:
|
||||||
|
self._std_offset = datetime.timedelta(seconds=stdoffset)
|
||||||
|
else:
|
||||||
|
self._std_offset = ZERO
|
||||||
|
if dstoffset is not None:
|
||||||
|
self._dst_offset = datetime.timedelta(seconds=dstoffset)
|
||||||
|
elif dstabbr and stdoffset is not None:
|
||||||
|
self._dst_offset = self._std_offset+datetime.timedelta(hours=+1)
|
||||||
|
else:
|
||||||
|
self._dst_offset = ZERO
|
||||||
|
if start is None:
|
||||||
|
self._start_delta = relativedelta.relativedelta(
|
||||||
|
hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
|
||||||
|
else:
|
||||||
|
self._start_delta = start
|
||||||
|
if end is None:
|
||||||
|
self._end_delta = relativedelta.relativedelta(
|
||||||
|
hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
|
||||||
|
else:
|
||||||
|
self._end_delta = end
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
if self._isdst(dt):
|
||||||
|
return self._dst_offset
|
||||||
|
else:
|
||||||
|
return self._std_offset
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
if self._isdst(dt):
|
||||||
|
return self._dst_offset-self._std_offset
|
||||||
|
else:
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
if self._isdst(dt):
|
||||||
|
return self._dst_abbr
|
||||||
|
else:
|
||||||
|
return self._std_abbr
|
||||||
|
|
||||||
|
def _isdst(self, dt):
|
||||||
|
if not self._start_delta:
|
||||||
|
return False
|
||||||
|
year = datetime.date(dt.year,1,1)
|
||||||
|
start = year+self._start_delta
|
||||||
|
end = year+self._end_delta
|
||||||
|
dt = dt.replace(tzinfo=None)
|
||||||
|
if start < end:
|
||||||
|
return dt >= start and dt < end
|
||||||
|
else:
|
||||||
|
return dt >= start or dt < end
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, tzrange):
|
||||||
|
return False
|
||||||
|
return (self._std_abbr == other._std_abbr and
|
||||||
|
self._dst_abbr == other._dst_abbr and
|
||||||
|
self._std_offset == other._std_offset and
|
||||||
|
self._dst_offset == other._dst_offset and
|
||||||
|
self._start_delta == other._start_delta and
|
||||||
|
self._end_delta == other._end_delta)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(...)" % self.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
class tzstr(tzrange):
|
||||||
|
|
||||||
|
def __init__(self, s):
|
||||||
|
global parser
|
||||||
|
if not parser:
|
||||||
|
from dateutil import parser
|
||||||
|
self._s = s
|
||||||
|
|
||||||
|
res = parser._parsetz(s)
|
||||||
|
if res is None:
|
||||||
|
raise ValueError, "unknown string format"
|
||||||
|
|
||||||
|
# We must initialize it first, since _delta() needs
|
||||||
|
# _std_offset and _dst_offset set. Use False in start/end
|
||||||
|
# to avoid building it two times.
|
||||||
|
tzrange.__init__(self, res.stdabbr, res.stdoffset,
|
||||||
|
res.dstabbr, res.dstoffset,
|
||||||
|
start=False, end=False)
|
||||||
|
|
||||||
|
self._start_delta = self._delta(res.start)
|
||||||
|
if self._start_delta:
|
||||||
|
self._end_delta = self._delta(res.end, isend=1)
|
||||||
|
|
||||||
|
def _delta(self, x, isend=0):
|
||||||
|
kwargs = {}
|
||||||
|
if x.month is not None:
|
||||||
|
kwargs["month"] = x.month
|
||||||
|
if x.weekday is not None:
|
||||||
|
kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
|
||||||
|
if x.week > 0:
|
||||||
|
kwargs["day"] = 1
|
||||||
|
else:
|
||||||
|
kwargs["day"] = 31
|
||||||
|
elif x.day:
|
||||||
|
kwargs["day"] = x.day
|
||||||
|
elif x.yday is not None:
|
||||||
|
kwargs["yearday"] = x.yday
|
||||||
|
elif x.jyday is not None:
|
||||||
|
kwargs["nlyearday"] = x.jyday
|
||||||
|
if not kwargs:
|
||||||
|
# Default is to start on first sunday of april, and end
|
||||||
|
# on last sunday of october.
|
||||||
|
if not isend:
|
||||||
|
kwargs["month"] = 4
|
||||||
|
kwargs["day"] = 1
|
||||||
|
kwargs["weekday"] = relativedelta.SU(+1)
|
||||||
|
else:
|
||||||
|
kwargs["month"] = 10
|
||||||
|
kwargs["day"] = 31
|
||||||
|
kwargs["weekday"] = relativedelta.SU(-1)
|
||||||
|
if x.time is not None:
|
||||||
|
kwargs["seconds"] = x.time
|
||||||
|
else:
|
||||||
|
# Default is 2AM.
|
||||||
|
kwargs["seconds"] = 7200
|
||||||
|
if isend:
|
||||||
|
# Convert to standard time, to follow the documented way
|
||||||
|
# of working with the extra hour. See the documentation
|
||||||
|
# of the tzinfo class.
|
||||||
|
delta = self._dst_offset-self._std_offset
|
||||||
|
kwargs["seconds"] -= delta.seconds+delta.days*86400
|
||||||
|
return relativedelta.relativedelta(**kwargs)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||||
|
|
||||||
|
class _tzicalvtzcomp:
|
||||||
|
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
||||||
|
tzname=None, rrule=None):
|
||||||
|
self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
|
||||||
|
self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
|
||||||
|
self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom
|
||||||
|
self.isdst = isdst
|
||||||
|
self.tzname = tzname
|
||||||
|
self.rrule = rrule
|
||||||
|
|
||||||
|
class _tzicalvtz(datetime.tzinfo):
|
||||||
|
def __init__(self, tzid, comps=[]):
|
||||||
|
self._tzid = tzid
|
||||||
|
self._comps = comps
|
||||||
|
self._cachedate = []
|
||||||
|
self._cachecomp = []
|
||||||
|
|
||||||
|
def _find_comp(self, dt):
|
||||||
|
if len(self._comps) == 1:
|
||||||
|
return self._comps[0]
|
||||||
|
dt = dt.replace(tzinfo=None)
|
||||||
|
try:
|
||||||
|
return self._cachecomp[self._cachedate.index(dt)]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
lastcomp = None
|
||||||
|
lastcompdt = None
|
||||||
|
for comp in self._comps:
|
||||||
|
if not comp.isdst:
|
||||||
|
# Handle the extra hour in DST -> STD
|
||||||
|
compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True)
|
||||||
|
else:
|
||||||
|
compdt = comp.rrule.before(dt, inc=True)
|
||||||
|
if compdt and (not lastcompdt or lastcompdt < compdt):
|
||||||
|
lastcompdt = compdt
|
||||||
|
lastcomp = comp
|
||||||
|
if not lastcomp:
|
||||||
|
# RFC says nothing about what to do when a given
|
||||||
|
# time is before the first onset date. We'll look for the
|
||||||
|
# first standard component, or the first component, if
|
||||||
|
# none is found.
|
||||||
|
for comp in self._comps:
|
||||||
|
if not comp.isdst:
|
||||||
|
lastcomp = comp
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
lastcomp = comp[0]
|
||||||
|
self._cachedate.insert(0, dt)
|
||||||
|
self._cachecomp.insert(0, lastcomp)
|
||||||
|
if len(self._cachedate) > 10:
|
||||||
|
self._cachedate.pop()
|
||||||
|
self._cachecomp.pop()
|
||||||
|
return lastcomp
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return self._find_comp(dt).tzoffsetto
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
comp = self._find_comp(dt)
|
||||||
|
if comp.isdst:
|
||||||
|
return comp.tzoffsetdiff
|
||||||
|
else:
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return self._find_comp(dt).tzname
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<tzicalvtz %s>" % `self._tzid`
|
||||||
|
|
||||||
|
class tzical:
|
||||||
|
def __init__(self, fileobj):
|
||||||
|
global rrule
|
||||||
|
if not rrule:
|
||||||
|
from dateutil import rrule
|
||||||
|
|
||||||
|
if isinstance(fileobj, basestring):
|
||||||
|
self._s = fileobj
|
||||||
|
fileobj = open(fileobj)
|
||||||
|
elif hasattr(fileobj, "name"):
|
||||||
|
self._s = fileobj.name
|
||||||
|
else:
|
||||||
|
self._s = `fileobj`
|
||||||
|
|
||||||
|
self._vtz = {}
|
||||||
|
|
||||||
|
self._parse_rfc(fileobj.read())
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self._vtz.keys()
|
||||||
|
|
||||||
|
def get(self, tzid=None):
|
||||||
|
if tzid is None:
|
||||||
|
keys = self._vtz.keys()
|
||||||
|
if len(keys) == 0:
|
||||||
|
raise "no timezones defined"
|
||||||
|
elif len(keys) > 1:
|
||||||
|
raise "more than one timezone available"
|
||||||
|
tzid = keys[0]
|
||||||
|
return self._vtz.get(tzid)
|
||||||
|
|
||||||
|
def _parse_offset(self, s):
|
||||||
|
s = s.strip()
|
||||||
|
if not s:
|
||||||
|
raise ValueError, "empty offset"
|
||||||
|
if s[0] in ('+', '-'):
|
||||||
|
signal = (-1,+1)[s[0]=='+']
|
||||||
|
s = s[1:]
|
||||||
|
else:
|
||||||
|
signal = +1
|
||||||
|
if len(s) == 4:
|
||||||
|
return (int(s[:2])*3600+int(s[2:])*60)*signal
|
||||||
|
elif len(s) == 6:
|
||||||
|
return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal
|
||||||
|
else:
|
||||||
|
raise ValueError, "invalid offset: "+s
|
||||||
|
|
||||||
|
def _parse_rfc(self, s):
|
||||||
|
lines = s.splitlines()
|
||||||
|
if not lines:
|
||||||
|
raise ValueError, "empty string"
|
||||||
|
|
||||||
|
# Unfold
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i].rstrip()
|
||||||
|
if not line:
|
||||||
|
del lines[i]
|
||||||
|
elif i > 0 and line[0] == " ":
|
||||||
|
lines[i-1] += line[1:]
|
||||||
|
del lines[i]
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
invtz = False
|
||||||
|
comptype = None
|
||||||
|
for line in lines:
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
name, value = line.split(':', 1)
|
||||||
|
parms = name.split(';')
|
||||||
|
if not parms:
|
||||||
|
raise ValueError, "empty property name"
|
||||||
|
name = parms[0].upper()
|
||||||
|
parms = parms[1:]
|
||||||
|
if invtz:
|
||||||
|
if name == "BEGIN":
|
||||||
|
if value in ("STANDARD", "DAYLIGHT"):
|
||||||
|
# Process component
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError, "unknown component: "+value
|
||||||
|
comptype = value
|
||||||
|
founddtstart = False
|
||||||
|
tzoffsetfrom = None
|
||||||
|
tzoffsetto = None
|
||||||
|
rrulelines = []
|
||||||
|
tzname = None
|
||||||
|
elif name == "END":
|
||||||
|
if value == "VTIMEZONE":
|
||||||
|
if comptype:
|
||||||
|
raise ValueError, \
|
||||||
|
"component not closed: "+comptype
|
||||||
|
if not tzid:
|
||||||
|
raise ValueError, \
|
||||||
|
"mandatory TZID not found"
|
||||||
|
if not comps:
|
||||||
|
raise ValueError, \
|
||||||
|
"at least one component is needed"
|
||||||
|
# Process vtimezone
|
||||||
|
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
||||||
|
invtz = False
|
||||||
|
elif value == comptype:
|
||||||
|
if not founddtstart:
|
||||||
|
raise ValueError, \
|
||||||
|
"mandatory DTSTART not found"
|
||||||
|
if not tzoffsetfrom:
|
||||||
|
raise ValueError, \
|
||||||
|
"mandatory TZOFFSETFROM not found"
|
||||||
|
if not tzoffsetto:
|
||||||
|
raise ValueError, \
|
||||||
|
"mandatory TZOFFSETFROM not found"
|
||||||
|
# Process component
|
||||||
|
rr = None
|
||||||
|
if rrulelines:
|
||||||
|
rr = rrule.rrulestr("\n".join(rrulelines),
|
||||||
|
compatible=True,
|
||||||
|
ignoretz=True,
|
||||||
|
cache=True)
|
||||||
|
comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
|
||||||
|
(comptype == "DAYLIGHT"),
|
||||||
|
tzname, rr)
|
||||||
|
comps.append(comp)
|
||||||
|
comptype = None
|
||||||
|
else:
|
||||||
|
raise ValueError, \
|
||||||
|
"invalid component end: "+value
|
||||||
|
elif comptype:
|
||||||
|
if name == "DTSTART":
|
||||||
|
rrulelines.append(line)
|
||||||
|
founddtstart = True
|
||||||
|
elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
|
||||||
|
rrulelines.append(line)
|
||||||
|
elif name == "TZOFFSETFROM":
|
||||||
|
if parms:
|
||||||
|
raise ValueError, \
|
||||||
|
"unsupported %s parm: %s "%(name, parms[0])
|
||||||
|
tzoffsetfrom = self._parse_offset(value)
|
||||||
|
elif name == "TZOFFSETTO":
|
||||||
|
if parms:
|
||||||
|
raise ValueError, \
|
||||||
|
"unsupported TZOFFSETTO parm: "+parms[0]
|
||||||
|
tzoffsetto = self._parse_offset(value)
|
||||||
|
elif name == "TZNAME":
|
||||||
|
if parms:
|
||||||
|
raise ValueError, \
|
||||||
|
"unsupported TZNAME parm: "+parms[0]
|
||||||
|
tzname = value
|
||||||
|
elif name == "COMMENT":
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError, "unsupported property: "+name
|
||||||
|
else:
|
||||||
|
if name == "TZID":
|
||||||
|
if parms:
|
||||||
|
raise ValueError, \
|
||||||
|
"unsupported TZID parm: "+parms[0]
|
||||||
|
tzid = value
|
||||||
|
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError, "unsupported property: "+name
|
||||||
|
elif name == "BEGIN" and value == "VTIMEZONE":
|
||||||
|
tzid = None
|
||||||
|
comps = []
|
||||||
|
invtz = True
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||||
|
|
||||||
|
TZFILES = ["/etc/localtime", "localtime"]
|
||||||
|
TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"]
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
def gettz(name=None):
|
||||||
|
tz = None
|
||||||
|
if not name:
|
||||||
|
try:
|
||||||
|
name = os.environ["TZ"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
if name is None:
|
||||||
|
for filepath in TZFILES:
|
||||||
|
if not os.path.isabs(filepath):
|
||||||
|
filename = filepath
|
||||||
|
for path in TZPATHS:
|
||||||
|
filepath = os.path.join(path, filename)
|
||||||
|
if os.path.isfile(filepath):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
if os.path.isfile(filepath):
|
||||||
|
try:
|
||||||
|
tz = tzfile(filepath)
|
||||||
|
break
|
||||||
|
except (IOError, OSError, ValueError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if name and name[0] == ":":
|
||||||
|
name = name[:-1]
|
||||||
|
for path in TZPATHS:
|
||||||
|
filepath = os.path.join(path, name)
|
||||||
|
if not os.path.isfile(filepath):
|
||||||
|
filepath = filepath.replace(' ','_')
|
||||||
|
if not os.path.isfile(filepath):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
tz = tzfile(filepath)
|
||||||
|
break
|
||||||
|
except (IOError, OSError, ValueError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for c in name:
|
||||||
|
# name must have at least one offset to be a tzstr
|
||||||
|
if c in "0123456789":
|
||||||
|
try:
|
||||||
|
tz = tzstr(name)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if name in ("GMT", "UTC"):
|
||||||
|
tz = tzutc()
|
||||||
|
elif name in time.tzname:
|
||||||
|
tz = tzlocal()
|
||||||
|
return tz
|
||||||
|
|
||||||
|
# vim:ts=4:sw=4:et
|
|
@ -1,229 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2004, Mike Taylor
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions are met:
|
|
||||||
#
|
|
||||||
# * Redistributions of source code must retain the above copyright notice,
|
|
||||||
# this list of conditions, and the following disclaimer.
|
|
||||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
# this list of conditions, and the following disclaimer in the
|
|
||||||
# documentation and/or other materials provided with the distribution.
|
|
||||||
# * Neither the name of the author of this software nor the name of
|
|
||||||
# contributors to this software may be used to endorse or promote products
|
|
||||||
# derived from this software without specific prior written consent.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
|
|
||||||
__author__ = "Mike Taylor <bear@code-bear.com>"
|
|
||||||
__copyright__ = "Copyright (c) 2004 Mike Taylor"
|
|
||||||
__license__ = "BSD"
|
|
||||||
__revision__ = "$Id$"
|
|
||||||
|
|
||||||
import os, string, re, time
|
|
||||||
|
|
||||||
RE_SPECIAL = r'(?P<special>^[in|last|next]+)\s+'
|
|
||||||
RE_UNITS = r'\s+(?P<units>[hour|minute|second|day|week|month|year]+)'
|
|
||||||
RE_QUNITS = r'(?P<qunits>[0-9]+[hmsdwmy])'
|
|
||||||
RE_MODIFIER = r'(?P<modifier>[from|before|after|ago|prior]+)\s+'
|
|
||||||
|
|
||||||
CRE_SPECIAL = re.compile(RE_SPECIAL, re.IGNORECASE)
|
|
||||||
CRE_UNITS = re.compile(RE_UNITS, re.IGNORECASE)
|
|
||||||
CRE_QUNITS = re.compile(RE_QUNITS, re.IGNORECASE)
|
|
||||||
CRE_MODIFIER = re.compile(RE_MODIFIER, re.IGNORECASE)
|
|
||||||
|
|
||||||
# Used to adjust the returned date before/after the source
|
|
||||||
|
|
||||||
_Modifiers = {'from': 1,
|
|
||||||
'before': -1,
|
|
||||||
'after': 1,
|
|
||||||
'ago': 1,
|
|
||||||
'prior': -1}
|
|
||||||
|
|
||||||
_Minute = 60
|
|
||||||
_Hour = 60 * _Minute
|
|
||||||
_Day = 24 * _Hour
|
|
||||||
_Week = 7 * _Day
|
|
||||||
_Month = 30 * _Day
|
|
||||||
_Year = 365 * _Day
|
|
||||||
|
|
||||||
# This looks hokey - but it is a nice simple way to get
|
|
||||||
# the proper unit value and it has the advantage that
|
|
||||||
# later I can morph it into something localized.
|
|
||||||
# Any trailing s will be removed before lookup.
|
|
||||||
|
|
||||||
_Units = {'second': 1,
|
|
||||||
'sec': 1,
|
|
||||||
's': 1,
|
|
||||||
'minute': _Minute,
|
|
||||||
'min': _Minute,
|
|
||||||
'm': _Minute,
|
|
||||||
'hour': _Hour,
|
|
||||||
'hr': _Hour,
|
|
||||||
'h': _Hour,
|
|
||||||
'day': _Day,
|
|
||||||
'dy': _Day,
|
|
||||||
'd': _Day,
|
|
||||||
'week': _Week,
|
|
||||||
'wk': _Week,
|
|
||||||
'w': _Week,
|
|
||||||
'month': _Month,
|
|
||||||
'mth': _Month,
|
|
||||||
'm': _Month,
|
|
||||||
'year': _Year,
|
|
||||||
'yr': _Year,
|
|
||||||
'y': _Year}
|
|
||||||
|
|
||||||
def _buildTime(sourceTime, quantity, modifier, units):
|
|
||||||
"""Take quantity, modifier and units strings and convert them
|
|
||||||
into values, calcuate the time and return the adjusted
|
|
||||||
sourceTime
|
|
||||||
"""
|
|
||||||
# print '[%s][%s][%s]' % (quantity, modifier, units)
|
|
||||||
|
|
||||||
q = int(quantity)
|
|
||||||
|
|
||||||
if _Modifiers.has_key(modifier):
|
|
||||||
q = q * _Modifiers[modifier]
|
|
||||||
|
|
||||||
if units[-1] == 's':
|
|
||||||
units = units[:-1]
|
|
||||||
|
|
||||||
if _Units.has_key(units):
|
|
||||||
u = _Units[units]
|
|
||||||
else:
|
|
||||||
u = 1
|
|
||||||
|
|
||||||
# print 'sourceTime [%d]' % sourceTime
|
|
||||||
# print 'quantity [%d]' % q
|
|
||||||
# print 'units [%d]' % u
|
|
||||||
|
|
||||||
return sourceTime + (q * u)
|
|
||||||
|
|
||||||
|
|
||||||
def parse(timeString, sourceTime=None):
|
|
||||||
"""Parse timeString and return the number of seconds from sourceTime
|
|
||||||
that the timeString expression represents.
|
|
||||||
|
|
||||||
This version of parse understands only the more basic of expressions
|
|
||||||
in this form:
|
|
||||||
|
|
||||||
<quantity> <units> <modifier> <target>
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
5 minutes from now
|
|
||||||
last week
|
|
||||||
2 hours before noon
|
|
||||||
|
|
||||||
Valid units - hour, minute, second, month, week, day and year
|
|
||||||
(including their plural forms)
|
|
||||||
Valid modifiers - from, before, after, ago, prior
|
|
||||||
"""
|
|
||||||
|
|
||||||
if sourceTime == None:
|
|
||||||
sourceTime = int(time.time())
|
|
||||||
else:
|
|
||||||
sourceTime = int(sourceTime)
|
|
||||||
|
|
||||||
quantity = ''
|
|
||||||
units = ''
|
|
||||||
modifier = ''
|
|
||||||
target = ''
|
|
||||||
|
|
||||||
s = string.strip(string.lower(timeString))
|
|
||||||
|
|
||||||
m = CRE_SPECIAL.search(s)
|
|
||||||
|
|
||||||
if m <> None:
|
|
||||||
target = 'now'
|
|
||||||
|
|
||||||
if m.group('special') == 'last':
|
|
||||||
modifier = 'before'
|
|
||||||
else:
|
|
||||||
modifier = 'from'
|
|
||||||
|
|
||||||
s = s[m.end('special'):]
|
|
||||||
|
|
||||||
m = CRE_UNITS.search(s)
|
|
||||||
|
|
||||||
if m <> None:
|
|
||||||
units = m.group('units')
|
|
||||||
quantity = s[:m.start('units')]
|
|
||||||
s = s[m.end('units'):]
|
|
||||||
|
|
||||||
else:
|
|
||||||
m = CRE_MODIFIER.search(s)
|
|
||||||
|
|
||||||
if m <> None:
|
|
||||||
modifier = m.group('modifier')
|
|
||||||
target = s[m.end('modifier'):]
|
|
||||||
s = s[:m.start('modifier'):]
|
|
||||||
|
|
||||||
m = CRE_UNITS.search(s)
|
|
||||||
|
|
||||||
if m <> None:
|
|
||||||
units = m.group('units')
|
|
||||||
quantity = s[:m.start('units')]
|
|
||||||
target = s[m.end('units'):]
|
|
||||||
|
|
||||||
return _buildTime(sourceTime, quantity, modifier, units)
|
|
||||||
|
|
||||||
def _test(text, value):
|
|
||||||
print text
|
|
||||||
|
|
||||||
v = parse(text)
|
|
||||||
|
|
||||||
print '\t%s\t%d\t%d' % ((v == value), value, v)
|
|
||||||
|
|
||||||
#
|
|
||||||
# TODO
|
|
||||||
#
|
|
||||||
# - make month unit adjustment aware of the actual number of days in each
|
|
||||||
# month between source and target
|
|
||||||
# - handle edge case where quantity and unit are merged: 5s for 5 sec
|
|
||||||
# - handle compound/nested quantites and modifiers
|
|
||||||
# - bring unit test over from prototype into this file
|
|
||||||
# - convert 'five' to 5 and also 'twenty five' to 25
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
start = int(time.time())
|
|
||||||
tests = { '5 minutes from now': start + 5 * _Minute,
|
|
||||||
'5 min from now': start + 5 * _Minute,
|
|
||||||
'in 5 minutes': start + 5 * _Minute,
|
|
||||||
'5 days from today': start + 5 * _Day,
|
|
||||||
'5 days before today': start - 5 * _Day,
|
|
||||||
'5 minutes': start + 5 * _Minute,
|
|
||||||
'5 min': start + 5 * _Minute,
|
|
||||||
'30 seconds from now': start + 30,
|
|
||||||
'30 sec from now': start + 30,
|
|
||||||
'30 seconds': start + 30,
|
|
||||||
'30 sec': start + 30,
|
|
||||||
'1 week': start + _Week,
|
|
||||||
'1 wk': start + _Week,
|
|
||||||
'1 week': start + _Week,
|
|
||||||
'5 days': start + 5 * _Day,
|
|
||||||
'1 year': start + _Year,
|
|
||||||
'2 years': start + 2 * _Year}
|
|
||||||
|
|
||||||
for test in tests.keys():
|
|
||||||
_test(test, tests[test])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue