Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4e749a1

Browse files
committed
Issue #5094: The datetime module now has a simple concrete class
implementing ``datetime.tzinfo`` interface.
1 parent 510b622 commit 4e749a1

5 files changed

Lines changed: 483 additions & 26 deletions

File tree

Doc/library/datetime.rst

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,14 @@ For applications requiring more, :class:`datetime` and :class:`time` objects
2828
have an optional time zone information member, :attr:`tzinfo`, that can contain
2929
an instance of a subclass of the abstract :class:`tzinfo` class. These
3030
:class:`tzinfo` objects capture information about the offset from UTC time, the
31-
time zone name, and whether Daylight Saving Time is in effect. Note that no
32-
concrete :class:`tzinfo` classes are supplied by the :mod:`datetime` module.
33-
Supporting timezones at whatever level of detail is required is up to the
34-
application. The rules for time adjustment across the world are more political
35-
than rational, and there is no standard suitable for every application.
31+
time zone name, and whether Daylight Saving Time is in effect. Note that only
32+
one concrete :class:`tzinfo` class, the :class:`timezone` class, is supplied by the
33+
:mod:`datetime` module. The :class:`timezone` class can reprsent simple
34+
timezones with fixed offset from UTC such as UTC itself or North American EST and
35+
EDT timezones. Supporting timezones at whatever level of detail is
36+
required is up to the application. The rules for time adjustment across the
37+
world are more political than rational, change frequently, and there is no
38+
standard suitable for every application aside from UTC.
3639

3740
The :mod:`datetime` module exports the following constants:
3841

@@ -99,6 +102,14 @@ Available Types
99102
time adjustment (for example, to account for time zone and/or daylight saving
100103
time).
101104

105+
.. class:: timezone
106+
107+
A class that implements the :class:`tzinfo` abstract base class as a
108+
fixed offset from the UTC.
109+
110+
.. versionadded:: 3.2
111+
112+
102113
Objects of these types are immutable.
103114

104115
Objects of the :class:`date` type are always naive.
@@ -116,6 +127,7 @@ Subclass relationships::
116127
object
117128
timedelta
118129
tzinfo
130+
timezone
119131
time
120132
date
121133
datetime
@@ -660,8 +672,8 @@ Other constructors, all class methods:
660672

661673
Return the current UTC date and time, with :attr:`tzinfo` ``None``. This is like
662674
:meth:`now`, but returns the current UTC date and time, as a naive
663-
:class:`datetime` object. See also :meth:`now`.
664-
675+
:class:`datetime` object. An aware current UTC datetime can be obtained by
676+
calling ``datetime.now(timezone.utc)``. See also :meth:`now`.
665677

666678
.. classmethod:: datetime.fromtimestamp(timestamp, tz=None)
667679

@@ -1318,8 +1330,10 @@ Example:
13181330
:class:`tzinfo` is an abstract base class, meaning that this class should not be
13191331
instantiated directly. You need to derive a concrete subclass, and (at least)
13201332
supply implementations of the standard :class:`tzinfo` methods needed by the
1321-
:class:`datetime` methods you use. The :mod:`datetime` module does not supply
1322-
any concrete subclasses of :class:`tzinfo`.
1333+
:class:`datetime` methods you use. The :mod:`datetime` module supplies
1334+
a simple concrete subclass of :class:`tzinfo` :class:`timezone` which can reprsent
1335+
timezones with fixed offset from UTC such as UTC itself or North American EST and
1336+
EDT.
13231337

13241338
An instance of (a concrete subclass of) :class:`tzinfo` can be passed to the
13251339
constructors for :class:`datetime` and :class:`time` objects. The latter objects
@@ -1520,9 +1534,65 @@ arranged, as in the example, by expressing DST switch times in the time zone's
15201534
standard local time.
15211535

