[Python-checkins] r81981 - in python/branches/py3k: Doc/library/datetime.rst Lib/test/test_datetime.py Misc/ACKS Misc/NEWS Modules/datetimemodule.c

alexander.belopolsky python-checkins at python.org
Mon Jun 14 16:15:50 CEST 2010


Author: alexander.belopolsky
Date: Mon Jun 14 16:15:50 2010
New Revision: 81981

Log:
Issue #5094: The ``datetime`` module now has a simple concrete class
implementing ``datetime.tzinfo`` interface.


Modified:
   python/branches/py3k/Doc/library/datetime.rst
   python/branches/py3k/Lib/test/test_datetime.py
   python/branches/py3k/Misc/ACKS
   python/branches/py3k/Misc/NEWS
   python/branches/py3k/Modules/datetimemodule.c

Modified: python/branches/py3k/Doc/library/datetime.rst
==============================================================================
--- python/branches/py3k/Doc/library/datetime.rst	(original)
+++ python/branches/py3k/Doc/library/datetime.rst	Mon Jun 14 16:15:50 2010
@@ -28,11 +28,14 @@
 have an optional time zone information member, :attr:`tzinfo`, that can contain
 an instance of a subclass of the abstract :class:`tzinfo` class.  These
 :class:`tzinfo` objects capture information about the offset from UTC time, the
-time zone name, and whether Daylight Saving Time is in effect.  Note that no
-concrete :class:`tzinfo` classes are supplied by the :mod:`datetime` module.
-Supporting timezones at whatever level of detail is required is up to the
-application.  The rules for time adjustment across the world are more political
-than rational, and there is no standard suitable for every application.
+time zone name, and whether Daylight Saving Time is in effect.  Note that only
+one concrete :class:`tzinfo` class, the :class:`timezone` class, is supplied by the
+:mod:`datetime` module.  The :class:`timezone` class can reprsent simple
+timezones with fixed offset from UTC such as UTC itself or North American EST and
+EDT timezones.  Supporting timezones at whatever level of detail is
+required is up to the application.  The rules for time adjustment across the
+world are more political than rational, change frequently, and there is no
+standard suitable for every application aside from UTC.
 
 The :mod:`datetime` module exports the following constants:
 
@@ -99,6 +102,14 @@
    time adjustment (for example, to account for time zone and/or daylight saving
    time).
 
+.. class:: timezone
+
+   A class that implements the :class:`tzinfo` abstract base class as a
+   fixed offset from the UTC.
+
+   .. versionadded:: 3.2
+
+
 Objects of these types are immutable.
 
 Objects of the :class:`date` type are always naive.
@@ -116,6 +127,7 @@
    object
        timedelta
        tzinfo
+           timezone
        time
        date
            datetime
@@ -660,8 +672,8 @@
 
    Return the current UTC date and time, with :attr:`tzinfo` ``None``. This is like
    :meth:`now`, but returns the current UTC date and time, as a naive
-   :class:`datetime` object. See also :meth:`now`.
-
+   :class:`datetime` object.  An aware current UTC datetime can be obtained by
+   calling ``datetime.now(timezone.utc)``.  See also :meth:`now`.
 
 .. classmethod:: datetime.fromtimestamp(timestamp, tz=None)
 
@@ -1318,8 +1330,10 @@
 :class:`tzinfo` is an abstract base class, meaning that this class should not be
 instantiated directly.  You need to derive a concrete subclass, and (at least)
 supply implementations of the standard :class:`tzinfo` methods needed by the
-:class:`datetime` methods you use.  The :mod:`datetime` module does not supply
-any concrete subclasses of :class:`tzinfo`.
+:class:`datetime` methods you use.  The :mod:`datetime` module supplies
+a simple concrete subclass of :class:`tzinfo` :class:`timezone` which can reprsent
+timezones with fixed offset from UTC such as UTC itself or North American EST and
+EDT.
 
 An instance of (a concrete subclass of) :class:`tzinfo` can be passed to the
 constructors for :class:`datetime` and :class:`time` objects. The latter objects
