Skip to content

Commit c142bba

Browse files
committed
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.
1 parent f6a899f commit c142bba

File tree

6 files changed

+118
-10
lines changed

6 files changed

+118
-10
lines changed

Doc/library/time.rst

+21-2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ An explanation of some terminology and conventions is in order.
7777

7878
See :class:`struct_time` for a description of these objects.
7979

80+
.. versionchanged:: 3.3
81+
82+
The :class:`struct_time` type was extended to provide the
83+
:attr:`tm_gmtoff` and :attr:`tm_zone` attributes when platform
84+
supports corresponding ``struct tm`` members.
85+
8086
* Use the following functions to convert between time representations:
8187

8288
+-------------------------+-------------------------+-------------------------+
@@ -336,7 +342,6 @@ The module defines the following functions and data items:
336342

337343
.. versionadded:: 3.3
338344

339-
340345
.. function:: sleep(secs)
341346

342347
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:
433438
| ``%Y`` | Year with century as a decimal number. | |
434439
| | | |
435440
+-----------+------------------------------------------------+-------+
441+
| ``%z`` | Time zone offset indicating a positive or | |
442+
| | negative time difference from UTC/GMT of the | |
443+
| | form +HHMM or -HHMM, where H represents decimal| |
444+
| | hour digits and M represents decimal minute | |
445+
| | digits [-23:59, +23:59]. | |
446+
+-----------+------------------------------------------------+-------+
436447
| ``%Z`` | Time zone name (no characters if no time zone | |
437448
| | exists). | |
438449
+-----------+------------------------------------------------+-------+
@@ -532,6 +543,10 @@ The module defines the following functions and data items:
532543
+-------+-------------------+---------------------------------+
533544
| 8 | :attr:`tm_isdst` | 0, 1 or -1; see below |
534545
+-------+-------------------+---------------------------------+
546+
| N/A | :attr:`tm_zone` | abbreviation of timezone name |
547+
+-------+-------------------+---------------------------------+
548+
| N/A | :attr:`tm_gmtoff` | offset from UTC in seconds |
549+
+-------+-------------------+---------------------------------+
535550

536551
Note that unlike the C structure, the month value is a range of [1, 12], not
537552
[0, 11]. A ``-1`` argument as the daylight
@@ -542,6 +557,11 @@ The module defines the following functions and data items:
542557
:class:`struct_time`, or having elements of the wrong type, a
543558
:exc:`TypeError` is raised.
544559

560+
.. versionchanged:: 3.3
561+
562+
:attr:`tm_gmtoff` and :attr:`tm_zone` attributes are avaliable on
563+
platforms with C library supporting the corresponding fields in
564+
``struct tm``.
545565

546566
.. function:: time()
547567

@@ -552,7 +572,6 @@ The module defines the following functions and data items:
552572
lower value than a previous call if the system clock has been set back between
553573
the two calls.
554574

555-
556575
.. data:: timezone
557576

558577
The offset of the local (non-DST) timezone, in seconds west of UTC (negative in

Lib/_strptime.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -486,19 +486,19 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
486486

487487
return (year, month, day,
488488
hour, minute, second,
489-
weekday, julian, tz, gmtoff, tzname), fraction
489+
weekday, julian, tz, tzname, gmtoff), fraction
490490

491491
def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
492492
"""Return a time struct based on the input string and the
493493
format string."""
494494
tt = _strptime(data_string, format)[0]
495-
return time.struct_time(tt[:9])
495+
return time.struct_time(tt[:time._STRUCT_TM_ITEMS])
496496

497497
def _strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
498498
"""Return a class cls instance based on the input string and the
499499
format string."""
500500
tt, fraction = _strptime(data_string, format)
501-
gmtoff, tzname = tt[-2:]
501+
tzname, gmtoff = tt[-2:]
502502
args = tt[:6] + (fraction,)
503503
if gmtoff is not None:
504504
tzdelta = datetime_timedelta(seconds=gmtoff)

Lib/test/test_structseq.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ def test_cmp(self):
7878