15221536
Applications that can't bear such ambiguities should avoid using hybrid
1523-
:class:`tzinfo` subclasses; there are no ambiguities when using UTC, or any
1524-
other fixed-offset :class:`tzinfo` subclass (such as a class representing only
1525-
EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)).
1537+
:class:`tzinfo` subclasses; there are no ambiguities when using :class:`timezone`,
1538+
or any other fixed-offset :class:`tzinfo` subclass (such as a class representing
1539+
only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)).
1540+
1541+
1542+
.. _datetime-timezone:
1543+
1544+
:class:`timezone` Objects
1545+
--------------------------
1546+
1547+
A :class:`timezone` object represents a timezone that is defined by a
1548+
fixed offset from UTC. Note that objects of this class cannot be used
1549+
to represent timezone information in the locations where different
1550+
offsets are used in different days of the year or where historical
1551+
changes have been made to civil time.
1552+
1553+
1554+
.. class:: timezone(offset[, name])
1555+
1556+
The ``offset`` argument must be specified as a :class:`timedelta`
1557+
object representing the difference between the local time and UTC. It must
1558+
be within the range [``-timedelta(hours=23, minutes=59),
1559+
``timedelta(hours=23, minutes=59)``] and represent whole number of minutes,
1560+
otherwise :exc:`ValueError` is raised.
1561+
1562+
The ``name`` argument is optional. If specified it must be a string that
1563+
used as the value returned by the ``tzname(dt)`` method. Otherwise,
1564+
``tzname(dt)`` returns a string 'UTCsHH:MM', where s is the sign of
1565+
``offset``, HH and MM are two digits of ``offset.hours`` and
1566+
``offset.minutes`` respectively.
1567+
1568+
.. method:: timezone.utcoffset(self, dt)
1569+
1570+
Returns the fixed value specified when the :class:`timezone` instance is
1571+
constructed. The ``dt`` argument is ignored. The return value is a
1572+
:class:`timedelta` instance equal to the difference between the
1573+
local time and UTC.
1574+
1575+
.. method:: timezone.tzname(self, dt)
1576+
1577+
Returns the fixed value specified when the :class:`timezone` instance is
1578+
constructed or a string 'UTCsHH:MM', where s is the sign of
1579+
``offset``, HH and MM are two digits of ``offset.hours`` and
1580+
``offset.minutes`` respectively. The ``dt`` argument is ignored.
1581+
1582+
.. method:: timezone.dst(self, dt)
1583+
1584+
Always returns ``None``.
1585+
1586+
.. method:: timezone.fromutc(self, dt)
1587+
1588+
Returns ``dt + offset``. The ``dt`` argument must be aware with ``tzinfo``
1589+
set to ``self``.
1590+
1591+
Class attributes:
1592+
1593+
.. attribute:: timezone.utc
1594+
1595+
The UTC timezone, ``timezone(0, 'UTC')``.
15261596

15271597

15281598
.. _strftime-strptime-behavior:

Lib/test/test_datetime.py

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from datetime import timedelta
1616
from datetime import tzinfo
1717
from datetime import time
18+
from datetime import timezone
1819
from datetime import date, datetime
1920

2021
pickle_choices = [(pickle, pickle, proto) for proto in range(3)]
@@ -49,6 +50,7 @@ def test_constants(self):
4950
# tzinfo tests
5051

5152
class FixedOffset(tzinfo):
53+
5254
def __init__(self, offset, name, dstoffset=42):
5355
if isinstance(offset, int):
5456
offset = timedelta(minutes=offset)
@@ -67,6 +69,7 @@ def dst(self, dt):
6769
return self.__dstoffset
6870

6971
class PicklableFixedOffset(FixedOffset):
72+
7073
def __init__(self, offset=None, name=None, dstoffset=None):
7174
FixedOffset.__init__(self, offset, name, dstoffset)
7275

@@ -131,6 +134,97 @@ def test_pickling_subclass(self):
131134
self.assertEqual(derived.utcoffset(None), offset)
132135
self.assertEqual(derived.tzname(None), 'cookie')
133136

