|
1 """ This module provides a set of constructors and routines to convert |
|
2 between DateTime[Delta] instances and ISO representations of date |
|
3 and time. |
|
4 |
|
5 Note: Timezones are only interpreted by ParseDateTimeGMT(). All |
|
6 other constructors silently ignore the time zone information. |
|
7 |
|
8 Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com |
|
9 Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com |
|
10 See the documentation for further information on copyrights, |
|
11 or contact the author. |
|
12 |
|
13 """ |
|
14 import DateTime,Timezone |
|
15 import re,string |
|
16 |
|
17 # Grammar: ISO 8601 (not all, but what we need from it) |
|
18 _year = '(?P<year>\d?\d\d\d)' |
|
19 _month = '(?P<month>\d?\d)' |
|
20 _day = '(?P<day>\d?\d)' |
|
21 _hour = '(?P<hour>\d?\d)' |
|
22 _minute = '(?P<minute>\d?\d)' |
|
23 _second = '(?P<second>\d?\d(?:\.\d+)?)' |
|
24 _sign = '(?P<sign>[-+])' |
|
25 _week = 'W(?P<week>\d?\d)' |
|
26 _zone = Timezone.isozone |
|
27 |
|
28 _weekdate = _year + '-?(?:' + _week + '-?' + _day + '?)?' |
|
29 _date = _year + '-?' + '(?:' + _month + '-?' + _day + '?)?' |
|
30 _time = _hour + ':?' + _minute + ':?' + _second + '?(?:' + _zone + ')?' |
|
31 |
|
32 isodatetimeRE = re.compile(_date + '(?:[ T]' + _time + ')?$') |
|
33 isodateRE = re.compile(_date + '$') |
|
34 isotimeRE = re.compile(_time + '$') |
|
35 isodeltaRE = re.compile(_sign + '?' + _time + '$') |
|
36 isoweekRE = re.compile(_weekdate + '$') |
|
37 isoweektimeRE = re.compile(_weekdate + '(?:[ T]' + _time + ')?$') |
|
38 |
|
39 def WeekTime(year,isoweek=1,isoday=1,hour=0,minute=0,second=0.0): |
|
40 |
|
41 """Week(year,isoweek=1,isoday=1,hour=0,minute=0,second=0.0) |
|
42 |
|
43 Returns a DateTime instance pointing to the given ISO week and |
|
44 day. isoday defaults to 1, which corresponds to Monday in the |
|
45 ISO numbering. The time part is set as given. |
|
46 |
|
47 """ |
|
48 d = DateTime.DateTime(year,1,1,hour,minute,second) |
|
49 if d.iso_week[0] == year: |
|
50 # 1.1. belongs to year (backup to Monday) |
|
51 return d + (-d.day_of_week + 7 * (isoweek-1) + isoday-1) |
|
52 else: |
|
53 # 1.1. belongs to year-1 (advance to next Monday) |
|
54 return d + (7-d.day_of_week + 7 * (isoweek-1) + isoday-1) |
|
55 |
|
56 # Alias |
|
57 Week = WeekTime |
|
58 |
|
59 # Aliases for the other constructors (they all happen to already use |
|
60 # ISO format) |
|
61 Date = DateTime.Date |
|
62 Time = DateTime.Time |
|
63 TimeDelta = DateTime.TimeDelta |
|
64 |
|
65 def ParseDateTime(isostring,parse_isodatetime=isodatetimeRE.match, |
|
66 |
|
67 strip=string.strip,atoi=string.atoi,atof=string.atof): |
|
68 |
|
69 """ParseDateTime(isostring) |
|
70 |
|
71 Returns a DateTime instance reflecting the given ISO date. A |
|
72 time part is optional and must be delimited from the date by a |
|
73 space or 'T'. |
|
74 |
|
75 Time zone information is parsed, but not evaluated. |
|
76 |
|
77 """ |
|
78 s = strip(isostring) |
|
79 date = parse_isodatetime(s) |
|
80 if not date: |
|
81 raise ValueError,'wrong format, use YYYY-MM-DD HH:MM:SS' |
|
82 year,month,day,hour,minute,second,zone = date.groups() |
|
83 year = atoi(year) |
|
84 if month is None: |
|
85 month = 1 |
|
86 else: |
|
87 month = atoi(month) |
|
88 if day is None: |
|
89 day = 1 |
|
90 else: |
|
91 day = atoi(day) |
|
92 if hour is None: |
|
93 hour = 0 |
|
94 else: |
|
95 hour = atoi(hour) |
|
96 if minute is None: |
|
97 minute = 0 |
|
98 else: |
|
99 minute = atoi(minute) |
|
100 if second is None: |
|
101 second = 0.0 |
|
102 else: |
|
103 second = atof(second) |
|
104 return DateTime.DateTime(year,month,day,hour,minute,second) |
|
105 |
|
106 def ParseDateTimeGMT(isostring,parse_isodatetime=isodatetimeRE.match, |
|
107 |
|
108 strip=string.strip,atoi=string.atoi,atof=string.atof): |
|
109 |
|
110 """ParseDateTimeGMT(isostring) |
|
111 |
|
112 Returns a DateTime instance in UTC reflecting the given ISO |
|
113 date. A time part is optional and must be delimited from the |
|
114 date by a space or 'T'. Timezones are honored. |
|
115 |
|
116 """ |
|
117 s = strip(isostring) |
|
118 date = parse_isodatetime(s) |
|
119 if not date: |
|
120 raise ValueError,'wrong format, use YYYY-MM-DD HH:MM:SS' |
|
121 year,month,day,hour,minute,second,zone = date.groups() |
|
122 year = atoi(year) |
|
123 if month is None: |
|
124 month = 1 |
|
125 else: |
|
126 month = atoi(month) |
|
127 if day is None: |
|
128 day = 1 |
|
129 else: |
|
130 day = atoi(day) |
|
131 if hour is None: |
|
132 hour = 0 |
|
133 else: |
|
134 hour = atoi(hour) |
|
135 if minute is None: |
|
136 minute = 0 |
|
137 else: |
|
138 minute = atoi(minute) |
|
139 if second is None: |
|
140 second = 0.0 |
|
141 else: |
|
142 second = atof(second) |
|
143 offset = Timezone.utc_offset(zone) |
|
144 return DateTime.DateTime(year,month,day,hour,minute,second) - offset |
|
145 |
|
146 # Alias |
|
147 ParseDateTimeUTC = ParseDateTimeGMT |
|
148 |
|
149 def ParseDate(isostring,parse_isodate=isodateRE.match, |
|
150 |
|
151 strip=string.strip,atoi=string.atoi,atof=string.atof): |
|
152 |
|
153 """ParseDate(isostring) |
|
154 |
|
155 Returns a DateTime instance reflecting the given ISO date. A |
|
156 time part may not be included. |
|
157 |
|
158 """ |
|
159 s = strip(isostring) |
|
160 date = parse_isodate(s) |
|
161 if not date: |
|
162 raise ValueError,'wrong format, use YYYY-MM-DD' |
|
163 year,month,day = date.groups() |
|
164 year = atoi(year) |
|
165 if month is None: |
|
166 month = 1 |
|
167 else: |
|
168 month = atoi(month) |
|
169 if day is None: |
|
170 day = 1 |
|
171 else: |
|
172 day = atoi(day) |
|
173 return DateTime.DateTime(year,month,day) |
|
174 |
|
175 def ParseWeek(isostring,parse_isoweek=isoweekRE.match, |
|
176 |
|
177 strip=string.strip,atoi=string.atoi,atof=string.atof): |
|
178 |
|
179 """ParseWeek(isostring) |
|
180 |
|
181 Returns a DateTime instance reflecting the given ISO date. A |
|
182 time part may not be included. |
|
183 |
|
184 """ |
|
185 s = strip(isostring) |
|
186 date = parse_isoweek(s) |
|
187 if not date: |
|
188 raise ValueError,'wrong format, use yyyy-Www-d, e.g. 1998-W01-1' |
|
189 year,week,day = date.groups() |
|
190 year = atoi(year) |
|
191 if week is None: |
|
192 week = 1 |
|
193 else: |
|
194 week = atoi(week) |
|
195 if day is None: |
|
196 day = 1 |
|
197 else: |
|
198 day = atoi(day) |
|
199 return Week(year,week,day) |
|
200 |
|
201 def ParseWeekTime(isostring,parse_isoweektime=isoweektimeRE.match, |
|
202 |
|
203 strip=string.strip,atoi=string.atoi,atof=string.atof): |
|
204 |
|
205 """ParseWeekTime(isostring) |
|
206 |
|
207 Returns a DateTime instance reflecting the given ISO date. A |
|
208 time part is optional and must be delimited from the date by a |
|
209 space or 'T'. |
|
210 |
|
211 """ |
|
212 s = strip(isostring) |
|
213 date = parse_isoweektime(s) |
|
214 if not date: |
|
215 raise ValueError,'wrong format, use e.g. "1998-W01-1 12:00:30"' |
|
216 year,week,day,hour,minute,second,zone = date.groups() |
|
217 year = atoi(year) |
|
218 if week is None: |
|
219 week = 1 |
|
220 else: |
|
221 week = atoi(week) |
|
222 if day is None: |
|
223 day = 1 |
|
224 else: |
|
225 day = atoi(day) |
|
226 if hour is None: |
|
227 hour = 0 |
|
228 else: |
|
229 hour = atoi(hour) |
|
230 if minute is None: |
|
231 minute = 0 |
|
232 else: |
|
233 minute = atoi(minute) |
|
234 if second is None: |
|
235 second = 0.0 |
|
236 else: |
|
237 second = atof(second) |
|
238 return WeekTime(year,week,day,hour,minute,second) |
|
239 |
|
240 def ParseTime(isostring,parse_isotime=isotimeRE.match, |
|
241 |
|
242 strip=string.strip,atoi=string.atoi,atof=string.atof): |
|
243 |
|
244 """ParseTime(isostring) |
|
245 |
|
246 Returns a DateTimeDelta instance reflecting the given ISO time. |
|
247 Hours and minutes must be given, seconds are |
|
248 optional. Fractions of a second may also be used, |
|
249 e.g. 12:23:12.34. |
|
250 |
|
251 """ |
|
252 s = strip(isostring) |
|
253 time = parse_isotime(s) |
|
254 if not time: |
|
255 raise ValueError,'wrong format, use HH:MM:SS' |
|
256 hour,minute,second,zone = time.groups() |
|
257 hour = atoi(hour) |
|
258 minute = atoi(minute) |
|
259 if second is not None: |
|
260 second = atof(second) |
|
261 else: |
|
262 second = 0.0 |
|
263 return DateTime.TimeDelta(hour,minute,second) |
|
264 |
|
265 def ParseTimeDelta(isostring,parse_isodelta=isodeltaRE.match, |
|
266 |
|
267 strip=string.strip,atoi=string.atoi,atof=string.atof): |
|
268 |
|
269 """ParseTimeDelta(isostring) |
|
270 |
|
271 Returns a DateTimeDelta instance reflecting the given ISO time |
|
272 as delta. Hours and minutes must be given, seconds are |
|
273 optional. Fractions of a second may also be used, |
|
274 e.g. 12:23:12.34. In addition to the ISO standard a sign may be |
|
275 prepended to the time, e.g. -12:34. |
|
276 |
|
277 """ |
|
278 s = strip(isostring) |
|
279 time = parse_isodelta(s) |
|
280 if not time: |
|
281 raise ValueError,'wrong format, use [-]HH:MM:SS' |
|
282 sign,hour,minute,second,zone = time.groups() |
|
283 hour = atoi(hour) |
|
284 minute = atoi(minute) |
|
285 if second is not None: |
|
286 second = atof(second) |
|
287 else: |
|
288 second = 0.0 |
|
289 if sign and sign == '-': |
|
290 return -DateTime.TimeDelta(hour,minute,second) |
|
291 else: |
|
292 return DateTime.TimeDelta(hour,minute,second) |
|
293 |
|
294 def ParseAny(isostring): |
|
295 |
|
296 """ParseAny(isostring) |
|
297 |
|
298 Parses the given string and tries to convert it to a |
|
299 DateTime[Delta] instance. |
|
300 |
|
301 """ |
|
302 try: |
|
303 return ParseDateTime(isostring) |
|
304 except ValueError: |
|
305 pass |
|
306 try: |
|
307 return ParseWeekTime(isostring) |
|
308 except ValueError: |
|
309 pass |
|
310 try: |
|
311 return ParseTimeDelta(isostring) |
|
312 except ValueError: |
|
313 raise ValueError,'unsupported format: "%s"' % isostring |
|
314 |
|
315 def str(datetime,tz=None): |
|
316 |
|
317 """str(datetime,tz=DateTime.tz_offset(datetime)) |
|
318 |
|
319 Returns the datetime instance as ISO date string. tz can be |
|
320 given as DateTimeDelta instance providing the time zone |
|
321 difference from datetime's zone to UTC. It defaults to |
|
322 DateTime.tz_offset(datetime) which assumes local time. |
|
323 |
|
324 """ |
|
325 if tz is None: |
|
326 tz = datetime.gmtoffset() |
|
327 return '%04i-%02i-%02i %02i:%02i:%02i%+03i%02i' % ( |
|
328 datetime.year, datetime.month, datetime.day, |
|
329 datetime.hour, datetime.minute, datetime.second, |
|
330 tz.hour,tz.minute) |
|
331 |
|
332 def strGMT(datetime): |
|
333 |
|
334 """strGMT(datetime) |
|
335 |
|
336 Returns the datetime instance as ISO date string assuming it is |
|
337 given in GMT. |
|
338 |
|
339 """ |
|
340 return '%04i-%02i-%02i %02i:%02i:%02i+0000' % ( |
|
341 datetime.year, datetime.month, datetime.day, |
|
342 datetime.hour, datetime.minute, datetime.second) |
|
343 |
|
344 def strUTC(datetime): |
|
345 |
|
346 """strUTC(datetime) |
|
347 |
|
348 Returns the datetime instance as ISO date string assuming it is |
|
349 given in UTC. |
|
350 |
|
351 """ |
|
352 return '%04i-%02i-%02i %02i:%02i:%02i+0000' % ( |
|
353 datetime.year, datetime.month, datetime.day, |
|
354 datetime.hour, datetime.minute, datetime.second) |
|
355 |
|
356 # Testing |
|
357 if __name__ == '__main__': |
|
358 e = DateTime.Date(1900,1,1) |
|
359 for i in range(100000): |
|
360 d = e + i |
|
361 year,week,day = d.iso_week |
|
362 c = WeekTime(year,week,day) |
|
363 if d != c: |
|
364 print ' Check %s (given; %i) != %s (parsed)' % (d,d.day_of_week,c) |
|
365 elif i % 1000 == 0: |
|
366 print d,'ok' |