7979
def test_fields(self):
8080
t = time.gmtime()
81-
self.assertEqual(len(t), t.n_fields)
82-
self.assertEqual(t.n_fields, t.n_sequence_fields+t.n_unnamed_fields)
81+
self.assertEqual(len(t), t.n_sequence_fields)
82+
self.assertEqual(t.n_unnamed_fields, 0)
83+
self.assertEqual(t.n_fields, time._STRUCT_TM_ITEMS)
8384

8485
def test_constructor(self):
8586
t = time.struct_time

Lib/test/test_time.py

+52-1
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,58 @@ def test_timespec(self):
620620
for invalid in self.invalid_values:
621621
self.assertRaises(OverflowError, pytime_object_to_timespec, invalid)
622622

623-
623+
@unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
624+
def test_localtime_timezone(self):
625+
626+
# Get the localtime and examine it for the offset and zone.
627+
lt = time.localtime()
628+
self.assertTrue(hasattr(lt, "tm_gmtoff"))
629+
self.assertTrue(hasattr(lt, "tm_zone"))
630+
631+
# See if the offset and zone are similar to the module
632+
# attributes.
633+
if lt.tm_gmtoff is None:
634+
self.assertTrue(not hasattr(time, "timezone"))
635+
else:
636+
self.assertEqual(lt.tm_gmtoff, -[time.timezone, time.altzone][lt.tm_isdst])
637+
if lt.tm_zone is None:
638+
self.assertTrue(not hasattr(time, "tzname"))
639+
else:
640+
self.assertEqual(lt.tm_zone, time.tzname[lt.tm_isdst])
641+
642+
# Try and make UNIX times from the localtime and a 9-tuple
643+
# created from the localtime. Test to see that the times are
644+
# the same.
645+
t = time.mktime(lt); t9 = time.mktime(lt[:9])
646+
self.assertEqual(t, t9)
647+
648+
# Make localtimes from the UNIX times and compare them to
649+
# the original localtime, thus making a round trip.
650+
new_lt = time.localtime(t); new_lt9 = time.localtime(t9)
651+
self.assertEqual(new_lt, lt)
652+
self.assertEqual(new_lt.tm_gmtoff, lt.tm_gmtoff)
653+
self.assertEqual(new_lt.tm_zone, lt.tm_zone)
654+
self.assertEqual(new_lt9, lt)
655+
self.assertEqual(new_lt.tm_gmtoff, lt.tm_gmtoff)
656+
self.assertEqual(new_lt9.tm_zone, lt.tm_zone)
657+
658+
@unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
659+
def test_strptime_timezone(self):
660+
t = time.strptime("UTC", "%Z")
661+
self.assertEqual(t.tm_zone, 'UTC')
662+
t = time.strptime("+0500", "%z")
663+
self.assertEqual(t.tm_gmtoff, 5 * 3600)
664+
665+
@unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
666+
def test_short_times(self):
667+
668+
import pickle
669+
670+
# Load a short time structure using pickle.
671+
st = b"ctime\nstruct_time\np0\n((I2007\nI8\nI11\nI1\nI24\nI49\nI5\nI223\nI1\ntp1\n(dp2\ntp3\nRp4\n."
672+
lt = pickle.loads(st)
673+
self.assertIs(lt.tm_gmtoff, None)
674+
self.assertIs(lt.tm_zone, None)
624675