137+
class TestTimeZone(unittest.TestCase):
138+
139+
def setUp(self):
140+
self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
141+
self.EST = timezone(-timedelta(hours=5), 'EST')
142+
self.DT = datetime(2010, 1, 1)
143+
144+
def test_str(self):
145+
for tz in [self.ACDT, self.EST, timezone.utc,
146+
timezone.min, timezone.max]:
147+
self.assertEqual(str(tz), tz.tzname(None))
148+
149+
def test_class_members(self):
150+
limit = timedelta(hours=23, minutes=59)
151+
self.assertEquals(timezone.utc.utcoffset(None), ZERO)
152+
self.assertEquals(timezone.min.utcoffset(None), -limit)
153+
self.assertEquals(timezone.max.utcoffset(None), limit)
154+
155+
156+
def test_constructor(self):
157+
self.assertEquals(timezone.utc, timezone(timedelta(0)))
158+
# invalid offsets
159+
for invalid in [timedelta(microseconds=1), timedelta(1, 1),
160+
timedelta(seconds=1), timedelta(1), -timedelta(1)]:
161+
self.assertRaises(ValueError, timezone, invalid)
162+
self.assertRaises(ValueError, timezone, -invalid)
163+
164+
with self.assertRaises(TypeError): timezone(None)
165+
with self.assertRaises(TypeError): timezone(42)
166+
with self.assertRaises(TypeError): timezone(ZERO, None)
167+
with self.assertRaises(TypeError): timezone(ZERO, 42)
168+
169+
def test_inheritance(self):
170+
self.assertTrue(isinstance(timezone.utc, tzinfo))
171+
self.assertTrue(isinstance(self.EST, tzinfo))
172+
173+
def test_utcoffset(self):
174+
dummy = self.DT
175+
for h in [0, 1.5, 12]:
176+
offset = h * HOUR
177+
self.assertEquals(offset, timezone(offset).utcoffset(dummy))
178+
self.assertEquals(-offset, timezone(-offset).utcoffset(dummy))
179+
180+
with self.assertRaises(TypeError): self.EST.utcoffset('')
181+
with self.assertRaises(TypeError): self.EST.utcoffset(5)
182+
183+
184+
def test_dst(self):
185+
self.assertEquals(None, timezone.utc.dst(self.DT))
186+
187+
with self.assertRaises(TypeError): self.EST.dst('')
188+
with self.assertRaises(TypeError): self.EST.dst(5)
189+
190+
def test_tzname(self):
191+
self.assertEquals('UTC+00:00', timezone(ZERO).tzname(None))
192+
self.assertEquals('UTC-05:00', timezone(-5 * HOUR).tzname(None))
193+
self.assertEquals('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
194+
self.assertEquals('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
195+
self.assertEquals('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
196+
197+
with self.assertRaises(TypeError): self.EST.tzname('')
198+
with self.assertRaises(TypeError): self.EST.tzname(5)
199+
200+
def test_fromutc(self):
201+
with self.assertRaises(ValueError):
202+
timezone.utc.fromutc(self.DT)
203+
for tz in [self.EST, self.ACDT, Eastern]:
204+
utctime = self.DT.replace(tzinfo=tz)
205+
local = tz.fromutc(utctime)
206+
self.assertEquals(local - utctime, tz.utcoffset(local))
207+
self.assertEquals(local,
208+
self.DT.replace(tzinfo=timezone.utc))
209+
210+
def test_comparison(self):
211+
self.assertNotEqual(timezone(ZERO), timezone(HOUR))
212+
self.assertEqual(timezone(HOUR), timezone(HOUR))
213+
self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
214+
with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
215+
self.assertIn(timezone(ZERO), {timezone(ZERO)})
216+
217+
def test_aware_datetime(self):
218+
# test that timezone instances can be used by datetime
219+
t = datetime(1, 1, 1)
220+
for tz in [timezone.min, timezone.max, timezone.utc]:
221+
self.assertEquals(tz.tzname(t),
222+
t.replace(tzinfo=tz).tzname())
223+
self.assertEquals(tz.utcoffset(t),
224+
t.replace(tzinfo=tz).utcoffset())
225+
self.assertEquals(tz.dst(t),
226+
t.replace(tzinfo=tz).dst())
227+
134228
#############################################################################
135229
# Base clase for testing a particular aspect of timedelta, time, date and
136230
# datetime comparisons.
@@ -2729,20 +2823,21 @@ def test_tzinfo_now(self):
27292823
# We don't know which time zone we're in, and don't have a tzinfo
27302824
# class to represent it, so seeing whether a tz argument actually
27312825
# does a conversion is tricky.
2732-
weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
27332826
utc = FixedOffset(0, "utc", 0)
2734-
for dummy in range(3):
2735-
now = datetime.now(weirdtz)
2736-
self.assertTrue(now.tzinfo is weirdtz)
2737-
utcnow = datetime.utcnow().replace(tzinfo=utc)
2738-
now2 = utcnow.astimezone(weirdtz)
2739-
if abs(now - now2) < timedelta(seconds=30):
2740-
break
2741-
# Else the code is broken, or more than 30 seconds passed between
2742-
# calls; assuming the latter, just try again.
2743-
else:
2744-
# Three strikes and we're out.
2745-
self.fail("utcnow(), now(tz), or astimezone() may be broken")
2827+
for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
2828+
timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
2829+
for dummy in range(3):
2830+
now = datetime.now(weirdtz)
2831+
self.assertTrue(now.tzinfo is weirdtz)
2832+
utcnow = datetime.utcnow().replace(tzinfo=utc)
2833+
now2 = utcnow.astimezone(weirdtz)
2834+
if abs(now - now2) < timedelta(seconds=30):
2835+
break
2836+
# Else the code is broken, or more than 30 seconds passed between
2837+
# calls; assuming the latter, just try again.
2838+
else:
2839+
# Three strikes and we're out.
2840+
self.fail("utcnow(), now(tz), or astimezone() may be broken")
27462841

27472842
def test_tzinfo_fromtimestamp(self):
27482843
import time

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ Bob Kahn
406406
Kurt B. Kaiser
407407
Tamito Kajiyama
408408
Peter van Kampen
409+
Rafe Kaplan
409410
Jacob Kaplan-Moss
410411
Lou Kates
411412
Hiroaki Kawai

Misc/NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,14 @@ Library
13061306
Extension Modules
13071307
-----------------
13081308

1309+
- Issue #5094: The ``datetime`` module now has a simple concrete class
1310+
implementing ``datetime.tzinfo`` interface. Instances of the new
1311+
class, ``datetime.timezone``, return fixed name and UTC offset from
1312+
their ``tzname(dt)`` and ``utcoffset(dt)`` methods. The ``dst(dt)``
1313+
method always returns ``None``. A class attribute, ``utc`` contains
1314+
an instance representing the UTC timezone. Original patch by Rafe
1315+
Kaplan.
1316+
13091317
- Issue #8973: Add __all__ to struct module; this ensures that
13101318
help(struct) includes documentation for the struct.Struct class.
13111319

0 commit comments

Comments
 (0)