1 | # -*- coding: utf-8 -*-
|
---|
2 | #
|
---|
3 | # Copyright (C) 2003-2009 Edgewall Software
|
---|
4 | # Copyright (C) 2003-2006 Jonas Borgström <jonas@edgewall.com>
|
---|
5 | # Copyright (C) 2006 Matthew Good <trac@matt-good.net>
|
---|
6 | # Copyright (C) 2005-2006 Christian Boos <cboos@neuf.fr>
|
---|
7 | # All rights reserved.
|
---|
8 | #
|
---|
9 | # This software is licensed as described in the file COPYING, which
|
---|
10 | # you should have received as part of this distribution. The terms
|
---|
11 | # are also available at http://trac.edgewall.org/wiki/TracLicense.
|
---|
12 | #
|
---|
13 | # This software consists of voluntary contributions made by many
|
---|
14 | # individuals. For the exact contribution history, see the revision
|
---|
15 | # history and logs, available at http://trac.edgewall.org/log/.
|
---|
16 | #
|
---|
17 | # Author: Jonas Borgström <jonas@edgewall.com>
|
---|
18 | # Matthew Good <trac@matt-good.net>
|
---|
19 |
|
---|
20 | import locale
|
---|
21 | import math
|
---|
22 | import re
|
---|
23 | import sys
|
---|
24 | import time
|
---|
25 | from datetime import tzinfo, timedelta, datetime, date
|
---|
26 |
|
---|
27 | from trac.core import TracError
|
---|
28 | from trac.util.text import to_unicode
|
---|
29 | from trac.util.translation import _, ngettext
|
---|
30 |
|
---|
31 | # Date/time utilities
|
---|
32 |
|
---|
33 | # -- conversion
|
---|
34 |
|
---|
35 | def to_datetime(t, tzinfo=None):
|
---|
36 | """Convert `t` into a `datetime` object, using the following rules:
|
---|
37 |
|
---|
38 | - If `t` is already a `datetime` object, it is simply returned.
|
---|
39 | - If `t` is None, the current time will be used.
|
---|
40 | - If `t` is a number, it is interpreted as a timestamp.
|
---|
41 |
|
---|
42 | If no `tzinfo` is given, the local timezone will be used.
|
---|
43 |
|
---|
44 | Any other input will trigger a `TypeError`.
|
---|
45 | """
|
---|
46 | if t is None:
|
---|
47 | return datetime.now(tzinfo or localtz)
|
---|
48 | elif isinstance(t, datetime):
|
---|
49 | return t
|
---|
50 | elif isinstance(t, date):
|
---|
51 | return (tzinfo or localtz).localize(datetime(t.year, t.month, t.day))
|
---|
52 | elif isinstance(t, (int, long, float)):
|
---|
53 | if not (_min_ts <= t <= _max_ts):
|
---|
54 | # Handle microsecond timestamps for 0.11 compatibility
|
---|
55 | t *= 0.000001
|
---|
56 | if t < 0 and isinstance(t, float):
|
---|
57 | # Work around negative fractional times bug in Python 2.4
|
---|
58 | # http://bugs.python.org/issue1646728
|
---|
59 | frac, integer = math.modf(t)
|
---|
60 | return datetime.fromtimestamp(integer - 1, tzinfo or localtz) \
|
---|
61 | + timedelta(seconds=frac + 1)
|
---|
62 | return datetime.fromtimestamp(t, tzinfo or localtz)
|
---|
63 | raise TypeError('expecting datetime, int, long, float, or None; got %s' %
|
---|
64 | type(t))
|
---|
65 |
|
---|
66 | def to_timestamp(dt):
|
---|
67 | """Return the corresponding POSIX timestamp"""
|
---|
68 | if dt:
|
---|
69 | diff = dt - _epoc
|
---|
70 | return diff.days * 86400 + diff.seconds
|
---|
71 | else:
|
---|
72 | return 0
|
---|
73 |
|
---|
74 | def to_utimestamp(dt):
|
---|
75 | """Return a microsecond POSIX timestamp for the given `datetime`."""
|
---|
76 | if not dt:
|
---|
77 | return 0
|
---|
78 | diff = dt - _epoc
|
---|
79 | return (diff.days * 86400000000L + diff.seconds * 1000000
|
---|
80 | + diff.microseconds)
|
---|
81 |
|
---|
82 | def from_utimestamp(ts):
|
---|
83 | """Return the `datetime` for the given microsecond POSIX timestamp."""
|
---|
84 | return _epoc + timedelta(microseconds=ts or 0)
|
---|
85 |
|
---|
86 | # -- formatting
|
---|
87 |
|
---|
88 | _units = (
|
---|
89 | (3600*24*365, lambda r: ngettext('%(num)d year', '%(num)d years', r)),
|
---|
90 | (3600*24*30, lambda r: ngettext('%(num)d month', '%(num)d months', r)),
|
---|
91 | (3600*24*7, lambda r: ngettext('%(num)d week', '%(num)d weeks', r)),
|
---|
92 | (3600*24, lambda r: ngettext('%(num)d day', '%(num)d days', r)),
|
---|
93 | (3600, lambda r: ngettext('%(num)d hour', '%(num)d hours', r)),
|
---|
94 | (60, lambda r: ngettext('%(num)d minute', '%(num)d minutes', r)))
|
---|
95 |
|
---|
96 | def pretty_timedelta(time1, time2=None, resolution=None):
|
---|
97 | """Calculate time delta between two `datetime` objects.
|
---|
98 | (the result is somewhat imprecise, only use for prettyprinting).
|
---|
99 |
|
---|
100 | If either `time1` or `time2` is None, the current time will be used
|
---|
101 | instead.
|
---|
102 | """
|
---|
103 | time1 = to_datetime(time1)
|
---|
104 | time2 = to_datetime(time2)
|
---|
105 | if time1 > time2:
|
---|
106 | time2, time1 = time1, time2
|
---|
107 |
|
---|
108 | diff = time2 - time1
|
---|
109 | age_s = int(diff.days * 86400 + diff.seconds)
|
---|
110 | if resolution and age_s < resolution:
|
---|
111 | return ''
|
---|
112 | if age_s <= 60 * 1.9:
|
---|
113 | return ngettext('%(num)i second', '%(num)i seconds', age_s)
|
---|
114 | for u, format_units in _units:
|
---|
115 | r = float(age_s) / float(u)
|
---|
116 | if r >= 1.9:
|
---|
117 | r = int(round(r))
|
---|
118 | return format_units(r)
|
---|
119 | return ''
|
---|
120 |
|
---|
121 |
|
---|
122 | def format_datetime(t=None, format='%x %X', tzinfo=None):
|
---|
123 | """Format the `datetime` object `t` into an `unicode` string
|
---|
124 |
|
---|
125 | If `t` is None, the current time will be used.
|
---|
126 |
|
---|
127 | The formatting will be done using the given `format`, which consist
|
---|
128 | of conventional `strftime` keys. In addition the format can be 'iso8601'
|
---|
129 | to specify the international date format (compliant with RFC 3339).
|
---|
130 |
|
---|
131 | `tzinfo` will default to the local timezone if left to `None`.
|
---|
132 | """
|
---|
133 | tz = tzinfo or localtz
|
---|
134 | t = to_datetime(t, tzinfo).astimezone(tz)
|
---|
135 | normalize_Z = False
|
---|
136 | if format.lower().startswith('iso8601'):
|
---|
137 | if 'date' in format:
|
---|
138 | format = '%Y-%m-%d'
|
---|
139 | elif 'time' in format:
|
---|
140 | format = '%H:%M:%S%z'
|
---|
141 | normalize_Z = True
|
---|
142 | else:
|
---|
143 | format = '%Y-%m-%dT%H:%M:%S%z'
|
---|
144 | normalize_Z = True
|
---|
145 | text = t.strftime(format)
|
---|
146 | if normalize_Z:
|
---|
147 | text = text.replace('+0000', 'Z')
|
---|
148 | if not text.endswith('Z'):
|
---|
149 | text = text[:-2] + ":" + text[-2:]
|
---|
150 | encoding = locale.getlocale(locale.LC_TIME)[1] \
|
---|
151 | or locale.getpreferredencoding() or sys.getdefaultencoding()
|
---|
152 | return unicode(text, encoding, 'replace')
|
---|
153 |
|
---|
154 | def format_date(t=None, format='%x', tzinfo=None):
|
---|
155 | """Convenience method for formatting the date part of a `datetime` object.
|
---|
156 | See `format_datetime` for more details.
|
---|
157 | """
|
---|
158 | if format == 'iso8601':
|
---|
159 | format = 'iso8601date'
|
---|
160 | return format_datetime(t, format, tzinfo=tzinfo)
|
---|
161 |
|
---|
162 | def format_time(t=None, format='%X', tzinfo=None):
|
---|
163 | """Convenience method for formatting the time part of a `datetime` object.
|
---|
164 | See `format_datetime` for more details.
|
---|
165 | """
|
---|
166 | if format == 'iso8601':
|
---|
167 | format = 'iso8601time'
|
---|
168 | return format_datetime(t, format, tzinfo=tzinfo)
|
---|
169 |
|
---|
170 | def get_date_format_hint():
|
---|
171 | """Present the default format used by `format_date` in a human readable
|
---|
172 | form.
|
---|
173 | This is a format that will be recognized by `parse_date` when reading a
|
---|
174 | date.
|
---|
175 | """
|
---|
176 | t = datetime(1999, 10, 29, tzinfo=utc)
|
---|
177 | tmpl = format_date(t, tzinfo=utc)
|
---|
178 | return tmpl.replace('1999', 'YYYY', 1).replace('99', 'YY', 1) \
|
---|
179 | .replace('10', 'MM', 1).replace('29', 'DD', 1)
|
---|
180 |
|
---|
181 | def get_datetime_format_hint():
|
---|
182 | """Present the default format used by `format_datetime` in a human readable
|
---|
183 | form.
|
---|
184 | This is a format that will be recognized by `parse_date` when reading a
|
---|
185 | date.
|
---|
186 | """
|
---|
187 | t = datetime(1999, 10, 29, 23, 59, 58, tzinfo=utc)
|
---|
188 | tmpl = format_datetime(t, tzinfo=utc)
|
---|
189 | return tmpl.replace('1999', 'YYYY', 1).replace('99', 'YY', 1) \
|
---|
190 | .replace('10', 'MM', 1).replace('29', 'DD', 1) \
|
---|
191 | .replace('23', 'hh', 1).replace('11', 'hh', 1) \
|
---|
192 | .replace('59', 'mm', 1).replace('58', 'ss', 1)
|
---|
193 |
|
---|
194 | def http_date(t=None):
|
---|
195 | """Format `datetime` object `t` as a rfc822 timestamp"""
|
---|
196 | t = to_datetime(t).astimezone(utc)
|
---|
197 | weekdays = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
|
---|
198 | months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
|
---|
199 | 'Oct', 'Nov', 'Dec')
|
---|
200 | return '%s, %02d %s %04d %02d:%02d:%02d GMT' % (
|
---|
201 | weekdays[t.weekday()], t.day, months[t.month - 1], t.year,
|
---|
202 | t.hour, t.minute, t.second)
|
---|
203 |
|
---|
204 |
|
---|
205 | # -- parsing
|
---|
206 |
|
---|
207 | _ISO_8601_RE = re.compile(r'''
|
---|
208 | (\d\d\d\d)(?:-?(\d\d)(?:-?(\d\d))?)? # date
|
---|
209 | (?:T(\d\d)(?::?(\d\d)(?::?(\d\d))?)?)? # time
|
---|
210 | (Z?(?:([-+])?(\d\d):?(\d\d)?)?)?$ # timezone
|
---|
211 | ''', re.VERBOSE)
|
---|
212 |
|
---|
213 | def parse_date(text, tzinfo=None, hint='date'):
|
---|
214 | tzinfo = tzinfo or localtz
|
---|
215 | dt = None
|
---|
216 | text = text.strip()
|
---|
217 | # normalize ISO time
|
---|
218 | match = _ISO_8601_RE.match(text)
|
---|
219 | if match:
|
---|
220 | try:
|
---|
221 | g = match.groups()
|
---|
222 | years = g[0]
|
---|
223 | months = g[1] or '01'
|
---|
224 | days = g[2] or '01'
|
---|
225 | hours, minutes, seconds = [x or '00' for x in g[3:6]]
|
---|
226 | z, tzsign, tzhours, tzminutes = g[6:10]
|
---|
227 | if z:
|
---|
228 | tz = timedelta(hours=int(tzhours or '0'),
|
---|
229 | minutes=int(tzminutes or '0')).seconds / 60
|
---|
230 | if tz == 0:
|
---|
231 | tzinfo = utc
|
---|
232 | else:
|
---|
233 | tzinfo = FixedOffset(tzsign == '-' and -tz or tz,
|
---|
234 | '%s%s:%s' %
|
---|
235 | (tzsign, tzhours, tzminutes))
|
---|
236 | tm = time.strptime('%s ' * 6 % (years, months, days,
|
---|
237 | hours, minutes, seconds),
|
---|
238 | '%Y %m %d %H %M %S ')
|
---|
239 | dt = tzinfo.localize(datetime(*tm[0:6]))
|
---|
240 | except ValueError:
|
---|
241 | pass
|
---|
242 | if dt is None:
|
---|
243 | for format in ['%x %X', '%x, %X', '%X %x', '%X, %x', '%x', '%c',
|
---|
244 | '%b %d, %Y']:
|
---|
245 | try:
|
---|
246 | tm = time.strptime(text, format)
|
---|
247 | dt = tzinfo.localize(datetime(*tm[0:6]))
|
---|
248 | break
|
---|
249 | except ValueError:
|
---|
250 | continue
|
---|
251 | if dt is None:
|
---|
252 | dt = _parse_relative_time(text, tzinfo)
|
---|
253 | if dt is None:
|
---|
254 | hint = {'datetime': get_datetime_format_hint,
|
---|
255 | 'date': get_date_format_hint}.get(hint, lambda: hint)()
|
---|
256 | raise TracError(_('"%(date)s" is an invalid date, or the date format '
|
---|
257 | 'is not known. Try "%(hint)s" instead.',
|
---|
258 | date=text, hint=hint), _('Invalid Date'))
|
---|
259 | # Make sure we can convert it to a timestamp and back - fromtimestamp()
|
---|
260 | # may raise ValueError if larger than platform C localtime() or gmtime()
|
---|
261 | try:
|
---|
262 | to_datetime(to_timestamp(dt), tzinfo)
|
---|
263 | except ValueError:
|
---|
264 | raise TracError(_('The date "%(date)s" is outside valid range. '
|
---|
265 | 'Try a date closer to present time.', date=text),
|
---|
266 | _('Invalid Date'))
|
---|
267 | return dt
|
---|
268 |
|
---|
269 |
|
---|
270 | _REL_TIME_RE = re.compile(
|
---|
271 | r'(\d+\.?\d*)\s*'
|
---|
272 | r'(second|minute|hour|day|week|month|year|[hdwmy])s?\s*'
|
---|
273 | r'(?:ago)?$')
|
---|
274 | _time_intervals = dict(
|
---|
275 | second=lambda v: timedelta(seconds=v),
|
---|
276 | minute=lambda v: timedelta(minutes=v),
|
---|
277 | hour=lambda v: timedelta(hours=v),
|
---|
278 | day=lambda v: timedelta(days=v),
|
---|
279 | week=lambda v: timedelta(weeks=v),
|
---|
280 | month=lambda v: timedelta(days=30 * v),
|
---|
281 | year=lambda v: timedelta(days=365 * v),
|
---|
282 | h=lambda v: timedelta(hours=v),
|
---|
283 | d=lambda v: timedelta(days=v),
|
---|
284 | w=lambda v: timedelta(weeks=v),
|
---|
285 | m=lambda v: timedelta(days=30 * v),
|
---|
286 | y=lambda v: timedelta(days=365 * v),
|
---|
287 | )
|
---|
288 | _TIME_START_RE = re.compile(r'(this|last)\s*'
|
---|
289 | r'(second|minute|hour|day|week|month|year)$')
|
---|
290 | _time_starts = dict(
|
---|
291 | second=lambda now: now.replace(microsecond=0),
|
---|
292 | minute=lambda now: now.replace(microsecond=0, second=0),
|
---|
293 | hour=lambda now: now.replace(microsecond=0, second=0, minute=0),
|
---|
294 | day=lambda now: now.replace(microsecond=0, second=0, minute=0, hour=0),
|
---|
295 | week=lambda now: now.replace(microsecond=0, second=0, minute=0, hour=0) \
|
---|
296 | - timedelta(days=now.weekday()),
|
---|
297 | month=lambda now: now.replace(microsecond=0, second=0, minute=0, hour=0,
|
---|
298 | day=1),
|
---|
299 | year=lambda now: now.replace(microsecond=0, second=0, minute=0, hour=0,
|
---|
300 | day=1, month=1),
|
---|
301 | )
|
---|
302 |
|
---|
303 | def _parse_relative_time(text, tzinfo):
|
---|
304 | now = tzinfo.localize(datetime.now())
|
---|
305 | if text == 'now':
|
---|
306 | return now
|
---|
307 | if text == 'today':
|
---|
308 | return now.replace(microsecond=0, second=0, minute=0, hour=0)
|
---|
309 | if text == 'yesterday':
|
---|
310 | return now.replace(microsecond=0, second=0, minute=0, hour=0) \
|
---|
311 | - timedelta(days=1)
|
---|
312 | match = _REL_TIME_RE.match(text)
|
---|
313 | if match:
|
---|
314 | (value, interval) = match.groups()
|
---|
315 | return now - _time_intervals[interval](float(value))
|
---|
316 | match = _TIME_START_RE.match(text)
|
---|
317 | if match:
|
---|
318 | (which, start) = match.groups()
|
---|
319 | dt = _time_starts[start](now)
|
---|
320 | if which == 'last':
|
---|
321 | if start == 'month':
|
---|
322 | if dt.month > 1:
|
---|
323 | dt = dt.replace(month=dt.month - 1)
|
---|
324 | else:
|
---|
325 | dt = dt.replace(year=dt.year - 1, month=12)
|
---|
326 | else:
|
---|
327 | dt -= _time_intervals[start](1)
|
---|
328 | return dt
|
---|
329 | return None
|
---|
330 |
|
---|
331 |
|
---|
332 | # -- timezone utilities
|
---|
333 |
|
---|
334 | class FixedOffset(tzinfo):
|
---|
335 | """Fixed offset in minutes east from UTC."""
|
---|
336 |
|
---|
337 | def __init__(self, offset, name):
|
---|
338 | self._offset = timedelta(minutes=offset)
|
---|
339 | self.zone = name
|
---|
340 |
|
---|
341 | def __str__(self):
|
---|
342 | return self.zone
|
---|
343 |
|
---|
344 | def __repr__(self):
|
---|
345 | return '<FixedOffset "%s" %s>' % (self.zone, self._offset)
|
---|
346 |
|
---|
347 | def utcoffset(self, dt):
|
---|
348 | return self._offset
|
---|
349 |
|
---|
350 | def tzname(self, dt):
|
---|
351 | return self.zone
|
---|
352 |
|
---|
353 | def dst(self, dt):
|
---|
354 | return _zero
|
---|
355 |
|
---|
356 | def localize(self, dt, is_dst=False):
|
---|
357 | if dt.tzinfo is not None:
|
---|
358 | raise ValueError('Not naive datetime (tzinfo is already set)')
|
---|
359 | return dt.replace(tzinfo=self)
|
---|
360 |
|
---|
361 |
|
---|
362 | STDOFFSET = timedelta(seconds=-time.timezone)
|
---|
363 | if time.daylight:
|
---|
364 | DSTOFFSET = timedelta(seconds=-time.altzone)
|
---|
365 | else:
|
---|
366 | DSTOFFSET = STDOFFSET
|
---|
367 |
|
---|
368 | DSTDIFF = DSTOFFSET - STDOFFSET
|
---|
369 |
|
---|
370 |
|
---|
371 | class LocalTimezone(tzinfo):
|
---|
372 | """A 'local' time zone implementation"""
|
---|
373 |
|
---|
374 | def __str__(self):
|
---|
375 | return self.tzname(datetime.now())
|
---|
376 |
|
---|
377 | def __repr__(self):
|
---|
378 | return '<LocalTimezone "%s" %s "%s" %s>' % (
|
---|
379 | time.tzname[False], STDOFFSET,
|
---|
380 | time.tzname[True], DSTOFFSET)
|
---|
381 |
|
---|
382 | def utcoffset(self, dt):
|
---|
383 | if self._isdst(dt):
|
---|
384 | return DSTOFFSET
|
---|
385 | else:
|
---|
386 | return STDOFFSET
|
---|
387 |
|
---|
388 | def dst(self, dt):
|
---|
389 | if self._isdst(dt):
|
---|
390 | return DSTDIFF
|
---|
391 | else:
|
---|
392 | return _zero
|
---|
393 |
|
---|
394 | def tzname(self, dt):
|
---|
395 | return time.tzname[self._isdst(dt)]
|
---|
396 |
|
---|
397 | def _isdst(self, dt):
|
---|
398 | tt = (dt.year, dt.month, dt.day,
|
---|
399 | dt.hour, dt.minute, dt.second,
|
---|
400 | dt.weekday(), 0, -1)
|
---|
401 | try:
|
---|
402 | stamp = time.mktime(tt)
|
---|
403 | tt = time.localtime(stamp)
|
---|
404 | return tt.tm_isdst > 0
|
---|
405 | except OverflowError:
|
---|
406 | return False
|
---|
407 |
|
---|
408 | def localize(self, dt, is_dst=False):
|
---|
409 | if dt.tzinfo is not None:
|
---|
410 | raise ValueError('Not naive datetime (tzinfo is already set)')
|
---|
411 | return dt.replace(tzinfo=self)
|
---|
412 |
|
---|
413 |
|
---|
414 | utc = FixedOffset(0, 'UTC')
|
---|
415 | utcmin = datetime.min.replace(tzinfo=utc)
|
---|
416 | utcmax = datetime.max.replace(tzinfo=utc)
|
---|
417 | _epoc = datetime(1970, 1, 1, tzinfo=utc)
|
---|
418 | _zero = timedelta(0)
|
---|
419 | _min_ts = -(1 << 31)
|
---|
420 | _max_ts = (1 << 31) - 1
|
---|
421 |
|
---|
422 | localtz = LocalTimezone()
|
---|
423 |
|
---|
424 | # Use a makeshift timezone implementation if pytz is not available.
|
---|
425 | # This implementation only supports fixed offset time zones.
|
---|
426 | #
|
---|
427 | _timezones = [
|
---|
428 | FixedOffset(0, 'UTC'),
|
---|
429 | FixedOffset(-720, 'GMT -12:00'), FixedOffset(-660, 'GMT -11:00'),
|
---|
430 | FixedOffset(-600, 'GMT -10:00'), FixedOffset(-540, 'GMT -9:00'),
|
---|
431 | FixedOffset(-480, 'GMT -8:00'), FixedOffset(-420, 'GMT -7:00'),
|
---|
432 | FixedOffset(-360, 'GMT -6:00'), FixedOffset(-300, 'GMT -5:00'),
|
---|
433 | FixedOffset(-240, 'GMT -4:00'), FixedOffset(-180, 'GMT -3:00'),
|
---|
434 | FixedOffset(-120, 'GMT -2:00'), FixedOffset(-60, 'GMT -1:00'),
|
---|
435 | FixedOffset(0, 'GMT'), FixedOffset(60, 'GMT +1:00'),
|
---|
436 | FixedOffset(120, 'GMT +2:00'), FixedOffset(180, 'GMT +3:00'),
|
---|
437 | FixedOffset(240, 'GMT +4:00'), FixedOffset(300, 'GMT +5:00'),
|
---|
438 | FixedOffset(360, 'GMT +6:00'), FixedOffset(420, 'GMT +7:00'),
|
---|
439 | FixedOffset(480, 'GMT +8:00'), FixedOffset(540, 'GMT +9:00'),
|
---|
440 | FixedOffset(600, 'GMT +10:00'), FixedOffset(660, 'GMT +11:00'),
|
---|
441 | FixedOffset(720, 'GMT +12:00'), FixedOffset(780, 'GMT +13:00')]
|
---|
442 | _tzmap = dict([(z.zone, z) for z in _timezones])
|
---|
443 |
|
---|
444 | all_timezones = [z.zone for z in _timezones]
|
---|
445 |
|
---|
446 | try:
|
---|
447 | import pytz
|
---|
448 |
|
---|
449 | _tzoffsetmap = dict([(tz.utcoffset(None), tz) for tz in _timezones
|
---|
450 | if tz.zone != 'UTC'])
|
---|
451 |
|
---|
452 | def timezone(tzname):
|
---|
453 | """Fetch timezone instance by name or raise `KeyError`"""
|
---|
454 | tz = get_timezone(tzname)
|
---|
455 | if not tz:
|
---|
456 | raise KeyError(tzname)
|
---|
457 | return tz
|
---|
458 |
|
---|
459 | def get_timezone(tzname):
|
---|
460 | """Fetch timezone instance by name or return `None`"""
|
---|
461 | try:
|
---|
462 | # if given unicode parameter, pytz.timezone fails with:
|
---|
463 | # "type() argument 1 must be string, not unicode"
|
---|
464 | tz = pytz.timezone(to_unicode(tzname).encode('ascii', 'replace'))
|
---|
465 | except (KeyError, IOError):
|
---|
466 | tz = _tzmap.get(tzname)
|
---|
467 | if tz and tzname.startswith('Etc/'):
|
---|
468 | tz = _tzoffsetmap.get(tz.utcoffset(None))
|
---|
469 | return tz
|
---|
470 |
|
---|
471 | _pytz_zones = [tzname for tzname in pytz.common_timezones
|
---|
472 | if not tzname.startswith('Etc/') and
|
---|
473 | not tzname.startswith('GMT')]
|
---|
474 | # insert just the GMT timezones into the pytz zones at the right location
|
---|
475 | # the pytz zones already include UTC so skip it
|
---|
476 | from bisect import bisect
|
---|
477 | _gmt_index = bisect(_pytz_zones, 'GMT')
|
---|
478 | all_timezones = _pytz_zones[:_gmt_index] + all_timezones[1:] + \
|
---|
479 | _pytz_zones[_gmt_index:]
|
---|
480 |
|
---|
481 | except ImportError:
|
---|
482 | pytz = None
|
---|
483 |
|
---|
484 | def timezone(tzname):
|
---|
485 | """Fetch timezone instance by name or raise `KeyError`"""
|
---|
486 | return _tzmap[tzname]
|
---|
487 |
|
---|
488 | def get_timezone(tzname):
|
---|
489 | """Fetch timezone instance by name or return `None`"""
|
---|
490 | return _tzmap.get(tzname)
|
---|
491 |
|
---|