diff --git a/Doc/library/os.rst b/Doc/library/os.rst --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -808,13 +808,16 @@ as internal buffering of data. Availability: Unix. -.. function:: fstat(fd) +.. function:: fstat(fd, timestamp=None) Return status for file descriptor *fd*, like :func:`~os.stat`. Availability: Unix, Windows. -.. function:: fstatat(dirfd, path, flags=0) + .. versionchanged:: 3.3 + Added the *timestamp* argument. + +.. function:: fstatat(dirfd, path, flags=0, timestamp="float") Like :func:`stat` but if *path* is relative, it is taken as relative to *dirfd*. *flags* is optional and may be 0 or :data:`AT_SYMLINK_NOFOLLOW`. @@ -1696,7 +1699,7 @@ Files and Directories .. versionadded:: 3.3 -.. function:: lstat(path) +.. function:: lstat(path, timestamp=None) Perform the equivalent of an :c:func:`lstat` system call on the given path. Similar to :func:`~os.stat`, but does not follow symbolic links. On @@ -1706,6 +1709,9 @@ Files and Directories .. versionchanged:: 3.2 Added support for Windows 6.0 (Vista) symbolic links. + .. versionchanged:: 3.3 + The *timestamp* argument was added. + .. function:: lutimes(path[, times]) @@ -1955,7 +1961,7 @@ Files and Directories .. versionadded:: 3.3 -.. function:: stat(path) +.. function:: stat(path, timestamp=None) Perform the equivalent of a :c:func:`stat` system call on the given path. (This function follows symlinks; to stat a symlink use :func:`lstat`.) @@ -1975,6 +1981,11 @@ Files and Directories * :attr:`st_ctime` - platform dependent; time of most recent metadata change on Unix, or the time of creation on Windows) + :attr:`st_atime`, :attr:`st_mtime` and :attr:`st_ctime` values type is + :class:`float` by default, or :class:`int` if :func:`os.stat_float_times` is + ``False``. Set the optional *timestamp* argument to get another + :ref:`timestamp format `. + On some Unix systems (such as Linux), the following attributes may also be available: @@ -2030,6 +2041,9 @@ Files and Directories Availability: Unix, Windows. + .. versionchanged:: 3.3 + Added the *timestamp* argument. + .. function:: stat_float_times([newvalue]) @@ -2055,6 +2069,9 @@ Files and Directories are processed, this application should turn the feature off until the library has been corrected. + .. deprecated:: 3.3 + Use *timestamp* argument of stat functions instead. + .. function:: statvfs(path) diff --git a/Doc/library/time.rst b/Doc/library/time.rst --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -95,6 +95,16 @@ An explanation of some terminology and c | local time | | | +-------------------------+-------------------------+-------------------------+ +.. _timestamp-formats: + +* Python supports the following timestamp formats: + + * :class:`int` + * :class:`float` + * :class:`datetime.datetime` + * :class:`datetime.timedelta` + * :class:`decimal.Decimal` + The module defines the following functions and data items: @@ -118,7 +128,7 @@ The module defines the following functio Unlike the C function of the same name, there is no trailing newline. -.. function:: clock() +.. function:: clock(format=float) .. index:: single: CPU time @@ -135,16 +145,27 @@ The module defines the following functio :c:func:`QueryPerformanceCounter`. The resolution is typically better than one microsecond. + Return the time as a floating point number by default, set the optional + *format* argument to get another :ref:`timestamp format `. -.. function:: clock_getres(clk_id) + .. versionchanged:: 3.3 + Added the *format* argument. + + +.. function:: clock_getres(clk_id, format=float) Return the resolution (precision) of the specified clock *clk_id*. + Return the resolution as a floating point number by default, set the + optional *format* argument to get another :ref:`timestamp format `. + .. versionadded:: 3.3 -.. function:: clock_gettime(clk_id) +.. function:: clock_gettime(clk_id, format=float) Return the time of the specified clock *clk_id*. + Return the time as a floating point number by default, set the optional + *format* argument to get another :ref:`timestamp format `. .. versionadded:: 3.3 @@ -431,15 +452,20 @@ The module defines the following functio :exc:`TypeError` is raised. -.. function:: time() +.. function:: time(format=float) - Return the time as a floating point number expressed in seconds since the epoch, - in UTC. Note that even though the time is always returned as a floating point + Return the time expressed in seconds since the epoch in UTC. Return + the time as a floating point number by default, set the optional *format* + argument to get another :ref:`timestamp format `. + Note that even though the time is always returned as a floating point number, not all systems provide time with a better precision than 1 second. While this function normally returns non-decreasing values, it can return a lower value than a previous call if the system clock has been set back between the two calls. + .. versionchanged:: 3.3 + Added the *format* argument. + .. data:: timezone @@ -537,13 +563,15 @@ The module defines the following functio ('EET', 'EEST') -.. function:: wallclock() +.. function:: wallclock(format=float) .. index:: single: Wallclock single: benchmarking Return the current time in fractions of a second to the system's best ability. + Return the time as a floating point number by default, set the optional + *format* argument to get another :ref:`timestamp format `. Use this when the most accurate representation of wall-clock is required, i.e. when "processor time" is inappropriate. The reference point of the returned value is undefined so only the difference of consecutive calls is valid. diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -270,6 +270,34 @@ new, more precise information:: '' +Timestamp formats +================= + +:func:`time.clock`, :func:`time.clock_gettime`, :func:`time.clock_getres`, +:func:`time.time` and :func:`time.wallclock` now have an optional *format* +argument to choose the format of the timestamp. See the list of available +:ref:`timestamp formats `. + +Use :class:`decimal.Decimal` to support clock using a nanosecond resolution. + +:func:`os.fstat`, :func:`os.fstatat`, :func:`os.lstat` and :func:`os.stat` have +also a *timestamp* optional argument to choose the timestamp format of +``st_atime``, ``st_ctime`` and ``st_mtime`` fields. + +Example:: + + >>> time.time() + 1328006975.681211 + >>> time.time(format=int) + 1328006979 + >>> time.time(format=decimal.Decimal) + Decimal('1328006983.761119') + >>> time.time(format=datetime.datetime) + datetime.datetime(2012, 1, 31, 11, 49, 49, 409831) + >>> print(time.time(format=datetime.timedelta)) + 15370 days, 10:49:52.842116 + + Other Language Changes ====================== diff --git a/Include/pytime.h b/Include/pytime.h --- a/Include/pytime.h +++ b/Include/pytime.h @@ -12,6 +12,8 @@ functions and constants extern "C" { #endif +#include "object.h" + #ifdef HAVE_GETTIMEOFDAY typedef struct timeval _PyTime_timeval; #else @@ -37,6 +39,21 @@ do { \ ((tv_end.tv_sec - tv_start.tv_sec) + \ (tv_end.tv_usec - tv_start.tv_usec) * 0.000001) +typedef struct { + time_t seconds; + /* floatpart can be zero */ + size_t floatpart; + /* divisor cannot be zero */ + size_t divisor; + /* the resolution is 1/divisor */ +} _PyTime_t; + +/* Similar to POSIX gettimeofday. If system gettimeofday + fails or is not available, fall back to lower resolution clocks. */ +PyAPI_FUNC(void) _PyTime_get(_PyTime_t *tp); + +PyAPI_FUNC(PyObject*) _PyTime_AsFormat(_PyTime_t *ts, PyObject *format); + /* Dummy to force linking. */ PyAPI_FUNC(void) _PyTime_Init(void); diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -217,6 +217,30 @@ class StatAttributeTests(unittest.TestCa warnings.simplefilter("ignore", DeprecationWarning) self.check_stat_attributes(fname) + def test_stat_timestamp(self): + import datetime + import decimal + + old_value = os.stat_float_times() + try: + # test invalid timestamp format + self.assertRaises(ValueError, os.stat, self.fname, timestamp="abc") + self.assertRaises(ValueError, os.stat, self.fname, timestamp=decimal.Context) + + for float_times in (False, True): + os.stat_float_times(float_times) + t = os.stat(self.fname).st_mtime + if float_times: + self.assertIsInstance(t, float) + else: + self.assertIsInstance(t, int) + + for type in (int, float, decimal.Decimal, datetime.datetime, datetime.timedelta): + t = os.stat(self.fname, timestamp=type).st_mtime + self.assertIsInstance(t, type) + finally: + os.stat_float_times(old_value) + def test_statvfs_attributes(self): if not hasattr(os, "statvfs"): return diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -1,10 +1,10 @@ from test import support +import locale +import platform +import sys +import sysconfig import time import unittest -import locale -import sysconfig -import sys -import platform # Max year is only limited by the size of C int. SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4 @@ -331,6 +331,41 @@ class TimeTestCase(unittest.TestCase): pass self.assertEqual(time.strftime('%Z', tt), tzname) + def test_format(self): + import datetime, decimal + calls = [(time.time,)] + if hasattr(time, 'wallclock'): + calls.append((time.wallclock,)) + if hasattr(time, 'CLOCK_REALTIME'): + if hasattr(time, 'clock_gettime'): + calls.append((time.clock_gettime, time.CLOCK_REALTIME)) + if hasattr(time, 'clock_getres'): + calls.append((time.clock_getres, time.CLOCK_REALTIME)) + for call in calls: + func, *args = call + + # test invalid format + self.assertRaises(ValueError, func, *args, format="abc") + self.assertRaises(ValueError, func, *args, format=decimal.Context) + + # test float + timestamp = func(*args, format=float) + self.assertIsInstance(timestamp, float) + + # test int + timestamp = func(*args, format=int) + self.assertIsInstance(timestamp, int) + + # test decimal + timestamp = func(*args, format=decimal.Decimal) + self.assertIsInstance(timestamp, decimal.Decimal) + + # test datetime + timestamp = func(*args, format=datetime.datetime) + self.assertIsInstance(timestamp, datetime.datetime) + timestamp = func(*args, format=datetime.timedelta) + self.assertIsInstance(timestamp, datetime.timedelta) + def test_wallclock(self): t1 = time.wallclock() t2 = time.wallclock() diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1708,9 +1708,12 @@ stat_float_times(PyObject* self, PyObjec } static void -fill_time(PyObject *v, int index, time_t sec, unsigned long nsec) +fill_time(PyObject *v, int index, time_t sec, unsigned long nsec, + PyObject *timestamp) { PyObject *fval,*ival; + _PyTime_t ts; + #if SIZEOF_TIME_T > SIZEOF_LONG ival = PyLong_FromLongLong((PY_LONG_LONG)sec); #else @@ -1718,9 +1721,15 @@ fill_time(PyObject *v, int index, time_t #endif if (!ival) return; - if (_stat_float_times) { - fval = PyFloat_FromDouble(sec + 1e-9*nsec); - } else { + if (timestamp == NULL && _stat_float_times) + timestamp = (PyObject*)&PyFloat_Type; + if (timestamp != NULL) { + ts.seconds = sec; + ts.floatpart = nsec; + ts.divisor = 1000000000; + fval = _PyTime_AsFormat(&ts, timestamp); + } + else { fval = ival; Py_INCREF(fval); } @@ -1731,7 +1740,7 @@ fill_time(PyObject *v, int index, time_t /* pack a system stat C structure into the Python stat tuple (used by posix_stat() and posix_fstat()) */ static PyObject* -_pystat_fromstructstat(STRUCT_STAT *st) +_pystat_fromstructstat(STRUCT_STAT *st, PyObject *timestamp) { unsigned long ansec, mnsec, cnsec; PyObject *v = PyStructSequence_New(&StatResultType); @@ -1776,9 +1785,9 @@ _pystat_fromstructstat(STRUCT_STAT *st) #else ansec = mnsec = cnsec = 0; #endif - fill_time(v, 7, st->st_atime, ansec); - fill_time(v, 8, st->st_mtime, mnsec); - fill_time(v, 9, st->st_ctime, cnsec); + fill_time(v, 7, st->st_atime, ansec, timestamp); + fill_time(v, 8, st->st_mtime, mnsec, timestamp); + fill_time(v, 9, st->st_ctime, cnsec, timestamp); #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE PyStructSequence_SET_ITEM(v, ST_BLKSIZE_IDX, @@ -1829,7 +1838,7 @@ _pystat_fromstructstat(STRUCT_STAT *st) } static PyObject * -posix_do_stat(PyObject *self, PyObject *args, +posix_do_stat(PyObject *self, PyObject *args, PyObject *kw, char *format, #ifdef __VMS int (*statfunc)(const char *, STRUCT_STAT *, ...), @@ -1839,15 +1848,18 @@ posix_do_stat(PyObject *self, PyObject * char *wformat, int (*wstatfunc)(const wchar_t *, STRUCT_STAT *)) { + static char *kwlist[] = {"path", "timestamp", NULL}; STRUCT_STAT st; PyObject *opath; char *path; int res; PyObject *result; + PyObject *timestamp = NULL; #ifdef MS_WINDOWS PyObject *po; - if (PyArg_ParseTuple(args, wformat, &po)) { + if (PyArg_ParseTupleAndKeywords(args, kw, wformat, kwlist, + &po, ×tamp)) { wchar_t *wpath = PyUnicode_AsUnicode(po); if (wpath == NULL) return NULL; @@ -1858,15 +1870,17 @@ posix_do_stat(PyObject *self, PyObject * if (res != 0) return win32_error_object("stat", po); - return _pystat_fromstructstat(&st); + return _pystat_fromstructstat(&st, timestamp); } /* Drop the argument parsing error as narrow strings are also valid. */ PyErr_Clear(); -#endif - - if (!PyArg_ParseTuple(args, format, - PyUnicode_FSConverter, &opath)) + timestamp = NULL; +#endif + + if (!PyArg_ParseTupleAndKeywords(args, kw, format, kwlist, + PyUnicode_FSConverter, &opath, + ×tamp)) return NULL; #ifdef MS_WINDOWS if (win32_warn_bytes_api()) { @@ -1887,7 +1901,7 @@ posix_do_stat(PyObject *self, PyObject * #endif } else - result = _pystat_fromstructstat(&st); + result = _pystat_fromstructstat(&st, timestamp); Py_DECREF(opath); return result; @@ -3362,12 +3376,12 @@ PyDoc_STRVAR(posix_stat__doc__, Perform a stat system call on the given path."); static PyObject * -posix_stat(PyObject *self, PyObject *args) +posix_stat(PyObject *self, PyObject *args, PyObject *kw) { #ifdef MS_WINDOWS - return posix_do_stat(self, args, "O&:stat", STAT, "U:stat", win32_stat_w); + return posix_do_stat(self, args, kw, "O&|O:stat", STAT, "U:stat", win32_stat_w); #else - return posix_do_stat(self, args, "O&:stat", STAT, NULL, NULL); + return posix_do_stat(self, args, kw, "O&|O:stat", STAT, NULL, NULL); #endif } @@ -6367,16 +6381,16 @@ PyDoc_STRVAR(posix_lstat__doc__, Like stat(path), but do not follow symbolic links."); static PyObject * -posix_lstat(PyObject *self, PyObject *args) +posix_lstat(PyObject *self, PyObject *args, PyObject *kw) { #ifdef HAVE_LSTAT - return posix_do_stat(self, args, "O&:lstat", lstat, NULL, NULL); + return posix_do_stat(self, args, kw, "O&|O:lstat", lstat, NULL, NULL); #else /* !HAVE_LSTAT */ #ifdef MS_WINDOWS - return posix_do_stat(self, args, "O&:lstat", win32_lstat, "U:lstat", + return posix_do_stat(self, args, kw, "O&|O:lstat", win32_lstat, "U|O:lstat", win32_lstat_w); #else - return posix_do_stat(self, args, "O&:lstat", STAT, NULL, NULL); + return posix_do_stat(self, args, "kw, O&|O:lstat", STAT, NULL, NULL); #endif #endif /* !HAVE_LSTAT */ } @@ -7339,12 +7353,15 @@ PyDoc_STRVAR(posix_fstat__doc__, Like stat(), but for an open file descriptor."); static PyObject * -posix_fstat(PyObject *self, PyObject *args) -{ +posix_fstat(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"fd", "timestamp", NULL}; int fd; STRUCT_STAT st; int res; - if (!PyArg_ParseTuple(args, "i:fstat", &fd)) + PyObject *timestamp = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|O:fstat", kwlist, + &fd, ×tamp)) return NULL; #ifdef __VMS /* on OpenVMS we must ensure that all bytes are written to the file */ @@ -7363,7 +7380,7 @@ posix_fstat(PyObject *self, PyObject *ar #endif } - return _pystat_fromstructstat(&st); + return _pystat_fromstructstat(&st, timestamp); } PyDoc_STRVAR(posix_isatty__doc__, @@ -9654,15 +9671,18 @@ If path is relative and dirfd is the spe is interpreted relative to the current working directory."); static PyObject * -posix_fstatat(PyObject *self, PyObject *args) -{ +posix_fstatat(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"dirfd", "path", "flags", "timestamp", NULL}; PyObject *opath; char *path; STRUCT_STAT st; int dirfd, res, flags = 0; - - if (!PyArg_ParseTuple(args, "iO&|i:fstatat", - &dirfd, PyUnicode_FSConverter, &opath, &flags)) + PyObject *timestamp = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iO&|iO:fstatat", kwlist, + &dirfd, PyUnicode_FSConverter, &opath, + &flags, ×tamp)) return NULL; path = PyBytes_AsString(opath); @@ -9673,7 +9693,7 @@ posix_fstatat(PyObject *self, PyObject * if (res != 0) return posix_error(); - return _pystat_fromstructstat(&st); + return _pystat_fromstructstat(&st, timestamp); } #endif @@ -10537,7 +10557,7 @@ static PyMethodDef posix_methods[] = { #ifdef HAVE_FDOPENDIR {"fdlistdir", posix_fdlistdir, METH_VARARGS, posix_fdlistdir__doc__}, #endif - {"lstat", posix_lstat, METH_VARARGS, posix_lstat__doc__}, + {"lstat", (PyCFunction)posix_lstat, METH_VARARGS | METH_KEYWORDS, posix_lstat__doc__}, {"mkdir", posix_mkdir, METH_VARARGS, posix_mkdir__doc__}, #ifdef HAVE_NICE {"nice", posix_nice, METH_VARARGS, posix_nice__doc__}, @@ -10556,7 +10576,7 @@ static PyMethodDef posix_methods[] = { #endif /* !defined(HAVE_READLINK) && defined(MS_WINDOWS) */ {"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", (PyCFunction)posix_stat, METH_VARARGS | METH_KEYWORDS, posix_stat__doc__}, {"stat_float_times", stat_float_times, METH_VARARGS, stat_float_times__doc__}, #if defined(HAVE_SYMLINK) && !defined(MS_WINDOWS) {"symlink", posix_symlink, METH_VARARGS, posix_symlink__doc__}, @@ -10771,7 +10791,8 @@ static PyMethodDef posix_methods[] = { {"sendfile", (PyCFunction)posix_sendfile, METH_VARARGS | METH_KEYWORDS, posix_sendfile__doc__}, #endif - {"fstat", posix_fstat, METH_VARARGS, posix_fstat__doc__}, + {"fstat", (PyCFunction)posix_fstat, METH_VARARGS | METH_KEYWORDS, + posix_fstat__doc__}, {"isatty", posix_isatty, METH_VARARGS, posix_isatty__doc__}, #ifdef HAVE_PIPE {"pipe", posix_pipe, METH_NOARGS, posix_pipe__doc__}, @@ -10906,7 +10927,8 @@ static PyMethodDef posix_methods[] = { {"fchownat", posix_fchownat, METH_VARARGS, posix_fchownat__doc__}, #endif /* HAVE_FCHOWNAT */ #ifdef HAVE_FSTATAT - {"fstatat", posix_fstatat, METH_VARARGS, posix_fstatat__doc__}, + {"fstatat", (PyCFunction)posix_fstatat, METH_VARARGS | METH_KEYWORDS, + posix_fstatat__doc__}, #endif #ifdef HAVE_FUTIMESAT {"futimesat", posix_futimesat, METH_VARARGS, posix_futimesat__doc__}, diff --git a/Modules/timemodule.c b/Modules/timemodule.c --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -40,20 +40,26 @@ #include #endif +#if (defined(MS_WINDOWS) && !defined(__BORLANDC__)) || defined(HAVE_CLOCK) +# define HAVE_PYCLOCK +#endif + /* Forward declarations */ static int floatsleep(double); -static double floattime(void); static PyObject * -time_time(PyObject *self, PyObject *unused) +time_time(PyObject *self, PyObject *args, PyObject *kwargs) { - double secs; - secs = floattime(); - if (secs == 0.0) { - PyErr_SetFromErrno(PyExc_IOError); + static char *kwlist[] = {"format", NULL}; + PyObject *format = NULL; + _PyTime_t ts; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O:time", kwlist, + &format)) return NULL; - } - return PyFloat_FromDouble(secs); + + _PyTime_get(&ts); + return _PyTime_AsFormat(&ts, format); } PyDoc_STRVAR(time_doc, @@ -72,55 +78,80 @@ Fractions of a second may be present if #endif #endif -static PyObject * -pyclock(void) +static int +pyclock(_PyTime_t *ts) { - clock_t value; - value = clock(); - if (value == (clock_t)-1) { + clock_t processor_time; + processor_time = clock(); + if (processor_time == (clock_t)-1) { PyErr_SetString(PyExc_RuntimeError, "the processor time used is not available " "or its value cannot be represented"); - return NULL; + return -1; } - return PyFloat_FromDouble((double)value / CLOCKS_PER_SEC); + ts->seconds = 0; + assert(sizeof(clock_t) <= sizeof(size_t)); + ts->floatpart = Py_SAFE_DOWNCAST(processor_time, clock_t, size_t); + ts->divisor = CLOCKS_PER_SEC; + return 0; } #endif /* HAVE_CLOCK */ #if defined(MS_WINDOWS) && !defined(__BORLANDC__) /* Win32 has better clock replacement; we have our own version, due to Mark Hammond and Tim Peters */ -static PyObject * -time_clock(PyObject *self, PyObject *unused) +static int +win32_pyclock(_PyTime_t *ts) { static LARGE_INTEGER ctrStart; - static double divisor = 0.0; + static LONGLONG cpu_frequency = 0; LARGE_INTEGER now; - double diff; + LONGLONG dt; - if (divisor == 0.0) { + if (cpu_frequency == 0) { LARGE_INTEGER freq; QueryPerformanceCounter(&ctrStart); - if (!QueryPerformanceFrequency(&freq) || freq.QuadPart == 0) { + if (!QueryPerformanceFrequency(&freq) || freq.QuadPart == 0) + { /* Unlikely to happen - this works on all intel machines at least! Revert to clock() */ - return pyclock(); + return pyclock(ts); } - divisor = (double)freq.QuadPart; + cpu_frequency = freq.QuadPart; } QueryPerformanceCounter(&now); - diff = (double)(now.QuadPart - ctrStart.QuadPart); - return PyFloat_FromDouble(diff / divisor); + dt = now.QuadPart - ctrStart.QuadPart; + + ts->seconds = 0; + assert(sizeof(LONGLONG) <= sizeof(size_t)); + ts->floatpart = Py_SAFE_DOWNCAST(dt, LONGLONG, size_t); + ts->divisor = Py_SAFE_DOWNCAST(cpu_frequency, LONGLONG, size_t); + return 0; } +#endif -#elif defined(HAVE_CLOCK) +#ifdef HAVE_PYCLOCK +static PyObject * +time_clock(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"format", NULL}; + _PyTime_t ts; + PyObject *format = NULL; -static PyObject * -time_clock(PyObject *self, PyObject *unused) -{ - return pyclock(); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O:clock", kwlist, + &format)) + return NULL; + +#if defined(MS_WINDOWS) && !defined(__BORLANDC__) + if (win32_pyclock(&ts)) + return NULL; +#else + if (pyclock(&ts)) + return NULL; +#endif + return _PyTime_AsFormat(&ts, format); } -#endif /* HAVE_CLOCK */ +#endif #ifdef HAVE_CLOCK @@ -134,13 +165,17 @@ records."); #ifdef HAVE_CLOCK_GETTIME static PyObject * -time_clock_gettime(PyObject *self, PyObject *args) +time_clock_gettime(PyObject *self, PyObject *args, PyObject *kwargs) { + static char *kwlist[] = {"clk_id", "format", NULL}; + PyObject *format = NULL; int ret; clockid_t clk_id; struct timespec tp; + _PyTime_t ts; - if (!PyArg_ParseTuple(args, "i:clock_gettime", &clk_id)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|O:clock_gettime", kwlist, + &clk_id, &format)) return NULL; ret = clock_gettime((clockid_t)clk_id, &tp); @@ -148,8 +183,10 @@ time_clock_gettime(PyObject *self, PyObj PyErr_SetFromErrno(PyExc_IOError); return NULL; } - - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); + ts.seconds = tp.tv_sec; + ts.floatpart = tp.tv_nsec; + ts.divisor = 1000000000; + return _PyTime_AsFormat(&ts, format); } PyDoc_STRVAR(clock_gettime_doc, @@ -160,13 +197,17 @@ Return the time of the specified clock c #ifdef HAVE_CLOCK_GETRES static PyObject * -time_clock_getres(PyObject *self, PyObject *args) +time_clock_getres(PyObject *self, PyObject *args, PyObject *kwargs) { + static char *kwlist[] = {"clk_id", "format", NULL}; + PyObject *format = NULL; int ret; clockid_t clk_id; struct timespec tp; + _PyTime_t ts; - if (!PyArg_ParseTuple(args, "i:clock_getres", &clk_id)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|O:clock_getres", kwlist, + &clk_id, &format)) return NULL; ret = clock_getres((clockid_t)clk_id, &tp); @@ -174,8 +215,10 @@ time_clock_getres(PyObject *self, PyObje PyErr_SetFromErrno(PyExc_IOError); return NULL; } - - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); + ts.seconds = tp.tv_sec; + ts.floatpart = tp.tv_nsec; + ts.divisor = 1000000000; + return _PyTime_AsFormat(&ts, format); } PyDoc_STRVAR(clock_getres_doc, @@ -763,12 +806,14 @@ the local timezone used by methods such should not be relied on."); #endif /* HAVE_WORKING_TZSET */ -static PyObject * -time_wallclock(PyObject *self, PyObject *unused) +static int +pywallclock(_PyTime_t *ts) { #if defined(MS_WINDOWS) && !defined(__BORLANDC__) - return time_clock(self, NULL); -#elif defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + return win32_pyclock(ts); +#else + +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) static int clk_index = 0; clockid_t clk_ids[] = { #ifdef CLOCK_MONOTONIC_RAW @@ -788,18 +833,38 @@ time_wallclock(PyObject *self, PyObject clockid_t clk_id = clk_ids[clk_index]; ret = clock_gettime(clk_id, &tp); if (ret == 0) - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); + { + ts->seconds = tp.tv_sec; + ts->floatpart = tp.tv_nsec; + ts->divisor = 1000000000; + return 0; + } clk_index++; if (Py_ARRAY_LENGTH(clk_ids) <= clk_index) clk_index = -1; } - return time_time(self, NULL); -#else - return time_time(self, NULL); +#endif /* defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) */ + _PyTime_get(ts); + return 0; #endif } +static PyObject * +time_wallclock(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"format", NULL}; + PyObject *format = NULL; + _PyTime_t ts; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O:wallclock", kwlist, + &format)) + return NULL; + if (pywallclock(&ts)) + return NULL; + return _PyTime_AsFormat(&ts, format); +} + PyDoc_STRVAR(wallclock_doc, "wallclock() -> float\n\ \n\ @@ -919,15 +984,17 @@ PyInit_timezone(PyObject *m) { static PyMethodDef time_methods[] = { - {"time", time_time, METH_NOARGS, time_doc}, -#if (defined(MS_WINDOWS) && !defined(__BORLANDC__)) || defined(HAVE_CLOCK) - {"clock", time_clock, METH_NOARGS, clock_doc}, + {"time", (PyCFunction)time_time, METH_VARARGS | METH_KEYWORDS, time_doc}, +#ifdef HAVE_PYCLOCK + {"clock", (PyCFunction)time_clock, METH_VARARGS | METH_KEYWORDS, clock_doc}, #endif #ifdef HAVE_CLOCK_GETTIME - {"clock_gettime", time_clock_gettime, METH_VARARGS, clock_gettime_doc}, + {"clock_gettime", (PyCFunction)time_clock_gettime, + METH_VARARGS | METH_KEYWORDS, clock_gettime_doc}, #endif #ifdef HAVE_CLOCK_GETRES - {"clock_getres", time_clock_getres, METH_VARARGS, clock_getres_doc}, + {"clock_getres", (PyCFunction)time_clock_getres, + METH_VARARGS | METH_KEYWORDS, clock_getres_doc}, #endif {"sleep", time_sleep, METH_VARARGS, sleep_doc}, {"gmtime", time_gmtime, METH_VARARGS, gmtime_doc}, @@ -944,7 +1011,8 @@ static PyMethodDef time_methods[] = { #ifdef HAVE_WORKING_TZSET {"tzset", time_tzset, METH_NOARGS, tzset_doc}, #endif - {"wallclock", time_wallclock, METH_NOARGS, wallclock_doc}, + {"wallclock", (PyCFunction)time_wallclock, + METH_VARARGS | METH_KEYWORDS, wallclock_doc}, {NULL, NULL} /* sentinel */ }; @@ -1029,15 +1097,6 @@ PyInit_time(void) return m; } -static double -floattime(void) -{ - _PyTime_timeval t; - _PyTime_gettimeofday(&t); - return (double)t.tv_sec + t.tv_usec*0.000001; -} - - /* Implement floatsleep() for various platforms. When interrupted (or when another error occurs), return -1 and set an exception; else return 0. */ diff --git a/Python/pytime.c b/Python/pytime.c --- a/Python/pytime.c +++ b/Python/pytime.c @@ -19,8 +19,14 @@ extern int ftime(struct timeb *); #endif /* MS_WINDOWS */ #endif /* HAVE_FTIME */ +#define MILLISECONDS 1000 +#define MICROSECONDS 1000000 +#define NANOSECONDS 1000000000 + +static PyObject* datetime_module = NULL; + void -_PyTime_gettimeofday(_PyTime_timeval *tp) +_PyTime_get(_PyTime_t *ts) { /* There are three ways to get the time: (1) gettimeofday() -- resolution in microseconds @@ -30,27 +36,409 @@ _PyTime_gettimeofday(_PyTime_timeval *tp Since on some systems (e.g. SCO ODT 3.0) gettimeofday() may fail, so we fall back on ftime() or time(). Note: clock resolution does not imply clock accuracy! */ + +#ifdef HAVE_GETTIMEOFDAY + struct timeval tv; + int err; +#endif +#if defined(HAVE_FTIME) + struct timeb t; +#endif + #ifdef HAVE_GETTIMEOFDAY #ifdef GETTIMEOFDAY_NO_TZ - if (gettimeofday(tp) == 0) + err = gettimeofday(&tv); +#else /* !GETTIMEOFDAY_NO_TZ */ + err = gettimeofday(&tv, (struct timezone *)NULL); +#endif /* !GETTIMEOFDAY_NO_TZ */ + if (err == 0) + { + ts->seconds = tv.tv_sec; + ts->floatpart = tv.tv_usec; + ts->divisor = MICROSECONDS; return; -#else /* !GETTIMEOFDAY_NO_TZ */ - if (gettimeofday(tp, (struct timezone *)NULL) == 0) - return; -#endif /* !GETTIMEOFDAY_NO_TZ */ + } #endif /* !HAVE_GETTIMEOFDAY */ + #if defined(HAVE_FTIME) + ftime(&t); + ts->seconds = t.time; + ts->floatpart = t.millitm; + ts->divisor = MILLISECONDS; +#else /* !HAVE_FTIME */ + ts->seconds = time(NULL); + ts->floatpart = 0; + ts->divisor = 1; +#endif /* !HAVE_FTIME */ +} + +void +_PyTime_gettimeofday(_PyTime_timeval *tv) +{ + _PyTime_t ts; + size_t k; + + _PyTime_get(&ts); + tv->tv_sec = ts.seconds; + if (ts.floatpart) { + if (ts.floatpart > ts.divisor) { + tv->tv_sec += ts.floatpart / ts.divisor; + ts.floatpart = ts.floatpart % ts.divisor; + } + if (MICROSECONDS >= ts.divisor) { + k = MICROSECONDS / ts.divisor; + tv->tv_usec = ts.floatpart * k; + } + else { + k = ts.divisor / MICROSECONDS; + tv->tv_usec = ts.floatpart / k; + } + } + else { + tv->tv_usec = 0; + } +} + +static PyObject* +PyLong_FromTime_t(time_t value) +{ +#ifdef SIZEOF_TIME_T <= SIZEOF_LONG + return PyLong_FromLong(value); +#else + assert(sizeof(time_t) <= sizeof(PY_LONG_LONG)); + return PyLong_FromLongLong(value); +#endif +} + +/* Convert a timestamp to a PyFloat object */ +static PyObject* +_PyTime_AsFloat(_PyTime_t *ts) +{ + double d; + d = (double)ts->seconds; + d += (double)ts->floatpart / (double)ts->divisor; + return PyFloat_FromDouble(d); +} + +/* Convert a timestamp to a PyLong object */ +static PyObject* +_PyTime_AsLong(_PyTime_t *ts) +{ + PyObject *a, *b, *c; + + a = PyLong_FromTime_t(ts->seconds); + if (a == NULL) + return NULL; + b = PyLong_FromSize_t(ts->floatpart / ts->divisor); + if (b == NULL) { - struct timeb t; - ftime(&t); - tp->tv_sec = t.time; - tp->tv_usec = t.millitm * 1000; + Py_DECREF(a); + return NULL; } -#else /* !HAVE_FTIME */ - tp->tv_sec = time(NULL); - tp->tv_usec = 0; -#endif /* !HAVE_FTIME */ - return; + c = PyNumber_Add(a, b); + Py_DECREF(a); + Py_DECREF(b); + return c; +} + +/* Convert a timestamp to a decimal.Decimal object */ +static PyObject* +_PyTime_AsDecimal(_PyTime_t *ts) +{ + static PyObject* module = NULL; + static PyObject* decimal = NULL; + static PyObject* context = NULL; + /* resolution cache (1/divisor), dictionary of int (divisor)=>Decimal */ + static PyObject* exponents = NULL; + PyObject *t = NULL; + PyObject *key, *exponent, *quantized; + _Py_IDENTIFIER(quantize); + _Py_IDENTIFIER(__truediv__); + + if (!module) { + module = PyImport_ImportModuleNoBlock("decimal"); + if (module == NULL) + return NULL; + } + + if (!decimal) { + decimal = PyObject_GetAttrString(module, "Decimal"); + if (decimal == NULL) + return NULL; + } + + if (context == NULL) + { + /* context = decimal.Context(22, rounding=decimal.ROUND_HALF_EVEN) */ + PyObject *context_class, *rounding; + /* Use 12 decimal digits to store 10,000 years in seconds + 9 + decimal digits for the floating part in nanoseconds + 1 decimal + digit to round correctly */ + context_class = PyObject_GetAttrString(module, "Context"); + if (context_class == NULL) + return NULL; + rounding = PyObject_GetAttrString(module, "ROUND_HALF_EVEN"); + if (rounding == NULL) + { + Py_DECREF(context_class); + return NULL; + } + context = PyObject_CallFunction(context_class, "iO", 22, rounding); + Py_DECREF(context_class); + Py_DECREF(rounding); + if (context == NULL) + return NULL; + } + + /* t = decimal.Decimal(value, context) */ + if (ts->seconds) { + PyObject *f = PyLong_FromTime_t(ts->seconds); + t = PyObject_CallFunction(decimal, "OO", f, context); + Py_CLEAR(f); + } + else { + t = PyObject_CallFunction(decimal, "iO", 0, context); + } + if (t == NULL) + return NULL; + + if (ts->floatpart) + { + /* t += decimal.Decimal(floatpart, ctx) / decimal.Decimal(divisor, ctx) */ + PyObject *a, *b, *c, *d, *x; + + x = PyLong_FromSize_t(ts->floatpart); + if (x == NULL) + goto error; + a = PyObject_CallFunction(decimal, "OO", x, context); + Py_CLEAR(x); + if (a == NULL) + goto error; + + x = PyLong_FromSize_t(ts->divisor); + if (x == NULL) + { + Py_DECREF(a); + goto error; + } + b = PyObject_CallFunction(decimal, "OO", x, context); + Py_CLEAR(x); + if (b == NULL) + { + Py_DECREF(a); + goto error; + } + + c = _PyObject_CallMethodId(a, &PyId___truediv__, "OO", + b, context); + Py_DECREF(a); + Py_DECREF(b); + if (c == NULL) + goto error; + + d = PyNumber_Add(t, c); + Py_DECREF(c); + if (d == NULL) + goto error; + Py_DECREF(t); + t = d; + } + + if (exponents == NULL) { + exponents = PyDict_New(); + if (exponents == NULL) + goto error; + } + + key = PyLong_FromSize_t(ts->divisor); + if (key == NULL) + goto error; + exponent = PyDict_GetItem(exponents, key); + if (exponent == NULL) { + /* exponent = decimal.Decimal(1) / decimal.Decimal(resolution) */ + PyObject *one, *divisor; + + one = PyObject_CallFunction(decimal, "i", 1); + if (one == NULL) { + Py_DECREF(key); + goto error; + } + + divisor = PyObject_CallFunction(decimal, "O", key); + if (divisor == NULL) { + Py_DECREF(key); + Py_DECREF(one); + goto error; + } + + exponent = _PyObject_CallMethodId(one, &PyId___truediv__, "OO", + divisor, context); + Py_DECREF(one); + Py_DECREF(divisor); + if (exponent == NULL) { + Py_DECREF(key); + goto error; + } + + if (PyDict_SetItem(exponents, key, exponent) < 0) { + Py_DECREF(key); + Py_DECREF(exponent); + goto error; + } + Py_DECREF(key); + } + + /* t = t.quantize(exponent, None, context) */ + quantized = _PyObject_CallMethodId(t, &PyId_quantize, "OOO", + exponent, Py_None, context); + if (quantized == NULL) + goto error; + Py_DECREF(t); + t = quantized; + + return t; + +error: + Py_XDECREF(t); + return NULL; +} + +/* Convert a timestamp to a datetime.datetime object */ +PyObject* +_PyTime_AsDatetime(_PyTime_t *ts) +{ + static PyObject* datetime = NULL; + _Py_IDENTIFIER(fromtimestamp); + PyObject *decimal, *result; + + if (!datetime_module) { + datetime_module = PyImport_ImportModuleNoBlock("datetime"); + if (datetime_module == NULL) + return NULL; + } + + if (!datetime) { + datetime = PyObject_GetAttrString(datetime_module, "datetime"); + if (datetime == NULL) + return NULL; + } + + decimal = _PyTime_AsDecimal(ts); + if (decimal == NULL) + return NULL; + result = _PyObject_CallMethodId(datetime, &PyId_fromtimestamp, + "O", decimal); + Py_DECREF(decimal); + return result; +} + +/* Convert a timestamp to a datetime.timedelta object */ +PyObject* +_PyTime_AsTimedelta(_PyTime_t *ts) +{ + static PyObject* timedelta = NULL; + unsigned long microseconds; + PyObject *seconds_obj, *microseconds_obj, *result; + + if (!datetime_module) { + datetime_module = PyImport_ImportModuleNoBlock("datetime"); + if (datetime_module == NULL) + return NULL; + } + + if (!timedelta) { + timedelta = PyObject_GetAttrString(datetime_module, "timedelta"); + if (timedelta == NULL) + return NULL; + } + + seconds_obj = PyLong_FromTime_t(ts->seconds); + if (seconds_obj == NULL) + return NULL; + if (ts->floatpart) { + size_t k; + if (ts->floatpart > ts->divisor) { + PyObject *b, *c; + b = PyLong_FromSize_t(ts->floatpart / ts->divisor); + ts->floatpart = ts->floatpart % ts->divisor; + if (b == NULL) { + Py_DECREF(seconds_obj); + return NULL; + } + c = PyNumber_Add(seconds_obj, b); + Py_DECREF(seconds_obj); + Py_DECREF(b); + seconds_obj = c; + } + if (MICROSECONDS >= ts->divisor) { + k = MICROSECONDS / ts->divisor; + microseconds = Py_SAFE_DOWNCAST(ts->floatpart * k, + size_t, unsigned long); + } + else { + k = ts->divisor / MICROSECONDS; + microseconds = Py_SAFE_DOWNCAST(ts->floatpart / k, + size_t, unsigned long); + } + } + else { + microseconds = 0; + } + microseconds_obj = PyLong_FromSize_t(microseconds); + if (microseconds_obj == NULL) { + Py_DECREF(seconds_obj); + return NULL; + } + result = PyObject_CallFunction(timedelta, "iOO", + 0, seconds_obj, microseconds_obj); + Py_DECREF(seconds_obj); + Py_DECREF(microseconds_obj); + return result; +} + +/* Convert a timestamp to the specified format. + + The result type depends on the requested format. + + Raise a ValueError if the format is unknown. */ +PyObject* +_PyTime_AsFormat(_PyTime_t *ts, PyObject *format) +{ + assert(ts->divisor != 0); + + if (format == NULL || (PyTypeObject *)format == &PyFloat_Type) + return _PyTime_AsFloat(ts); + if ((PyTypeObject *)format == &PyLong_Type) + return _PyTime_AsLong(ts); + + if (PyType_Check(format)) + { + PyObject *module, *name; + _Py_IDENTIFIER(__name__); + _Py_IDENTIFIER(__module__); + + module = _PyObject_GetAttrId(format, &PyId___module__); + name = _PyObject_GetAttrId(format, &PyId___name__); + if (module != NULL && PyUnicode_Check(module) + && name != NULL && PyUnicode_Check(name)) + { + if (PyUnicode_CompareWithASCIIString(module, "decimal") == 0 + && PyUnicode_CompareWithASCIIString(name, "Decimal") == 0) + return _PyTime_AsDecimal(ts); + if (PyUnicode_CompareWithASCIIString(module, "datetime") == 0) + { + if (PyUnicode_CompareWithASCIIString(name, "datetime") == 0) + return _PyTime_AsDatetime(ts); + if (PyUnicode_CompareWithASCIIString(name, "timedelta") == 0) + return _PyTime_AsTimedelta(ts); + } + } + else + PyErr_Clear(); + } + + PyErr_Format(PyExc_ValueError, "Unknown timestamp format: %R", format); + return NULL; } void