Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/boltons/timeutils.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:18:57 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:d30785e31577 |
|---|---|
| 1 # -*- coding: utf-8 -*- | |
| 2 """Python's :mod:`datetime` module provides some of the most complex | |
| 3 and powerful primitives in the Python standard library. Time is | |
| 4 nontrivial, but thankfully its support is first-class in | |
| 5 Python. ``dateutils`` provides some additional tools for working with | |
| 6 time. | |
| 7 | |
| 8 Additionally, timeutils provides a few basic utilities for working | |
| 9 with timezones in Python. The Python :mod:`datetime` module's | |
| 10 documentation describes how to create a | |
| 11 :class:`~datetime.datetime`-compatible :class:`~datetime.tzinfo` | |
| 12 subtype. It even provides a few examples. | |
| 13 | |
| 14 The following module defines usable forms of the timezones in those | |
| 15 docs, as well as a couple other useful ones, :data:`UTC` (aka GMT) and | |
| 16 :data:`LocalTZ` (representing the local timezone as configured in the | |
| 17 operating system). For timezones beyond these, as well as a higher | |
| 18 degree of accuracy in corner cases, check out `pytz`_ and `dateutil`_. | |
| 19 | |
| 20 .. _pytz: https://pypi.python.org/pypi/pytz | |
| 21 .. _dateutil: https://dateutil.readthedocs.io/en/stable/index.html | |
| 22 """ | |
| 23 | |
| 24 import re | |
| 25 import time | |
| 26 import bisect | |
| 27 import operator | |
| 28 from datetime import tzinfo, timedelta, date, datetime | |
| 29 | |
| 30 | |
| 31 def total_seconds(td): | |
| 32 """For those with older versions of Python, a pure-Python | |
| 33 implementation of Python 2.7's :meth:`~datetime.timedelta.total_seconds`. | |
| 34 | |
| 35 Args: | |
| 36 td (datetime.timedelta): The timedelta to convert to seconds. | |
| 37 Returns: | |
| 38 float: total number of seconds | |
| 39 | |
| 40 >>> td = timedelta(days=4, seconds=33) | |
| 41 >>> total_seconds(td) | |
| 42 345633.0 | |
| 43 """ | |
| 44 a_milli = 1000000.0 | |
| 45 td_ds = td.seconds + (td.days * 86400) # 24 * 60 * 60 | |
| 46 td_micro = td.microseconds + (td_ds * a_milli) | |
| 47 return td_micro / a_milli | |
| 48 | |
| 49 | |
| 50 def dt_to_timestamp(dt): | |
| 51 """Converts from a :class:`~datetime.datetime` object to an integer | |
| 52 timestamp, suitable interoperation with :func:`time.time` and | |
| 53 other `Epoch-based timestamps`. | |
| 54 | |
| 55 .. _Epoch-based timestamps: https://en.wikipedia.org/wiki/Unix_time | |
| 56 | |
| 57 >>> abs(round(time.time() - dt_to_timestamp(datetime.utcnow()), 2)) | |
| 58 0.0 | |
| 59 | |
| 60 ``dt_to_timestamp`` supports both timezone-aware and naïve | |
| 61 :class:`~datetime.datetime` objects. Note that it assumes naïve | |
| 62 datetime objects are implied UTC, such as those generated with | |
| 63 :meth:`datetime.datetime.utcnow`. If your datetime objects are | |
| 64 local time, such as those generated with | |
| 65 :meth:`datetime.datetime.now`, first convert it using the | |
| 66 :meth:`datetime.datetime.replace` method with ``tzinfo=`` | |
| 67 :class:`LocalTZ` object in this module, then pass the result of | |
| 68 that to ``dt_to_timestamp``. | |
| 69 """ | |
| 70 if dt.tzinfo: | |
| 71 td = dt - EPOCH_AWARE | |
| 72 else: | |
| 73 td = dt - EPOCH_NAIVE | |
| 74 return total_seconds(td) | |
| 75 | |
| 76 | |
| 77 _NONDIGIT_RE = re.compile(r'\D') | |
| 78 | |
| 79 | |
| 80 def isoparse(iso_str): | |
| 81 """Parses the limited subset of `ISO8601-formatted time`_ strings as | |
| 82 returned by :meth:`datetime.datetime.isoformat`. | |
| 83 | |
| 84 >>> epoch_dt = datetime.utcfromtimestamp(0) | |
| 85 >>> iso_str = epoch_dt.isoformat() | |
| 86 >>> print(iso_str) | |
| 87 1970-01-01T00:00:00 | |
| 88 >>> isoparse(iso_str) | |
| 89 datetime.datetime(1970, 1, 1, 0, 0) | |
| 90 | |
| 91 >>> utcnow = datetime.utcnow() | |
| 92 >>> utcnow == isoparse(utcnow.isoformat()) | |
| 93 True | |
| 94 | |
| 95 For further datetime parsing, see the `iso8601`_ package for strict | |
| 96 ISO parsing and `dateutil`_ package for loose parsing and more. | |
| 97 | |
| 98 .. _ISO8601-formatted time: https://en.wikipedia.org/wiki/ISO_8601 | |
| 99 .. _iso8601: https://pypi.python.org/pypi/iso8601 | |
| 100 .. _dateutil: https://pypi.python.org/pypi/python-dateutil | |
| 101 | |
| 102 """ | |
| 103 dt_args = [int(p) for p in _NONDIGIT_RE.split(iso_str)] | |
| 104 return datetime(*dt_args) | |
| 105 | |
| 106 | |
| 107 _BOUNDS = [(0, timedelta(seconds=1), 'second'), | |
| 108 (1, timedelta(seconds=60), 'minute'), | |
| 109 (1, timedelta(seconds=3600), 'hour'), | |
| 110 (1, timedelta(days=1), 'day'), | |
| 111 (1, timedelta(days=7), 'week'), | |
| 112 (2, timedelta(days=30), 'month'), | |
| 113 (1, timedelta(days=365), 'year')] | |
| 114 _BOUNDS = [(b[0] * b[1], b[1], b[2]) for b in _BOUNDS] | |
| 115 _BOUND_DELTAS = [b[0] for b in _BOUNDS] | |
| 116 | |
| 117 _FLOAT_PATTERN = r'[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?' | |
| 118 _PARSE_TD_RE = re.compile(r"((?P<value>%s)\s*(?P<unit>\w)\w*)" % _FLOAT_PATTERN) | |
| 119 _PARSE_TD_KW_MAP = dict([(unit[0], unit + 's') | |
| 120 for _, _, unit in reversed(_BOUNDS[:-2])]) | |
| 121 | |
| 122 | |
| 123 def parse_timedelta(text): | |
| 124 """Robustly parses a short text description of a time period into a | |
| 125 :class:`datetime.timedelta`. Supports weeks, days, hours, minutes, | |
| 126 and seconds, with or without decimal points: | |
| 127 | |
| 128 Args: | |
| 129 text (str): Text to parse. | |
| 130 Returns: | |
| 131 datetime.timedelta | |
| 132 Raises: | |
| 133 ValueError: on parse failure. | |
| 134 | |
| 135 >>> parse_td('1d 2h 3.5m 0s') == timedelta(days=1, seconds=7410) | |
| 136 True | |
| 137 | |
| 138 Also supports full words and whitespace. | |
| 139 | |
| 140 >>> parse_td('2 weeks 1 day') == timedelta(days=15) | |
| 141 True | |
| 142 | |
| 143 Negative times are supported, too: | |
| 144 | |
| 145 >>> parse_td('-1.5 weeks 3m 20s') == timedelta(days=-11, seconds=43400) | |
| 146 True | |
| 147 """ | |
| 148 td_kwargs = {} | |
| 149 for match in _PARSE_TD_RE.finditer(text): | |
| 150 value, unit = match.group('value'), match.group('unit') | |
| 151 try: | |
| 152 unit_key = _PARSE_TD_KW_MAP[unit] | |
| 153 except KeyError: | |
| 154 raise ValueError('invalid time unit %r, expected one of %r' | |
| 155 % (unit, _PARSE_TD_KW_MAP.keys())) | |
| 156 try: | |
| 157 value = float(value) | |
| 158 except ValueError: | |
| 159 raise ValueError('invalid time value for unit %r: %r' | |
| 160 % (unit, value)) | |
| 161 td_kwargs[unit_key] = value | |
| 162 return timedelta(**td_kwargs) | |
| 163 | |
| 164 | |
| 165 parse_td = parse_timedelta # legacy alias | |
| 166 | |
| 167 | |
| 168 def _cardinalize_time_unit(unit, value): | |
| 169 # removes dependency on strutils; nice and simple because | |
| 170 # all time units cardinalize normally | |
| 171 if value == 1: | |
| 172 return unit | |
| 173 return unit + 's' | |
| 174 | |
| 175 | |
| 176 def decimal_relative_time(d, other=None, ndigits=0, cardinalize=True): | |
| 177 """Get a tuple representing the relative time difference between two | |
| 178 :class:`~datetime.datetime` objects or one | |
| 179 :class:`~datetime.datetime` and now. | |
| 180 | |
| 181 Args: | |
| 182 d (datetime): The first datetime object. | |
| 183 other (datetime): An optional second datetime object. If | |
| 184 unset, defaults to the current time as determined | |
| 185 :meth:`datetime.utcnow`. | |
| 186 ndigits (int): The number of decimal digits to round to, | |
| 187 defaults to ``0``. | |
| 188 cardinalize (bool): Whether to pluralize the time unit if | |
| 189 appropriate, defaults to ``True``. | |
| 190 Returns: | |
| 191 (float, str): A tuple of the :class:`float` difference and | |
| 192 respective unit of time, pluralized if appropriate and | |
| 193 *cardinalize* is set to ``True``. | |
| 194 | |
| 195 Unlike :func:`relative_time`, this method's return is amenable to | |
| 196 localization into other languages and custom phrasing and | |
| 197 formatting. | |
| 198 | |
| 199 >>> now = datetime.utcnow() | |
| 200 >>> decimal_relative_time(now - timedelta(days=1, seconds=3600), now) | |
| 201 (1.0, 'day') | |
| 202 >>> decimal_relative_time(now - timedelta(seconds=0.002), now, ndigits=5) | |
| 203 (0.002, 'seconds') | |
| 204 >>> decimal_relative_time(now, now - timedelta(days=900), ndigits=1) | |
| 205 (-2.5, 'years') | |
| 206 | |
| 207 """ | |
| 208 if other is None: | |
| 209 other = datetime.utcnow() | |
| 210 diff = other - d | |
| 211 diff_seconds = total_seconds(diff) | |
| 212 abs_diff = abs(diff) | |
| 213 b_idx = bisect.bisect(_BOUND_DELTAS, abs_diff) - 1 | |
| 214 bbound, bunit, bname = _BOUNDS[b_idx] | |
| 215 f_diff = diff_seconds / total_seconds(bunit) | |
| 216 rounded_diff = round(f_diff, ndigits) | |
| 217 if cardinalize: | |
| 218 return rounded_diff, _cardinalize_time_unit(bname, abs(rounded_diff)) | |
| 219 return rounded_diff, bname | |
| 220 | |
| 221 | |
| 222 def relative_time(d, other=None, ndigits=0): | |
| 223 """Get a string representation of the difference between two | |
| 224 :class:`~datetime.datetime` objects or one | |
| 225 :class:`~datetime.datetime` and the current time. Handles past and | |
| 226 future times. | |
| 227 | |
| 228 Args: | |
| 229 d (datetime): The first datetime object. | |
| 230 other (datetime): An optional second datetime object. If | |
| 231 unset, defaults to the current time as determined | |
| 232 :meth:`datetime.utcnow`. | |
| 233 ndigits (int): The number of decimal digits to round to, | |
| 234 defaults to ``0``. | |
| 235 Returns: | |
| 236 A short English-language string. | |
| 237 | |
| 238 >>> now = datetime.utcnow() | |
| 239 >>> relative_time(now, ndigits=1) | |
| 240 '0 seconds ago' | |
| 241 >>> relative_time(now - timedelta(days=1, seconds=36000), ndigits=1) | |
| 242 '1.4 days ago' | |
| 243 >>> relative_time(now + timedelta(days=7), now, ndigits=1) | |
| 244 '1 week from now' | |
| 245 | |
| 246 """ | |
| 247 drt, unit = decimal_relative_time(d, other, ndigits, cardinalize=True) | |
| 248 phrase = 'ago' | |
| 249 if drt < 0: | |
| 250 phrase = 'from now' | |
| 251 return '%g %s %s' % (abs(drt), unit, phrase) | |
| 252 | |
| 253 | |
| 254 def strpdate(string, format): | |
| 255 """Parse the date string according to the format in `format`. Returns a | |
| 256 :class:`date` object. Internally, :meth:`datetime.strptime` is used to | |
| 257 parse the string and thus conversion specifiers for time fields (e.g. `%H`) | |
| 258 may be provided; these will be parsed but ignored. | |
| 259 | |
| 260 Args: | |
| 261 string (str): The date string to be parsed. | |
| 262 format (str): The `strptime`_-style date format string. | |
| 263 Returns: | |
| 264 datetime.date | |
| 265 | |
| 266 .. _`strptime`: https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior | |
| 267 | |
| 268 >>> strpdate('2016-02-14', '%Y-%m-%d') | |
| 269 datetime.date(2016, 2, 14) | |
| 270 >>> strpdate('26/12 (2015)', '%d/%m (%Y)') | |
| 271 datetime.date(2015, 12, 26) | |
| 272 >>> strpdate('20151231 23:59:59', '%Y%m%d %H:%M:%S') | |
| 273 datetime.date(2015, 12, 31) | |
| 274 >>> strpdate('20160101 00:00:00.001', '%Y%m%d %H:%M:%S.%f') | |
| 275 datetime.date(2016, 1, 1) | |
| 276 """ | |
| 277 whence = datetime.strptime(string, format) | |
| 278 return whence.date() | |
| 279 | |
| 280 | |
| 281 def daterange(start, stop, step=1, inclusive=False): | |
| 282 """In the spirit of :func:`range` and :func:`xrange`, the `daterange` | |
| 283 generator that yields a sequence of :class:`~datetime.date` | |
| 284 objects, starting at *start*, incrementing by *step*, until *stop* | |
| 285 is reached. | |
| 286 | |
| 287 When *inclusive* is True, the final date may be *stop*, **if** | |
| 288 *step* falls evenly on it. By default, *step* is one day. See | |
| 289 details below for many more details. | |
| 290 | |
| 291 Args: | |
| 292 start (datetime.date): The starting date The first value in | |
| 293 the sequence. | |
| 294 stop (datetime.date): The stopping date. By default not | |
| 295 included in return. Can be `None` to yield an infinite | |
| 296 sequence. | |
| 297 step (int): The value to increment *start* by to reach | |
| 298 *stop*. Can be an :class:`int` number of days, a | |
| 299 :class:`datetime.timedelta`, or a :class:`tuple` of integers, | |
| 300 `(year, month, day)`. Positive and negative *step* values | |
| 301 are supported. | |
| 302 inclusive (bool): Whether or not the *stop* date can be | |
| 303 returned. *stop* is only returned when a *step* falls evenly | |
| 304 on it. | |
| 305 | |
| 306 >>> christmas = date(year=2015, month=12, day=25) | |
| 307 >>> boxing_day = date(year=2015, month=12, day=26) | |
| 308 >>> new_year = date(year=2016, month=1, day=1) | |
| 309 >>> for day in daterange(christmas, new_year): | |
| 310 ... print(repr(day)) | |
| 311 datetime.date(2015, 12, 25) | |
| 312 datetime.date(2015, 12, 26) | |
| 313 datetime.date(2015, 12, 27) | |
| 314 datetime.date(2015, 12, 28) | |
| 315 datetime.date(2015, 12, 29) | |
| 316 datetime.date(2015, 12, 30) | |
| 317 datetime.date(2015, 12, 31) | |
| 318 >>> for day in daterange(christmas, boxing_day): | |
| 319 ... print(repr(day)) | |
| 320 datetime.date(2015, 12, 25) | |
| 321 >>> for day in daterange(date(2017, 5, 1), date(2017, 8, 1), | |
| 322 ... step=(0, 1, 0), inclusive=True): | |
| 323 ... print(repr(day)) | |
| 324 datetime.date(2017, 5, 1) | |
| 325 datetime.date(2017, 6, 1) | |
| 326 datetime.date(2017, 7, 1) | |
| 327 datetime.date(2017, 8, 1) | |
| 328 | |
| 329 *Be careful when using stop=None, as this will yield an infinite | |
| 330 sequence of dates.* | |
| 331 """ | |
| 332 if not isinstance(start, date): | |
| 333 raise TypeError("start expected datetime.date instance") | |
| 334 if stop and not isinstance(stop, date): | |
| 335 raise TypeError("stop expected datetime.date instance or None") | |
| 336 try: | |
| 337 y_step, m_step, d_step = step | |
| 338 except TypeError: | |
| 339 y_step, m_step, d_step = 0, 0, step | |
| 340 else: | |
| 341 y_step, m_step = int(y_step), int(m_step) | |
| 342 if isinstance(d_step, int): | |
| 343 d_step = timedelta(days=int(d_step)) | |
| 344 elif isinstance(d_step, timedelta): | |
| 345 pass | |
| 346 else: | |
| 347 raise ValueError('step expected int, timedelta, or tuple' | |
| 348 ' (year, month, day), not: %r' % step) | |
| 349 | |
| 350 if stop is None: | |
| 351 finished = lambda now, stop: False | |
| 352 elif start < stop: | |
| 353 finished = operator.gt if inclusive else operator.ge | |
| 354 else: | |
| 355 finished = operator.lt if inclusive else operator.le | |
| 356 now = start | |
| 357 | |
| 358 while not finished(now, stop): | |
| 359 yield now | |
| 360 if y_step or m_step: | |
| 361 m_y_step, cur_month = divmod(now.month + m_step, 12) | |
| 362 now = now.replace(year=now.year + y_step + m_y_step, | |
| 363 month=cur_month or 12) | |
| 364 now = now + d_step | |
| 365 return | |
| 366 | |
| 367 | |
| 368 # Timezone support (brought in from tzutils) | |
| 369 | |
| 370 | |
| 371 ZERO = timedelta(0) | |
| 372 HOUR = timedelta(hours=1) | |
| 373 | |
| 374 | |
| 375 class ConstantTZInfo(tzinfo): | |
| 376 """ | |
| 377 A :class:`~datetime.tzinfo` subtype whose *offset* remains constant | |
| 378 (no daylight savings). | |
| 379 | |
| 380 Args: | |
| 381 name (str): Name of the timezone. | |
| 382 offset (datetime.timedelta): Offset of the timezone. | |
| 383 """ | |
| 384 def __init__(self, name="ConstantTZ", offset=ZERO): | |
| 385 self.name = name | |
| 386 self.offset = offset | |
| 387 | |
| 388 @property | |
| 389 def utcoffset_hours(self): | |
| 390 return total_seconds(self.offset) / (60 * 60) | |
| 391 | |
| 392 def utcoffset(self, dt): | |
| 393 return self.offset | |
| 394 | |
| 395 def tzname(self, dt): | |
| 396 return self.name | |
| 397 | |
| 398 def dst(self, dt): | |
| 399 return ZERO | |
| 400 | |
| 401 def __repr__(self): | |
| 402 cn = self.__class__.__name__ | |
| 403 return '%s(name=%r, offset=%r)' % (cn, self.name, self.offset) | |
| 404 | |
| 405 | |
| 406 UTC = ConstantTZInfo('UTC') | |
| 407 EPOCH_AWARE = datetime.fromtimestamp(0, UTC) | |
| 408 EPOCH_NAIVE = datetime.utcfromtimestamp(0) | |
| 409 | |
| 410 | |
| 411 class LocalTZInfo(tzinfo): | |
| 412 """The ``LocalTZInfo`` type takes data available in the time module | |
| 413 about the local timezone and makes a practical | |
| 414 :class:`datetime.tzinfo` to represent the timezone settings of the | |
| 415 operating system. | |
| 416 | |
| 417 For a more in-depth integration with the operating system, check | |
| 418 out `tzlocal`_. It builds on `pytz`_ and implements heuristics for | |
| 419 many versions of major operating systems to provide the official | |
| 420 ``pytz`` tzinfo, instead of the LocalTZ generalization. | |
| 421 | |
| 422 .. _tzlocal: https://pypi.python.org/pypi/tzlocal | |
| 423 .. _pytz: https://pypi.python.org/pypi/pytz | |
| 424 | |
| 425 """ | |
| 426 _std_offset = timedelta(seconds=-time.timezone) | |
| 427 _dst_offset = _std_offset | |
| 428 if time.daylight: | |
| 429 _dst_offset = timedelta(seconds=-time.altzone) | |
| 430 | |
| 431 def is_dst(self, dt): | |
| 432 dt_t = (dt.year, dt.month, dt.day, dt.hour, dt.minute, | |
| 433 dt.second, dt.weekday(), 0, -1) | |
| 434 local_t = time.localtime(time.mktime(dt_t)) | |
| 435 return local_t.tm_isdst > 0 | |
| 436 | |
| 437 def utcoffset(self, dt): | |
| 438 if self.is_dst(dt): | |
| 439 return self._dst_offset | |
| 440 return self._std_offset | |
| 441 | |
| 442 def dst(self, dt): | |
| 443 if self.is_dst(dt): | |
| 444 return self._dst_offset - self._std_offset | |
| 445 return ZERO | |
| 446 | |
| 447 def tzname(self, dt): | |
| 448 return time.tzname[self.is_dst(dt)] | |
| 449 | |
| 450 def __repr__(self): | |
| 451 return '%s()' % self.__class__.__name__ | |
| 452 | |
| 453 | |
| 454 LocalTZ = LocalTZInfo() | |
| 455 | |
| 456 | |
| 457 def _first_sunday_on_or_after(dt): | |
| 458 days_to_go = 6 - dt.weekday() | |
| 459 if days_to_go: | |
| 460 dt += timedelta(days_to_go) | |
| 461 return dt | |
| 462 | |
| 463 | |
| 464 # US DST Rules | |
| 465 # | |
| 466 # This is a simplified (i.e., wrong for a few cases) set of rules for US | |
| 467 # DST start and end times. For a complete and up-to-date set of DST rules | |
| 468 # and timezone definitions, visit the Olson Database (or try pytz): | |
| 469 # http://www.twinsun.com/tz/tz-link.htm | |
| 470 # http://sourceforge.net/projects/pytz/ (might not be up-to-date) | |
| 471 # | |
| 472 # In the US, since 2007, DST starts at 2am (standard time) on the second | |
| 473 # Sunday in March, which is the first Sunday on or after Mar 8. | |
| 474 DSTSTART_2007 = datetime(1, 3, 8, 2) | |
| 475 # and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov. | |
| 476 DSTEND_2007 = datetime(1, 11, 1, 1) | |
| 477 # From 1987 to 2006, DST used to start at 2am (standard time) on the first | |
| 478 # Sunday in April and to end at 2am (DST time; 1am standard time) on the last | |
| 479 # Sunday of October, which is the first Sunday on or after Oct 25. | |
| 480 DSTSTART_1987_2006 = datetime(1, 4, 1, 2) | |
| 481 DSTEND_1987_2006 = datetime(1, 10, 25, 1) | |
| 482 # From 1967 to 1986, DST used to start at 2am (standard time) on the last | |
| 483 # Sunday in April (the one on or after April 24) and to end at 2am (DST time; | |
| 484 # 1am standard time) on the last Sunday of October, which is the first Sunday | |
| 485 # on or after Oct 25. | |
| 486 DSTSTART_1967_1986 = datetime(1, 4, 24, 2) | |
| 487 DSTEND_1967_1986 = DSTEND_1987_2006 | |
| 488 | |
| 489 | |
| 490 class USTimeZone(tzinfo): | |
| 491 """Copied directly from the Python docs, the ``USTimeZone`` is a | |
| 492 :class:`datetime.tzinfo` subtype used to create the | |
| 493 :data:`Eastern`, :data:`Central`, :data:`Mountain`, and | |
| 494 :data:`Pacific` tzinfo types. | |
| 495 """ | |
| 496 def __init__(self, hours, reprname, stdname, dstname): | |
| 497 self.stdoffset = timedelta(hours=hours) | |
| 498 self.reprname = reprname | |
| 499 self.stdname = stdname | |
| 500 self.dstname = dstname | |
| 501 | |
| 502 def __repr__(self): | |
| 503 return self.reprname | |
| 504 | |
| 505 def tzname(self, dt): | |
| 506 if self.dst(dt): | |
| 507 return self.dstname | |
| 508 else: | |
| 509 return self.stdname | |
| 510 | |
| 511 def utcoffset(self, dt): | |
| 512 return self.stdoffset + self.dst(dt) | |
| 513 | |
| 514 def dst(self, dt): | |
| 515 if dt is None or dt.tzinfo is None: | |
| 516 # An exception may be sensible here, in one or both cases. | |
| 517 # It depends on how you want to treat them. The default | |
| 518 # fromutc() implementation (called by the default astimezone() | |
| 519 # implementation) passes a datetime with dt.tzinfo is self. | |
| 520 return ZERO | |
| 521 assert dt.tzinfo is self | |
| 522 | |
| 523 # Find start and end times for US DST. For years before 1967, return | |
| 524 # ZERO for no DST. | |
| 525 if 2006 < dt.year: | |
| 526 dststart, dstend = DSTSTART_2007, DSTEND_2007 | |
| 527 elif 1986 < dt.year < 2007: | |
| 528 dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006 | |
| 529 elif 1966 < dt.year < 1987: | |
| 530 dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986 | |
| 531 else: | |
| 532 return ZERO | |
| 533 | |
| 534 start = _first_sunday_on_or_after(dststart.replace(year=dt.year)) | |
| 535 end = _first_sunday_on_or_after(dstend.replace(year=dt.year)) | |
| 536 | |
| 537 # Can't compare naive to aware objects, so strip the timezone | |
| 538 # from dt first. | |
| 539 if start <= dt.replace(tzinfo=None) < end: | |
| 540 return HOUR | |
| 541 else: | |
| 542 return ZERO | |
| 543 | |
| 544 | |
| 545 Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") | |
| 546 Central = USTimeZone(-6, "Central", "CST", "CDT") | |
| 547 Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") | |
| 548 Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") |
