changeset: 74495:6fcdb1d46abb tag: tip user: Victor Stinner date: Tue Jan 24 14:27:20 2012 +0100 files: Doc/library/time.rst Modules/timemodule.c description: Add a format optional argument to time.time() to return the current time as a decimal.Decimal object, instead of a float. diff --git a/Doc/library/time.rst b/Doc/library/time.rst --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -444,9 +444,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/Modules/timemodule.c b/Modules/timemodule.c --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -44,16 +44,246 @@ 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 a decimal.Decimal object of the requested + resolution. */ +static PyObject* +time_to_decimal(time_t value, + floatpart_t floatpart, floatpart_t divisor, + int resolution) +{ + 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; + + 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 (value) { + PyObject *f = PyLong_FromTimeT(value); + t = PyObject_CallFunction(decimal, "OO", f, context); + Py_CLEAR(f); + } + else { + t = PyObject_CallFunction(decimal, "iO", 0, context); + } + if (t == NULL) + return NULL; + + if (floatpart) + { + /* t += decimal.Decimal(floatpart, ctx) / decimal.Decimal(divisor, ctx) */ + PyObject *a, *b, *c, *d, *x; + + x = PyLong_FromFloatpartT(floatpart); + if (x == NULL) + goto error; + a = PyObject_CallFunction(decimal, "OO", x, context); + Py_CLEAR(x); + if (a == NULL) + goto error; + + x = PyLong_FromFloatpartT(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 (resolution != 0) { + PyObject *key, *exponent, *quantized; + + if (exponents == NULL) { + exponents = PyDict_New(); + if (exponents == NULL) + goto error; + } + + key = PyLong_FromLong(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; +} + +static PyObject* +time_to_float(time_t value, + floatpart_t floatpart, floatpart_t divisor, + int resolution) +{ + double t; + t = (double)value; + t += (double)floatpart / (double)divisor; + return PyFloat_FromDouble(t); +} + +/* Convert an integer timestamp to the requested format. Arguments: + - value: int or float + - floatpart: floating part, int or float, can be zero + - divisor: divisor of the floating part, int or float, cannot be zero + - format: "float", "decimal", "tuple" + + The result type depends on the requested format. + + Raise a ValueError if the format is unknown. */ +static PyObject* +time_to_format(time_t value, + floatpart_t floatpart, floatpart_t divisor, + int resolution, + const char *format) +{ + assert(divisor != 0); + if (!format || strcmp(format, "float") == 0) { + return time_to_float(value, floatpart, divisor, resolution); + } + else if (strcmp(format, "decimal") == 0) { + return time_to_decimal(value, floatpart, divisor, resolution); + } + 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) { + const char *format = NULL; + _PyTime_timeval t; + + if (!PyArg_ParseTuple(args, "|s:time", &format)) + return NULL; + + _PyTime_gettimeofday(&t); + if (t.tv_sec == 0 && t.tv_usec == 0) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } - return PyFloat_FromDouble(secs); + return time_to_format(t.tv_sec, t.tv_usec, 1000000, -6, format); } PyDoc_STRVAR(time_doc, @@ -893,7 +1123,7 @@ PyInit_timezone(PyObject *m) { static PyMethodDef time_methods[] = { - {"time", time_time, METH_NOARGS, time_doc}, + {"time", time_time, METH_VARARGS, time_doc}, #if (defined(MS_WINDOWS) && !defined(__BORLANDC__)) || defined(HAVE_CLOCK) {"clock", time_clock, METH_NOARGS, clock_doc}, #endif