Package: python-suds
Version: 0.4.1-5
Severity: normal
Control: forwarded -1 https://fedorahosted.org/suds/ticket/353
Tags: upstream patch

The time and date handling in suds has massive issues, especially with
timezones. For example DST is not handled at all. The upstream bug
tracker has a three year old patch fixing the most obvious issues, it
would be nice to have this applied in the Debian package. A suitable git
patch is attached for your convenience.

-- 
Arto Jantunen

>From a591755a10d8abf090125ec11201187fb29aace0 Mon Sep 17 00:00:00 2001
From: Arto Jantunen <vi...@debian.org>
Date: Tue, 2 Jul 2013 10:27:18 +0300
Subject: [PATCH 1/1] Apply patch to fix timezone handling

---
 debian/patches/03-Fix-timezone-handling.patch |  898 +++++++++++++++++++++++++
 debian/patches/series                         |    1 +
 2 files changed, 899 insertions(+)
 create mode 100644 debian/patches/03-Fix-timezone-handling.patch

diff --git a/debian/patches/03-Fix-timezone-handling.patch b/debian/patches/03-Fix-timezone-handling.patch
new file mode 100644
index 0000000..d2f3fa4
--- /dev/null
+++ b/debian/patches/03-Fix-timezone-handling.patch
@@ -0,0 +1,898 @@
+From: gwalker
+Date: Sat Sep  4 11:48:00 EEST 2010
+Subject: Fix deficiencies in suds.sax.date module
+Bug: https://fedorahosted.org/suds/ticket/353
+
+There are several problems with the implementation of suds.sax.date:
+
+    * The ​timezone handling capabilities of the Python built-in classes
+    datetime.datetime and ​datetime.time are not used. When an xsd:dateTime or
+    xsd:time value is received an attempt is made to convert the value to
+    localtime, and then timezone naive datetime and time objects are
+    created. Any timezone information is not provided to the caller.
+
+    * Only the hour portion of a timezone offset is handled, and any minutes
+    are ignored. Multiple timezones do ​exist with an offset from UTC that is
+    not a exact multiple of 1 hour, so the minute part should definitely be
+    taken into account. (Asia/Kabul? +04:30, Australia/Adelaide? +09:30,
+    America/Caracas? -04:30, etc).
+
+    * Times are adjusted incorrectly if the local timezone has daylight
+    savings, as suds.sax.date does not take daylight savings into account.
+
+    * Milliseconds are handled incorrectly if an xsd:dateTime or xsd:time
+    value is received where the sub-second component is not exactly 6 digits
+    long.
+
+There are also a couple of small errors in suds.xsd.sxbuiltin related to
+suds.sax.date. In the classes XTime and XDateTime the handling of types does
+not match the corresponding handling in suds.sax.date.
+
+Attached is a replacement module for suds.sax.date that handles timezones
+correctly, populating the tzinfo property of datetime.datetime and
+datetime.time values with an appropriate ​datetime.tzinfo derived object. This
+allows the caller to easily convert results to localtime or UTC (using the new
+suds.sax.date.LocalTimezone or suds.sax.date.UtcTimezone classes) or arbitrary
+timezones (using ​pytz or similar) as required by their application. It does
+not make any assumptions about the timezone of xsd:dateTime and xsd:time
+values where no offset was included - this is left up to the caller.
+
+---
+ suds/sax/date.py      |  697 +++++++++++++++++++++++++++----------------------
+ suds/wsse.py          |   12 +-
+ suds/xsd/sxbuiltin.py |   10 +-
+ 3 files changed, 394 insertions(+), 325 deletions(-)
+
+diff --git a/suds/sax/date.py b/suds/sax/date.py
+index 6e31c4c..bb2d031 100644
+--- a/suds/sax/date.py
++++ b/suds/sax/date.py
+@@ -1,6 +1,6 @@
+ # This program is free software; you can redistribute it and/or modify
+ # it under the terms of the (LGPL) GNU Lesser General Public License as
+-# published by the Free Software Foundation; either version 3 of the 
++# published by the Free Software Foundation; either version 3 of the
+ # License, or (at your option) any later version.
+ #
+ # This program is distributed in the hope that it will be useful,
+@@ -12,367 +12,436 @@
+ # You should have received a copy of the GNU Lesser General Public License
+ # along with this program; if not, write to the Free Software
+ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+-# written by: Nathan Van Gheem (vangh...@gmail.com)
++# written by: Glen Walker
++# based on code by: Nathan Van Gheem (vangh...@gmail.com)
+ 
+ """
+-The I{xdate} module provides classes for converstion
+-between XML dates and python objects.
++The I{date} module provides classes for converstion between XML dates and
++Python objects.
+ """
+ 
+-from logging import getLogger
+-from suds import *
+-from suds.xsd import *
++
++__all__ = ('Date', 'Time', 'DateTime', 'FixedOffsetTimezone', 'UtcTimezone',
++           'LocalTimezone')
++
++
+ import time
+-import datetime as dt
++import datetime
+ import re
+ 
+-log = getLogger(__name__)
+ 
++SNIPPET_DATE = r'(?P<year>\d{1,})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
++SNIPPET_TIME = r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2}):(?P<second>\d{1,2})' + \
++               r'(?:\.(?P<subsecond>\d+))?'
++SNIPPET_ZONE = r'(?:(?P<tz_sign>[-+])(?P<tz_hour>\d{1,2})' + \
++               r'(?::?(?P<tz_minute>\d{1,2})(?::?(?P<tz_second>\d{1,2}))?)?)' + \
++               r'|(?P<tz_utc>[Zz])'
++
++PATTERN_DATE = r'^%s(?:%s)?$' % (SNIPPET_DATE, SNIPPET_ZONE)
++PATTERN_TIME = r'^%s(?:%s)?$' % (SNIPPET_TIME, SNIPPET_ZONE)
++PATTERN_DATETIME = r'^%s[T ]%s(?:%s)?$' % (SNIPPET_DATE, SNIPPET_TIME,
++                                           SNIPPET_ZONE)
++
++RE_DATE = re.compile(PATTERN_DATE)
++RE_TIME = re.compile(PATTERN_TIME)
++RE_DATETIME = re.compile(PATTERN_DATETIME)
++
++
++class Date(object):
++    """An XML date object supporting the xsd:date datatype.
++
++    @ivar value: The object value.
++    @type value: B{datetime}.I{date}
+ 
+-class Date:
+-    """
+-    An XML date object.
+-    Supported formats:
+-        - YYYY-MM-DD
+-        - YYYY-MM-DD(z|Z)
+-        - YYYY-MM-DD+06:00
+-        - YYYY-MM-DD-06:00
+-    @ivar date: The object value.
+-    @type date: B{datetime}.I{date}
+     """
+-    def __init__(self, date):
+-        """
+-        @param date: The value of the object.
+-        @type date: (date|str)
+-        @raise ValueError: When I{date} is invalid.
+-        """
+-        if isinstance(date, dt.date):
+-            self.date = date
+-            return
+-        if isinstance(date, basestring):
+-            self.date = self.__parse(date)
+-            return
+-        raise ValueError, type(date)
+-    
+-    def year(self):
+-        """
+-        Get the I{year} component.
+-        @return: The year.
+-        @rtype: int
+-        """
+-        return self.date.year
+-    
+-    def month(self):
+-        """
+-        Get the I{month} component.
+-        @return: The month.
+-        @rtype: int
+-        """
+-        return self.date.month
+-    
+-    def day(self):
+-        """
+-        Get the I{day} component.
+-        @return: The day.
+-        @rtype: int
+-        """
+-        return self.date.day
+-        
+-    def __parse(self, s):
++    __slots__ = ('value', )
++
++    def __init__(self, value):
++        """Constructor.
++
++        @param value: The date value of the object.
++        @type value: (datetime.date|str)
++        @raise ValueError: When I{value} is invalid.
++
+         """
+-        Parse the string date.
+-        Supported formats:
+-            - YYYY-MM-DD
+-            - YYYY-MM-DD(z|Z)
+-            - YYYY-MM-DD+06:00
+-            - YYYY-MM-DD-06:00
+-        Although, the TZ is ignored because it's meaningless
+-        without the time, right?
+-        @param s: A date string.
+-        @type s: str
++        if isinstance(value, datetime.date):
++            self.value = value
++        elif isinstance(value, basestring):
++            self.value = self.parse(value)
++        else:
++            raise ValueError('invalid type for Date(): %s' % (type(value), ))
++
++    @staticmethod
++    def parse(value):
++        """Parse the string date.
++
++        This supports the subset of ISO8601 used by xsd:date, but is lenient
++        with what is accepted, handling most reasonable syntax.
++
++        Any timezone is parsed but ignored  because a) it's meaningless without
++        a time and b) B{datetime}.I{date} does not support a tzinfo property.
++
++        @param value: A date string.
++        @type value: str
+         @return: A date object.
+-        @rtype: I{date}
++        @rtype: B{datetime}.I{date}
++
+         """
+-        try:
+-            year, month, day = s[:10].split('-', 2)
+-            year = int(year)
+-            month = int(month)
+-            day = int(day)
+-            return dt.date(year, month, day)
+-        except:
+-            log.debug(s, exec_info=True)
+-            raise ValueError, 'Invalid format "%s"' % s
+-        
++        match_result = RE_DATE.match(value)
++        if match_result is None:
++           raise ValueError('date data has invalid format "%s"' % (value, ))
++
++        value = date_from_match(match_result)
++
++        return value
++
+     def __str__(self):
+         return unicode(self)
+-    
++
+     def __unicode__(self):
+-        return self.date.isoformat()
++        return self.value.isoformat()
+ 
+ 
+-class Time:
+-    """
+-    An XML time object.
+-    Supported formats:
+-        - HH:MI:SS
+-        - HH:MI:SS(z|Z)
+-        - HH:MI:SS.ms
+-        - HH:MI:SS.ms(z|Z)
+-        - HH:MI:SS(+|-)06:00
+-        - HH:MI:SS.ms(+|-)06:00
+-    @ivar tz: The timezone
+-    @type tz: L{Timezone}
+-    @ivar date: The object value.
+-    @type date: B{datetime}.I{time}
++class Time(object):
++    """An XML time object supporting the xsd:time datatype.
++
++    @ivar time: The object value.
++    @type time: B{datetime}.I{time}
++
+     """
+-    
+-    def __init__(self, time, adjusted=True):
+-        """
+-        @param time: The value of the object.
+-        @type time: (time|str)
+-        @param adjusted: Adjust for I{local} Timezone.
+-        @type adjusted: boolean
+-        @raise ValueError: When I{time} is invalid.
+-        """
+-        self.tz = Timezone()
+-        if isinstance(time, dt.time):
+-            self.time = time
+-            return
+-        if isinstance(time, basestring):
+-            self.time = self.__parse(time)
+-            if adjusted:
+-                self.__adjust()
+-            return
+-        raise ValueError, type(time)
+-    
+-    def hour(self):
+-        """
+-        Get the I{hour} component.
+-        @return: The hour.
+-        @rtype: int
+-        """
+-        return self.time.hour
+-    
+-    def minute(self):
+-        """
+-        Get the I{minute} component.
+-        @return: The minute.
+-        @rtype: int
+-        """
+-        return self.time.minute
+-    
+-    def second(self):
+-        """
+-        Get the I{seconds} component.
+-        @return: The seconds.
+-        @rtype: int
++    __slots__ = ('value', )
++
++    def __init__(self, value):
++        """Constructor.
++
++        @param value: The time value of the object.
++        @type value: (dateime.time|str)
++        @raise ValueError: When I{value} is invalid.
++
+         """
+-        return self.time.second
+-    
+-    def microsecond(self):
++        if isinstance(value, datetime.time):
++            self.value = value
++        elif isinstance(value, basestring):
++            self.value = self.parse(value)
++        else:
++            raise ValueError('invalid type for Time(): %s' % (type(value), ))
++
++    @staticmethod
++    def parse(value):
++        """Parse the string date.
++
++        This supports the subset of ISO8601 used by xsd:time, but is lenient
++        with what is accepted, handling most reasonable syntax.
++
++        @param value: A time string.
++        @type value: str
++        @return: A time object.
++        @rtype: B{datetime}.I{time}
++
+         """
+-        Get the I{microsecond} component.
+-        @return: The microsecond.
+-        @rtype: int
++        match_result = RE_TIME.match(value)
++        if match_result is None:
++           raise ValueError('date data has invalid format "%s"' % (value, ))
++
++        date = time_from_match(match_result)
++        tzinfo = tzinfo_from_match(match_result)
++
++        value = date.replace(tzinfo=tzinfo)
++
++        return value
++
++    def __str__(self):
++        return unicode(self)
++
++    def __unicode__(self):
++        return self.time.isoformat()
++
++
++class DateTime(object):
++    """An XML datetime object supporting the xsd:dateTime datatype.
++
++    @ivar value: The object value.
++    @type value: B{datetime}.I{datetime}
++
++    """
++    __slots__ = ('value', )
++
++    def __init__(self, value):
++        """Constructor.
++
++        @param value: The datetime value of the object.
++        @type value: (datetime.datetime|str)
++        @raise ValueError: When I{value} is invalid.
++
+         """
+-        return self.time.microsecond
+-    
+-    def __adjust(self):
++        if isinstance(value, datetime.datetime):
++            self.value = value
++        elif isinstance(value, basestring):
++            self.value = self.parse(value)
++        else:
++            raise ValueError('invalid type for DateTime(): %s' % (type(value), ))
++
++    @staticmethod
++    def parse(value):
++        """Parse the string datetime.
++
++        This supports the subset of ISO8601 used by xsd:dateTime, but is lenient
++        with what is accepted, handling most reasonable syntax.
++
++        @param value: A datetime string.
++        @type value: str
++        @return: A datetime object.
++        @rtype: B{datetime}.I{datetime}
++
+         """
+-        Adjust for TZ offset.
++        match_result = RE_DATETIME.match(value)
++        if match_result is None:
++           raise ValueError('date data has invalid format "%s"' % (value, ))
++
++        date = date_from_match(match_result)
++        time = time_from_match(match_result)
++        tzinfo = tzinfo_from_match(match_result)
++
++        value = datetime.datetime.combine(date, time)
++        value = value.replace(tzinfo=tzinfo)
++
++        return value
++
++    def __str__(self):
++        return unicode(self)
++
++    def __unicode__(self):
++        return self.value.isoformat()
++
++
++class FixedOffsetTimezone(datetime.tzinfo):
++    """A timezone with a fixed offset and no daylight savings adjustment.
++
++    http://docs.python.org/library/datetime.html#datetime.tzinfo
++
++    """
++
++    def __init__(self, offset):
++        """Constructor.
++
++        @ivar offset: The fixed offset of the timezone.
++        @type offset: B{datetime}.I{timedelta}
++
+         """
+-        if hasattr(self, 'offset'):
+-            today = dt.date.today()
+-            delta = self.tz.adjustment(self.offset)
+-            d = dt.datetime.combine(today, self.time)
+-            d = ( d + delta )
+-            self.time = d.time()
+-        
+-    def __parse(self, s):
++        self.__offset = offset
++
++    def utcoffset(self, dt):
+         """
+-        Parse the string date.
+-        Patterns:
+-            - HH:MI:SS
+-            - HH:MI:SS(z|Z)
+-            - HH:MI:SS.ms
+-            - HH:MI:SS.ms(z|Z)
+-            - HH:MI:SS(+|-)06:00
+-            - HH:MI:SS.ms(+|-)06:00
+-        @param s: A time string.
+-        @type s: str
+-        @return: A time object.
+-        @rtype: B{datetime}.I{time}
++        http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset
++
+         """
+-        try:
+-            offset = None
+-            part = Timezone.split(s)
+-            hour, minute, second = part[0].split(':', 2)
+-            hour = int(hour)
+-            minute = int(minute)
+-            second, ms = self.__second(second)
+-            if len(part) == 2:
+-                self.offset = self.__offset(part[1])
+-            if ms is None:
+-                return dt.time(hour, minute, second)
+-            else:
+-                return dt.time(hour, minute, second, ms)
+-        except:
+-            log.debug(s, exec_info=True)
+-            raise ValueError, 'Invalid format "%s"' % s
+-        
+-    def __second(self, s):
++        return self.__offset
++
++    def tzname(self, dt):
+         """
+-        Parse the seconds and microseconds.
+-        The microseconds are truncated to 999999 due to a restriction in
+-        the python datetime.datetime object.
+-        @param s: A string representation of the seconds.
+-        @type s: str
+-        @return: Tuple of (sec,ms)
+-        @rtype: tuple.
++        http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname
++
+         """
+-        part = s.split('.')
+-        if len(part) > 1:
+-            return (int(part[0]), int(part[1][:6]))
++        sign = '+'
++        if self.__offset < datetime.timedelta():
++            sign = '-'
++
++        # total_seconds was introduced in Python 2.7
++        if hasattr(self.__offset, 'total_seconds'):
++            total_seconds = self.__offset.total_seconds()
++        else:
++            total_seconds = (self.__offset.days * 24 * 60 * 60) + \
++                            (self.__offset.seconds) + \
++                            (self.__offset.microseconds / 1000000.0)
++
++        hours = total_seconds // (60 * 60)
++        total_seconds -= hours * 60 * 60
++
++        minutes = total_seconds // 60
++        total_seconds -= minutes * 60
++
++        seconds = total_seconds // 1
++        total_seconds -= seconds
++
++        if seconds:
++            return '%s%02d:%02d:%02d' % (sign, hours, minutes, seconds)
+         else:
+-            return (int(part[0]), None)
+-        
+-    def __offset(self, s):
++            return '%s%02d:%02d' % (sign, hours, minutes)
++
++    def dst(self, dt):
+         """
+-        Parse the TZ offset.
+-        @param s: A string representation of the TZ offset.
+-        @type s: str
+-        @return: The signed offset in hours.
+-        @rtype: str
++        http://docs.python.org/library/datetime.html#datetime.tzinfo.dst
++
+         """
+-        if len(s) == len('-00:00'):
+-            return int(s[:3])
+-        if len(s) == 0:
+-            return self.tz.local
+-        if len(s) == 1:
+-            return 0
+-        raise Exception()
++        return datetime.timedelta(0)
+ 
+     def __str__(self):
+         return unicode(self)
+-    
++
+     def __unicode__(self):
+-        time = self.time.isoformat()
+-        if self.tz.local:
+-            return '%s%+.2d:00' % (time, self.tz.local)
+-        else:
+-            return '%sZ' % time
++        return 'FixedOffsetTimezone %s' % (self.tzname(None), )
++
++
++class UtcTimezone(FixedOffsetTimezone):
++    """The UTC timezone.
+ 
++    http://docs.python.org/library/datetime.html#datetime.tzinfo
+ 
+-class DateTime(Date,Time):
+     """
+-    An XML time object.
+-    Supported formats:
+-        - YYYY-MM-DDB{T}HH:MI:SS
+-        - YYYY-MM-DDB{T}HH:MI:SS(z|Z)
+-        - YYYY-MM-DDB{T}HH:MI:SS.ms
+-        - YYYY-MM-DDB{T}HH:MI:SS.ms(z|Z)
+-        - YYYY-MM-DDB{T}HH:MI:SS(+|-)06:00
+-        - YYYY-MM-DDB{T}HH:MI:SS.ms(+|-)06:00
+-    @ivar datetime: The object value.
+-    @type datetime: B{datetime}.I{datedate}
++
++    def __init__(self):
++        """Constructor."""
++        FixedOffsetTimezone.__init__(self, datetime.timedelta(0))
++
++    def tzname(self, dt):
++        """
++        http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname
++
++        """
++        return 'UTC'
++
++    def __str__(self):
++        return unicode(self)
++
++    def __unicode__(self):
++        return 'UtcTimezone'
++
++
++class LocalTimezone(datetime.tzinfo):
++    """The local timezone of the operating system.
++
++    http://docs.python.org/library/datetime.html#datetime.tzinfo
++
+     """
+-    def __init__(self, date):
++
++    def __init__(self):
++        """Constructor."""
++        self.__offset = datetime.timedelta(seconds=-time.timezone)
++        if time.daylight:
++            self.__dst_offset = datetime.timedelta(seconds=-time.altzone)
++        else:
++            self.__dst_offset = None
++
++    def utcoffset(self, dt):
++        """
++        http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset
++
++        """
++        if self.__is_daylight_time(dt):
++            return self.__dst_offset
++        else:
++            return self.__offset
++
++    def dst(self, dt):
+         """
+-        @param date: The value of the object.
+-        @type date: (datetime|str)
+-        @raise ValueError: When I{tm} is invalid.
++        http://docs.python.org/library/datetime.html#datetime.tzinfo.dst
++
+         """
+-        if isinstance(date, dt.datetime):
+-            Date.__init__(self, date.date())
+-            Time.__init__(self, date.time())
+-            self.datetime = \
+-                dt.datetime.combine(self.date, self.time)
+-            return
+-        if isinstance(date, basestring):
+-            part = date.split('T')
+-            Date.__init__(self, part[0])
+-            Time.__init__(self, part[1], 0)
+-            self.datetime = \
+-                dt.datetime.combine(self.date, self.time)
+-            self.__adjust()
+-            return
+-        raise ValueError, type(date)
+-    
+-    def __adjust(self):
++        if self.__is_daylight_time(dt):
++            return self.__dst_offset - self.__offset
++        else:
++            return datetime.timedelta(0)
++
++    def tzname(self, dt):
+         """
+-        Adjust for TZ offset.
++        http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname
++
+         """
+-        if not hasattr(self, 'offset'):
+-            return
+-        delta = self.tz.adjustment(self.offset)
+-        try:
+-            d = ( self.datetime + delta )
+-            self.datetime = d
+-            self.date = d.date()
+-            self.time = d.time()
+-        except OverflowError:
+-            log.warn('"%s" caused overflow, not-adjusted', self.datetime)
++        if self.__is_daylight_time(dt):
++            return time.tzname[1]
++        else:
++            return time.tzname[0]
++
++    def __is_daylight_time(self, dt):
++        if not time.daylight:
++            return False
++        time_tuple = dt.replace(tzinfo=None).timetuple()
++        time_tuple = time.localtime(time.mktime(time_tuple))
++        return time_tuple.tm_isdst > 0
+ 
+     def __str__(self):
+         return unicode(self)
+-    
++
+     def __unicode__(self):
+-        s = []
+-        s.append(Date.__unicode__(self))
+-        s.append(Time.__unicode__(self))
+-        return 'T'.join(s)
+-    
+-    
+-class UTC(DateTime):
+-    """
+-    Represents current UTC time.
++        dt = datetime.datetime.now()
++        return 'LocalTimezone %s offset: %s dst: %s' \
++            % (self.tzname(dt), self.utcoffset(dt), self.dst(dt))
++
++
++def date_from_match(match_object):
++    """Create a date object from a regular expression match.
++
++    The regular expression match is expected to be from RE_DATE or RE_DATETIME.
++
++    @param match_object: The regular expression match.
++    @type value: B{re}.I{MatchObject}
++    @return: A date object.
++    @rtype: B{datetime}.I{date}
++
+     """
+-    
+-    def __init__(self, date=None):
+-        if date is None:
+-            date = dt.datetime.utcnow()
+-        DateTime.__init__(self, date)
+-        self.tz.local = 0
+-    
+-    
+-class Timezone:
++    year = int(match_object.group('year'))
++    month = int(match_object.group('month'))
++    day = int(match_object.group('day'))
++
++    return datetime.date(year, month, day)
++
++
++def time_from_match(match_object):
++    """Create a time object from a regular expression match.
++
++    The regular expression match is expected to be from RE_TIME or RE_DATETIME.
++
++    @param match_object: The regular expression match.
++    @type value: B{re}.I{MatchObject}
++    @return: A date object.
++    @rtype: B{datetime}.I{time}
++
+     """
+-    Timezone object used to do TZ conversions
+-    @cvar local: The (A) local TZ offset.
+-    @type local: int
+-    @cvar patten: The regex patten to match TZ.
+-    @type patten: re.Pattern
++    hour = int(match_object.group('hour'))
++    minute = int(match_object.group('minute'))
++    second = int(match_object.group('second'))
++    subsecond = match_object.group('subsecond')
++
++    microsecond = 0
++    if subsecond is not None:
++        subsecond_denominator = 10.0 ** len(subsecond)
++        subsecond = int(subsecond)
++        microsecond = subsecond * (1000000 / subsecond_denominator)
++        microsecond = int(round(microsecond))
++
++    return datetime.time(hour, minute, second, microsecond)
++
++
++def tzinfo_from_match(match_object):
++    """Create a timezone information object from a regular expression match.
++
++    The regular expression match is expected to be from RE_DATE, RE_TIME or
++    RE_DATETIME.
++
++    @param match_object: The regular expression match.
++    @type value: B{re}.I{MatchObject}
++    @return: A timezone information object.
++    @rtype: B{datetime}.I{tzinfo}
++
+     """
+-    
+-    pattern = re.compile('([zZ])|([\-\+][0-9]{2}:[0-9]{2})')
+-    
+-    LOCAL = ( 0-time.timezone/60/60 )
+-
+-    def __init__(self, offset=None):
+-        if offset is None:
+-            offset = self.LOCAL
+-        self.local = offset
+-    
+-    @classmethod
+-    def split(cls, s):
+-        """
+-        Split the TZ from string.
+-        @param s: A string containing a timezone
+-        @type s: basestring
+-        @return: The split parts.
+-        @rtype: tuple
+-        """
+-        m = cls.pattern.search(s)
+-        if m is None:
+-            return (s,)
+-        x = m.start(0)
+-        return (s[:x], s[x:])
++    tz_utc = match_object.group('tz_utc')
++    tz_sign = match_object.group('tz_sign')
++    tz_hour = int(match_object.group('tz_hour') or 0)
++    tz_minute = int(match_object.group('tz_minute') or 0)
++    tz_second = int(match_object.group('tz_second') or 0)
+ 
+-    def adjustment(self, offset):
+-        """
+-        Get the adjustment to the I{local} TZ.
+-        @return: The delta between I{offset} and local TZ.
+-        @rtype: B{datetime}.I{timedelta}
+-        """
+-        delta = ( self.local - offset )
+-        return dt.timedelta(hours=delta)
++    tzinfo = None
++    if tz_utc is not None:
++        tzinfo = UtcTimezone()
++    elif tz_sign is not None:
++        tz_delta = datetime.timedelta(hours=tz_hour,
++                                      minutes=tz_minute,
++                                      seconds=tz_second)
++        if tz_delta == datetime.timedelta():
++            tzinfo = UtcTimezone()
++        else:
++            tz_multiplier = int('%s1' % (tz_sign, ))
++            tz_delta = tz_multiplier * tz_delta
++            tzinfo = FixedOffsetTimezone(tz_delta)
++
++    return tzinfo
+\ No newline at end of file
+diff --git a/suds/wsse.py b/suds/wsse.py
+index 2a697c1..6cd70a0 100644
+--- a/suds/wsse.py
++++ b/suds/wsse.py
+@@ -22,7 +22,7 @@ from logging import getLogger
+ from suds import *
+ from suds.sudsobject import Object
+ from suds.sax.element import Element
+-from suds.sax.date import UTC
++from suds.sax.date import DateTime, UtcTimezone
+ from datetime import datetime, timedelta
+ 
+ try:
+@@ -90,11 +90,11 @@ class Token(Object):
+     
+     @classmethod
+     def utc(cls):
+-        return datetime.utcnow()
++        return datetime.utcnow().replace(tzinfo=UtcTimezone())
+     
+     @classmethod
+     def sysdate(cls):
+-        utc = UTC()
++        utc = DateTime(self.utc())
+         return str(utc)
+     
+     def __init__(self):
+@@ -178,7 +178,7 @@ class UsernameToken(Token):
+             root.append(n)
+         if self.created is not None:
+             n = Element('Created', ns=wsuns)
+-            n.setText(str(UTC(self.created)))
++            n.setText(str(DateTime(self.created)))
+             root.append(n)
+         return root
+ 
+@@ -204,9 +204,9 @@ class Timestamp(Token):
+     def xml(self):
+         root = Element("Timestamp", ns=wsuns)
+         created = Element('Created', ns=wsuns)
+-        created.setText(str(UTC(self.created)))
++        created.setText(str(DateTime(self.created)))
+         expires = Element('Expires', ns=wsuns)
+-        expires.setText(str(UTC(self.expires)))
++        expires.setText(str(DateTime(self.expires)))
+         root.append(created)
+         root.append(expires)
+         return root
+\ No newline at end of file
+diff --git a/suds/xsd/sxbuiltin.py b/suds/xsd/sxbuiltin.py
+index f8cf428..a3543bd 100644
+--- a/suds/xsd/sxbuiltin.py
++++ b/suds/xsd/sxbuiltin.py
+@@ -138,7 +138,7 @@ class XDate(XBuiltin):
+     def translate(self, value, topython=True):
+         if topython:
+             if isinstance(value, basestring) and len(value):
+-                return Date(value).date
++                return Date(value).value
+             else:
+                 return None
+         else:
+@@ -156,11 +156,11 @@ class XTime(XBuiltin):
+     def translate(self, value, topython=True):
+         if topython:
+             if isinstance(value, basestring) and len(value):
+-                return Time(value).time
++                return Time(value).value
+             else:
+                 return None
+         else:
+-            if isinstance(value, dt.date):
++            if isinstance(value, dt.time):
+                 return str(Time(value))
+             else:
+                 return value
+@@ -174,11 +174,11 @@ class XDateTime(XBuiltin):
+     def translate(self, value, topython=True):
+         if topython:
+             if isinstance(value, basestring) and len(value):
+-                return DateTime(value).datetime
++                return DateTime(value).value
+             else:
+                 return None
+         else:
+-            if isinstance(value, dt.date):
++            if isinstance(value, dt.datetime):
+                 return str(DateTime(value))
+             else:
+                 return value
diff --git a/debian/patches/series b/debian/patches/series
index 280967c..f88c02a 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,2 +1,3 @@
 01-remove-makefile
 02-fix-unsecure-cache-path.patch
+03-Fix-timezone-handling.patch
-- 
1.7.10.4

Reply via email to