@@ -1520,9 +1534,65 @@
 standard local time.
 
 Applications that can't bear such ambiguities should avoid using hybrid
-:class:`tzinfo` subclasses; there are no ambiguities when using UTC, or any
-other fixed-offset :class:`tzinfo` subclass (such as a class representing only
-EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)).
+:class:`tzinfo` subclasses; there are no ambiguities when using :class:`timezone`,
+or any other fixed-offset :class:`tzinfo` subclass (such as a class representing
+only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)).
+
+
+.. _datetime-timezone:
+
+:class:`timezone` Objects
+--------------------------
+
+A :class:`timezone` object represents a timezone that is defined by a
+fixed offset from UTC.  Note that objects of this class cannot be used
+to represent timezone information in the locations where different
+offsets are used in different days of the year or where historical
+changes have been made to civil time.
+
+
+.. class:: timezone(offset[, name])
+
+  The ``offset`` argument must be specified as a :class:`timedelta`
+  object representing the difference between the local time and UTC.  It must
+  be within the range [``-timedelta(hours=23, minutes=59),
+  ``timedelta(hours=23, minutes=59)``] and represent whole number of minutes,
+  otherwise :exc:`ValueError` is raised.
+
+  The ``name`` argument is optional.  If specified it must be a string that
+  used as the value returned by the ``tzname(dt)`` method.  Otherwise,
+  ``tzname(dt)`` returns a string 'UTCsHH:MM', where s is the sign of
+  ``offset``, HH and MM are two digits of ``offset.hours`` and
+  ``offset.minutes`` respectively.
+
+.. method:: timezone.utcoffset(self, dt)
+
+  Returns the fixed value specified when the :class:`timezone` instance is
+  constructed.  The ``dt`` argument is ignored.  The return value is a
+  :class:`timedelta` instance equal to the difference between the
+  local time and UTC.
+
+.. method:: timezone.tzname(self, dt)
+
+  Returns the fixed value specified when the :class:`timezone` instance is
+  constructed or a string 'UTCsHH:MM', where s is the sign of
+  ``offset``, HH and MM are two digits of ``offset.hours`` and
+  ``offset.minutes`` respectively.  The ``dt`` argument is ignored.
+
+.. method:: timezone.dst(self, dt)
+
+  Always returns ``None``.
+
+.. method:: timezone.fromutc(self, dt)
+
+  Returns ``dt + offset``.  The ``dt`` argument must be aware with ``tzinfo``
+  set to ``self``.
+
+Class attributes:
+
+.. attribute:: timezone.utc
+
+   The UTC timezone, ``timezone(0, 'UTC')``.
 
 
 .. _strftime-strptime-behavior:

Modified: python/branches/py3k/Lib/test/test_datetime.py
==============================================================================
--- python/branches/py3k/Lib/test/test_datetime.py	(original)
+++ python/branches/py3k/Lib/test/test_datetime.py	Mon Jun 14 16:15:50 2010
@@ -15,6 +15,7 @@
 from datetime import timedelta
 from datetime import tzinfo
 from datetime import time
+from datetime import timezone
 from datetime import date, datetime
 
 pickle_choices = [(pickle, pickle, proto) for proto in range(3)]
@@ -49,6 +50,7 @@
 # tzinfo tests
 
 class FixedOffset(tzinfo):
+
     def __init__(self, offset, name, dstoffset=42):
         if isinstance(offset, int):
             offset = timedelta(minutes=offset)
@@ -67,6 +69,7 @@
         return self.__dstoffset
 
 class PicklableFixedOffset(FixedOffset):
+
     def __init__(self, offset=None, name=None, dstoffset=None):
         FixedOffset.__init__(self, offset, name, dstoffset)
 
