]> granicus.if.org Git - python/commitdiff
Issue #1667546: On platforms supporting tm_zone and tm_gmtoff fields
authorAlexander Belopolsky <alexander.belopolsky@gmail.com>
Thu, 14 Jun 2012 02:15:26 +0000 (22:15 -0400)
committerAlexander Belopolsky <alexander.belopolsky@gmail.com>
Thu, 14 Jun 2012 02:15:26 +0000 (22:15 -0400)
in struct tm, time.struct_time objects returned by time.gmtime(),
time.localtime() and time.strptime() functions now have tm_zone and
tm_gmtoff attributes.  Original patch by Paul Boddie.

Doc/library/time.rst
Lib/_strptime.py
Lib/test/test_structseq.py
Lib/test/test_time.py
Misc/NEWS
Modules/timemodule.c

index 2a765ac17c70cedb8bdca71d1355d5d53520588f..3faabf736e2a0f4d81c59095bb3365c9f12fdb85 100644 (file)
@@ -77,6 +77,12 @@ An explanation of some terminology and conventions is in order.
 
   See :class:`struct_time` for a description of these objects.
 
+  .. versionchanged:: 3.3
+
+  The :class:`struct_time` type was extended to provide the
+  :attr:`tm_gmtoff` and :attr:`tm_zone` attributes when platform
+  supports corresponding ``struct tm`` members.
+
 * Use the following functions to convert between time representations:
 
   +-------------------------+-------------------------+-------------------------+
@@ -336,7 +342,6 @@ The module defines the following functions and data items:
 
    .. versionadded:: 3.3
 
-
 .. function:: sleep(secs)
 
    Suspend execution for the given number of seconds.  The argument may be a
@@ -433,6 +438,12 @@ The module defines the following functions and data items:
    | ``%Y``    | Year with century as a decimal number.         |       |
    |           |                                                |       |
    +-----------+------------------------------------------------+-------+
+   | ``%z``    | Time zone offset indicating a positive or      |       |
+   |           | negative time difference from UTC/GMT of the   |       |
+   |           | form +HHMM or -HHMM, where H represents decimal|       |
+   |           | hour digits and M represents decimal minute    |       |
+   |           | digits [-23:59, +23:59].                       |       |
+   +-----------+------------------------------------------------+-------+
    | ``%Z``    | Time zone name (no characters if no time zone  |       |
    |           | exists).                                       |       |
    +-----------+------------------------------------------------+-------+
@@ -532,6 +543,10 @@ The module defines the following functions and data items:
    +-------+-------------------+---------------------------------+
    | 8     | :attr:`tm_isdst`  | 0, 1 or -1; see below           |
    +-------+-------------------+---------------------------------+
+   | N/A   | :attr:`tm_zone`   | abbreviation of timezone name   |
+   +-------+-------------------+---------------------------------+
+   | N/A   | :attr:`tm_gmtoff` | offset from UTC in seconds      |
+   +-------+-------------------+---------------------------------+
 
    Note that unlike the C structure, the month value is a range of [1, 12], not
    [0, 11].  A ``-1`` argument as the daylight
@@ -542,6 +557,11 @@ The module defines the following functions and data items:
    :class:`struct_time`, or having elements of the wrong type, a
    :exc:`TypeError` is raised.
 
+  .. versionchanged:: 3.3
+
+  :attr:`tm_gmtoff` and :attr:`tm_zone` attributes are avaliable on
+  platforms with C library supporting the corresponding fields in
+  ``struct tm``.
 
 .. function:: time()
 
@@ -552,7 +572,6 @@ The module defines the following functions and data items:
    lower value than a previous call if the system clock has been set back between
    the two calls.
 
-
 .. data:: timezone
 
    The offset of the local (non-DST) timezone, in seconds west of UTC (negative in
index fa06376992c396cb5a23f770f5da64d59d8bd219..b0cd3d619e10f92092edd7e06d1c0608dd419343 100644 (file)
@@ -486,19 +486,19 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
 
     return (year, month, day,
             hour, minute, second,
-            weekday, julian, tz, gmtoff, tzname), fraction
+            weekday, julian, tz, tzname, gmtoff), fraction
 
 def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
     """Return a time struct based on the input string and the
     format string."""
     tt = _strptime(data_string, format)[0]
