diff -r c2217813dbab Doc/lib/libtime.tex --- a/Doc/lib/libtime.tex Fri Feb 23 12:05:41 2007 +0100 +++ b/Doc/lib/libtime.tex Tue Mar 20 01:15:13 2007 +0100 @@ -80,9 +80,9 @@ The time value as returned by \function{ The time value as returned by \function{gmtime()}, \function{localtime()}, and \function{strptime()}, and accepted by \function{asctime()}, \function{mktime()} and \function{strftime()}, -is a sequence of 9 integers. The return values of \function{gmtime()}, -\function{localtime()}, and \function{strptime()} also offer attribute -names for individual fields. +may be considered as a sequence of 9 integers. The return values of +\function{gmtime()}, \function{localtime()}, and \function{strptime()} +also offer attribute names for individual fields. \begin{tableiii}{c|l|l}{textrm}{Index}{Attribute}{Values} \lineiii{0}{\member{tm_year}}{(for example, 1993)} @@ -94,6 +94,8 @@ names for individual fields. \lineiii{6}{\member{tm_wday}}{range [0,6], Monday is 0} \lineiii{7}{\member{tm_yday}}{range [1,366]} \lineiii{8}{\member{tm_isdst}}{0, 1 or -1; see below} + \lineiii{-}{\member{tm_gmtoff}}{offset from UTC/GMT in seconds east of the Prime Meridian; see below} + \lineiii{-}{\member{tm_zone}}{time zone name; see below} \end{tableiii} Note that unlike the C structure, the month value is a @@ -102,6 +104,18 @@ daylight savings flag, passed to \functi daylight savings flag, passed to \function{mktime()} will usually result in the correct daylight savings state to be filled in. +The \member{tm_gmtoff} and \member{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 \function{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 \member{tm_gmtoff} and +\member{tm_zone} as the last two elements, then pass this tuple to the +\class{struct_time} constructor. It is also possible to omit +\member{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 \exception{TypeError} is raised. @@ -109,6 +123,9 @@ expecting a \class{struct_time}, or havi \versionchanged[The time value sequence was changed from a tuple to a \class{struct_time}, with the addition of attribute names for the fields]{2.2} +\versionchanged[The time value sequence was extended to provide the + \member{tm_gmtoff} and \member{tm_zone} attributes as + described above]{2.6} \end{itemize} The module defines the following functions and data items: @@ -204,6 +221,24 @@ represented as a valid time, either \exc \exception{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. +\end{funcdesc} + +\begin{funcdesc}{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 \function{mktime()} to convert local times +with the ability of \function{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 \function{time()}. If +the input value cannot be represented as a valid time, either +\exception{OverflowError} or \exception{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. \end{funcdesc} \begin{funcdesc}{sleep}{secs} @@ -258,6 +293,10 @@ specification, and are replaced by the i \lineiii{\%X}{Locale's appropriate time representation.}{} \lineiii{\%y}{Year without century as a decimal number [00,99].}{} \lineiii{\%Y}{Year with century as a decimal number.}{} + \lineiii{\%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 [00,59].}{} \lineiii{\%Z}{Time zone name (no characters if no time zone exists).}{} \lineiii{\%\%}{A literal \character{\%} character.}{} \end{tableiii} @@ -339,6 +378,18 @@ call if the system clock has been set ba call if the system clock has been set back between the two calls. \end{funcdesc} +\begin{funcdesc}{timegm}{t} +This is the inverse function of \function{gmtime()}. Its argument +is the \class{struct_time} or full 9-tuple which expresses the time in +\emph{GMT/UTC} time, not local time as expected by +\function{mktime()}. It returns a floating point number, for +compatibility with \function{time()}. If the input value cannot be +represented as a valid time, either \exception{OverflowError} or +\exception{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. +\end{funcdesc} + \begin{datadesc}{timezone} The offset of the local (non-DST) timezone, in seconds west of UTC (negative in most of Western Europe, positive in the US, zero in the @@ -455,5 +506,7 @@ usually located at \file{/usr/share/zone the functions in the \module{time} module.} \seemodule{calendar}{General calendar-related functions. \function{timegm()} is the inverse of - \function{gmtime()} from this module.} + \function{gmtime()} from this module, offering an + alternative implementation of \function{timegm()} + from this module.} \end{seealso} diff -r c2217813dbab Lib/_strptime.py --- a/Lib/_strptime.py Fri Feb 23 12:05:41 2007 +0100 +++ b/Lib/_strptime.py Tue Mar 20 01:15:13 2007 +0100 @@ -203,6 +203,7 @@ class TimeRE(dict): #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'), @@ -299,6 +300,7 @@ def strptime(data_string, format="%a %b month = day = 1 hour = minute = second = 0 tz = -1 + tzoffset = None # Default to -1 to signify that values not known; not critical to have, # though week_of_year = -1 @@ -374,6 +376,10 @@ def strptime(data_string, format="%a %b 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. @@ -432,6 +438,19 @@ def strptime(data_string, format="%a %b day = datetime_result.day if weekday == -1: weekday = datetime_date(year, month, day).weekday() + # Add extra time structure named attributes. + if tz != -1: + tzname = found_dict.get("Z") + if found_zone in ("utc", "gmt"): + gmtoff = 0 + else: + gmtoff = [-time.timezone, -time.altzone][tz] + elif tzoffset is not None: + gmtoff = tzoffset * 60 + tzname = None + else: + gmtoff = 0 + tzname = None return time.struct_time((year, month, day, hour, minute, second, - weekday, julian, tz)) + weekday, julian, tz, gmtoff, tzname)) diff -r c2217813dbab Lib/test/test_time.py --- a/Lib/test/test_time.py Fri Feb 23 12:05:41 2007 +0100 +++ b/Lib/test/test_time.py Tue Mar 20 01:15:13 2007 +0100 @@ -202,6 +202,102 @@ class TimeTestCase(unittest.TestCase): t1 = time.mktime(time.localtime(None)) self.assert_(0 <= (t1-t0) < 0.2) + 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. + self.assert_(lt.tm_gmtoff is None and not hasattr(time, "timezone") or lt.tm_gmtoff == -[time.timezone, time.altzone][lt.tm_isdst]) + self.assert_(lt.tm_zone is None and not hasattr(time, "tzname") or 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.assert_(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.assert_(time.mktimetz(time.gmtime(0)) == 0) + self.assert_(time.mktimetz(time.localtime(0)) == 0) + self.assert_(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.assert_(time.mktimetz(lt) == time.mktimetz(lt)) + self.assert_(time.mktimetz(gt) == time.mktimetz(gt)) + self.assert_(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_main(): test_support.run_unittest(TimeTestCase) diff -r c2217813dbab Modules/timemodule.c --- a/Modules/timemodule.c Fri Feb 23 12:05:41 2007 +0100 +++ b/Modules/timemodule.c Tue Mar 20 01:15:13 2007 +0100 @@ -231,6 +231,8 @@ static PyStructSequence_Field struct_tim {"tm_wday", NULL}, {"tm_yday", NULL}, {"tm_isdst", NULL}, + {"tm_gmtoff", NULL}, + {"tm_zone", NULL}, {0} }; @@ -244,8 +246,11 @@ static PyTypeObject StructTimeType; static PyTypeObject StructTimeType; static PyObject * -tmtotuple(struct tm *p) -{ +tmtotuple(struct tm *p, int isgmt) +{ +#ifndef HAVE_STRUCT_TM_TM_ZONE + PyObject *timezone, *altzone, *tzname; +#endif PyObject *v = PyStructSequence_New(&StructTimeType); if (v == NULL) return NULL; @@ -261,6 +266,41 @@ 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 + SET(9, p->tm_gmtoff); + PyStructSequence_SET_ITEM(v, 10, PyString_FromString(p->tm_zone)); +#else + if (isgmt) { + SET(9, 0); + PyStructSequence_SET_ITEM(v, 10, PyString_FromString("GMT")); + } else { + timezone = PyDict_GetItemString(moddict, "timezone"); + altzone = PyDict_GetItemString(moddict, "altzone"); + if ((timezone != NULL) && PyInt_Check(timezone) && (altzone != NULL) && PyInt_Check(altzone)) { + switch (p->tm_isdst) { + case 0: + SET(9, -PyInt_AsLong(timezone)); + break; + case 1: + SET(9, -PyInt_AsLong(altzone)); + break; + default: + SET(9, 0); + break; + } + } else { + PyStructSequence_SET_ITEM(v, 9, Py_None); + Py_INCREF(Py_None); + } + tzname = PyDict_GetItemString(moddict, "tzname"); + if ((tzname != NULL) && PyTuple_Check(tzname) && (p->tm_isdst != -1)) { + PyStructSequence_SET_ITEM(v, 10, PyString_FromString(PyString_AsString(PyTuple_GetItem(tzname, p->tm_isdst)))); + } else { + PyStructSequence_SET_ITEM(v, 10, Py_None); + Py_INCREF(Py_None); + } + } +#endif #undef SET if (PyErr_Occurred()) { Py_XDECREF(v); @@ -271,7 +311,7 @@ tmtotuple(struct tm *p) } 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); @@ -287,7 +327,7 @@ time_convert(double when, struct tm * (* #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; @@ -318,7 +358,7 @@ time_gmtime(PyObject *self, PyObject *ar 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, @@ -334,14 +374,23 @@ time_localtime(PyObject *self, PyObject 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, "localtime([seconds]) -> (tm_year,tm_mon,tm_day,tm_hour,tm_min,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."); static int gettmarg(PyObject *args, struct tm *p) @@ -360,6 +409,17 @@ gettmarg(PyObject *args, struct tm *p) &p->tm_yday, &p->tm_isdst)) return 0; +#ifdef HAVE_STRUCT_TM_TM_ZONE + /* Add GMT/UTC offset where a time structure is provided. */ + if (PyType_IsSubtype(args->ob_type, &StructTimeType)) { + PyObject *gmtoff = ((PyStructSequence *)(args))->ob_item[9]; + PyObject *zone = ((PyStructSequence *)(args))->ob_item[10]; + if (gmtoff != Py_None) + p->tm_gmtoff = PyInt_AsLong(gmtoff); + if (zone != Py_None) + p->tm_zone = PyString_AsString(zone); + } +#endif if (y < 1900) { PyObject *accept = PyDict_GetItemString(moddict, "accept2dyear"); @@ -503,7 +563,6 @@ Parse a string to a time tuple according 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) { @@ -575,6 +634,7 @@ time_mktime(PyObject *self, PyObject *ar time_t tt; if (!PyArg_ParseTuple(args, "O:mktime", &tup)) return NULL; + /* Seemingly useless calls to make things "NeXT robust" (rev. 6061). */ tt = time(&tt); buf = *localtime(&tt); if (!gettmarg(tup, &buf)) @@ -591,7 +651,105 @@ PyDoc_STRVAR(mktime_doc, 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 void +adjusttime(int isdst, time_t *tt) +{ + PyObject *timezone, *altzone; + timezone = PyDict_GetItemString(moddict, "timezone"); + altzone = PyDict_GetItemString(moddict, "altzone"); + if ((timezone != NULL) && PyInt_Check(timezone) && (altzone != NULL) && PyInt_Check(altzone)) + switch (isdst) { + case 0: + *tt -= PyInt_AsLong(timezone); + break; + case 1: + *tt -= PyInt_AsLong(altzone); + break; + default: + break; + } +} + +/* A function producing UNIX times, optionally consulting time tuple + * or structure information in order to handle local times. + */ + +static PyObject * +unixtime(PyObject *self, PyObject *args, int toutc) +{ + PyObject *tup; + struct tm buf; + time_t tt; + long gmtoff; + if (!PyArg_ParseTuple(args, "O:time", &tup)) + return NULL; + + /* 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; +#else + gmtoff = 0; +#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. */ + adjusttime(buf.tm_isdst, &tt); + return PyFloat_FromDouble((double)tt); +} + +static PyObject * +time_timegm(PyObject *self, PyObject *args) +{ + return unixtime(self, args, 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 *args) +{ + return unixtime(self, args, 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 @@ -734,6 +892,8 @@ static PyMethodDef time_methods[] = { {"ctime", time_ctime, METH_VARARGS, ctime_doc}, #ifdef HAVE_MKTIME {"mktime", time_mktime, METH_VARARGS, mktime_doc}, + {"timegm", time_timegm, METH_VARARGS, timegm_doc}, + {"mktimetz", time_mktimetz, METH_VARARGS, mktimetz_doc}, #endif #ifdef HAVE_STRFTIME {"strftime", time_strftime, METH_VARARGS, strftime_doc}, @@ -787,8 +947,10 @@ asctime() -- convert time tuple to strin 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");