timeutils.py 20 KB

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