Index: Doc/library/time.rst =================================================================== --- Doc/library/time.rst (revision 81758) +++ Doc/library/time.rst (working copy) @@ -103,6 +103,12 @@ +-------+-------------------+---------------------------------+ | 8 | :attr:`tm_isdst` | 0, 1 or -1; see below | +-------+-------------------+---------------------------------+ + | - | :attr:`tm_gmtoff` | offset from UTC/GMT in seconds | + | | | east of the Prime Meridian; | + | | | see below | + +-------+-------------------+---------------------------------+ + | - | :attr:`tm_zone` | time zone name; see below | + +-------+-------------------+---------------------------------+ Note that unlike the C structure, the month value is a range of [1, 12], not [0, 11]. @@ -110,6 +116,19 @@ A ``-1`` argument as the daylight savings flag, passed to :func:`mktime` will usually result in the correct daylight savings state to be filled in. + To maintain backwards compatibility, the :attr:`tm_gmtoff` and + :attr:`tm_zone` attributes are not + included in the visual representation of each :class:`struct_time`, but + they can be inspected as named, read-only attributes. Since functions + such as :func:`mktime` accept either a 9-tuple or a + :class:`struct_time`, to manually construct a time with specific values + of these attributes, first construct an 11-tuple using the normal + 9-tuple extended to include values for :attr:`tm_gmtoff` and + :attr:`tm_zone` as the last two elements, then pass this tuple to the + :class:`struct_time` constructor. It is also possible to omit + :attr:`tm_zone` and to provide a 10-tuple to the :class:`struct_time` + constructor. + When a tuple with an incorrect length is passed to a function expecting a :class:`struct_time`, or having elements of the wrong type, a :exc:`TypeError` is raised. @@ -133,6 +152,12 @@ +-------------------------+-------------------------+-------------------------+ +.. versionchanged:: 3.2 + + The time value sequence was extended to provide the + :attr:`tm_gmtoff` and :attr:`tm_zone` attributes as + described above. + The module defines the following functions and data items: @@ -223,7 +248,27 @@ whether the invalid value is caught by Python or the underlying C libraries). The earliest date for which it can generate a time is platform-dependent. +.. function:: mktimetz(t) + This function interprets times in arbitrary time zones, producing a + value indicating the number of seconds since the epoch for each time. + It combines the ability of :func:`mktime` to convert local times + with the ability of :func:`timegm` to convert GMT/UTC times, but + is not constrained by the system's current time zone, instead + obtaining time zone information directly from its argument where + possible. Its argument is the :class:`struct_time` (which may provide + time zone information) or a 9-tuple (which does not, causing the + argument to be interpreted as a time in the GMT/UTC zone). It returns + a floating point number, for compatibility with :func:`time`. If + the input value cannot be represented as a valid time, either + :exc:`OverflowError` or :exc:`ValueError` will be raised + (which depends on whether the invalid value is caught by Python or the + underlying C libraries). The earliest date for which it can generate + a time is platform-dependent. + +.. versionadded:: 3.2 + + .. function:: sleep(secs) Suspend execution for the given number of seconds. The argument may be a @@ -320,6 +365,12 @@ | ``%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). | | +-----------+------------------------------------------------+-------+ @@ -404,7 +455,20 @@ lower value than a previous call if the system clock has been set back between the two calls. +.. function:: timegm(t) + This is the inverse function of :func:`gmtime`. Its argument + is a :class:`struct_time` or full 9-tuple which expresses the time in + GMT/UTC time, not local time as expected by + :func:`mktime`. It returns a floating point number, for + compatibility with :func:`time`. If the input value cannot be + represented as a valid time, either :exc:`OverflowError` or + :exc:`ValueError` will be raised (which depends on whether the + invalid value is caught by Python or the underlying C libraries). The + earliest date for which it can generate a time is platform-dependent. + +.. versionadded:: 3.2 + .. data:: timezone The offset of the local (non-DST) timezone, in seconds west of UTC (negative in @@ -512,7 +576,9 @@ Module :mod:`calendar` General calendar-related functions. :func:`timegm` is the inverse of - :func:`gmtime` from this module. + :func:`gmtime` from this module, offering an + alternative implementation of :func:`timegm` + from this module. .. rubric:: Footnotes Index: Lib/_strptime.py =================================================================== --- Lib/_strptime.py (revision 81758) +++ Lib/_strptime.py (working copy) @@ -204,6 +204,7 @@ #XXX: Does 'Y' need to worry about having less or more than # 4 digits? 'Y': r"(?P\d\d\d\d)", + 'z': r"(?P[+-]\d\d[0-5]\d)", 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'), 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'), 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'), @@ -337,6 +338,7 @@ month = day = 1 hour = minute = second = fraction = 0 tz = -1 + tzoffset = None # Default to -1 to signify that values not known; not critical to have, # though week_of_year = -1 @@ -417,6 +419,10 @@ else: # W starts week on Monday. week_of_year_start = 0 + elif group_key == 'z': + tzoffset = int(found_dict['z'][1:3]) * 60 + int(found_dict['z'][3:5]) + if found_dict['z'][0] == "-": + tzoffset = -tzoffset elif group_key == 'Z': # Since -1 is default value only need to worry about setting tz if # it can be something other than -1. Index: Lib/test/test_time.py =================================================================== --- Lib/test/test_time.py (revision 81758) +++ Lib/test/test_time.py (working copy) @@ -234,6 +234,275 @@ # This should not cause an exception time.strftime("%B", (2009,2,1,0,0,0,0,0,0)) + def test_localtime_timezone(self): + + # Get the localtime and examine it for the offset and zone. + lt = time.localtime() + self.assert_(hasattr(lt, "tm_gmtoff")) + self.assert_(hasattr(lt, "tm_zone")) + + # See if the offset and zone are similar to the module + # attributes. + if lt.tm_gmtoff is None: + self.assert_(not hasattr(time, "timezone")) + else: + self.assertEqual(lt.tm_gmtoff, -[time.timezone, time.altzone][lt.tm_isdst]) + if lt.tm_zone is None: + self.assert_(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.assert_(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.assert_(new_lt == lt) + self.assert_(new_lt.tm_gmtoff is None and not hasattr(time, "timezone") or new_lt.tm_gmtoff == lt.tm_gmtoff) + self.assert_(new_lt.tm_zone is None and not hasattr(time, "tzname") or new_lt.tm_zone == lt.tm_zone) + self.assert_(new_lt9 == lt) + self.assert_(new_lt9.tm_gmtoff is None and not hasattr(time, "timezone") or new_lt.tm_gmtoff == lt.tm_gmtoff) + self.assert_(new_lt9.tm_zone is None and not hasattr(time, "tzname") or new_lt9.tm_zone == lt.tm_zone) + + def test_timegm(self): + + # Check round trip using GMT/UTC times. + self.assert_(time.timegm(time.gmtime(0)) == 0) + + # Check equivalence in reinterpreting times. + self.assertEqual(time.timegm(time.localtime(0)), -time.mktime(time.gmtime(0))) + + # Check round trip using GMT/UTC times for the current time. + t = time.time() + dt = abs(time.timegm(time.gmtime(t)) - t) + self.assert_(0 <= dt < 1) + + # Check that the local time, reinterpreted as a GMT/UTC time, + # differs from the original UNIX time by only the timezone + # offset. + lt = time.localtime(t) + dt = abs(time.timegm(lt) + [time.timezone, time.altzone][lt.tm_isdst] - t) + self.assert_(0 <= dt < 1) + + def test_mktimetz(self): + + # Check basic properties. + self.assertEqual(time.mktimetz(time.gmtime(0)), 0) + self.assertEqual(time.mktimetz(time.localtime(0)), 0) + self.assertEqual(time.mktimetz(time.gmtime(0)[:9]), 0) + + # Check the properties for the current time. + t = time.time() + lt = time.localtime(t) + gt = time.gmtime(t) + self.assertEqual(time.mktimetz(lt), time.mktimetz(lt)) + self.assertEqual(time.mktimetz(gt), time.mktimetz(gt)) + self.assertEqual(time.mktimetz(lt), time.mktimetz(gt)) + self.assert_(0 <= abs(time.mktimetz(lt) - t) < 1) + self.assert_(0 <= abs(time.mktimetz(gt) - t) < 1) + + # Adjust time zones and measure differences. + # Get the gmtime from above, retain the "clock time" but modify the time + # zone so that the real time is either past the original gmtime or + # before it. + fmt = "%Y-%m-%d %H:%M:%S " + + # Try for other times of the year (possible DST differences). + # In order to check that various corrections work, attempt to provoke + # different DST adjustments. + month_plus_5 = ((gt[1] + 4) % 12) + 1 + month_minus_5 = ((gt[1] - 4) % 12) + 1 + + # Reset the tm_isdst field in order to provoke a DST lookup. + # One or the other of these other times should employ a different DST + # status for many time zones. + gt_plus_5 = time.struct_time(gt[0:1] + (month_plus_5,) + gt[2:8] + (-1, gt.tm_gmtoff, gt.tm_zone)) + gt_minus_5 = time.struct_time(gt[0:1] + (month_minus_5,) + gt[2:8] + (-1, gt.tm_gmtoff, gt.tm_zone)) + + # Try each of the times. + for gt_test in (gt, gt_plus_5, gt_minus_5): + + # Make the basis of each revised time (this is the "clock time" + # string). + timestr = time.strftime(fmt, gt_test) + + # Remember the UNIX time value for the time. + t_test = time.mktimetz(gt_test) + + # For adjustments of -15 to +15 hours... + for i in range(-15, 16): + + # And 0 to 50 minutes (in 10 minute intervals)... + for j in range(0, 60, 10): + + # Times with offsets such as -1500, -0715 will be later than + # the original time. + if i < 0: + sign = "-" + difference = -i * 3600 + j * 60 + + # Times with offsets such as +0715, +1500 will be earlier + # than the original time. + else: + sign = "+" + difference = -(i * 3600 + j * 60) + + # Make a new time string and interpret it. + pt = time.strptime(timestr + sign + ("%02d%02d" % (abs(i), j)), fmt + "%z") + + # Measure the difference. + dt = abs(time.mktimetz(pt) - t_test - difference) + self.assert_(0 <= dt < 1) + + def test_short_times(self): + + import pickle + + # Load a short time structure using pickle. + st = "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.assert_(lt.tm_gmtoff is None) + self.assert_(lt.tm_zone is None) + + def test_localtime_timezone(self): + + # Get the localtime and examine it for the offset and zone. + lt = time.localtime() + self.assert_(hasattr(lt, "tm_gmtoff")) + self.assert_(hasattr(lt, "tm_zone")) + + # See if the offset and zone are similar to the module + # attributes. + if lt.tm_gmtoff is None: + self.assert_(not hasattr(time, "timezone")) + else: + self.assertEqual(lt.tm_gmtoff, -[time.timezone, time.altzone][lt.tm_isdst]) + if lt.tm_zone is None: + self.assert_(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.assert_(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.assert_(new_lt == lt) + self.assert_(new_lt.tm_gmtoff is None and not hasattr(time, "timezone") or new_lt.tm_gmtoff == lt.tm_gmtoff) + self.assert_(new_lt.tm_zone is None and not hasattr(time, "tzname") or new_lt.tm_zone == lt.tm_zone) + self.assert_(new_lt9 == lt) + self.assert_(new_lt9.tm_gmtoff is None and not hasattr(time, "timezone") or new_lt.tm_gmtoff == lt.tm_gmtoff) + self.assert_(new_lt9.tm_zone is None and not hasattr(time, "tzname") or new_lt9.tm_zone == lt.tm_zone) + + def test_timegm(self): + + # Check round trip using GMT/UTC times. + self.assert_(time.timegm(time.gmtime(0)) == 0) + + # Check equivalence in reinterpreting times. + self.assertEqual(time.timegm(time.localtime(0)), -time.mktime(time.gmtime(0))) + + # Check round trip using GMT/UTC times for the current time. + t = time.time() + dt = abs(time.timegm(time.gmtime(t)) - t) + self.assert_(0 <= dt < 1) + + # Check that the local time, reinterpreted as a GMT/UTC time, + # differs from the original UNIX time by only the timezone + # offset. + lt = time.localtime(t) + dt = abs(time.timegm(lt) + [time.timezone, time.altzone][lt.tm_isdst] - t) + self.assert_(0 <= dt < 1) + + def test_mktimetz(self): + + # Check basic properties. + self.assertEqual(time.mktimetz(time.gmtime(0)), 0) + self.assertEqual(time.mktimetz(time.localtime(0)), 0) + self.assertEqual(time.mktimetz(time.gmtime(0)[:9]), 0) + + # Check the properties for the current time. + t = time.time() + lt = time.localtime(t) + gt = time.gmtime(t) + self.assertEqual(time.mktimetz(lt), time.mktimetz(lt)) + self.assertEqual(time.mktimetz(gt), time.mktimetz(gt)) + self.assertEqual(time.mktimetz(lt), time.mktimetz(gt)) + self.assert_(0 <= abs(time.mktimetz(lt) - t) < 1) + self.assert_(0 <= abs(time.mktimetz(gt) - t) < 1) + + # Adjust time zones and measure differences. + # Get the gmtime from above, retain the "clock time" but modify the time + # zone so that the real time is either past the original gmtime or + # before it. + fmt = "%Y-%m-%d %H:%M:%S " + + # Try for other times of the year (possible DST differences). + # In order to check that various corrections work, attempt to provoke + # different DST adjustments. + month_plus_5 = ((gt[1] + 4) % 12) + 1 + month_minus_5 = ((gt[1] - 4) % 12) + 1 + + # Reset the tm_isdst field in order to provoke a DST lookup. + # One or the other of these other times should employ a different DST + # status for many time zones. + gt_plus_5 = time.struct_time(gt[0:1] + (month_plus_5,) + gt[2:8] + (-1, gt.tm_gmtoff, gt.tm_zone)) + gt_minus_5 = time.struct_time(gt[0:1] + (month_minus_5,) + gt[2:8] + (-1, gt.tm_gmtoff, gt.tm_zone)) + + # Try each of the times. + for gt_test in (gt, gt_plus_5, gt_minus_5): + + # Make the basis of each revised time (this is the "clock time" + # string). + timestr = time.strftime(fmt, gt_test) + + # Remember the UNIX time value for the time. + t_test = time.mktimetz(gt_test) + + # For adjustments of -15 to +15 hours... + for i in range(-15, 16): + + # And 0 to 50 minutes (in 10 minute intervals)... + for j in range(0, 60, 10): + + # Times with offsets such as -1500, -0715 will be later than + # the original time. + if i < 0: + sign = "-" + difference = -i * 3600 + j * 60 + + # Times with offsets such as +0715, +1500 will be earlier + # than the original time. + else: + sign = "+" + difference = -(i * 3600 + j * 60) + + # Make a new time string and interpret it. + pt = time.strptime(timestr + sign + ("%02d%02d" % (abs(i), j)), fmt + "%z") + + # Measure the difference. + dt = abs(time.mktimetz(pt) - t_test - difference) + # print(time.mktimetz(pt), t_test, difference, dt) + self.assert_(0 <= dt < 1) + + 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.assert_(lt.tm_gmtoff is None) + self.assert_(lt.tm_zone is None) + def test_main(): support.run_unittest(TimeTestCase, TestLocale) Index: Modules/timemodule.c =================================================================== --- Modules/timemodule.c (revision 81758) +++ Modules/timemodule.c (working copy) @@ -217,6 +217,8 @@ {"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"}, + {"tm_gmtoff", "offset from UTC in seconds"}, + {"tm_zone", "abbreviation of timezone name"}, {0} }; @@ -236,8 +238,39 @@ static int initialized; static PyTypeObject StructTimeType; +#ifndef HAVE_STRUCT_TM_TM_ZONE +static void +_tmtotuple_tz_helper(struct tm *p, PyObject *v) +{ + PyObject *tzname; + time_t tt = 0; + + if (adjusttime(p->tm_isdst, &tt)) + PyStructSequence_SET_ITEM(v, 9, PyInt_FromLong(tt)); + else { + PyStructSequence_SET_ITEM(v, 9, Py_None); + Py_INCREF(Py_None); + } + + tzname = PyDict_GetItemString(moddict, "tzname"); + if ((tzname != NULL) && PyTuple_Check(tzname) && + PyTuple_Size(tzname) >= 2 && (p->tm_isdst >= 0)) { + PyObject *tzstrobj = PyTuple_GET_ITEM(tzname, + (p->tm_isdst > 0)); + if (!PyString_Check(tzstrobj)) + goto notzname; + Py_INCREF(tzstrobj); + PyStructSequence_SET_ITEM(v, 10, tzstrobj); + } else { + notzname: + PyStructSequence_SET_ITEM(v, 10, Py_None); + Py_INCREF(Py_None); + } +} +#endif /* !HAVE_STRUCT_TM_TM_ZONE */ + static PyObject * -tmtotuple(struct tm *p) +tmtotuple(struct tm *p, int isgmt) { PyObject *v = PyStructSequence_New(&StructTimeType); if (v == NULL) @@ -254,6 +287,19 @@ 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 + SET(9, p->tm_gmtoff); + PyStructSequence_SET_ITEM(v, 10, + PyUnicode_Decode(p->tm_zone, strlen(p->tm_zone), + TZNAME_ENCODING, NULL)); +#else + if (isgmt) { + SET(9, 0); + PyStructSequence_SET_ITEM(v, 10, PyUnicode_FromString("GMT")); + } + else + _tmtotuple_tz_helper(p, v); +#endif /* HAVE_STRUCT_TM_TM_ZONE */ #undef SET if (PyErr_Occurred()) { Py_XDECREF(v); @@ -287,7 +333,7 @@ } static PyObject * -time_convert(double when, struct tm * (*function)(const time_t *)) +time_convert(double when, struct tm * (*function)(const time_t *), int isgmt) { struct tm *p; time_t whent = _PyTime_DoubleToTimet(when); @@ -303,7 +349,7 @@ #endif return PyErr_SetFromErrno(PyExc_ValueError); } - return tmtotuple(p); + return tmtotuple(p, isgmt); } /* Parse arg tuple that can contain an optional float-or-None value; @@ -334,7 +380,7 @@ double when; if (!parse_time_double_args(args, "|O:gmtime", &when)) return NULL; - return time_convert(when, gmtime); + return time_convert(when, gmtime, 1); } PyDoc_STRVAR(gmtime_doc, @@ -350,7 +396,7 @@ double when; if (!parse_time_double_args(args, "|O:localtime", &when)) return NULL; - return time_convert(when, localtime); + return time_convert(when, localtime, 0); } PyDoc_STRVAR(localtime_doc, @@ -358,8 +404,51 @@ tm_sec,tm_wday,tm_yday,tm_isdst)\n\ \n\ Convert seconds since the Epoch to a time tuple expressing local time.\n\ -When 'seconds' is not passed in, convert the current time instead."); +When 'seconds' is not passed in, convert the current time instead.\n\ +\n\ +If time zone information is available, the tm_gmtoff and tm_zone\n\ +attributes may be consulted on the time tuple to obtain, respectively,\n\ +the location of the associated time zone in seconds east of GMT/UTC\n\ +and the name of the zone. Note that the value of tm_gmtoff may be\n\ +negative for time zones west of the prime meridian, for example.\n\ +\n\ +Where no time zone information is available, tm_gmtoff and tm_zone\n\ +will both be None."); + +/* Macros copied from structseq.c for getzonedata. + */ +static char real_length_key[] = "n_fields"; + +#define REAL_SIZE_TP(tp) PyLong_AsLong( \ + PyDict_GetItemString((tp)->tp_dict, real_length_key)) +#define REAL_SIZE(op) REAL_SIZE_TP(Py_TYPE(op)) + +/* A function extracting zone data from time structures. + */ + +static void +getzonedata(PyObject *tup, int *isdst, long *gmtoff, const char **zone) +{ + if (PyType_IsSubtype(tup->ob_type, &StructTimeType) && (REAL_SIZE(tup) >= 11)) { + PyObject *isdst_element, *gmtoff_element, *zone_element; + isdst_element = ((PyStructSequence *)(tup))->ob_item[8]; + if (isdst_element != Py_None) + *isdst = PyLong_AsLong(isdst_element); + gmtoff_element = ((PyStructSequence *)(tup))->ob_item[9]; + if (gmtoff_element != Py_None) + *gmtoff = PyLong_AsLong(gmtoff_element); + if (zone != NULL) { + zone_element = ((PyStructSequence *)(tup))->ob_item[10]; + if (zone_element != Py_None) + *zone = _PyUnicode_AsString(zone_element); + } + } +} + +/* A function converting a Python time structure into a POSIX time structure. + */ + static int gettmarg(PyObject *args, struct tm *p) { @@ -395,7 +484,12 @@ return 0; } Py_DECREF(t); - +#ifdef HAVE_STRUCT_TM_TM_ZONE + /* Add GMT/UTC offset where a time structure is provided. */ + getzonedata(args, &(p->tm_isdst), &(p->tm_gmtoff), &(p->tm_zone)); + if (PyErr_Occurred()) + return 0; +#endif if (y < 1900) { PyObject *accept = PyDict_GetItemString(moddict, "accept2dyear"); @@ -637,7 +731,6 @@ Parse a string to a time tuple according to a format specification.\n\ See the library reference manual for formatting codes (same as strftime())."); - static PyObject * time_asctime(PyObject *self, PyObject *args) { @@ -720,7 +813,106 @@ 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."); + +/* A time adjustment function employing the module's timezone and + * altzone attributes. This reverts any local time correction done by + * mktime. + */ + +static int +adjusttime(int isdst, time_t *tt) +{ + PyObject *timezone, *altzone; + timezone = PyDict_GetItemString(moddict, "timezone"); + altzone = PyDict_GetItemString(moddict, "altzone"); + if ((timezone != NULL) && PyLong_Check(timezone) && + (altzone != NULL) && PyLong_Check(altzone)) { + if (isdst == 0) + *tt -= PyLong_AsLong(timezone); + else if (isdst > 0) + *tt -= PyLong_AsLong(altzone); + return 1; + } + return 0; +} + +/* A function producing UNIX times, optionally consulting time tuple + * or structure information in order to handle local times. + */ + +static PyObject * +unixtime(PyObject *self, PyObject *tup, int toutc) +{ + struct tm buf; + time_t tt; + long gmtoff, isdst; + + /* Seemingly useless calls to make things "NeXT robust" (rev. 6061). */ + tt = time(&tt); + buf = *localtime(&tt); + if (!gettmarg(tup, &buf)) + return NULL; + + /* Remember things before mktime changes them. */ +#ifdef HAVE_STRUCT_TM_TM_ZONE + gmtoff = buf.tm_gmtoff; + isdst = buf.tm_isdst; +#else + gmtoff = 0; + isdst = 0; + getzonedata(tup, &isdst, &gmtoff, NULL); +#endif + + tt = mktime(&buf); + if (tt == (time_t)(-1)) { + PyErr_SetString(PyExc_OverflowError, + "timegm or mktimetz argument out of range"); + return NULL; + } + + /* Adjust the result using tm_gmtoff, if possible. */ + if (toutc) { + tt -= gmtoff; + } + + /* Revert any mktime corrections. + * Use the original isdst value if appropriate; otherwise, get the value + * produced by mktime. + */ + adjusttime(isdst != -1 ? isdst : buf.tm_isdst, &tt); + return PyFloat_FromDouble((double)tt); +} + +static PyObject * +time_timegm(PyObject *self, PyObject *arg) +{ + return unixtime(self, arg, 0); +} + +PyDoc_STRVAR(timegm_doc, +"timegm(tuple) -> floating point number\n\ +\n\ +Convert a time tuple considered to be in the GMT/UTC time zone to\n\ +seconds since the Epoch. Note that timegm(gmtime(0)) should always\n\ +return zero."); + +static PyObject * +time_mktimetz(PyObject *self, PyObject *arg) +{ + return unixtime(self, arg, 1); +} + +PyDoc_STRVAR(mktimetz_doc, +"mktimetz(tuple) -> floating point number\n\ +\n\ +Convert a time tuple to seconds since the Epoch using time zone\n\ +information available on the time tuple if possible. Note that\n\ +mktimetz(gmtime(0)) and mktimetz(localtime(0)) should both always\n\ +return zero."); #endif /* HAVE_MKTIME */ #ifdef HAVE_WORKING_TZSET @@ -863,6 +1055,8 @@ {"ctime", time_ctime, METH_VARARGS, ctime_doc}, #ifdef HAVE_MKTIME {"mktime", time_mktime, METH_O, mktime_doc}, + {"timegm", time_timegm, METH_O, timegm_doc}, + {"mktimetz", time_mktimetz, METH_O, mktimetz_doc}, #endif #ifdef HAVE_STRFTIME {"strftime", time_strftime, METH_VARARGS, strftime_doc}, @@ -916,8 +1110,10 @@ asctime() -- convert time tuple to string\n\ ctime() -- convert time in seconds to string\n\ mktime() -- convert local time tuple to seconds since Epoch\n\ +mktimetz() -- convert time structure with time zone to seconds since Epoch\n\ strftime() -- convert time tuple to string according to format specification\n\ strptime() -- parse string to time tuple according to format specification\n\ +timegm() -- convert UTC time tuple to seconds since Epoch\n\ tzset() -- change the local timezone");