diff -r 4e84e45e191b Include/datetime.h --- a/Include/datetime.h Fri Dec 19 11:21:56 2014 -0500 +++ b/Include/datetime.h Fri Dec 19 16:12:00 2014 -0800 @@ -17,8 +17,9 @@ * 4 hour 1 byte, 0-23 * 5 minute 1 byte, 0-59 * 6 second 1 byte, 0-59 - * 7 usecond 3 bytes, 0-999999 - * 10 + * 7 usecond 3 bytes, 0-999999, 4 bits in MSB for pickle versionning + * 10 nsecond 2 bytes, 0-999 + * 12 */ /* # of bytes for year, month, and day. */ @@ -27,9 +28,23 @@ /* # of bytes for hour, minute, second, and usecond. */ #define _PyDateTime_TIME_DATASIZE 6 -/* # of bytes for year, month, day, hour, minute, second, and usecond. */ -#define _PyDateTime_DATETIME_DATASIZE 10 +/* # of bytes for year, month, day, hour, minute, second, usecond and nsecond. */ +#define _PyDateTime_DATETIME_DATASIZE_V0 10 +#define _PyDateTime_DATETIME_DATASIZE_V1 12 +#define _PyDateTime_DATETIME_DATASIZE _PyDateTime_DATETIME_DATASIZE_V1 +/* max usecond is hex(1e6) = 0x0f4240, hence the 4 upper bits that + * are not used to encode the number of milliseconds can be used for + * pickle versioning. + */ +#define _PyDateTime_USECOND_MSB_VALUE_MASK 0x0f +#define _PyDateTime_USECOND_MSB_PICKLEVERSION_MASK 0xf0 +/* up to 15 pickle versions are available + * version 0 is vanilla + * version 1 adds nanosecond (this version) + * version 2..15 are available for future upgrades + */ +#define _PyDateTime_VERSION 1 typedef struct { @@ -121,10 +136,16 @@ #define PyDateTime_DATE_GET_HOUR(o) (((PyDateTime_DateTime*)o)->data[4]) #define PyDateTime_DATE_GET_MINUTE(o) (((PyDateTime_DateTime*)o)->data[5]) #define PyDateTime_DATE_GET_SECOND(o) (((PyDateTime_DateTime*)o)->data[6]) -#define PyDateTime_DATE_GET_MICROSECOND(o) \ - ((((PyDateTime_DateTime*)o)->data[7] << 16) | \ - (((PyDateTime_DateTime*)o)->data[8] << 8) | \ +#define PyDateTime_DATE_GET_MICROSECOND(o) \ + (((((PyDateTime_DateTime*)o)->data[7] & _PyDateTime_USECOND_MSB_VALUE_MASK) << 16) | \ + (((PyDateTime_DateTime*)o)->data[8] << 8) | \ ((PyDateTime_DateTime*)o)->data[9]) +#define PyDateTime_DATE_GET_NANOSECOND(o) \ + ((((PyDateTime_Date*)o)->data[10] << 8) | \ + ((PyDateTime_Date*)o)->data[11]) + +#define PyDateTime_DATE_PICKLE_GET_VERSION(o) (((((PyDateTime_DateTime*)o)->data[7]) & _PyDateTime_USECOND_MSB_PICKLEVERSION_MASK) >> 4 ) +#define PyDateTime_DATE_PICKLE_SET_VERSION(o,v) (((PyDateTime_DateTime*)o)->data[7] |= (v<<4)) /* Apply for time instances. */ #define PyDateTime_TIME_GET_HOUR(o) (((PyDateTime_Time*)o)->data[0]) @@ -153,13 +174,14 @@ /* constructors */ PyObject *(*Date_FromDate)(int, int, int, PyTypeObject*); - PyObject *(*DateTime_FromDateAndTime)(int, int, int, int, int, int, int, + PyObject *(*DateTime_FromDateAndTime)(int, int, int, int, int, int, int, int, PyObject*, PyTypeObject*); PyObject *(*Time_FromTime)(int, int, int, int, PyObject*, PyTypeObject*); PyObject *(*Delta_FromDelta)(int, int, int, int, PyTypeObject*); /* constructors for the DB API */ PyObject *(*DateTime_FromTimestamp)(PyObject*, PyObject*, PyObject*); + PyObject *(*DateTime_FromNanoseconds)(PyObject*, PyObject*, PyObject*); PyObject *(*Date_FromTimestamp)(PyObject*, PyObject*); } PyDateTime_CAPI; @@ -230,6 +252,10 @@ PyDateTimeAPI->DateTime_FromTimestamp( \ (PyObject*) (PyDateTimeAPI->DateTimeType), args, NULL) +#define PyDateTime_FromNanoseconds(args) \ + PyDateTimeAPI->DateTime_FromNanoseconds( \ + (PyObject*) (PyDateTimeAPI->DateTimeType), args, NULL) + #define PyDate_FromTimestamp(args) \ PyDateTimeAPI->Date_FromTimestamp( \ (PyObject*) (PyDateTimeAPI->DateType), args) diff -r 4e84e45e191b Lib/datetime.py --- a/Lib/datetime.py Fri Dec 19 11:21:56 2014 -0500 +++ b/Lib/datetime.py Fri Dec 19 16:12:00 2014 -0800 @@ -631,6 +631,7 @@ __new__() fromtimestamp() + fromnanoseconds() today() fromordinal() @@ -1387,6 +1388,11 @@ if tz is not None: result = tz.fromutc(result) return result + + @classmethod + def fromnanoseconds(cls, ns): + "Construct a date from a number of nanoseconds." + return cls(ns) @classmethod def utcfromtimestamp(cls, t): diff -r 4e84e45e191b Modules/_datetimemodule.c --- a/Modules/_datetimemodule.c Fri Dec 19 11:21:56 2014 -0500 +++ b/Modules/_datetimemodule.c Fri Dec 19 16:12:00 2014 -0800 @@ -50,6 +50,7 @@ #define DATE_GET_MINUTE PyDateTime_DATE_GET_MINUTE #define DATE_GET_SECOND PyDateTime_DATE_GET_SECOND #define DATE_GET_MICROSECOND PyDateTime_DATE_GET_MICROSECOND +#define DATE_GET_NANOSECOND PyDateTime_DATE_GET_NANOSECOND /* Date accessors for date and datetime. */ #define SET_YEAR(o, v) (((o)->data[0] = ((v) & 0xff00) >> 8), \ @@ -62,10 +63,12 @@ #define DATE_SET_MINUTE(o, v) (PyDateTime_DATE_GET_MINUTE(o) = (v)) #define DATE_SET_SECOND(o, v) (PyDateTime_DATE_GET_SECOND(o) = (v)) #define DATE_SET_MICROSECOND(o, v) \ - (((o)->data[7] = ((v) & 0xff0000) >> 16), \ - ((o)->data[8] = ((v) & 0x00ff00) >> 8), \ + (((o)->data[7] = (((v) & 0xff0000) >> 16) | (_PyDateTime_VERSION << 4)), \ + ((o)->data[8] = ((v) & 0x00ff00) >> 8), \ ((o)->data[9] = ((v) & 0x0000ff))) - +#define DATE_SET_NANOSECOND(o, v) \ + (((o)->data[10] = ((v) & 0xff00) >> 8), \ + ((o)->data[11] = ((v) & 0x00ff))) /* Time accessors for time. */ #define TIME_GET_HOUR PyDateTime_TIME_GET_HOUR #define TIME_GET_MINUTE PyDateTime_TIME_GET_MINUTE @@ -669,11 +672,11 @@ /* Create a datetime instance with no range checking. */ static PyObject * new_datetime_ex(int year, int month, int day, int hour, int minute, - int second, int usecond, PyObject *tzinfo, PyTypeObject *type) + int second, int usecond, int nsecond, PyObject *tzinfo, PyTypeObject *type) { PyDateTime_DateTime *self; char aware = tzinfo != Py_None; - + self = (PyDateTime_DateTime *) (type->tp_alloc(type, aware)); if (self != NULL) { self->hastzinfo = aware; @@ -682,16 +685,18 @@ DATE_SET_MINUTE(self, minute); DATE_SET_SECOND(self, second); DATE_SET_MICROSECOND(self, usecond); + DATE_SET_NANOSECOND(self, nsecond); if (aware) { Py_INCREF(tzinfo); self->tzinfo = tzinfo; } } + return (PyObject *)self; } -#define new_datetime(y, m, d, hh, mm, ss, us, tzinfo) \ - new_datetime_ex(y, m, d, hh, mm, ss, us, tzinfo, \ +#define new_datetime(y, m, d, hh, mm, ss, us, ns, tzinfo) \ + new_datetime_ex(y, m, d, hh, mm, ss, us, ns, tzinfo, \ &PyDateTime_DateTimeType) /* Create a time instance with no range checking. */ @@ -1131,6 +1136,20 @@ return PyBytes_FromStringAndSize(freplacement, strlen(freplacement)); } +static PyObject * +make_Nreplacement(PyObject *object) +{ + char Nreplacement[64]; + if (PyTime_Check(object)) + sprintf(Nreplacement, "%09d", (int) (TIME_GET_MICROSECOND(object) * 1e3)); + else if (PyDateTime_Check(object)) + sprintf(Nreplacement, "%09d", (int) (DATE_GET_MICROSECOND(object) * 1e3 + DATE_GET_NANOSECOND(object))); + else + sprintf(Nreplacement, "%09d", 0); + + return PyBytes_FromStringAndSize(Nreplacement, strlen(Nreplacement)); +} + /* I sure don't want to reproduce the strftime code from the time module, * so this imports the module and calls it. All the hair is due to * giving special meanings to the %z, %Z and %f format codes via a @@ -1147,6 +1166,7 @@ PyObject *zreplacement = NULL; /* py string, replacement for %z */ PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ PyObject *freplacement = NULL; /* py string, replacement for %f */ + PyObject *Nreplacement = NULL; /* py string, replacement for %N */ const char *pin; /* pointer to next char in input format */ Py_ssize_t flen; /* length of input format */ @@ -1249,6 +1269,18 @@ ptoappend = PyBytes_AS_STRING(freplacement); ntoappend = PyBytes_GET_SIZE(freplacement); } + else if (ch == 'N') { + /* format nanoseconds */ + if (Nreplacement == NULL) { + Nreplacement = make_Nreplacement(object); + if (Nreplacement == NULL) + goto Done; + } + assert(Nreplacement != NULL); + assert(PyBytes_Check(Nreplacement)); + ptoappend = PyBytes_AS_STRING(Nreplacement); + ntoappend = PyBytes_GET_SIZE(Nreplacement); + } else { /* percent followed by neither z nor Z */ ptoappend = pin - 2; @@ -1295,6 +1327,7 @@ Py_DECREF(time); } Done: + Py_XDECREF(Nreplacement); Py_XDECREF(freplacement); Py_XDECREF(zreplacement); Py_XDECREF(Zreplacement); @@ -3947,6 +3980,12 @@ } static PyObject * +datetime_nanosecond(PyDateTime_DateTime *self, void *unused) +{ + return PyLong_FromLong(DATE_GET_NANOSECOND(self)); +} + +static PyObject * datetime_tzinfo(PyDateTime_DateTime *self, void *unused) { PyObject *result = HASTZINFO(self) ? self->tzinfo : Py_None; @@ -3959,7 +3998,8 @@ {"minute", (getter)datetime_minute}, {"second", (getter)datetime_second}, {"microsecond", (getter)datetime_microsecond}, - {"tzinfo", (getter)datetime_tzinfo}, + {"nanosecond", (getter)datetime_nanosecond}, + {"tzinfo", (getter)datetime_tzinfo}, {NULL} }; @@ -3977,6 +4017,7 @@ { PyObject *self = NULL; PyObject *state; + Py_ssize_t sz; int year; int month; int day; @@ -3990,7 +4031,8 @@ if (PyTuple_GET_SIZE(args) >= 1 && PyTuple_GET_SIZE(args) <= 2 && PyBytes_Check(state = PyTuple_GET_ITEM(args, 0)) && - PyBytes_GET_SIZE(state) == _PyDateTime_DATETIME_DATASIZE && + (((sz = PyBytes_GET_SIZE(state)) == _PyDateTime_DATETIME_DATASIZE) + || (sz == _PyDateTime_DATETIME_DATASIZE_V0)) && MONTH_IS_SANE(PyBytes_AS_STRING(state)[2])) { PyDateTime_DateTime *me; @@ -4009,7 +4051,11 @@ if (me != NULL) { char *pdata = PyBytes_AS_STRING(state); - memcpy(me->data, pdata, _PyDateTime_DATETIME_DATASIZE); + memset(me->data, 0, _PyDateTime_DATETIME_DATASIZE); + memcpy(me->data, pdata, sz); + + PyDateTime_DATE_PICKLE_SET_VERSION(me, _PyDateTime_VERSION); + me->hashcode = -1; me->hastzinfo = aware; if (aware) { @@ -4029,9 +4075,11 @@ return NULL; if (check_tzinfo_subclass(tzinfo) < 0) return NULL; + self = new_datetime_ex(year, month, day, - hour, minute, second, usecond, + hour, minute, second, usecond, 0, /* default constructor does not expose ns */ tzinfo, type); + } return self; } @@ -4219,6 +4267,44 @@ return self; } +/* Return new local datetime from a number of nanoseconds. */ +static PyObject * +datetime_fromnanoseconds(PyObject *cls, PyObject *args, PyObject *kw) +{ + PyObject *self; + long ns, nval; + time_t timet; + long us; + PyObject *tzinfo = Py_None; + static char *keywords[] = {"ns", "tz", NULL}; + TM_FUNC f; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "l|O:fromnanoseconds", + keywords, &ns, &tzinfo)) + return NULL; + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + + timet = ns / 1000000000L; + nval = ns - timet * 1000000000L; + us = nval / 1000; + f = (tzinfo == Py_None)? localtime : gmtime, + self = datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo); + + if (self != NULL && tzinfo != Py_None) { + /* Convert UTC to tzinfo's zone. */ + PyObject *temp = self; + + self = _PyObject_CallMethodId(tzinfo, &PyId_fromutc, "O", self); + Py_DECREF(temp); + } + + DATE_SET_NANOSECOND((PyDateTime_DateTime *)self, nval - us * 1000); + + return self; +} + + /* Return new UTC datetime from timestamp (Python timestamp -- a double). */ static PyObject * datetime_utcfromtimestamp(PyObject *cls, PyObject *args) @@ -4336,6 +4422,7 @@ int second = DATE_GET_SECOND(date) + GET_TD_SECONDS(delta) * factor; int microsecond = DATE_GET_MICROSECOND(date) + GET_TD_MICROSECONDS(delta) * factor; + int nanosecond = DATE_GET_NANOSECOND(date); assert(factor == 1 || factor == -1); if (normalize_datetime(&year, &month, &day, @@ -4343,7 +4430,7 @@ return NULL; else return new_datetime(year, month, day, - hour, minute, second, microsecond, + hour, minute, second, microsecond, nanosecond, HASTZINFO(date) ? date->tzinfo : Py_None); } @@ -4465,7 +4552,17 @@ const char *type_name = Py_TYPE(self)->tp_name; PyObject *baserepr; - if (DATE_GET_MICROSECOND(self)) { + if (DATE_GET_NANOSECOND(self)) { + baserepr = PyUnicode_FromFormat( + "%s(%d, %d, %d, %d, %d, %d, %d, %d)", + type_name, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self), + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self), + DATE_GET_NANOSECOND(self)); + } + else if (DATE_GET_MICROSECOND(self)) { baserepr = PyUnicode_FromFormat( "%s(%d, %d, %d, %d, %d, %d, %d)", type_name, @@ -4508,10 +4605,17 @@ char buffer[100]; PyObject *result; int us = DATE_GET_MICROSECOND(self); + int ns = DATE_GET_NANOSECOND(self); if (!PyArg_ParseTupleAndKeywords(args, kw, "|C:isoformat", keywords, &sep)) return NULL; - if (us) + if (ns) + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d.%09d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), us * 1e3 + ns); + else if (us) result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d.%06d", GET_YEAR(self), GET_MONTH(self), GET_DAY(self), (int)sep, @@ -4743,6 +4847,7 @@ local_time = new_datetime(timep->tm_year + 1900, timep->tm_mon + 1, timep->tm_mday, timep->tm_hour, timep->tm_min, timep->tm_sec, DATE_GET_MICROSECOND(utc_time), + DATE_GET_NANOSECOND(utc_time), utc_time->tzinfo); if (local_time == NULL) goto error; @@ -4899,7 +5004,7 @@ "timestamp out of range"); return NULL; } - result = PyFloat_FromDouble(timestamp + DATE_GET_MICROSECOND(self) / 1e6); + result = PyFloat_FromDouble(timestamp + DATE_GET_MICROSECOND(self) / 1e6 + DATE_GET_NANOSECOND(self) / 1e9); } return result; } @@ -5018,6 +5123,10 @@ METH_VARARGS | METH_KEYWORDS | METH_CLASS, PyDoc_STR("timestamp[, tz] -> tz's local time from POSIX timestamp.")}, + {"fromnanoseconds", (PyCFunction)datetime_fromnanoseconds, + METH_VARARGS | METH_KEYWORDS | METH_CLASS, + PyDoc_STR("ns[, tz] -> tz's local time from POSIX timestamp.")}, + {"utcfromtimestamp", (PyCFunction)datetime_utcfromtimestamp, METH_VARARGS | METH_CLASS, PyDoc_STR("timestamp -> UTC datetime from a POSIX timestamp " @@ -5165,6 +5274,7 @@ new_time_ex, new_delta_ex, datetime_fromtimestamp, + datetime_fromnanoseconds, date_fromtimestamp }; @@ -5264,12 +5374,12 @@ /* datetime values */ d = PyDateTime_DateTimeType.tp_dict; - x = new_datetime(1, 1, 1, 0, 0, 0, 0, Py_None); + x = new_datetime(1, 1, 1, 0, 0, 0, 0, 0, Py_None); if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) return NULL; Py_DECREF(x); - x = new_datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999, Py_None); + x = new_datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999, 999, Py_None); if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) return NULL; Py_DECREF(x); @@ -5310,7 +5420,7 @@ Py_DECREF(x); /* Epoch */ - PyDateTime_Epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0, + PyDateTime_Epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0, 0, PyDateTime_TimeZone_UTC); if (PyDateTime_Epoch == NULL) return NULL;