@@ -131,6 +134,97 @@
             self.assertEqual(derived.utcoffset(None), offset)
             self.assertEqual(derived.tzname(None), 'cookie')
 
+class TestTimeZone(unittest.TestCase):
+
+    def setUp(self):
+        self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
+        self.EST = timezone(-timedelta(hours=5), 'EST')
+        self.DT = datetime(2010, 1, 1)
+
+    def test_str(self):
+        for tz in [self.ACDT, self.EST, timezone.utc,
+                   timezone.min, timezone.max]:
+            self.assertEqual(str(tz), tz.tzname(None))
+
+    def test_class_members(self):
+        limit = timedelta(hours=23, minutes=59)
+        self.assertEquals(timezone.utc.utcoffset(None), ZERO)
+        self.assertEquals(timezone.min.utcoffset(None), -limit)
+        self.assertEquals(timezone.max.utcoffset(None), limit)
+
+
+    def test_constructor(self):
+        self.assertEquals(timezone.utc, timezone(timedelta(0)))
+        # invalid offsets
+        for invalid in [timedelta(microseconds=1), timedelta(1, 1),
+                        timedelta(seconds=1), timedelta(1), -timedelta(1)]:
+            self.assertRaises(ValueError, timezone, invalid)
+            self.assertRaises(ValueError, timezone, -invalid)
+
+        with self.assertRaises(TypeError): timezone(None)
+        with self.assertRaises(TypeError): timezone(42)
+        with self.assertRaises(TypeError): timezone(ZERO, None)
+        with self.assertRaises(TypeError): timezone(ZERO, 42)
+
+    def test_inheritance(self):
+        self.assertTrue(isinstance(timezone.utc, tzinfo))
+        self.assertTrue(isinstance(self.EST, tzinfo))
+
+    def test_utcoffset(self):
+        dummy = self.DT
+        for h in [0, 1.5, 12]:
+            offset = h * HOUR
+            self.assertEquals(offset, timezone(offset).utcoffset(dummy))
+            self.assertEquals(-offset, timezone(-offset).utcoffset(dummy))
+
+        with self.assertRaises(TypeError): self.EST.utcoffset('')
+        with self.assertRaises(TypeError): self.EST.utcoffset(5)
+
+
+    def test_dst(self):
+        self.assertEquals(None, timezone.utc.dst(self.DT))
+
+        with self.assertRaises(TypeError): self.EST.dst('')
+        with self.assertRaises(TypeError): self.EST.dst(5)
+
+    def test_tzname(self):
+        self.assertEquals('UTC+00:00', timezone(ZERO).tzname(None))
+        self.assertEquals('UTC-05:00', timezone(-5 * HOUR).tzname(None))
+        self.assertEquals('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
+        self.assertEquals('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
+        self.assertEquals('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
+
+        with self.assertRaises(TypeError): self.EST.tzname('')
+        with self.assertRaises(TypeError): self.EST.tzname(5)
+
+    def test_fromutc(self):
+        with self.assertRaises(ValueError):
+            timezone.utc.fromutc(self.DT)
+        for tz in [self.EST, self.ACDT, Eastern]:
+            utctime = self.DT.replace(tzinfo=tz)
+            local = tz.fromutc(utctime)
+            self.assertEquals(local - utctime, tz.utcoffset(local))
+            self.assertEquals(local,
+                              self.DT.replace(tzinfo=timezone.utc))
+
+    def test_comparison(self):
+        self.assertNotEqual(timezone(ZERO), timezone(HOUR))
+        self.assertEqual(timezone(HOUR), timezone(HOUR))
+        self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
+        with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
+        self.assertIn(timezone(ZERO), {timezone(ZERO)})
+
+    def test_aware_datetime(self):
+        # test that timezone instances can be used by datetime
+        t = datetime(1, 1, 1)
+        for tz in [timezone.min, timezone.max, timezone.utc]:
+            self.assertEquals(tz.tzname(t),
+                              t.replace(tzinfo=tz).tzname())
+            self.assertEquals(tz.utcoffset(t),
+                              t.replace(tzinfo=tz).utcoffset())
+            self.assertEquals(tz.dst(t),
+                              t.replace(tzinfo=tz).dst())
+
 #############################################################################
 # Base clase for testing a particular aspect of timedelta, time, date and
 # datetime comparisons.
@@ -2729,20 +2823,21 @@
         # We don't know which time zone we're in, and don't have a tzinfo
         # class to represent it, so seeing whether a tz argument actually
         # does a conversion is tricky.
-        weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
         utc = FixedOffset(0, "utc", 0)
-        for dummy in range(3):
-            now = datetime.now(weirdtz)
-            self.assertTrue(now.tzinfo is weirdtz)
-            utcnow = datetime.utcnow().replace(tzinfo=utc)
-            now2 = utcnow.astimezone(weirdtz)
-            if abs(now - now2) < timedelta(seconds=30):
-                break
-            # Else the code is broken, or more than 30 seconds passed between
-            # calls; assuming the latter, just try again.
-        else:
-            # Three strikes and we're out.
-            self.fail("utcnow(), now(tz), or astimezone() may be broken")
+        for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
+                        timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
+            for dummy in range(3):
+                now = datetime.now(weirdtz)
+                self.assertTrue(now.tzinfo is weirdtz)
+                utcnow = datetime.utcnow().replace(tzinfo=utc)
+                now2 = utcnow.astimezone(weirdtz)
+                if abs(now - now2) < timedelta(seconds=30):
+                    break
+                # Else the code is broken, or more than 30 seconds passed between
+                # calls; assuming the latter, just try again.
+            else:
+                # Three strikes and we're out.
+                self.fail("utcnow(), now(tz), or astimezone() may be broken")
 
     def test_tzinfo_fromtimestamp(self):
         import time

Modified: python/branches/py3k/Misc/ACKS
==============================================================================
--- python/branches/py3k/Misc/ACKS	(original)
+++ python/branches/py3k/Misc/ACKS	Mon Jun 14 16:15:50 2010
@@ -406,6 +406,7 @@
 Kurt B. Kaiser
 Tamito Kajiyama
 Peter van Kampen
+Rafe Kaplan
 Jacob Kaplan-Moss
 Lou Kates
 Hiroaki Kawai

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Mon Jun 14 16:15:50 2010
@@ -1306,6 +1306,14 @@
 Extension Modules
 -----------------
 
+- Issue #5094: The ``datetime`` module now has a simple concrete class
+  implementing ``datetime.tzinfo`` interface.  Instances of the new
+  class, ``datetime.timezone``, return fixed name and UTC offset from
+  their ``tzname(dt)`` and ``utcoffset(dt)`` methods.  The ``dst(dt)``
+  method always returns ``None``.  A class attribute, ``utc`` contains
+  an instance representing the UTC timezone.  Original patch by Rafe
+  Kaplan.
+
 - Issue #8973: Add __all__ to struct module; this ensures that
   help(struct) includes documentation for the struct.Struct class.
 

Modified: python/branches/py3k/Modules/datetimemodule.c
==============================================================================
--- python/branches/py3k/Modules/datetimemodule.c	(original)
+++ python/branches/py3k/Modules/datetimemodule.c	Mon Jun 14 16:15:50 2010
@@ -102,6 +102,7 @@
 static PyTypeObject PyDateTime_DeltaType;
 static PyTypeObject PyDateTime_TimeType;
 static PyTypeObject PyDateTime_TZInfoType;
+static PyTypeObject PyDateTime_TimeZoneType;
 
 /* ---------------------------------------------------------------------------
  * Math utilities.
@@ -771,6 +772,52 @@
 #define new_delta(d, s, us, normalize)  \
     new_delta_ex(d, s, us, normalize, &PyDateTime_DeltaType)
 
+
+typedef struct
+{
+    PyObject_HEAD
+    PyObject *offset;
+    PyObject *name;
+} PyDateTime_TimeZone;
+
+/* Create new timezone instance checking offset range.  This
+   function does not check the name argument.  Caller must assure
+   that offset is a timedelta instance and name is either NULL
+   or a unicode object. */
+static PyObject *
+new_timezone(PyObject *offset, PyObject *name)
+{
+    PyDateTime_TimeZone *self;
+    PyTypeObject *type = &PyDateTime_TimeZoneType;
+
+    assert(offset != NULL);
+    assert(PyDelta_Check(offset));
+    assert(name == NULL || PyUnicode_Check(name));
+
+    if (GET_TD_MICROSECONDS(offset) != 0 || GET_TD_SECONDS(offset) % 60 != 0) {
+        PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
+                     " representing a whole number of minutes");
+        return NULL;
+    }
+    if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
+        GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
+        PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
+                     " strictly between -timedelta(hours=24) and"
+                     " timedelta(hours=24).");
+        return NULL;
+    }
+
+    self = (PyDateTime_TimeZone *)(type->tp_alloc(type, 0));
+    if (self == NULL) {
+        return NULL;
+    }
+    Py_INCREF(offset);
+    self->offset = offset;
+    Py_XINCREF(name);
+    self->name = name;
+    return (PyObject *)self;
+}
+
 /* ---------------------------------------------------------------------------
  * tzinfo helpers.
  */