-    return time.struct_time(tt[:9])
+    return time.struct_time(tt[:time._STRUCT_TM_ITEMS])
 
 def _strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
     """Return a class cls instance based on the input string and the
     format string."""
     tt, fraction = _strptime(data_string, format)
-    gmtoff, tzname = tt[-2:]
+    tzname, gmtoff = tt[-2:]
     args = tt[:6] + (fraction,)
     if gmtoff is not None:
         tzdelta = datetime_timedelta(seconds=gmtoff)
index d6c63b792fe0432eb9de54ba4bed0fd72763a7f7..a89e9556c14e930742c84327513fbcd320070376 100644 (file)
@@ -78,8 +78,9 @@ class StructSeqTest(unittest.TestCase):
 
     def test_fields(self):
         t = time.gmtime()
-        self.assertEqual(len(t), t.n_fields)
-        self.assertEqual(t.n_fields, t.n_sequence_fields+t.n_unnamed_fields)
+        self.assertEqual(len(t), t.n_sequence_fields)
+        self.assertEqual(t.n_unnamed_fields, 0)
+        self.assertEqual(t.n_fields, time._STRUCT_TM_ITEMS)
 
     def test_constructor(self):
         t = time.struct_time
index 9ea8f0cd2a2f0e7751894bf5843d66fdec054d3f..63e14530ebfcf3599a8a4b637280fc1316d09119 100644 (file)
@@ -620,7 +620,58 @@ class TestPytime(unittest.TestCase):
         for invalid in self.invalid_values:
             self.assertRaises(OverflowError, pytime_object_to_timespec, invalid)
 
