diff --git a/Doc/lib/libos.tex b/Doc/lib/libos.tex index 17b7c67db04..2b4728c9b50 100644 --- a/Doc/lib/libos.tex +++ b/Doc/lib/libos.tex @@ -854,9 +854,10 @@ the \ctype{stat} structure, namely: \member{st_ctime} (time of most recent content modification or metadata change). -\versionchanged [The time values are floats, measuring - seconds. Fractions of a second may be reported if the system - supports that]{2.3} +\versionchanged [If \function{stat_float_times} returns true, the time +values are floats, measuring seconds. Fractions of a second may be +reported if the system supports that. On Mac OS, the times are always +floats. See \function{stat_float_times} for further discussion. ]{2.3} On some Unix systems (such as Linux), the following attributes may also be available: @@ -899,6 +900,32 @@ Availability: Macintosh, \UNIX, Windows. [Added access to values as attributes of the returned object]{2.2} \end{funcdesc} +\begin{funcdesc}{stat_float_times}{\optional{newvalue}} +Determine whether \class{stat_result} represents time stamps as float +objects. If newval is True, future calls to stat() return floats, if +it is False, future calls return ints. If newval is omitted, return +the current setting. + +For compatibility with older Python versions, accessing +\class{stat_result} as a tuple always returns integers. For +compatibility with Python 2.2, accessing the time stamps by field name +also returns integers. Applications that want to determine the +fractions of a second in a time stamp can use this function to have +time stamps represented as floats. Whether they will actually observe +non-zero fractions depends on the system. + +Future Python releases will change the default of this settings; +applications that cannot deal with floating point time stamps can then +use this function to turn the feature off. + +It is recommended that this setting is only changed at program startup +time in the \var{__main__} module; libraries should never change this +setting. If an application uses a library that works incorrectly if +floating point time stamps are processed, this application should turn +the feature off until the library has been corrected. + +\end{funcdesc} + \begin{funcdesc}{statvfs}{path} Perform a \cfunction{statvfs()} system call on the given path. The return value is an object whose attributes describe the filesystem on diff --git a/Doc/whatsnew/whatsnew23.tex b/Doc/whatsnew/whatsnew23.tex index 85f664372f4..2b1469830ea 100644 --- a/Doc/whatsnew/whatsnew23.tex +++ b/Doc/whatsnew/whatsnew23.tex @@ -1067,6 +1067,31 @@ in \module{xml.dom.minidom} can now generate XML output in a particular encoding, by specifying an optional encoding argument to the \method{toxml()} and \method{toprettyxml()} methods of DOM nodes. +\item The \function{stat} family of functions can now report fractions +of a second in a time stamp. Similar to \function{time.time}, such +time stamps are represented as floats. + +During testing, it was found that some applications break if time +stamps are floats. For compatibility, when using the tuple interface +of the \class{stat_result}, time stamps are represented as integers. +When using named fields (first introduced in Python 2.2), time stamps +are still represented as ints, unless \function{os.stat_float_times} +is invoked: + +\begin{verbatim} +>>> os.stat_float_times(True) +>>> os.stat("/tmp").st_mtime +1034791200.6335014 +\end{verbatim} + +In Python 2.4, the default will change to return floats. + +Application developers should use this feature only if all their +libraries work properly when confronted with floating point time +stamps (or use the tuple API). If used, the feature should be +activated on application level, instead of trying to activate it on a +per-use basis. + \end{itemize} diff --git a/Include/structseq.h b/Include/structseq.h index 67cd947df37..ee6a04ec362 100644 --- a/Include/structseq.h +++ b/Include/structseq.h @@ -19,6 +19,8 @@ typedef struct PyStructSequence_Desc { int n_in_sequence; } PyStructSequence_Desc; +extern char* PyStructSequence_UnnamedField; + PyAPI_FUNC(void) PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc); diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 0f2fbb9752f..53324b5d204 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -678,6 +678,10 @@ static PyStructSequence_Field stat_result_fields[] = { {"st_uid", "user ID of owner"}, {"st_gid", "group ID of owner"}, {"st_size", "total size, in bytes"}, + /* The NULL is replaced with PyStructSequence_UnnamedField later. */ + {NULL, "integer time of last access"}, + {NULL, "integer time of last modification"}, + {NULL, "integer time of last change"}, {"st_atime", "time of last access"}, {"st_mtime", "time of last modification"}, {"st_ctime", "time of last change"}, @@ -694,9 +698,9 @@ static PyStructSequence_Field stat_result_fields[] = { }; #ifdef HAVE_ST_BLKSIZE -#define ST_BLKSIZE_IDX 10 +#define ST_BLKSIZE_IDX 13 #else -#define ST_BLKSIZE_IDX 9 +#define ST_BLKSIZE_IDX 12 #endif #ifdef HAVE_ST_BLOCKS @@ -749,13 +753,73 @@ static PyStructSequence_Desc statvfs_result_desc = { static PyTypeObject StatResultType; static PyTypeObject StatVFSResultType; +static newfunc structseq_new; + +static PyObject * +statresult_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyStructSequence *result; + int i; + + result = (PyStructSequence*)structseq_new(type, args, kwds); + if (!result) + return NULL; + /* If we have been initialized from a tuple, + st_?time might be set to None. Initialize it + from the int slots. */ + for (i = 7; i <= 9; i++) { + if (result->ob_item[i+3] == Py_None) { + Py_DECREF(Py_None); + Py_INCREF(result->ob_item[i]); + result->ob_item[i+3] = result->ob_item[i]; + } + } + return (PyObject*)result; +} + + + +/* If true, st_?time is float. */ +static int _stat_float_times = 0; + +PyDoc_STRVAR(stat_float_times__doc__, +"stat_float_times([newval]) -> oldval\n\n\ +Determine whether os.[lf]stat represents time stamps as float objects.\n\ +If newval is True, future calls to stat() return floats, if it is False,\n\ +future calls return ints. \n\ +If newval is omitted, return the current setting.\n"); + +static PyObject* +stat_float_times(PyObject* self, PyObject *args) +{ + int newval = -1; + if (!PyArg_ParseTuple(args, "|i:stat_float_times", &newval)) + return NULL; + if (newval == -1) + /* Return old value */ + return PyBool_FromLong(_stat_float_times); + _stat_float_times = newval; + Py_INCREF(Py_None); + return Py_None; +} static void fill_time(PyObject *v, int index, time_t sec, unsigned long nsec) { - PyObject *val; - val = PyFloat_FromDouble(sec + 1e-9*nsec); - PyStructSequence_SET_ITEM(v, index, val); + PyObject *fval,*ival; +#if SIZEOF_TIME_T > SIZEOF_LONG + ival = PyLong_FromLongLong((LONG_LONG)sec); +#else + ival = PyInt_FromLong((long)sec); +#endif + if (_stat_float_times) { + fval = PyFloat_FromDouble(sec + 1e-9*nsec); + } else { + fval = ival; + Py_INCREF(fval); + } + PyStructSequence_SET_ITEM(v, index, ival); + PyStructSequence_SET_ITEM(v, index+3, fval); } /* pack a system stat C structure into the Python stat tuple @@ -6802,6 +6866,7 @@ static PyMethodDef posix_methods[] = { {"rename", posix_rename, METH_VARARGS, posix_rename__doc__}, {"rmdir", posix_rmdir, METH_VARARGS, posix_rmdir__doc__}, {"stat", posix_stat, METH_VARARGS, posix_stat__doc__}, + {"stat_float_times", stat_float_times, METH_VARARGS, stat_float_times__doc__}, #ifdef HAVE_SYMLINK {"symlink", posix_symlink, METH_VARARGS, posix_symlink__doc__}, #endif /* HAVE_SYMLINK */ @@ -7296,7 +7361,12 @@ INITFUNC(void) #endif stat_result_desc.name = MODNAME ".stat_result"; + stat_result_desc.fields[7].name = PyStructSequence_UnnamedField; + stat_result_desc.fields[8].name = PyStructSequence_UnnamedField; + stat_result_desc.fields[9].name = PyStructSequence_UnnamedField; PyStructSequence_InitType(&StatResultType, &stat_result_desc); + structseq_new = StatResultType.tp_new; + StatResultType.tp_new = statresult_new; Py_INCREF((PyObject*) &StatResultType); PyModule_AddObject(m, "stat_result", (PyObject*) &StatResultType); diff --git a/Objects/structseq.c b/Objects/structseq.c index fdc56cb793d..0f5a0123b9d 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -8,6 +8,10 @@ static char visible_length_key[] = "n_sequence_fields"; static char real_length_key[] = "n_fields"; +/* Fields with this name have only a field index, not a field name. + They are only allowed for indices < n_visible_fields. */ +char *PyStructSequence_UnnamedField = "unnamed field"; + #define VISIBLE_SIZE(op) ((op)->ob_size) #define VISIBLE_SIZE_TP(tp) PyInt_AsLong( \ PyDict_GetItemString((tp)->tp_dict, visible_length_key)) @@ -332,10 +336,12 @@ PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc) { PyObject *dict; PyMemberDef* members; - int n_members, i; + int n_members, n_unnamed_members, i, k; + n_unnamed_members = 0; for (i = 0; desc->fields[i].name != NULL; ++i) - ; + if (desc->fields[0].name == PyStructSequence_UnnamedField) + n_unnamed_members++; n_members = i; memcpy(type, &_struct_sequence_template, sizeof(PyTypeObject)); @@ -345,17 +351,20 @@ PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc) sizeof(PyObject*)*(n_members-1); type->tp_itemsize = 0; - members = PyMem_NEW(PyMemberDef, n_members+1); + members = PyMem_NEW(PyMemberDef, n_members-n_unnamed_members+1); - for (i = 0; i < n_members; ++i) { - members[i].name = desc->fields[i].name; - members[i].type = T_OBJECT; - members[i].offset = offsetof(PyStructSequence, ob_item) + for (i = k = 0; i < n_members; ++i) { + if (desc->fields[i].name == PyStructSequence_UnnamedField) + continue; + members[k].name = desc->fields[i].name; + members[k].type = T_OBJECT; + members[k].offset = offsetof(PyStructSequence, ob_item) + i * sizeof(PyObject*); - members[i].flags = READONLY; - members[i].doc = desc->fields[i].doc; + members[k].flags = READONLY; + members[k].doc = desc->fields[i].doc; + k++; } - members[n_members].name = NULL; + members[k].name = NULL; type->tp_members = members;