diff --git a/Doc/library/time.rst b/Doc/library/time.rst --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -431,9 +431,10 @@ 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, + Return the time as a floating point number, or a :class:`decimal.Decimal` if + format is ``"decimal"``, expressed in seconds since the epoch, in UTC. 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 diff --git a/Include/pytime.h b/Include/pytime.h --- a/Include/pytime.h +++ b/Include/pytime.h @@ -37,6 +37,25 @@ 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; + /* log10 of the clock resoltion, the real resolution is 10^resolution, + resolution is in range [-9; 0] */ + int resolution; +} _PyTime_t; + +#ifdef HAVE_CLOCK_GETTIME +PyAPI_FUNC(void) _PyTime_FromTimespec(_PyTime_t *self, struct timespec *ts); +#endif + +/* Similar to POSIX gettimeofday. If system gettimeofday + fails or is not available, fall back to lower resolution clocks. */ +PyAPI_FUNC(int) _PyTime_get(_PyTime_t *tp); + /* Dummy to force linking. */ PyAPI_FUNC(void) _PyTime_Init(void); 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,11 @@ from test import support +import decimal +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 +332,23 @@ class TimeTestCase(unittest.TestCase): pass self.assertEqual(time.strftime('%Z', tt), tzname) + def test_format(self): + 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 + ["xxx"])) + # test decimal + timestamp = func(*(args + ["decimal"])) + self.assertIsInstance(timestamp, decimal.Decimal) + def test_wallclock(self): t1 = time.wallclock() t2 = time.wallclock() diff --git a/Modules/timemodule.c b/Modules/timemodule.c --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -40,56 +40,264 @@ #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); + +typedef unsigned long floatpart_t; + +static PyObject* +PyLong_FromTimeT(time_t value) +{ +#ifdef HAVE_LONG_LONG + return PyLong_FromUnsignedLongLong(value); +#else + assert(sizeof(time_t) <= sizeof(unsigned long)); + return PyLong_FromUnsignedLong(value); +#endif +} + +static PyObject* +PyLong_FromFloatpartT(floatpart_t value) +{ +#ifdef HAVE_LONG_LONG + return PyLong_FromUnsignedLongLong(value); +#else + assert(sizeof(floatpart_t) <= sizeof(unsigned long)); + return PyLong_FromUnsignedLong(value); +#endif +} + +/* Convert a timestamp to double */ +double +_PyTime_AsDouble(_PyTime_t *ts) +{ + double t; + t = (double)ts->seconds; + t += (double)ts->floatpart / (double)ts->divisor; + return t; +} + + +/* 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; + /* 10^resolution cache, dictionary of int=>Decimal */ + static PyObject* exponents = NULL; + PyObject *t = NULL; + PyObject *key, *exponent, *quantized; + + 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_FromTimeT(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_FromFloatpartT(ts->floatpart); + if (x == NULL) + goto error; + a = PyObject_CallFunction(decimal, "OO", x, context); + Py_CLEAR(x); + if (a == NULL) + goto error; + + x = PyLong_FromFloatpartT(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 = PyNumber_TrueDivide(a, b); + 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_FromLong(ts->resolution); + if (key == NULL) + goto error; + exponent = PyDict_GetItem(exponents, key); + if (exponent == NULL) { + /* exponent = decimal.Decimal(10) ** decimal.Decimal(resolution) */ + PyObject *ten, *pow; + + ten = PyObject_CallFunction(decimal, "i", 10); + if (ten == NULL) { + Py_DECREF(key); + goto error; + } + + pow = PyObject_CallFunction(decimal, "O", key); + if (pow == NULL) { + Py_DECREF(key); + Py_DECREF(ten); + goto error; + } + + exponent = PyNumber_Power(ten, pow, Py_None); + Py_DECREF(ten); + Py_DECREF(pow); + 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_CallMethod(t, "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 the specified format. + + The result type depends on the requested format. + + Raise a ValueError if the format is unknown. */ +static PyObject* +_PyTime_AsFormat(_PyTime_t *ts, const char *format) +{ + assert(ts->divisor != 0); + assert(-9 <= ts->resolution && ts->resolution <= 0); + if (!format || strcmp(format, "float") == 0) { + double t = _PyTime_AsDouble(ts); + return PyFloat_FromDouble(t); + } + else if (strcmp(format, "decimal") == 0) { + return _PyTime_AsDecimal(ts); + } + else { + PyErr_Format(PyExc_ValueError, "Unknown format: %s", format); + return NULL; + } +} static PyObject * -time_time(PyObject *self, PyObject *unused) +time_time(PyObject *self, PyObject *args) { - double secs; - secs = floattime(); - if (secs == 0.0) { - PyErr_SetFromErrno(PyExc_IOError); + const char *format = NULL; + _PyTime_t ts; + + if (!PyArg_ParseTuple(args, "|s:time", &format)) return NULL; - } - return PyFloat_FromDouble(secs); + + if (_PyTime_get(&ts) == -1) + return NULL; + return _PyTime_AsFormat(&ts, format); } + PyDoc_STRVAR(time_doc, "time() -> floating point number\n\ \n\ Return the current time in seconds since the Epoch.\n\ Fractions of a second may be present if the system clock provides them."); -#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) + +/* Compute -floor(log(x) / log(10)) */ +static int +int_log10(size_t x) { - static LARGE_INTEGER ctrStart; - static double divisor = 0.0; - LARGE_INTEGER now; - double diff; - - if (divisor == 0.0) { - LARGE_INTEGER freq; - QueryPerformanceCounter(&ctrStart); - if (!QueryPerformanceFrequency(&freq) || freq.QuadPart == 0) { - /* Unlikely to happen - this works on all intel - machines at least! Revert to clock() */ - return PyFloat_FromDouble(((double)clock()) / - CLOCKS_PER_SEC); - } - divisor = (double)freq.QuadPart; + int log10; + log10 = 0; + while (x >= 10) { + log10 -= 1; + x /= 10; } - QueryPerformanceCounter(&now); - diff = (double)(now.QuadPart - ctrStart.QuadPart); - return PyFloat_FromDouble(diff / divisor); + return log10; } -#elif defined(HAVE_CLOCK) +#ifdef HAVE_CLOCK #ifndef CLOCKS_PER_SEC #ifdef CLK_TCK @@ -99,13 +307,83 @@ time_clock(PyObject *self, PyObject *unu #endif #endif -static PyObject * -time_clock(PyObject *self, PyObject *unused) +static int +pyclock(_PyTime_t *ts) { - return PyFloat_FromDouble(((double)clock()) / CLOCKS_PER_SEC); + clock_t processor_time; + static int resolution = 1; + 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 -1; + } + if (resolution == 1) + resolution = int_log10(CLOCKS_PER_SEC); + ts->seconds = 0; + ts->floatpart = Py_SAFE_DOWNCAST(processor_time, clock_t, size_t); + ts->divisor = CLOCKS_PER_SEC; + ts->resolution = resolution ; + 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 int +win32_pyclock(_PyTime_t *ts) +{ + static LARGE_INTEGER ctrStart; + static LARGE_INTEGER cpu_frequency = 0; + static int resolution; + LARGE_INTEGER now; + double diff; + + if (cpu_frequency == 0) { + LARGE_INTEGER freq; + QueryPerformanceCounter(&ctrStart); + if (!QueryPerformanceFrequency(&freq) || freq.QuadPart == 0) + { + /* Unlikely to happen - this works on all intel + machines at least! Revert to clock() */ + return pyclock(ts); + } + cpu_frequency = freq.QuadPart; + resolution = int_log10(Py_SAFE_DOWNCAST(cpu_frequency, + LARGE_INTEGER, size_t)); + } + QueryPerformanceCounter(&now); + ts->value = 0; + ts->floatpart = Py_SAFE_DOWNCAST(now.QuadPart - ctrStart.QuadPart, + LARGE_INTEGER, size_t); + ts->divisor = cpu_frequency; + ts->resolution = resolution; + return 0; +} +#endif + +#ifdef HAVE_PYCLOCK +static PyObject * +time_clock(PyObject *self, PyObject *args) +{ + _PyTime_t ts; + const char *format = NULL; + + if (!PyArg_ParseTuple(args, "|s:clock", &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 #ifdef HAVE_CLOCK PyDoc_STRVAR(clock_doc, @@ -120,11 +398,13 @@ records."); static PyObject * time_clock_gettime(PyObject *self, PyObject *args) { + const char *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_ParseTuple(args, "i|s:clock_gettime", &clk_id, &format)) return NULL; ret = clock_gettime((clockid_t)clk_id, &tp); @@ -132,8 +412,8 @@ time_clock_gettime(PyObject *self, PyObj PyErr_SetFromErrno(PyExc_IOError); return NULL; } - - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); + _PyTime_FromTimespec(&ts, &tp); + return _PyTime_AsFormat(&ts, format); } PyDoc_STRVAR(clock_gettime_doc, @@ -146,11 +426,13 @@ Return the time of the specified clock c static PyObject * time_clock_getres(PyObject *self, PyObject *args) { + const char *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_ParseTuple(args, "i|s:clock_getres", &clk_id, &format)) return NULL; ret = clock_getres((clockid_t)clk_id, &tp); @@ -158,8 +440,8 @@ time_clock_getres(PyObject *self, PyObje PyErr_SetFromErrno(PyExc_IOError); return NULL; } - - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); + _PyTime_FromTimespec(&ts, &tp); + return _PyTime_AsFormat(&ts, format); } PyDoc_STRVAR(clock_getres_doc, @@ -278,8 +560,12 @@ parse_time_double_args(PyObject *args, c if (!PyArg_ParseTuple(args, format, &ot)) return 0; - if (ot == NULL || ot == Py_None) - *pwhen = floattime(); + if (ot == NULL || ot == Py_None) { + _PyTime_t ts; + if (_PyTime_get(&ts) == -1) + return -1; + *pwhen = _PyTime_AsDouble(&ts); + } else { double when = PyFloat_AsDouble(ot); if (PyErr_Occurred()) @@ -737,12 +1023,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 @@ -762,18 +1050,32 @@ 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); + { + _PyTime_FromTimespec(ts, &tp); + 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) */ + return _PyTime_get(ts); #endif } +static PyObject * +time_wallclock(PyObject *self, PyObject *args) +{ + const char *format = NULL; + _PyTime_t ts; + if (!PyArg_ParseTuple(args, "|s:time", &format)) + return NULL; + if (pywallclock(&ts)) + return NULL; + return _PyTime_AsFormat(&ts, format); +} + PyDoc_STRVAR(wallclock_doc, "wallclock() -> float\n\ \n\ @@ -893,9 +1195,9 @@ 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", time_time, METH_VARARGS, time_doc}, +#ifdef HAVE_PYCLOCK + {"clock", time_clock, METH_VARARGS, clock_doc}, #endif #ifdef HAVE_CLOCK_GETTIME {"clock_gettime", time_clock_gettime, METH_VARARGS, clock_gettime_doc}, @@ -918,7 +1220,7 @@ static PyMethodDef time_methods[] = { #ifdef HAVE_WORKING_TZSET {"tzset", time_tzset, METH_NOARGS, tzset_doc}, #endif - {"wallclock", time_wallclock, METH_NOARGS, wallclock_doc}, + {"wallclock", time_wallclock, METH_VARARGS, wallclock_doc}, {NULL, NULL} /* sentinel */ }; @@ -1003,15 +1305,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,9 +19,28 @@ extern int ftime(struct timeb *); #endif /* MS_WINDOWS */ #endif /* HAVE_FTIME */ +#define MILLISECONDS 1000 +#define MICROSECONDS 1000000 +#define NANOSECONDS 1000000000 + +#ifdef HAVE_CLOCK_GETTIME void -_PyTime_gettimeofday(_PyTime_timeval *tp) +_PyTime_FromTimespec(_PyTime_t *self, struct timespec *ts) { + self->seconds = ts->tv_sec; + self->floatpart = ts->tv_nsec; + self->divisor = NANOSECONDS; + self->resolution = -9; +} +#endif + +int +_PyTime_get(_PyTime_t *ts) +{ +#if defined(HAVE_FTIME) + struct timeb t; +#endif + /* There are three ways to get the time: (1) gettimeofday() -- resolution in microseconds (2) ftime() -- resolution in milliseconds @@ -31,26 +50,67 @@ _PyTime_gettimeofday(_PyTime_timeval *tp 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; #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; + ts->resolution = -6; + return 0; + } +#endif /* !HAVE_GETTIMEOFDAY */ + +#if defined(HAVE_FTIME) + ftime(&t); + ts->seconds = t.time; + ts->floatpart = t.millitm; + ts->divisor = MILLISECONDS; + ts->resolution = -3; +#else /* !HAVE_FTIME */ + ts->seconds = time(NULL); + ts->floatpart = 0; + ts->divisor = 1; + ts->resolution = 0; +#endif /* !HAVE_FTIME */ + return 0; +} + +void +_PyTime_gettimeofday(_PyTime_timeval *tv) +{ + _PyTime_t ts; + size_t k; + + if (_PyTime_get(&ts) == -1) { + tv->tv_sec = 0; + tv->tv_usec = 0; return; -#else /* !GETTIMEOFDAY_NO_TZ */ - if (gettimeofday(tp, (struct timezone *)NULL) == 0) - return; -#endif /* !GETTIMEOFDAY_NO_TZ */ -#endif /* !HAVE_GETTIMEOFDAY */ -#if defined(HAVE_FTIME) - { - struct timeb t; - ftime(&t); - tp->tv_sec = t.time; - tp->tv_usec = t.millitm * 1000; } -#else /* !HAVE_FTIME */ - tp->tv_sec = time(NULL); - tp->tv_usec = 0; -#endif /* !HAVE_FTIME */ - return; + 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; + } } void