1 | """Utility functions and date/time routines. |
---|
2 | |
---|
3 | Copyright 2002-2006 John J Lee <jjl@pobox.com> |
---|
4 | |
---|
5 | This code is free software; you can redistribute it and/or modify it |
---|
6 | under the terms of the BSD or ZPL 2.1 licenses (see the file |
---|
7 | COPYING.txt included with the distribution). |
---|
8 | |
---|
9 | """ |
---|
10 | |
---|
11 | import re, string, time, warnings |
---|
12 | |
---|
13 | def deprecation(message): |
---|
14 | warnings.warn(message, DeprecationWarning, stacklevel=3) |
---|
15 | def hide_deprecations(): |
---|
16 | warnings.filterwarnings('ignore', category=DeprecationWarning) |
---|
17 | def reset_deprecations(): |
---|
18 | warnings.filterwarnings('default', category=DeprecationWarning) |
---|
19 | |
---|
20 | |
---|
21 | def isstringlike(x): |
---|
22 | try: x+"" |
---|
23 | except: return False |
---|
24 | else: return True |
---|
25 | |
---|
26 | ## def caller(): |
---|
27 | ## try: |
---|
28 | ## raise SyntaxError |
---|
29 | ## except: |
---|
30 | ## import sys |
---|
31 | ## return sys.exc_traceback.tb_frame.f_back.f_back.f_code.co_name |
---|
32 | |
---|
33 | |
---|
34 | from calendar import timegm |
---|
35 | |
---|
36 | # Date/time conversion routines for formats used by the HTTP protocol. |
---|
37 | |
---|
38 | EPOCH = 1970 |
---|
39 | def my_timegm(tt): |
---|
40 | year, month, mday, hour, min, sec = tt[:6] |
---|
41 | if ((year >= EPOCH) and (1 <= month <= 12) and (1 <= mday <= 31) and |
---|
42 | (0 <= hour <= 24) and (0 <= min <= 59) and (0 <= sec <= 61)): |
---|
43 | return timegm(tt) |
---|
44 | else: |
---|
45 | return None |
---|
46 | |
---|
47 | days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] |
---|
48 | months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", |
---|
49 | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
---|
50 | months_lower = [] |
---|
51 | for month in months: months_lower.append(month.lower()) |
---|
52 | |
---|
53 | |
---|
54 | def time2isoz(t=None): |
---|
55 | """Return a string representing time in seconds since epoch, t. |
---|
56 | |
---|
57 | If the function is called without an argument, it will use the current |
---|
58 | time. |
---|
59 | |
---|
60 | The format of the returned string is like "YYYY-MM-DD hh:mm:ssZ", |
---|
61 | representing Universal Time (UTC, aka GMT). An example of this format is: |
---|
62 | |
---|
63 | 1994-11-24 08:49:37Z |
---|
64 | |
---|
65 | """ |
---|
66 | if t is None: t = time.time() |
---|
67 | year, mon, mday, hour, min, sec = time.gmtime(t)[:6] |
---|
68 | return "%04d-%02d-%02d %02d:%02d:%02dZ" % ( |
---|
69 | year, mon, mday, hour, min, sec) |
---|
70 | |
---|
71 | def time2netscape(t=None): |
---|
72 | """Return a string representing time in seconds since epoch, t. |
---|
73 | |
---|
74 | If the function is called without an argument, it will use the current |
---|
75 | time. |
---|
76 | |
---|
77 | The format of the returned string is like this: |
---|
78 | |
---|
79 | Wed, DD-Mon-YYYY HH:MM:SS GMT |
---|
80 | |
---|
81 | """ |
---|
82 | if t is None: t = time.time() |
---|
83 | year, mon, mday, hour, min, sec, wday = time.gmtime(t)[:7] |
---|
84 | return "%s %02d-%s-%04d %02d:%02d:%02d GMT" % ( |
---|
85 | days[wday], mday, months[mon-1], year, hour, min, sec) |
---|
86 | |
---|
87 | |
---|
88 | UTC_ZONES = {"GMT": None, "UTC": None, "UT": None, "Z": None} |
---|
89 | |
---|
90 | timezone_re = re.compile(r"^([-+])?(\d\d?):?(\d\d)?$") |
---|
91 | def offset_from_tz_string(tz): |
---|
92 | offset = None |
---|
93 | if UTC_ZONES.has_key(tz): |
---|
94 | offset = 0 |
---|
95 | else: |
---|
96 | m = timezone_re.search(tz) |
---|
97 | if m: |
---|
98 | offset = 3600 * int(m.group(2)) |
---|
99 | if m.group(3): |
---|
100 | offset = offset + 60 * int(m.group(3)) |
---|
101 | if m.group(1) == '-': |
---|
102 | offset = -offset |
---|
103 | return offset |
---|
104 | |
---|
105 | def _str2time(day, mon, yr, hr, min, sec, tz): |
---|
106 | # translate month name to number |
---|
107 | # month numbers start with 1 (January) |
---|
108 | try: |
---|
109 | mon = months_lower.index(mon.lower())+1 |
---|
110 | except ValueError: |
---|
111 | # maybe it's already a number |
---|
112 | try: |
---|
113 | imon = int(mon) |
---|
114 | except ValueError: |
---|
115 | return None |
---|
116 | if 1 <= imon <= 12: |
---|
117 | mon = imon |
---|
118 | else: |
---|
119 | return None |
---|
120 | |
---|
121 | # make sure clock elements are defined |
---|
122 | if hr is None: hr = 0 |
---|
123 | if min is None: min = 0 |
---|
124 | if sec is None: sec = 0 |
---|
125 | |
---|
126 | yr = int(yr) |
---|
127 | day = int(day) |
---|
128 | hr = int(hr) |
---|
129 | min = int(min) |
---|
130 | sec = int(sec) |
---|
131 | |
---|
132 | if yr < 1000: |
---|
133 | # find "obvious" year |
---|
134 | cur_yr = time.localtime(time.time())[0] |
---|
135 | m = cur_yr % 100 |
---|
136 | tmp = yr |
---|
137 | yr = yr + cur_yr - m |
---|
138 | m = m - tmp |
---|
139 | if abs(m) > 50: |
---|
140 | if m > 0: yr = yr + 100 |
---|
141 | else: yr = yr - 100 |
---|
142 | |
---|
143 | # convert UTC time tuple to seconds since epoch (not timezone-adjusted) |
---|
144 | t = my_timegm((yr, mon, day, hr, min, sec, tz)) |
---|
145 | |
---|
146 | if t is not None: |
---|
147 | # adjust time using timezone string, to get absolute time since epoch |
---|
148 | if tz is None: |
---|
149 | tz = "UTC" |
---|
150 | tz = tz.upper() |
---|
151 | offset = offset_from_tz_string(tz) |
---|
152 | if offset is None: |
---|
153 | return None |
---|
154 | t = t - offset |
---|
155 | |
---|
156 | return t |
---|
157 | |
---|
158 | |
---|
159 | strict_re = re.compile(r"^[SMTWF][a-z][a-z], (\d\d) ([JFMASOND][a-z][a-z]) (\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$") |
---|
160 | wkday_re = re.compile( |
---|
161 | r"^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*", re.I) |
---|
162 | loose_http_re = re.compile( |
---|
163 | r"""^ |
---|
164 | (\d\d?) # day |
---|
165 | (?:\s+|[-\/]) |
---|
166 | (\w+) # month |
---|
167 | (?:\s+|[-\/]) |
---|
168 | (\d+) # year |
---|
169 | (?: |
---|
170 | (?:\s+|:) # separator before clock |
---|
171 | (\d\d?):(\d\d) # hour:min |
---|
172 | (?::(\d\d))? # optional seconds |
---|
173 | )? # optional clock |
---|
174 | \s* |
---|
175 | ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone |
---|
176 | \s* |
---|
177 | (?:\(\w+\))? # ASCII representation of timezone in parens. |
---|
178 | \s*$""", re.X) |
---|
179 | def http2time(text): |
---|
180 | """Returns time in seconds since epoch of time represented by a string. |
---|
181 | |
---|
182 | Return value is an integer. |
---|
183 | |
---|
184 | None is returned if the format of str is unrecognized, the time is outside |
---|
185 | the representable range, or the timezone string is not recognized. If the |
---|
186 | string contains no timezone, UTC is assumed. |
---|
187 | |
---|
188 | The timezone in the string may be numerical (like "-0800" or "+0100") or a |
---|
189 | string timezone (like "UTC", "GMT", "BST" or "EST"). Currently, only the |
---|
190 | timezone strings equivalent to UTC (zero offset) are known to the function. |
---|
191 | |
---|
192 | The function loosely parses the following formats: |
---|
193 | |
---|
194 | Wed, 09 Feb 1994 22:23:32 GMT -- HTTP format |
---|
195 | Tuesday, 08-Feb-94 14:15:29 GMT -- old rfc850 HTTP format |
---|
196 | Tuesday, 08-Feb-1994 14:15:29 GMT -- broken rfc850 HTTP format |
---|
197 | 09 Feb 1994 22:23:32 GMT -- HTTP format (no weekday) |
---|
198 | 08-Feb-94 14:15:29 GMT -- rfc850 format (no weekday) |
---|
199 | 08-Feb-1994 14:15:29 GMT -- broken rfc850 format (no weekday) |
---|
200 | |
---|
201 | The parser ignores leading and trailing whitespace. The time may be |
---|
202 | absent. |
---|
203 | |
---|
204 | If the year is given with only 2 digits, the function will select the |
---|
205 | century that makes the year closest to the current date. |
---|
206 | |
---|
207 | """ |
---|
208 | # fast exit for strictly conforming string |
---|
209 | m = strict_re.search(text) |
---|
210 | if m: |
---|
211 | g = m.groups() |
---|
212 | mon = months_lower.index(g[1].lower()) + 1 |
---|
213 | tt = (int(g[2]), mon, int(g[0]), |
---|
214 | int(g[3]), int(g[4]), float(g[5])) |
---|
215 | return my_timegm(tt) |
---|
216 | |
---|
217 | # No, we need some messy parsing... |
---|
218 | |
---|
219 | # clean up |
---|
220 | text = text.lstrip() |
---|
221 | text = wkday_re.sub("", text, 1) # Useless weekday |
---|
222 | |
---|
223 | # tz is time zone specifier string |
---|
224 | day, mon, yr, hr, min, sec, tz = [None]*7 |
---|
225 | |
---|
226 | # loose regexp parse |
---|
227 | m = loose_http_re.search(text) |
---|
228 | if m is not None: |
---|
229 | day, mon, yr, hr, min, sec, tz = m.groups() |
---|
230 | else: |
---|
231 | return None # bad format |
---|
232 | |
---|
233 | return _str2time(day, mon, yr, hr, min, sec, tz) |
---|
234 | |
---|
235 | |
---|
236 | iso_re = re.compile( |
---|
237 | """^ |
---|
238 | (\d{4}) # year |
---|
239 | [-\/]? |
---|
240 | (\d\d?) # numerical month |
---|
241 | [-\/]? |
---|
242 | (\d\d?) # day |
---|
243 | (?: |
---|
244 | (?:\s+|[-:Tt]) # separator before clock |
---|
245 | (\d\d?):?(\d\d) # hour:min |
---|
246 | (?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional) |
---|
247 | )? # optional clock |
---|
248 | \s* |
---|
249 | ([-+]?\d\d?:?(:?\d\d)? |
---|
250 | |Z|z)? # timezone (Z is "zero meridian", i.e. GMT) |
---|
251 | \s*$""", re.X) |
---|
252 | def iso2time(text): |
---|
253 | """ |
---|
254 | As for http2time, but parses the ISO 8601 formats: |
---|
255 | |
---|
256 | 1994-02-03 14:15:29 -0100 -- ISO 8601 format |
---|
257 | 1994-02-03 14:15:29 -- zone is optional |
---|
258 | 1994-02-03 -- only date |
---|
259 | 1994-02-03T14:15:29 -- Use T as separator |
---|
260 | 19940203T141529Z -- ISO 8601 compact format |
---|
261 | 19940203 -- only date |
---|
262 | |
---|
263 | """ |
---|
264 | # clean up |
---|
265 | text = text.lstrip() |
---|
266 | |
---|
267 | # tz is time zone specifier string |
---|
268 | day, mon, yr, hr, min, sec, tz = [None]*7 |
---|
269 | |
---|
270 | # loose regexp parse |
---|
271 | m = iso_re.search(text) |
---|
272 | if m is not None: |
---|
273 | # XXX there's an extra bit of the timezone I'm ignoring here: is |
---|
274 | # this the right thing to do? |
---|
275 | yr, mon, day, hr, min, sec, tz, _ = m.groups() |
---|
276 | else: |
---|
277 | return None # bad format |
---|
278 | |
---|
279 | return _str2time(day, mon, yr, hr, min, sec, tz) |
---|