@@ -3261,7 +3308,7 @@
     PyObject_GenericGetAttr,                    /* tp_getattro */
     0,                                          /* tp_setattro */
     0,                                          /* tp_as_buffer */
-    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */
     tzinfo_doc,                                 /* tp_doc */
     0,                                          /* tp_traverse */
     0,                                          /* tp_clear */
@@ -3283,6 +3330,206 @@
     0,                                          /* tp_free */
 };
 
+static char *timezone_kws[] = {"offset", "name", NULL};
+
+static PyObject *
+timezone_new(PyTypeObject *type, PyObject *args, PyObject *kw)
+{
+    PyObject *offset;
+    PyObject *name = NULL;
+    if (PyArg_ParseTupleAndKeywords(args, kw, "O!|O!:timezone", timezone_kws,
+                                    &PyDateTime_DeltaType, &offset,
+                                    &PyUnicode_Type, &name))
+        return new_timezone(offset, name);
+
+    return NULL;
+}
+
+static void
+timezone_dealloc(PyDateTime_TimeZone *self)
+{
+    Py_CLEAR(self->offset);
+    Py_CLEAR(self->name);
+    Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject *
+timezone_richcompare(PyDateTime_TimeZone *self,
+                     PyDateTime_TimeZone *other, int op)
+{
+    if (op != Py_EQ && op != Py_NE) {
+        Py_INCREF(Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+    return delta_richcompare(self->offset, other->offset, op);
+}
+
+static long
+timezone_hash(PyDateTime_TimeZone *self)
+{
+    return delta_hash((PyDateTime_Delta *)self->offset);
+}
+
+/* Check argument type passed to tzname, utcoffset, or dst methods.
+   Returns 0 for good argument.  Returns -1 and sets exception info
+   otherwise.
+ */
+static int
+_timezone_check_argument(PyObject *dt, const char *meth)
+{
+    if (dt == Py_None || PyDateTime_Check(dt))
+        return 0;
+    PyErr_Format(PyExc_TypeError, "%s(dt) argument must be a datetime instance"
+                 " or None, not %.200s", meth, Py_TYPE(dt)->tp_name);
+    return -1;
+}
+
+static PyObject *
+timezone_str(PyDateTime_TimeZone *self)
+{
+    char buf[10];
+    int hours, minutes, seconds;
+    PyObject *offset;
+    char sign;
+
+    if (self->name != NULL) {
+        Py_INCREF(self->name);
+        return self->name;
+    }
+    /* Offset is normalized, so it is negative if days < 0 */
+    if (GET_TD_DAYS(self->offset) < 0) {
+        sign = '-';
+        offset = delta_negative((PyDateTime_Delta *)self->offset);
+        if (offset == NULL)
+            return NULL;
+    }
+    else {
+        sign = '+';
+        offset = self->offset;
+        Py_INCREF(offset);
+    }
+    /* Offset is not negative here. */
+    seconds = GET_TD_SECONDS(offset);
+    Py_DECREF(offset);
+    minutes = divmod(seconds, 60, &seconds);
+    hours = divmod(minutes, 60, &minutes);
+    assert(seconds == 0);
+    /* XXX ignore sub-minute data, curently not allowed. */
+    PyOS_snprintf(buf, sizeof(buf), "UTC%c%02d:%02d", sign, hours, minutes);
+
+    return PyUnicode_FromString(buf);
+}
+
+static PyObject *
+timezone_tzname(PyDateTime_TimeZone *self, PyObject *dt)
+{
+    if (_timezone_check_argument(dt, "tzname") == -1)
+        return NULL;
+
+    return timezone_str(self);
+}
+
+static PyObject *
+timezone_utcoffset(PyDateTime_TimeZone *self, PyObject *dt)
+{
+    if (_timezone_check_argument(dt, "utcoffset") == -1)
+        return NULL;
+
+    Py_INCREF(self->offset);
+    return self->offset;
+}
+
+static PyObject *
+timezone_dst(PyObject *self, PyObject *dt)
+{
+    if (_timezone_check_argument(dt, "dst") == -1)
+        return NULL;
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta,
+                       int factor);
+
+static PyObject *
+timezone_fromutc(PyDateTime_TimeZone *self, PyDateTime_DateTime *dt)
+{
+    if (! PyDateTime_Check(dt)) {
+        PyErr_SetString(PyExc_TypeError,
+                        "fromutc: argument must be a datetime");
+        return NULL;
+    }
+    if (! HASTZINFO(dt) || dt->tzinfo != (PyObject *)self) {
+        PyErr_SetString(PyExc_ValueError, "fromutc: dt.tzinfo "
+                        "is not self");
+        return NULL;
+    }
+
+    return add_datetime_timedelta(dt, (PyDateTime_Delta *)self->offset, 1);
+}
+
+static PyMethodDef timezone_methods[] = {
+    {"tzname", (PyCFunction)timezone_tzname, METH_O,
+     PyDoc_STR("If name is specified when timezone is created, returns the name."
+               "  Otherwise returns offset as 'UTC(+|-)HHMM'.")},
+
+    {"utcoffset", (PyCFunction)timezone_utcoffset, METH_O,
+     PyDoc_STR("Returns fixed offset.  Ignores its argument.")},
+
+    {"dst", (PyCFunction)timezone_dst, METH_O,
+     PyDoc_STR("Returns None.  Ignores its argument.")},
+
+    {"fromutc", (PyCFunction)timezone_fromutc, METH_O,
+     PyDoc_STR("datetime in UTC -> datetime in local time.")},
+
+    {NULL, NULL}
+};
+
+static char timezone_doc[] =
+PyDoc_STR("Fixed offset from UTC implementation of tzinfo.");
+
+static PyTypeObject PyDateTime_TimeZoneType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "datetime.timezone",              /* tp_name */
+    sizeof(PyDateTime_TimeZone),      /* tp_basicsize */
+    0,                                /* tp_itemsize */
+    (destructor)timezone_dealloc,     /* tp_dealloc */
+    0,                                /* tp_print */
+    0,                                /* tp_getattr */
+    0,                                /* tp_setattr */
+    0,                                /* tp_reserved */
+    0,                                /* tp_repr */
+    0,                                /* tp_as_number */
+    0,                                /* tp_as_sequence */
+    0,                                /* tp_as_mapping */
+    (hashfunc)timezone_hash,          /* tp_hash */
+    0,                                /* tp_call */
+    (reprfunc)timezone_str,           /* tp_str */
+    0,                                /* tp_getattro */
+    0,                                /* tp_setattro */
+    0,                                /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT,               /* tp_flags */
+    timezone_doc,                     /* tp_doc */
+    0,                                /* tp_traverse */
+    0,                                /* tp_clear */
+    (richcmpfunc)timezone_richcompare,/* tp_richcompare */
+    0,                                /* tp_weaklistoffset */
+    0,                                /* tp_iter */
+    0,                                /* tp_iternext */
+    timezone_methods,                 /* tp_methods */
+    0,                                /* tp_members */
+    0,                                /* tp_getset */
+    &PyDateTime_TZInfoType,           /* tp_base */
+    0,                                /* tp_dict */
+    0,                                /* tp_descr_get */
+    0,                                /* tp_descr_set */
+    0,                                /* tp_dictoffset */
+    0,                                /* tp_init */
+    0,                                /* tp_alloc */
+    timezone_new,                     /* tp_new */
+};
+
 /*
  * PyDateTime_Time implementation.
  */
@@ -4971,6 +5218,7 @@
     PyObject *m;        /* a module object */
     PyObject *d;        /* its dict */
     PyObject *x;
+    PyObject *delta;
 
     m = PyModule_Create(&datetimemodule);
     if (m == NULL)
@@ -4986,6 +5234,8 @@
         return NULL;
     if (PyType_Ready(&PyDateTime_TZInfoType) < 0)
         return NULL;
+    if (PyType_Ready(&PyDateTime_TimeZoneType) < 0)
+        return NULL;
 
     /* timedelta values */
     d = PyDateTime_DeltaType.tp_dict;
@@ -5059,6 +5309,36 @@
         return NULL;
     Py_DECREF(x);
 
+    /* timezone values */
+    d = PyDateTime_TimeZoneType.tp_dict;
+
+    delta = new_delta(0, 0, 0, 0);
+    if (delta == NULL)
+        return NULL;
+    x = new_timezone(delta, NULL);
+    Py_DECREF(delta);
+    if (x == NULL || PyDict_SetItemString(d, "utc", x) < 0)
+        return NULL;
+    Py_DECREF(x);
+
+    delta = new_delta(-1, 60, 0, 1); /* -23:59 */
+    if (delta == NULL)
+        return NULL;
+    x = new_timezone(delta, NULL);
+    Py_DECREF(delta);
+    if (x == NULL || PyDict_SetItemString(d, "min", x) < 0)
+        return NULL;
+    Py_DECREF(x);
+
+    delta = new_delta(0, (23 * 60 + 59) * 60, 0, 0); /* +23:59 */
+    if (delta == NULL)
+        return NULL;
+    x = new_timezone(delta, NULL);
+    Py_DECREF(delta);
+    if (x == NULL || PyDict_SetItemString(d, "max", x) < 0)
+        return NULL;
+    Py_DECREF(x);
+
     /* module initialization */
     PyModule_AddIntConstant(m, "MINYEAR", MINYEAR);
     PyModule_AddIntConstant(m, "MAXYEAR", MAXYEAR);
@@ -5079,6 +5359,9 @@
     Py_INCREF(&PyDateTime_TZInfoType);
     PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType);
 
+    Py_INCREF(&PyDateTime_TimeZoneType);
+    PyModule_AddObject(m, "timezone", (PyObject *) &PyDateTime_TimeZoneType);
+
     x = PyCapsule_New(&CAPI, PyDateTime_CAPSULE_NAME, NULL);
     if (x == NULL)
         return NULL;


More information about the Python-checkins mailing list