[3] | 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) |
---|