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. |