625676
def test_main():
626677
support.run_unittest(

Misc/NEWS

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ Core and Builtins
2121
Library
2222
-------
2323

24+
- Issue #1667546: On platforms supporting tm_zone and tm_gmtoff fields
25+
in struct tm, time.struct_time objects returned by time.gmtime(),
26+
time.localtime() and time.strptime() functions now have tm_zone and
27+
tm_gmtoff attributes. Original patch by Paul Boddie.
28+
2429
- Rename adjusted attribute to adjustable in time.get_clock_info() result.
2530

2631
- Issue #3518: Remove references to non-existent BaseManager.from_address()

Modules/timemodule.c

+34-2
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@ static PyStructSequence_Field struct_time_type_fields[] = {
275275
{"tm_wday", "day of week, range [0, 6], Monday is 0"},
276276
{"tm_yday", "day of year, range [1, 366]"},
277277
{"tm_isdst", "1 if summer time is in effect, 0 if not, and -1 if unknown"},
278+
#ifdef HAVE_STRUCT_TM_TM_ZONE
279+
{"tm_zone", "abbreviation of timezone name"},
280+
{"tm_gmtoff", "offset from UTC in seconds"},
281+
#endif /* HAVE_STRUCT_TM_TM_ZONE */
278282
{0}
279283
};
280284

@@ -294,6 +298,7 @@ static PyStructSequence_Desc struct_time_type_desc = {
294298
static int initialized;
295299
static PyTypeObject StructTimeType;
296300

301+
297302
static PyObject *
298303
tmtotuple(struct tm *p)
299304
{
@@ -312,6 +317,11 @@ tmtotuple(struct tm *p)
312317
SET(6, (p->tm_wday + 6) % 7); /* Want Monday == 0 */
313318
SET(7, p->tm_yday + 1); /* Want January, 1 == 1 */
314319
SET(8, p->tm_isdst);
320+
#ifdef HAVE_STRUCT_TM_TM_ZONE
321+
PyStructSequence_SET_ITEM(v, 9,
322+
PyUnicode_DecodeLocale(p->tm_zone, "surrogateescape"));
323+
SET(10, p->tm_gmtoff);
324+
#endif /* HAVE_STRUCT_TM_TM_ZONE */
315325
#undef SET
316326
if (PyErr_Occurred()) {
317327
Py_XDECREF(v);
@@ -371,7 +381,10 @@ PyDoc_STRVAR(gmtime_doc,
371381
tm_sec, tm_wday, tm_yday, tm_isdst)\n\
372382
\n\
373383
Convert seconds since the Epoch to a time tuple expressing UTC (a.k.a.\n\
374-
GMT). When 'seconds' is not passed in, convert the current time instead.");
384+
GMT). When 'seconds' is not passed in, convert the current time instead.\n\
385+
\n\
386+
If the platform supports the tm_gmtoff and tm_zone, they are available as\n\
387+
attributes only.");
375388

376389
static int
377390
pylocaltime(time_t *timep, struct tm *result)
@@ -438,6 +451,17 @@ gettmarg(PyObject *args, struct tm *p)
438451
p->tm_mon--;
439452
p->tm_wday = (p->tm_wday + 1) % 7;
440453
p->tm_yday--;
454+
#ifdef HAVE_STRUCT_TM_TM_ZONE
455+
if (Py_TYPE(args) == &StructTimeType) {
456+
PyObject *item;
457+
item = PyTuple_GET_ITEM(args, 9);
458+
p->tm_zone = item == Py_None ? NULL : _PyUnicode_AsString(item);
459+
item = PyTuple_GET_ITEM(args, 10);
460+
p->tm_gmtoff = item == Py_None ? 0 : PyLong_AsLong(item);
461+
if (PyErr_Occurred())
462+
return 0;
463+
}
464+
#endif /* HAVE_STRUCT_TM_TM_ZONE */
441465
return 1;
442466
}
443467

@@ -778,7 +802,10 @@ time_mktime(PyObject *self, PyObject *tup)
778802
PyDoc_STRVAR(mktime_doc,
779803
"mktime(tuple) -> floating point number\n\
780804
\n\
781-
Convert a time tuple in local time to seconds since the Epoch.");
805+
Convert a time tuple in local time to seconds since the Epoch.\n\
806+
Note that mktime(gmtime(0)) will not generally return zero for most\n\
807+
time zones; instead the returned value will either be equal to that\n\
808+
of the timezone or altzone attributes on the time module.");
782809
#endif /* HAVE_MKTIME */
783810

784811
#ifdef HAVE_WORKING_TZSET
@@ -1443,6 +1470,11 @@ PyInit_time(void)
14431470
#endif
14441471
}
14451472
Py_INCREF(&StructTimeType);
1473+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1474+
PyModule_AddIntConstant(m, "_STRUCT_TM_ITEMS", 11);
1475+
#else
1476+
PyModule_AddIntConstant(m, "_STRUCT_TM_ITEMS", 9);
1477+
#endif
14461478
PyModule_AddObject(m, "struct_time", (PyObject*) &StructTimeType);
14471479
initialized = 1;
14481480
return m;

0 commit comments

Comments
 (0)