1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 The I{xdate} module provides classes for converstion
19 between XML dates and python objects.
20 """
21
22 from logging import getLogger
23 from suds import *
24 from suds.xsd import *
25 import time
26 import datetime as dt
27 import re
28
29 log = getLogger(__name__)
33 """
34 An XML date object.
35 Supported formats:
36 - YYYY-MM-DD
37 - YYYY-MM-DD(z|Z)
38 - YYYY-MM-DD+06:00
39 - YYYY-MM-DD-06:00
40 @ivar date: The object value.
41 @type date: B{datetime}.I{date}
42 """
44 """
45 @param date: The value of the object.
46 @type date: (date|str)
47 @raise ValueError: When I{date} is invalid.
48 """
49 if isinstance(date, dt.date):
50 self.date = date
51 return
52 if isinstance(date, basestring):
53 self.date = self.__parse(date)
54 return
55 raise ValueError, type(date)
56
58 """
59 Get the I{year} component.
60 @return: The year.
61 @rtype: int
62 """
63 return self.date.year
64
66 """
67 Get the I{month} component.
68 @return: The month.
69 @rtype: int
70 """
71 return self.date.month
72
74 """
75 Get the I{day} component.
76 @return: The day.
77 @rtype: int
78 """
79 return self.date.day
80
82 """
83 Parse the string date.
84 Supported formats:
85 - YYYY-MM-DD
86 - YYYY-MM-DD(z|Z)
87 - YYYY-MM-DD+06:00
88 - YYYY-MM-DD-06:00
89 Although, the TZ is ignored because it's meaningless
90 without the time, right?
91 @param s: A date string.
92 @type s: str
93 @return: A date object.
94 @rtype: I{date}
95 """
96 try:
97 year, month, day = s[:10].split('-', 2)
98 year = int(year)
99 month = int(month)
100 day = int(day)
101 return dt.date(year, month, day)
102 except:
103 log.debug(s, exec_info=True)
104 raise ValueError, 'Invalid format "%s"' % s
105
108
110 return self.date.isoformat()
111
114 """
115 An XML time object.
116 Supported formats:
117 - HH:MI:SS
118 - HH:MI:SS(z|Z)
119 - HH:MI:SS.ms
120 - HH:MI:SS.ms(z|Z)
121 - HH:MI:SS(+|-)06:00
122 - HH:MI:SS.ms(+|-)06:00
123 @ivar tz: The timezone
124 @type tz: L{Timezone}
125 @ivar date: The object value.
126 @type date: B{datetime}.I{time}
127 """
128
129 - def __init__(self, time, adjusted=True):
130 """
131 @param time: The value of the object.
132 @type time: (time|str)
133 @param adjusted: Adjust for I{local} Timezone.
134 @type adjusted: boolean
135 @raise ValueError: When I{time} is invalid.
136 """
137 self.tz = Timezone()
138 if isinstance(time, dt.time):
139 self.time = time
140 return
141 if isinstance(time, basestring):
142 self.time = self.__parse(time)
143 if adjusted:
144 self.__adjust()
145 return
146 raise ValueError, type(time)
147
149 """
150 Get the I{hour} component.
151 @return: The hour.
152 @rtype: int
153 """
154 return self.time.hour
155
157 """
158 Get the I{minute} component.
159 @return: The minute.
160 @rtype: int
161 """
162 return self.time.minute
163
165 """
166 Get the I{seconds} component.
167 @return: The seconds.
168 @rtype: int
169 """
170 return self.time.second
171
173 """
174 Get the I{microsecond} component.
175 @return: The microsecond.
176 @rtype: int
177 """
178 return self.time.microsecond
179
181 """
182 Adjust for TZ offset.
183 """
184 if hasattr(self, 'offset'):
185 today = dt.date.today()
186 delta = self.tz.adjustment(self.offset)
187 d = dt.datetime.combine(today, self.time)
188 d = ( d + delta )
189 self.time = d.time()
190
192 """
193 Parse the string date.
194 Patterns:
195 - HH:MI:SS
196 - HH:MI:SS(z|Z)
197 - HH:MI:SS.ms
198 - HH:MI:SS.ms(z|Z)
199 - HH:MI:SS(+|-)06:00
200 - HH:MI:SS.ms(+|-)06:00
201 @param s: A time string.
202 @type s: str
203 @return: A time object.
204 @rtype: B{datetime}.I{time}
205 """
206 try:
207 offset = None
208 part = Timezone.split(s)
209 hour, minute, second = part[0].split(':', 2)
210 hour = int(hour)
211 minute = int(minute)
212 second, ms = self.__second(second)
213 if len(part) == 2:
214 self.offset = self.__offset(part[1])
215 if ms is None:
216 return dt.time(hour, minute, second)
217 else:
218 return dt.time(hour, minute, second, ms)
219 except:
220 log.debug(s, exec_info=True)
221 raise ValueError, 'Invalid format "%s"' % s
222
224 """
225 Parse the seconds and microseconds.
226 The microseconds are truncated to 999999 due to a restriction in
227 the python datetime.datetime object.
228 @param s: A string representation of the seconds.
229 @type s: str
230 @return: Tuple of (sec,ms)
231 @rtype: tuple.
232 """
233 part = s.split('.')
234 if len(part) > 1:
235 return (int(part[0]), int(part[1][:6]))
236 else:
237 return (int(part[0]), None)
238
240 """
241 Parse the TZ offset.
242 @param s: A string representation of the TZ offset.
243 @type s: str
244 @return: The signed offset in hours.
245 @rtype: str
246 """
247 if len(s) == len('-00:00'):
248 return int(s[:3])
249 if len(s) == 0:
250 return self.tz.local
251 if len(s) == 1:
252 return 0
253 raise Exception()
254
257
259 time = self.time.isoformat()
260 if self.tz.local:
261 return '%s%+.2d:00' % (time, self.tz.local)
262 else:
263 return '%sZ' % time
264
267 """
268 An XML time object.
269 Supported formats:
270 - YYYY-MM-DDB{T}HH:MI:SS
271 - YYYY-MM-DDB{T}HH:MI:SS(z|Z)
272 - YYYY-MM-DDB{T}HH:MI:SS.ms
273 - YYYY-MM-DDB{T}HH:MI:SS.ms(z|Z)
274 - YYYY-MM-DDB{T}HH:MI:SS(+|-)06:00
275 - YYYY-MM-DDB{T}HH:MI:SS.ms(+|-)06:00
276 @ivar datetime: The object value.
277 @type datetime: B{datetime}.I{datedate}
278 """
280 """
281 @param date: The value of the object.
282 @type date: (datetime|str)
283 @raise ValueError: When I{tm} is invalid.
284 """
285 if isinstance(date, dt.datetime):
286 Date.__init__(self, date.date())
287 Time.__init__(self, date.time())
288 self.datetime = \
289 dt.datetime.combine(self.date, self.time)
290 return
291 if isinstance(date, basestring):
292 part = date.split('T')
293 Date.__init__(self, part[0])
294 Time.__init__(self, part[1], 0)
295 self.datetime = \
296 dt.datetime.combine(self.date, self.time)
297 self.__adjust()
298 return
299 raise ValueError, type(date)
300
302 """
303 Adjust for TZ offset.
304 """
305 if not hasattr(self, 'offset'):
306 return
307 delta = self.tz.adjustment(self.offset)
308 try:
309 d = ( self.datetime + delta )
310 self.datetime = d
311 self.date = d.date()
312 self.time = d.time()
313 except OverflowError:
314 log.warn('"%s" caused overflow, not-adjusted', self.datetime)
315
318
324
325
326 -class UTC(DateTime):
327 """
328 Represents current UTC time.
329 """
330
336
339 """
340 Timezone object used to do TZ conversions
341 @cvar local: The (A) local TZ offset.
342 @type local: int
343 @cvar patten: The regex patten to match TZ.
344 @type patten: re.Pattern
345 """
346
347 pattern = re.compile('([zZ])|([\-\+][0-9]{2}:[0-9]{2})')
348
349 LOCAL = ( 0-time.timezone/60/60 ) + time.daylight
350
352 if offset is None:
353 offset = self.LOCAL
354 self.local = offset
355
356 @classmethod
358 """
359 Split the TZ from string.
360 @param s: A string containing a timezone
361 @type s: basestring
362 @return: The split parts.
363 @rtype: tuple
364 """
365 m = cls.pattern.search(s)
366 if m is None:
367 return (s,)
368 x = m.start(0)
369 return (s[:x], s[x:])
370
372 """
373 Get the adjustment to the I{local} TZ.
374 @return: The delta between I{offset} and local TZ.
375 @rtype: B{datetime}.I{timedelta}
376 """
377 delta = ( self.local - offset )
378 return dt.timedelta(hours=delta)
379