-
+    @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
+    def test_localtime_timezone(self):
+
+        # Get the localtime and examine it for the offset and zone.
+        lt = time.localtime()
+        self.assertTrue(hasattr(lt, "tm_gmtoff"))
+        self.assertTrue(hasattr(lt, "tm_zone"))
+
+        # See if the offset and zone are similar to the module
+        # attributes.
+        if lt.tm_gmtoff is None:
+            self.assertTrue(not hasattr(time, "timezone"))
+        else:
+            self.assertEqual(lt.tm_gmtoff, -[time.timezone, time.altzone][lt.tm_isdst])
+        if lt.tm_zone is None:
+            self.assertTrue(not hasattr(time, "tzname"))
+        else:
+            self.assertEqual(lt.tm_zone, time.tzname[lt.tm_isdst])
+
+        # Try and make UNIX times from the localtime and a 9-tuple
+        # created from the localtime. Test to see that the times are
+        # the same.
+        t = time.mktime(lt); t9 = time.mktime(lt[:9])
+        self.assertEqual(t, t9)
+
+        # Make localtimes from the UNIX times and compare them to
+        # the original localtime, thus making a round trip.
+        new_lt = time.localtime(t); new_lt9 = time.localtime(t9)
+        self.assertEqual(new_lt, lt)
+        self.assertEqual(new_lt.tm_gmtoff, lt.tm_gmtoff)
+        self.assertEqual(new_lt.tm_zone, lt.tm_zone)
+        self.assertEqual(new_lt9, lt)
+        self.assertEqual(new_lt.tm_gmtoff, lt.tm_gmtoff)
+        self.assertEqual(new_lt9.tm_zone, lt.tm_zone)
+
+    @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
+    def test_strptime_timezone(self):
+        t = time.strptime("UTC", "%Z")
+        self.assertEqual(t.tm_zone, 'UTC')
+        t = time.strptime("+0500", "%z")
+        self.assertEqual(t.tm_gmtoff, 5 * 3600)
+
+    @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
+    def test_short_times(self):
+
+        import pickle
+
+        # Load a short time structure using pickle.
+        st = b"ctime\nstruct_time\np0\n((I2007\nI8\nI11\nI1\nI24\nI49\nI5\nI223\nI1\ntp1\n(dp2\ntp3\nRp4\n."
+        lt = pickle.loads(st)
+        self.assertIs(lt.tm_gmtoff, None)
+        self.assertIs(lt.tm_zone, None)
 
 def test_main():
     support.run_unittest(
index e47766a56b75e6eba4ef60d66c94d8881f838b83..f3b886cc90935551f49f706d232b315614a498a0 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -21,6 +21,11 @@ Core and Builtins
 Library
 -------
 
+- Issue #1667546: On platforms supporting tm_zone and tm_gmtoff fields
+  in struct tm, time.struct_time objects returned by time.gmtime(),
+  time.localtime() and time.strptime() functions now have tm_zone and
+  tm_gmtoff attributes.  Original patch by Paul Boddie.
+
 - Rename adjusted attribute to adjustable in time.get_clock_info() result.
 
 - Issue #3518: Remove references to non-existent BaseManager.from_address()
index 0a9c4319f6af07947b17c7a9c0596c414d053a6d..161407de45946974ee477747d4a4b260cd90f190 100644 (file)
@@ -275,6 +275,10 @@ static PyStructSequence_Field struct_time_type_fields[] = {
     {"tm_wday", "day of week, range [0, 6], Monday is 0"},
     {"tm_yday", "day of year, range [1, 366]"},
     {"tm_isdst", "1 if summer time is in effect, 0 if not, and -1 if unknown"},
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+    {"tm_zone", "abbreviation of timezone name"},
+    {"tm_gmtoff", "offset from UTC in seconds"},
+#endif /* HAVE_STRUCT_TM_TM_ZONE */
     {0}
 };
 
@@ -294,6 +298,7 @@ static PyStructSequence_Desc struct_time_type_desc = {
 static int initialized;
 static PyTypeObject StructTimeType;
 
+
 static PyObject *
 tmtotuple(struct tm *p)
 {
@@ -312,6 +317,11 @@ tmtotuple(struct tm *p)
     SET(6, (p->tm_wday + 6) % 7); /* Want Monday == 0 */
     SET(7, p->tm_yday + 1);        /* Want January, 1 == 1 */
     SET(8, p->tm_isdst);
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+    PyStructSequence_SET_ITEM(v, 9,
+        PyUnicode_DecodeLocale(p->tm_zone, "surrogateescape"));
+    SET(10, p->tm_gmtoff);
+#endif /* HAVE_STRUCT_TM_TM_ZONE */
 #undef SET
     if (PyErr_Occurred()) {
         Py_XDECREF(v);
@@ -371,7 +381,10 @@ PyDoc_STRVAR(gmtime_doc,
                        tm_sec, tm_wday, tm_yday, tm_isdst)\n\
 \n\
 Convert seconds since the Epoch to a time tuple expressing UTC (a.k.a.\n\
-GMT).  When 'seconds' is not passed in, convert the current time instead.");
+GMT).  When 'seconds' is not passed in, convert the current time instead.\n\
+\n\
+If the platform supports the tm_gmtoff and tm_zone, they are available as\n\
+attributes only.");
 
 static int
 pylocaltime(time_t *timep, struct tm *result)
@@ -438,6 +451,17 @@ gettmarg(PyObject *args, struct tm *p)
     p->tm_mon--;
     p->tm_wday = (p->tm_wday + 1) % 7;
     p->tm_yday--;
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+    if (Py_TYPE(args) == &StructTimeType) {
+        PyObject *item;
+        item = PyTuple_GET_ITEM(args, 9);
+        p->tm_zone = item == Py_None ? NULL : _PyUnicode_AsString(item);
+        item = PyTuple_GET_ITEM(args, 10);
+        p->tm_gmtoff = item == Py_None ? 0 : PyLong_AsLong(item);
+        if (PyErr_Occurred())
+            return 0;
+    }
+#endif /* HAVE_STRUCT_TM_TM_ZONE */
     return 1;
 }
 
@@ -778,7 +802,10 @@ time_mktime(PyObject *self, PyObject *tup)
 PyDoc_STRVAR(mktime_doc,
 "mktime(tuple) -> floating point number\n\
 \n\
-Convert a time tuple in local time to seconds since the Epoch.");
+Convert a time tuple in local time to seconds since the Epoch.\n\
+Note that mktime(gmtime(0)) will not generally return zero for most\n\
+time zones; instead the returned value will either be equal to that\n\
+of the timezone or altzone attributes on the time module.");
 #endif /* HAVE_MKTIME */
 
 #ifdef HAVE_WORKING_TZSET
@@ -1443,6 +1470,11 @@ PyInit_time(void)
 #endif
     }
     Py_INCREF(&StructTimeType);
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+    PyModule_AddIntConstant(m, "_STRUCT_TM_ITEMS", 11);
+#else
+    PyModule_AddIntConstant(m, "_STRUCT_TM_ITEMS", 9);
+#endif
     PyModule_AddObject(m, "struct_time", (PyObject*) &StructTimeType);
     initialized = 1;
     return m;