|
From: <ps...@us...> - 2008-12-12 08:05:47
|
Revision: 1476
http://pywebsvcs.svn.sourceforge.net/pywebsvcs/?rev=1476&view=rev
Author: psha
Date: 2008-12-12 08:05:39 +0000 (Fri, 12 Dec 2008)
Log Message:
-----------
Fix timezone handling in TCtimes
Time
* When parsing convert all times (dateTime and gTime elements) to local
timezone if possible (year must be in range [datetime.MINYEAR, datetime.MAXYEAR])
* dateTime and gTime are serialized to UTC timezone. Argument is treated as
local time
Date
* Date formats (gDate, gYear, ...) ignore timezone portion e.g. values
1968+15:00, 1968Z and 1968 are equal. So this part is not confirming to
specs.
* Serialize all dates without timezone part.
Fixed bugs: #2133723 #2014802
Modified Paths:
--------------
trunk/zsi/ZSI/TCtimes.py
trunk/zsi/test/test_TCtimes.py
Modified: trunk/zsi/ZSI/TCtimes.py
===================================================================
--- trunk/zsi/ZSI/TCtimes.py 2008-12-11 04:20:26 UTC (rev 1475)
+++ trunk/zsi/ZSI/TCtimes.py 2008-12-12 08:05:39 UTC (rev 1476)
@@ -9,14 +9,11 @@
import operator, re, time as _time
from time import mktime as _mktime, localtime as _localtime, gmtime as _gmtime
from datetime import tzinfo as _tzinfo, timedelta as _timedelta,\
- datetime as _datetime
+ datetime as _datetime, MINYEAR, MAXYEAR
from math import modf as _modf
-_niltime = [
- 0, 0, 0, # year month day
- 0, 0, 0, # hour minute second
- 0, 0, 0 # weekday, julian day, dst flag
-]
+# Year, month or day may be None
+_niltime = [None] * 3 + [0] * 6
#### Code added to check current timezone offset
_zero = _timedelta(0)
@@ -24,7 +21,6 @@
if _time.daylight: _dstoffset = _timedelta(seconds=-_time.altzone)
_dstdiff = _dstoffset - _stdoffset
-
class _localtimezone(_tzinfo):
""" """
def dst(self, dt):
@@ -75,6 +71,41 @@
"""datetime -> minutes east of UTC (negative for west of UTC)."""
return self.__offset
+def _tz_to_tzinfo(tz):
+ if not tz:
+ return _localtimezone()
+ if tz == "Z": tz = "+00:00"
+ h, m = map(int, tz.split(':'))
+ if h < 0: m = -m
+ return _fixedoffset(60 * h + m)
+
+def _fix_timezone(tv, tz_from = "Z", tz_to = None):
+ if None in tv[3:5]: # Hour or minute is absent
+ return tv
+
+ # Fix local copy of time tuple
+ ltv = list(_fix_none_fields(tv))
+
+ if ltv[0] < MINYEAR or ltv[0] > MAXYEAR:
+ return tv # Unable to fix timestamp
+
+ _tz_from = _tz_to_tzinfo(tz_from)
+ _tz_to = _tz_to_tzinfo(tz_to)
+
+ ltv[:6] = _datetime(*(ltv[:6] + [0, _tz_from])).astimezone(_tz_to).timetuple()[:6]
+
+ # Patch local copy with original values
+ for i in range(0, 6):
+ if tv[i] is None: ltv[i] = None
+
+ return tuple(ltv)
+
+def _fix_none_fields(tv):
+ ltv = list(tv)
+ if ltv[0] is None: ltv[0] = MINYEAR + 1 # Year is absent
+ if ltv[1] is None: ltv[1] = 1 # Month is absent
+ if ltv[2] is None: ltv[2] = 1 # Day is absent
+ return tuple(ltv)
def _dict_to_tuple(d):
'''Convert a dictionary to a time tuple. Depends on key values in the
@@ -96,42 +127,10 @@
if v:
msec,sec = _modf(float(v))
retval[6],retval[5] = int(round(msec*1000)), int(sec)
-
- v = d.get('tz')
- if v and v != 'Z':
- h,m = map(int, v.split(':'))
- # check for time zone offset, if within the same timezone,
- # ignore offset specific calculations
- offset=_localtimezone().utcoffset(_datetime.now())
- local_offset_hour = offset.seconds/3600
- local_offset_min = (offset.seconds%3600)%60
- if local_offset_hour > 12:
- local_offset_hour -= 24
-
- if local_offset_hour != h or local_offset_min != m:
- if h<0:
- #TODO: why is this set to server
- #foff = _fixedoffset(-((abs(h)*60+m)),"server")
- foff = _fixedoffset(-((abs(h)*60+m)))
- else:
- #TODO: why is this set to server
- #foff = _fixedoffset((abs(h)*60+m),"server")
- foff = _fixedoffset((abs(h)*60+m))
-
- dt = _datetime(retval[0],retval[1],retval[2],retval[3],retval[4],
- retval[5],0,foff)
-
- # update dict with calculated timezone
- localdt=dt.astimezone(_localtimezone())
- retval[0] = localdt.year
- retval[1] = localdt.month
- retval[2] = localdt.day
- retval[3] = localdt.hour
- retval[4] = localdt.minute
- retval[5] = localdt.second
-
+
if d.get('neg', 0):
- retval[0:5] = map(operator.__neg__, retval[0:5])
+ retval[0:5] = map(lambda x: (x is not None or x) and operator.__neg__(x), retval[0:5])
+
return tuple(retval)
@@ -188,6 +187,7 @@
'''Gregorian times.
'''
lex_pattern = tag = format = None
+ fix_timezone = False
def text_to_data(self, text, elt, ps):
'''convert text into typecode specific data.
@@ -203,7 +203,12 @@
except ValueError, e:
#raise EvaluateException(str(e))
raise
-
+
+ if self.fix_timezone:
+ retval = _fix_timezone(retval, tz_from = m.groupdict().get('tz'), tz_to = None)
+
+ retval = _fix_none_fields(retval)
+
if self.pyclass is not None:
return self.pyclass(retval)
return retval
@@ -212,6 +217,9 @@
if type(pyobj) in _floattypes or type(pyobj) in _inttypes:
pyobj = _gmtime(pyobj)
+ if self.fix_timezone:
+ pyobj = _fix_timezone(pyobj, tz_from = None, tz_to = "Z")
+
d = {}
pyobj = tuple(pyobj)
if 1 in map(lambda x: x < 0, pyobj[0:6]):
@@ -220,17 +228,18 @@
else:
d['neg'] = ''
+ d = {}
+ for k,i in [ ('Y', 0), ('M', 1), ('D', 2), ('h', 3), ('m', 4), ('s', 5) ]:
+ d[k] = pyobj[i]
+
ms = pyobj[6]
if not ms or not hasattr(self, 'format_ms'):
- d = { 'Y': pyobj[0], 'M': pyobj[1], 'D': pyobj[2],
- 'h': pyobj[3], 'm': pyobj[4], 's': pyobj[5], }
return self.format % d
if ms > 999:
raise ValueError, 'milliseconds must be a integer between 0 and 999'
- d = { 'Y': pyobj[0], 'M': pyobj[1], 'D': pyobj[2],
- 'h': pyobj[3], 'm': pyobj[4], 's': pyobj[5], 'ms':ms, }
+ d['ms'] = ms
return self.format_ms % d
@@ -245,6 +254,7 @@
tag, format = 'dateTime', '%(Y)04d-%(M)02d-%(D)02dT%(h)02d:%(m)02d:%(s)02dZ'
format_ms = format[:-1] + '.%(ms)03dZ'
type = (SCHEMA.XSD3, 'dateTime')
+ fix_timezone = True
class gDate(Gregorian):
'''A date.
@@ -253,7 +263,7 @@
lex_pattern = re.compile('^' r'(?P<neg>-?)' \
'(?P<Y>\d{4,})-' r'(?P<M>\d\d)-' r'(?P<D>\d\d)' \
r'(?P<tz>Z|([-+]\d\d:\d\d))?' '$')
- tag, format = 'date', '%(Y)04d-%(M)02d-%(D)02dZ'
+ tag, format = 'date', '%(Y)04d-%(M)02d-%(D)02d'
type = (SCHEMA.XSD3, 'date')
class gYearMonth(Gregorian):
@@ -263,7 +273,7 @@
lex_pattern = re.compile('^' r'(?P<neg>-?)' \
'(?P<Y>\d{4,})-' r'(?P<M>\d\d)' \
r'(?P<tz>Z|([-+]\d\d:\d\d))?' '$')
- tag, format = 'gYearMonth', '%(Y)04d-%(M)02dZ'
+ tag, format = 'gYearMonth', '%(Y)04d-%(M)02d'
type = (SCHEMA.XSD3, 'gYearMonth')
class gYear(Gregorian):
@@ -273,7 +283,7 @@
lex_pattern = re.compile('^' r'(?P<neg>-?)' \
'(?P<Y>\d{4,})' \
r'(?P<tz>Z|([-+]\d\d:\d\d))?' '$')
- tag, format = 'gYear', '%(Y)04dZ'
+ tag, format = 'gYear', '%(Y)04d'
type = (SCHEMA.XSD3, 'gYear')
class gMonthDay(Gregorian):
@@ -283,7 +293,7 @@
lex_pattern = re.compile('^' r'(?P<neg>-?)' \
r'--(?P<M>\d\d)-' r'(?P<D>\d\d)' \
r'(?P<tz>Z|([-+]\d\d:\d\d))?' '$')
- tag, format = 'gMonthDay', '---%(M)02d-%(D)02dZ'
+ tag, format = 'gMonthDay', '--%(M)02d-%(D)02d'
type = (SCHEMA.XSD3, 'gMonthDay')
@@ -294,7 +304,7 @@
lex_pattern = re.compile('^' r'(?P<neg>-?)' \
r'---(?P<D>\d\d)' \
r'(?P<tz>Z|([-+]\d\d:\d\d))?' '$')
- tag, format = 'gDay', '---%(D)02dZ'
+ tag, format = 'gDay', '---%(D)02d'
type = (SCHEMA.XSD3, 'gDay')
class gMonth(Gregorian):
@@ -302,9 +312,9 @@
'''
parselist = [ (None,'gMonth') ]
lex_pattern = re.compile('^' r'(?P<neg>-?)' \
- r'---(?P<M>\d\d)' \
+ r'--(?P<M>\d\d)' \
r'(?P<tz>Z|([-+]\d\d:\d\d))?' '$')
- tag, format = 'gMonth', '---%(M)02dZ'
+ tag, format = 'gMonth', '--%(M)02d'
type = (SCHEMA.XSD3, 'gMonth')
class gTime(Gregorian):
@@ -317,5 +327,6 @@
tag, format = 'time', '%(h)02d:%(m)02d:%(s)02dZ'
format_ms = format[:-1] + '.%(ms)03dZ'
type = (SCHEMA.XSD3, 'time')
+ fix_timezone = True
if __name__ == '__main__': print _copyright
Modified: trunk/zsi/test/test_TCtimes.py
===================================================================
--- trunk/zsi/test/test_TCtimes.py 2008-12-11 04:20:26 UTC (rev 1475)
+++ trunk/zsi/test/test_TCtimes.py 2008-12-12 08:05:39 UTC (rev 1476)
@@ -10,21 +10,32 @@
class TestCase(unittest.TestCase):
'''Examples from "Definitive XML Schema, Priscilla Walmsley, p237-246
'''
- def check_dateTime_local_offset(self):
- # UTC with local timezone offset
- #
- typecode = TC.gDateTime()
- off_hour = time.altzone/60/60
- off_min = time.altzone%60
- stamp_offset = '1968-04-02T13:20:00+%02d:%02d' %(off_hour,off_min)
- data = typecode.text_to_data(stamp_offset, None, None)
- stamp = typecode.get_formatted_content(data)
+ def _check_data2data(self, tc, data, correct, msg):
+ tmp = tc.text_to_data(data, None, None)
+ stamp = tc.get_formatted_content(tmp)
- correct = "1968-04-01T22:20:00Z"
self.failUnless(stamp == correct,
- 'dateTime with local offset(%s), expecting "%s" got "%s"' %(
- stamp_offset, correct, stamp))
+ '%s with local offset(%s): expecting "%s" got "%s"' %(
+ msg, data, correct, stamp))
+ def check_datetime_timezone(self):
+ # UTC with local timezone offset
+ # Base example from http://www.w3.org/TR/xmlschema11-2/#dateTime-lexical-mapping
+
+ correct = "2002-10-10T17:00:00Z"
+ for t in ['2002-10-10T12:00:00-05:00', '2002-10-10T19:00:00+02:00', '2002-10-10T17:00:00+00:00', '2002-10-10T17:00:00Z']:
+ self._check_data2data(TC.gDateTime(), t, correct, 'dateTime')
+
+ def check_time_timezone(self):
+ correct = "17:30:00Z"
+ for t in ['12:30:00-05:00', '19:30:00+02:00', '17:30:00+00:00']:
+ self._check_data2data(TC.gTime(), t, correct, 'time')
+
+ def check_date_timezone(self):
+ correct = "2002-10-10"
+ for t in ['2002-10-10-05:00', '2002-10-10+02:00', '2002-10-10+00:00', '2002-10-10Z']:
+ self._check_data2data(TC.gDate(), t, correct, 'date')
+
def check_valid_dateTime(self):
typecode = TC.gDateTime()
for i in ('1968-04-02T13:20:00', '1968-04-02T13:20:15.5',
@@ -42,7 +53,7 @@
def check_serialize_microseconds(self):
dateTime = '1968-04-02T13:20:15.511Z'
typecode = TC.gDateTime()
- text = typecode.get_formatted_content((1968, 4, 2, 13, 20, 15, 511, 0, 0))
+ text = typecode.get_formatted_content((1968, 4, 2, 13 - time.timezone / 3600, 20, 15, 511, 0, 0))
self.failUnless(text == dateTime,
'did not serialze correctly %s, not equal %s' %(text, dateTime))
@@ -60,7 +71,7 @@
typecode.get_formatted_content(bad)
def check_parse_microseconds2(self):
- good = (1968, 4, 2, 13, 20, 15, 500, 0, 0)
+ good = (1968, 4, 2, 13 - time.timezone / 3600, 20, 15, 500, 0, 0)
typecode = TC.gDateTime()
data = typecode.text_to_data('1968-04-02T13:20:15.5Z', None,None)
self.failUnless(data == good,
@@ -109,11 +120,32 @@
for i in ('68-04-02', '1968-4-2', '1968/04/02', '04-02-1968',):
self.failUnlessRaises(Exception, typecode.text_to_data, i, None, None),
+ def check_valid_cast(self):
+ text = "2002-10-10T17:00:00Z"
+ tc = TC.gDateTime()
+ data = tc.text_to_data(text, None, None)
+
+ tct = TC.gTime()
+ tcd = TC.gDate()
+ self.assertEquals("2002-10-10", tcd.get_formatted_content(data), "Invalid cast from gDateTime to gDate")
+ self.assertEquals("17:00:00Z", tct.get_formatted_content(data), "Invalid cast from gDateTime to gTime")
+
def broke_invalid_date_april31(self):
# No checks for valid date April 30 days
typecode = TC.gDate()
self.failUnlessRaises(Exception, typecode.text_to_data, '1968-04-31', None, None),
+ def check_gdates(self):
+ def _assert_tc(tc, val, msg):
+ self.assertEquals(val, tc.get_formatted_content(tc.text_to_data(val, None, None)), "%s: %s" % (msg, val))
+
+ _assert_tc(TC.gYear(), '1984', "Invalid gYear")
+ _assert_tc(TC.gYearMonth(), '1984-10', "Invalid gYearMonth")
+ _assert_tc(TC.gMonth(), '--10', "Invalid gMonth")
+ _assert_tc(TC.gMonthDay(), '--10-30', "Invalid gMonthDay")
+ _assert_tc(TC.gDay(), '---30', "Invalid gDay")
+ _assert_tc(TC.gDate(), '1984-10-30', "Invalid gDate")
+
#
# Creates permutation of test options: "check", "check_any", etc
#
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|