diff options
Diffstat (limited to 'Modules/_datetimemodule.c')
-rw-r--r-- | Modules/_datetimemodule.c | 1075 |
1 files changed, 743 insertions, 332 deletions
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index f419cb2..291920d 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -56,6 +56,7 @@ class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType" #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_FOLD PyDateTime_DATE_GET_FOLD /* Date accessors for date and datetime. */ #define SET_YEAR(o, v) (((o)->data[0] = ((v) & 0xff00) >> 8), \ @@ -71,12 +72,14 @@ class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType" (((o)->data[7] = ((v) & 0xff0000) >> 16), \ ((o)->data[8] = ((v) & 0x00ff00) >> 8), \ ((o)->data[9] = ((v) & 0x0000ff))) +#define DATE_SET_FOLD(o, v) (PyDateTime_DATE_GET_FOLD(o) = (v)) /* Time accessors for time. */ #define TIME_GET_HOUR PyDateTime_TIME_GET_HOUR #define TIME_GET_MINUTE PyDateTime_TIME_GET_MINUTE #define TIME_GET_SECOND PyDateTime_TIME_GET_SECOND #define TIME_GET_MICROSECOND PyDateTime_TIME_GET_MICROSECOND +#define TIME_GET_FOLD PyDateTime_TIME_GET_FOLD #define TIME_SET_HOUR(o, v) (PyDateTime_TIME_GET_HOUR(o) = (v)) #define TIME_SET_MINUTE(o, v) (PyDateTime_TIME_GET_MINUTE(o) = (v)) #define TIME_SET_SECOND(o, v) (PyDateTime_TIME_GET_SECOND(o) = (v)) @@ -84,6 +87,7 @@ class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType" (((o)->data[3] = ((v) & 0xff0000) >> 16), \ ((o)->data[4] = ((v) & 0x00ff00) >> 8), \ ((o)->data[5] = ((v) & 0x0000ff))) +#define TIME_SET_FOLD(o, v) (PyDateTime_TIME_GET_FOLD(o) = (v)) /* Delta accessors for timedelta. */ #define GET_TD_DAYS(o) (((PyDateTime_Delta *)(o))->days) @@ -184,12 +188,12 @@ divide_nearest(PyObject *m, PyObject *n) * and the number of days before that month in the same year. These * are correct for non-leap years only. */ -static int _days_in_month[] = { +static const int _days_in_month[] = { 0, /* unused; this vector uses 1-based indexing */ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; -static int _days_before_month[] = { +static const int _days_before_month[] = { 0, /* unused; this vector uses 1-based indexing */ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; @@ -417,7 +421,7 @@ check_date_args(int year, int month, int day) * aren't, raise ValueError and return -1. */ static int -check_time_args(int h, int m, int s, int us) +check_time_args(int h, int m, int s, int us, int fold) { if (h < 0 || h > 23) { PyErr_SetString(PyExc_ValueError, @@ -439,6 +443,11 @@ check_time_args(int h, int m, int s, int us) "microsecond must be in 0..999999"); return -1; } + if (fold != 0 && fold != 1) { + PyErr_SetString(PyExc_ValueError, + "fold must be either 0 or 1"); + return -1; + } return 0; } @@ -674,8 +683,8 @@ new_date_ex(int year, int month, int day, PyTypeObject *type) /* 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) +new_datetime_ex2(int year, int month, int day, int hour, int minute, + int second, int usecond, PyObject *tzinfo, int fold, PyTypeObject *type) { PyDateTime_DateTime *self; char aware = tzinfo != Py_None; @@ -692,18 +701,27 @@ new_datetime_ex(int year, int month, int day, int hour, int minute, Py_INCREF(tzinfo); self->tzinfo = tzinfo; } + DATE_SET_FOLD(self, fold); } 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, \ +static PyObject * +new_datetime_ex(int year, int month, int day, int hour, int minute, + int second, int usecond, PyObject *tzinfo, PyTypeObject *type) +{ + return new_datetime_ex2(year, month, day, hour, minute, second, usecond, + tzinfo, 0, type); +} + +#define new_datetime(y, m, d, hh, mm, ss, us, tzinfo, fold) \ + new_datetime_ex2(y, m, d, hh, mm, ss, us, tzinfo, fold, \ &PyDateTime_DateTimeType) /* Create a time instance with no range checking. */ static PyObject * -new_time_ex(int hour, int minute, int second, int usecond, - PyObject *tzinfo, PyTypeObject *type) +new_time_ex2(int hour, int minute, int second, int usecond, + PyObject *tzinfo, int fold, PyTypeObject *type) { PyDateTime_Time *self; char aware = tzinfo != Py_None; @@ -720,12 +738,20 @@ new_time_ex(int hour, int minute, int second, int usecond, Py_INCREF(tzinfo); self->tzinfo = tzinfo; } + TIME_SET_FOLD(self, fold); } return (PyObject *)self; } -#define new_time(hh, mm, ss, us, tzinfo) \ - new_time_ex(hh, mm, ss, us, tzinfo, &PyDateTime_TimeType) +static PyObject * +new_time_ex(int hour, int minute, int second, int usecond, + PyObject *tzinfo, PyTypeObject *type) +{ + return new_time_ex2(hour, minute, second, usecond, tzinfo, 0, type); +} + +#define new_time(hh, mm, ss, us, tzinfo, fold) \ + new_time_ex2(hh, mm, ss, us, tzinfo, fold, &PyDateTime_TimeType) /* Create a timedelta instance. Normalize the members iff normalize is * true. Passing false is a speed optimization, if you know for sure @@ -873,7 +899,7 @@ get_tzinfo_member(PyObject *self) * this returns NULL. Else result is returned. */ static PyObject * -call_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg) +call_tzinfo_method(PyObject *tzinfo, const char *name, PyObject *tzinfoarg) { PyObject *offset; @@ -887,10 +913,10 @@ call_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg) if (offset == Py_None || offset == NULL) return offset; if (PyDelta_Check(offset)) { - if (GET_TD_MICROSECONDS(offset) != 0 || GET_TD_SECONDS(offset) % 60 != 0) { + if (GET_TD_MICROSECONDS(offset) != 0) { Py_DECREF(offset); PyErr_Format(PyExc_ValueError, "offset must be a timedelta" - " representing a whole number of minutes"); + " representing a whole number of seconds"); return NULL; } if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) || @@ -1002,6 +1028,30 @@ append_keyword_tzinfo(PyObject *repr, PyObject *tzinfo) return repr; } +/* repr is like "someclass(arg1, arg2)". If fold isn't 0, + * stuff + * ", fold=" + repr(tzinfo) + * before the closing ")". + */ +static PyObject * +append_keyword_fold(PyObject *repr, int fold) +{ + PyObject *temp; + + assert(PyUnicode_Check(repr)); + if (fold == 0) + return repr; + /* Get rid of the trailing ')'. */ + assert(PyUnicode_READ_CHAR(repr, PyUnicode_GET_LENGTH(repr)-1) == ')'); + temp = PyUnicode_Substring(repr, 0, PyUnicode_GET_LENGTH(repr) - 1); + Py_DECREF(repr); + if (temp == NULL) + return NULL; + repr = PyUnicode_FromFormat("%U, fold=%d)", temp, fold); + Py_DECREF(temp); + return repr; +} + /* --------------------------------------------------------------------------- * String format helpers. */ @@ -1009,10 +1059,10 @@ append_keyword_tzinfo(PyObject *repr, PyObject *tzinfo) static PyObject * format_ctime(PyDateTime_Date *date, int hours, int minutes, int seconds) { - static const char *DayNames[] = { + static const char * const DayNames[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; - static const char *MonthNames[] = { + static const char * const MonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; @@ -1057,10 +1107,8 @@ format_utcoffset(char *buf, size_t buflen, const char *sep, } /* Offset is normalized, so it is negative if days < 0 */ if (GET_TD_DAYS(offset) < 0) { - PyObject *temp = offset; sign = '-'; - offset = delta_negative((PyDateTime_Delta *)offset); - Py_DECREF(temp); + Py_SETREF(offset, delta_negative((PyDateTime_Delta *)offset)); if (offset == NULL) return -1; } @@ -1072,10 +1120,11 @@ format_utcoffset(char *buf, size_t buflen, const char *sep, Py_DECREF(offset); minutes = divmod(seconds, 60, &seconds); hours = divmod(minutes, 60, &minutes); - assert(seconds == 0); - /* XXX ignore sub-minute data, currently not allowed. */ - PyOS_snprintf(buf, buflen, "%c%02d%s%02d", sign, hours, sep, minutes); - + if (seconds == 0) + PyOS_snprintf(buf, buflen, "%c%02d%s%02d", sign, hours, sep, minutes); + else + PyOS_snprintf(buf, buflen, "%c%02d%s%02d%s%02d", sign, hours, + sep, minutes, sep, seconds); return 0; } @@ -1170,7 +1219,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, assert(object && format && timetuple); assert(PyUnicode_Check(format)); /* Convert the input format to a C string and size */ - pin = _PyUnicode_AsStringAndSize(format, &flen); + pin = PyUnicode_AsUTF8AndSize(format, &flen); if (!pin) return NULL; @@ -1238,7 +1287,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, } assert(Zreplacement != NULL); assert(PyUnicode_Check(Zreplacement)); - ptoappend = _PyUnicode_AsStringAndSize(Zreplacement, + ptoappend = PyUnicode_AsUTF8AndSize(Zreplacement, &ntoappend); if (ptoappend == NULL) goto Done; @@ -1323,7 +1372,7 @@ time_time(void) if (time != NULL) { _Py_IDENTIFIER(time); - result = _PyObject_CallMethodId(time, &PyId_time, "()"); + result = _PyObject_CallMethodId(time, &PyId_time, NULL); Py_DECREF(time); } return result; @@ -2307,7 +2356,7 @@ static PyMethodDef delta_methods[] = { {NULL, NULL}, }; -static char delta_doc[] = +static const char delta_doc[] = PyDoc_STR("Difference between two datetime values."); static PyNumberMethods delta_as_number = { @@ -2462,27 +2511,19 @@ date_new(PyTypeObject *type, PyObject *args, PyObject *kw) static PyObject * date_local_from_object(PyObject *cls, PyObject *obj) { - struct tm *tm; + struct tm tm; time_t t; if (_PyTime_ObjectToTime_t(obj, &t, _PyTime_ROUND_FLOOR) == -1) return NULL; - tm = localtime(&t); - if (tm == NULL) { - /* unconvertible time */ -#ifdef EINVAL - if (errno == 0) - errno = EINVAL; -#endif - PyErr_SetFromErrno(PyExc_OSError); + if (_PyTime_localtime(t, &tm) != 0) return NULL; - } return PyObject_CallFunction(cls, "iii", - tm->tm_year + 1900, - tm->tm_mon + 1, - tm->tm_mday); + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday); } /* Return new date from current time. @@ -2648,7 +2689,7 @@ date_isoformat(PyDateTime_Date *self) static PyObject * date_str(PyDateTime_Date *self) { - return _PyObject_CallMethodId((PyObject *)self, &PyId_isoformat, "()"); + return _PyObject_CallMethodId((PyObject *)self, &PyId_isoformat, NULL); } @@ -2674,7 +2715,7 @@ date_strftime(PyDateTime_Date *self, PyObject *args, PyObject *kw) &format)) return NULL; - tuple = _PyObject_CallMethodId((PyObject *)self, &PyId_timetuple, "()"); + tuple = _PyObject_CallMethodId((PyObject *)self, &PyId_timetuple, NULL); if (tuple == NULL) return NULL; result = wrap_strftime((PyObject *)self, format, tuple, @@ -2786,9 +2827,10 @@ static PyObject *date_getstate(PyDateTime_Date *self); static Py_hash_t date_hash(PyDateTime_Date *self) { - if (self->hashcode == -1) + if (self->hashcode == -1) { self->hashcode = generic_hash( (unsigned char *)self->data, _PyDateTime_DATE_DATASIZE); + } return self->hashcode; } @@ -2886,7 +2928,7 @@ static PyMethodDef date_methods[] = { {NULL, NULL} }; -static char date_doc[] = +static const char date_doc[] = PyDoc_STR("date(year, month, day) --> date object"); static PyNumberMethods date_as_number = { @@ -3047,10 +3089,8 @@ tzinfo_fromutc(PyDateTime_TZInfo *self, PyObject *dt) if (dst == Py_None) goto Inconsistent; if (delta_bool((PyDateTime_Delta *)dst) != 0) { - PyObject *temp = result; - result = add_datetime_timedelta((PyDateTime_DateTime *)result, - (PyDateTime_Delta *)dst, 1); - Py_DECREF(temp); + Py_SETREF(result, add_datetime_timedelta((PyDateTime_DateTime *)result, + (PyDateTime_Delta *)dst, 1)); if (result == NULL) goto Fail; } @@ -3080,37 +3120,34 @@ Fail: static PyObject * tzinfo_reduce(PyObject *self) { - PyObject *args, *state, *tmp; + PyObject *args, *state; PyObject *getinitargs, *getstate; _Py_IDENTIFIER(__getinitargs__); _Py_IDENTIFIER(__getstate__); - tmp = PyTuple_New(0); - if (tmp == NULL) - return NULL; - getinitargs = _PyObject_GetAttrId(self, &PyId___getinitargs__); if (getinitargs != NULL) { - args = PyObject_CallObject(getinitargs, tmp); + args = _PyObject_CallNoArg(getinitargs); Py_DECREF(getinitargs); if (args == NULL) { - Py_DECREF(tmp); return NULL; } } else { PyErr_Clear(); - args = tmp; - Py_INCREF(args); + + args = PyTuple_New(0); + if (args == NULL) { + return NULL; + } } getstate = _PyObject_GetAttrId(self, &PyId___getstate__); if (getstate != NULL) { - state = PyObject_CallObject(getstate, tmp); + state = _PyObject_CallNoArg(getstate); Py_DECREF(getstate); if (state == NULL) { Py_DECREF(args); - Py_DECREF(tmp); return NULL; } } @@ -3119,13 +3156,12 @@ tzinfo_reduce(PyObject *self) PyErr_Clear(); state = Py_None; dictptr = _PyObject_GetDictPtr(self); - if (dictptr && *dictptr && PyDict_Size(*dictptr)) + if (dictptr && *dictptr && PyDict_Size(*dictptr)) { state = *dictptr; + } Py_INCREF(state); } - Py_DECREF(tmp); - if (state == Py_None) { Py_DECREF(state); return Py_BuildValue("(ON)", Py_TYPE(self), args); @@ -3155,7 +3191,7 @@ static PyMethodDef tzinfo_methods[] = { {NULL, NULL} }; -static char tzinfo_doc[] = +static const char tzinfo_doc[] = PyDoc_STR("Abstract base class for time zone info objects."); static PyTypeObject PyDateTime_TZInfoType = { @@ -3287,6 +3323,11 @@ timezone_str(PyDateTime_TimeZone *self) Py_INCREF(self->name); return self->name; } + if ((PyObject *)self == PyDateTime_TimeZone_UTC || + (GET_TD_DAYS(self->offset) == 0 && + GET_TD_SECONDS(self->offset) == 0 && + GET_TD_MICROSECONDS(self->offset) == 0)) + return PyUnicode_FromString("UTC"); /* Offset is normalized, so it is negative if days < 0 */ if (GET_TD_DAYS(self->offset) < 0) { sign = '-'; @@ -3382,7 +3423,7 @@ static PyMethodDef timezone_methods[] = { {NULL, NULL} }; -static char timezone_doc[] = +static const char timezone_doc[] = PyDoc_STR("Fixed offset from UTC implementation of tzinfo."); static PyTypeObject PyDateTime_TimeZoneType = { @@ -3466,12 +3507,19 @@ time_tzinfo(PyDateTime_Time *self, void *unused) return result; } +static PyObject * +time_fold(PyDateTime_Time *self, void *unused) +{ + return PyLong_FromLong(TIME_GET_FOLD(self)); +} + static PyGetSetDef time_getset[] = { {"hour", (getter)time_hour}, {"minute", (getter)time_minute}, {"second", (getter)py_time_second}, {"microsecond", (getter)time_microsecond}, - {"tzinfo", (getter)time_tzinfo}, + {"tzinfo", (getter)time_tzinfo}, + {"fold", (getter)time_fold}, {NULL} }; @@ -3480,7 +3528,7 @@ static PyGetSetDef time_getset[] = { */ static char *time_kws[] = {"hour", "minute", "second", "microsecond", - "tzinfo", NULL}; + "tzinfo", "fold", NULL}; static PyObject * time_new(PyTypeObject *type, PyObject *args, PyObject *kw) @@ -3492,13 +3540,14 @@ time_new(PyTypeObject *type, PyObject *args, PyObject *kw) int second = 0; int usecond = 0; PyObject *tzinfo = Py_None; + int fold = 0; /* Check for invocation from pickle with __getstate__ state */ if (PyTuple_GET_SIZE(args) >= 1 && PyTuple_GET_SIZE(args) <= 2 && PyBytes_Check(state = PyTuple_GET_ITEM(args, 0)) && PyBytes_GET_SIZE(state) == _PyDateTime_TIME_DATASIZE && - ((unsigned char) (PyBytes_AS_STRING(state)[0])) < 24) + (0x7F & ((unsigned char) (PyBytes_AS_STRING(state)[0]))) < 24) { PyDateTime_Time *me; char aware; @@ -3523,19 +3572,26 @@ time_new(PyTypeObject *type, PyObject *args, PyObject *kw) Py_INCREF(tzinfo); me->tzinfo = tzinfo; } + if (pdata[0] & (1 << 7)) { + me->data[0] -= 128; + me->fold = 1; + } + else { + me->fold = 0; + } } return (PyObject *)me; } - if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO", time_kws, + if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO$i", time_kws, &hour, &minute, &second, &usecond, - &tzinfo)) { - if (check_time_args(hour, minute, second, usecond) < 0) + &tzinfo, &fold)) { + if (check_time_args(hour, minute, second, usecond, fold) < 0) return NULL; if (check_tzinfo_subclass(tzinfo) < 0) return NULL; - self = new_time_ex(hour, minute, second, usecond, tzinfo, - type); + self = new_time_ex2(hour, minute, second, usecond, tzinfo, fold, + type); } return self; } @@ -3585,6 +3641,7 @@ time_repr(PyDateTime_Time *self) int m = TIME_GET_MINUTE(self); int s = TIME_GET_SECOND(self); int us = TIME_GET_MICROSECOND(self); + int fold = TIME_GET_FOLD(self); PyObject *result = NULL; if (us) @@ -3597,33 +3654,68 @@ time_repr(PyDateTime_Time *self) result = PyUnicode_FromFormat("%s(%d, %d)", type_name, h, m); if (result != NULL && HASTZINFO(self)) result = append_keyword_tzinfo(result, self->tzinfo); + if (result != NULL && fold) + result = append_keyword_fold(result, fold); return result; } static PyObject * time_str(PyDateTime_Time *self) { - return _PyObject_CallMethodId((PyObject *)self, &PyId_isoformat, "()"); + return _PyObject_CallMethodId((PyObject *)self, &PyId_isoformat, NULL); } static PyObject * -time_isoformat(PyDateTime_Time *self, PyObject *unused) +time_isoformat(PyDateTime_Time *self, PyObject *args, PyObject *kw) { char buf[100]; + char *timespec = NULL; + static char *keywords[] = {"timespec", NULL}; PyObject *result; int us = TIME_GET_MICROSECOND(self); + static char *specs[][2] = { + {"hours", "%02d"}, + {"minutes", "%02d:%02d"}, + {"seconds", "%02d:%02d:%02d"}, + {"milliseconds", "%02d:%02d:%02d.%03d"}, + {"microseconds", "%02d:%02d:%02d.%06d"}, + }; + size_t given_spec; - if (us) - result = PyUnicode_FromFormat("%02d:%02d:%02d.%06d", - TIME_GET_HOUR(self), - TIME_GET_MINUTE(self), - TIME_GET_SECOND(self), - us); - else - result = PyUnicode_FromFormat("%02d:%02d:%02d", - TIME_GET_HOUR(self), - TIME_GET_MINUTE(self), - TIME_GET_SECOND(self)); + if (!PyArg_ParseTupleAndKeywords(args, kw, "|s:isoformat", keywords, ×pec)) + return NULL; + + if (timespec == NULL || strcmp(timespec, "auto") == 0) { + if (us == 0) { + /* seconds */ + given_spec = 2; + } + else { + /* microseconds */ + given_spec = 4; + } + } + else { + for (given_spec = 0; given_spec < Py_ARRAY_LENGTH(specs); given_spec++) { + if (strcmp(timespec, specs[given_spec][0]) == 0) { + if (given_spec == 3) { + /* milliseconds */ + us = us / 1000; + } + break; + } + } + } + + if (given_spec == Py_ARRAY_LENGTH(specs)) { + PyErr_Format(PyExc_ValueError, "Unknown timespec value"); + return NULL; + } + else { + result = PyUnicode_FromFormat(specs[given_spec][1], + TIME_GET_HOUR(self), TIME_GET_MINUTE(self), + TIME_GET_SECOND(self), us); + } if (result == NULL || !HASTZINFO(self) || self->tzinfo == Py_None) return result; @@ -3750,9 +3842,23 @@ static Py_hash_t time_hash(PyDateTime_Time *self) { if (self->hashcode == -1) { - PyObject *offset; - - offset = time_utcoffset((PyObject *)self, NULL); + PyObject *offset, *self0; + if (TIME_GET_FOLD(self)) { + self0 = new_time_ex2(TIME_GET_HOUR(self), + TIME_GET_MINUTE(self), + TIME_GET_SECOND(self), + TIME_GET_MICROSECOND(self), + HASTZINFO(self) ? self->tzinfo : Py_None, + 0, Py_TYPE(self)); + if (self0 == NULL) + return -1; + } + else { + self0 = (PyObject *)self; + Py_INCREF(self0); + } + offset = time_utcoffset(self0, NULL); + Py_DECREF(self0); if (offset == NULL) return -1; @@ -3798,15 +3904,24 @@ time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw) int ss = TIME_GET_SECOND(self); int us = TIME_GET_MICROSECOND(self); PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; + int fold = TIME_GET_FOLD(self); - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO:replace", + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO$i:replace", time_kws, - &hh, &mm, &ss, &us, &tzinfo)) + &hh, &mm, &ss, &us, &tzinfo, &fold)) return NULL; tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo); if (tuple == NULL) return NULL; clone = time_new(Py_TYPE(self), tuple, NULL); + if (clone != NULL) { + if (fold != 0 && fold != 1) { + PyErr_SetString(PyExc_ValueError, + "fold must be either 0 or 1"); + return NULL; + } + TIME_SET_FOLD(clone, fold); + } Py_DECREF(tuple); return clone; } @@ -3819,7 +3934,7 @@ time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw) * __getstate__ isn't exposed. */ static PyObject * -time_getstate(PyDateTime_Time *self) +time_getstate(PyDateTime_Time *self, int proto) { PyObject *basestate; PyObject *result = NULL; @@ -3827,6 +3942,9 @@ time_getstate(PyDateTime_Time *self) basestate = PyBytes_FromStringAndSize((char *)self->data, _PyDateTime_TIME_DATASIZE); if (basestate != NULL) { + if (proto > 3 && TIME_GET_FOLD(self)) + /* Set the first bit of the first byte */ + PyBytes_AS_STRING(basestate)[0] |= (1 << 7); if (! HASTZINFO(self) || self->tzinfo == Py_None) result = PyTuple_Pack(1, basestate); else @@ -3837,16 +3955,27 @@ time_getstate(PyDateTime_Time *self) } static PyObject * +time_reduce_ex(PyDateTime_Time *self, PyObject *args) +{ + int proto; + if (!PyArg_ParseTuple(args, "i:__reduce_ex__", &proto)) + return NULL; + + return Py_BuildValue("(ON)", Py_TYPE(self), time_getstate(self, proto)); +} + +static PyObject * time_reduce(PyDateTime_Time *self, PyObject *arg) { - return Py_BuildValue("(ON)", Py_TYPE(self), time_getstate(self)); + return Py_BuildValue("(ON)", Py_TYPE(self), time_getstate(self, 2)); } static PyMethodDef time_methods[] = { - {"isoformat", (PyCFunction)time_isoformat, METH_NOARGS, - PyDoc_STR("Return string in ISO 8601 format, HH:MM:SS[.mmmmmm]" - "[+HH:MM].")}, + {"isoformat", (PyCFunction)time_isoformat, METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("Return string in ISO 8601 format, [HH[:MM[:SS[.mmm[uuu]]]]]" + "[+HH:MM].\n\n" + "timespec specifies what components of the time to include.\n")}, {"strftime", (PyCFunction)time_strftime, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("format -> strftime() style string.")}, @@ -3866,13 +3995,16 @@ static PyMethodDef time_methods[] = { {"replace", (PyCFunction)time_replace, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("Return time with new specified fields.")}, + {"__reduce_ex__", (PyCFunction)time_reduce_ex, METH_VARARGS, + PyDoc_STR("__reduce_ex__(proto) -> (cls, state)")}, + {"__reduce__", (PyCFunction)time_reduce, METH_NOARGS, PyDoc_STR("__reduce__() -> (cls, state)")}, {NULL, NULL} }; -static char time_doc[] = +static const char time_doc[] = PyDoc_STR("time([hour[, minute[, second[, microsecond[, tzinfo]]]]]) --> a time object\n\ \n\ All arguments are optional. tzinfo may be None, or an instance of\n\ @@ -3960,12 +4092,19 @@ datetime_tzinfo(PyDateTime_DateTime *self, void *unused) return result; } +static PyObject * +datetime_fold(PyDateTime_DateTime *self, void *unused) +{ + return PyLong_FromLong(DATE_GET_FOLD(self)); +} + static PyGetSetDef datetime_getset[] = { {"hour", (getter)datetime_hour}, {"minute", (getter)datetime_minute}, {"second", (getter)datetime_second}, {"microsecond", (getter)datetime_microsecond}, - {"tzinfo", (getter)datetime_tzinfo}, + {"tzinfo", (getter)datetime_tzinfo}, + {"fold", (getter)datetime_fold}, {NULL} }; @@ -3975,7 +4114,7 @@ static PyGetSetDef datetime_getset[] = { static char *datetime_kws[] = { "year", "month", "day", "hour", "minute", "second", - "microsecond", "tzinfo", NULL + "microsecond", "tzinfo", "fold", NULL }; static PyObject * @@ -3990,6 +4129,7 @@ datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) int minute = 0; int second = 0; int usecond = 0; + int fold = 0; PyObject *tzinfo = Py_None; /* Check for invocation from pickle with __getstate__ state */ @@ -3997,7 +4137,7 @@ datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) PyTuple_GET_SIZE(args) <= 2 && PyBytes_Check(state = PyTuple_GET_ITEM(args, 0)) && PyBytes_GET_SIZE(state) == _PyDateTime_DATETIME_DATASIZE && - MONTH_IS_SANE(PyBytes_AS_STRING(state)[2])) + MONTH_IS_SANE(PyBytes_AS_STRING(state)[2] & 0x7F)) { PyDateTime_DateTime *me; char aware; @@ -4022,28 +4162,73 @@ datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) Py_INCREF(tzinfo); me->tzinfo = tzinfo; } + if (pdata[2] & (1 << 7)) { + me->data[2] -= 128; + me->fold = 1; + } + else { + me->fold = 0; + } } return (PyObject *)me; } - if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiiiO", datetime_kws, + if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiiiO$i", datetime_kws, &year, &month, &day, &hour, &minute, - &second, &usecond, &tzinfo)) { + &second, &usecond, &tzinfo, &fold)) { if (check_date_args(year, month, day) < 0) return NULL; - if (check_time_args(hour, minute, second, usecond) < 0) + if (check_time_args(hour, minute, second, usecond, fold) < 0) return NULL; if (check_tzinfo_subclass(tzinfo) < 0) return NULL; - self = new_datetime_ex(year, month, day, + self = new_datetime_ex2(year, month, day, hour, minute, second, usecond, - tzinfo, type); + tzinfo, fold, type); } return self; } -/* TM_FUNC is the shared type of localtime() and gmtime(). */ -typedef struct tm *(*TM_FUNC)(const time_t *timer); +/* TM_FUNC is the shared type of _PyTime_localtime() and + * _PyTime_gmtime(). */ +typedef int (*TM_FUNC)(time_t timer, struct tm*); + +/* As of version 2015f max fold in IANA database is + * 23 hours at 1969-09-30 13:00:00 in Kwajalein. */ +static long long max_fold_seconds = 24 * 3600; +/* NB: date(1970,1,1).toordinal() == 719163 */ +static long long epoch = 719163LL * 24 * 60 * 60; + +static long long +utc_to_seconds(int year, int month, int day, + int hour, int minute, int second) +{ + long long ordinal = ymd_to_ord(year, month, day); + return ((ordinal * 24 + hour) * 60 + minute) * 60 + second; +} + +static long long +local(long long u) +{ + struct tm local_time; + time_t t; + u -= epoch; + t = u; + if (t != u) { + PyErr_SetString(PyExc_OverflowError, + "timestamp out of range for platform time_t"); + return -1; + } + /* XXX: add bounds checking */ + if (_PyTime_localtime(t, &local_time) != 0) + return -1; + return utc_to_seconds(local_time.tm_year + 1900, + local_time.tm_mon + 1, + local_time.tm_mday, + local_time.tm_hour, + local_time.tm_min, + local_time.tm_sec); +} /* Internal helper. * Build datetime from a time_t and a distinct count of microseconds. @@ -4053,72 +4238,46 @@ static PyObject * datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us, PyObject *tzinfo) { - struct tm *tm; + struct tm tm; + int year, month, day, hour, minute, second, fold = 0; - tm = f(&timet); - if (tm == NULL) { -#ifdef EINVAL - if (errno == 0) - errno = EINVAL; -#endif - return PyErr_SetFromErrno(PyExc_OSError); - } + if (f(timet, &tm) != 0) + return NULL; + year = tm.tm_year + 1900; + month = tm.tm_mon + 1; + day = tm.tm_mday; + hour = tm.tm_hour; + minute = tm.tm_min; /* The platform localtime/gmtime may insert leap seconds, - * indicated by tm->tm_sec > 59. We don't care about them, + * indicated by tm.tm_sec > 59. We don't care about them, * except to the extent that passing them on to the datetime * constructor would raise ValueError for a reason that * made no sense to the user. */ - if (tm->tm_sec > 59) - tm->tm_sec = 59; - return PyObject_CallFunction(cls, "iiiiiiiO", - tm->tm_year + 1900, - tm->tm_mon + 1, - tm->tm_mday, - tm->tm_hour, - tm->tm_min, - tm->tm_sec, - us, - tzinfo); -} - -static time_t -_PyTime_DoubleToTimet(double x) -{ - time_t result; - double diff; - - result = (time_t)x; - /* How much info did we lose? time_t may be an integral or - * floating type, and we don't know which. If it's integral, - * we don't know whether C truncates, rounds, returns the floor, - * etc. If we lost a second or more, the C rounding is - * unreasonable, or the input just doesn't fit in a time_t; - * call it an error regardless. Note that the original cast to - * time_t can cause a C error too, but nothing we can do to - * worm around that. - */ - diff = x - (double)result; - if (diff <= -1.0 || diff >= 1.0) { - PyErr_SetString(PyExc_OverflowError, - "timestamp out of range for platform time_t"); - result = (time_t)-1; - } - return result; -} + second = Py_MIN(59, tm.tm_sec); -/* Round a double to the nearest long. |x| must be small enough to fit - * in a C long; this is not checked. - */ -static double -_PyTime_RoundHalfEven(double x) -{ - double rounded = round(x); - if (fabs(x-rounded) == 0.5) - /* halfway case: round to even */ - rounded = 2.0*round(x/2.0); - return rounded; + if (tzinfo == Py_None && f == _PyTime_localtime) { + long long probe_seconds, result_seconds, transition; + + result_seconds = utc_to_seconds(year, month, day, + hour, minute, second); + /* Probe max_fold_seconds to detect a fold. */ + probe_seconds = local(epoch + timet - max_fold_seconds); + if (probe_seconds == -1) + return NULL; + transition = result_seconds - probe_seconds - max_fold_seconds; + if (transition < 0) { + probe_seconds = local(epoch + timet + transition); + if (probe_seconds == -1) + return NULL; + if (probe_seconds == result_seconds) + fold = 1; + } + } + return new_datetime_ex2(year, month, day, hour, + minute, second, us, tzinfo, fold, + (PyTypeObject *)cls); } /* Internal helper. @@ -4129,32 +4288,17 @@ _PyTime_RoundHalfEven(double x) * to get that much precision (e.g., C time() isn't good enough). */ static PyObject * -datetime_from_timestamp(PyObject *cls, TM_FUNC f, double timestamp, +datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, PyObject *tzinfo) { time_t timet; - double fraction; - int us; + long us; - timet = _PyTime_DoubleToTimet(timestamp); - if (timet == (time_t)-1 && PyErr_Occurred()) + if (_PyTime_ObjectToTimeval(timestamp, + &timet, &us, _PyTime_ROUND_HALF_EVEN) == -1) return NULL; - fraction = timestamp - (double)timet; - us = (int)_PyTime_RoundHalfEven(fraction * 1e6); - if (us < 0) { - /* Truncation towards zero is not what we wanted - for negative numbers (Python's mod semantics) */ - timet -= 1; - us += 1000000; - } - /* If timestamp is less than one microsecond smaller than a - * full second, round up. Otherwise, ValueErrors are raised - * for some floats. */ - if (us == 1000000) { - timet += 1; - us = 0; - } - return datetime_from_timet_and_us(cls, f, timet, us, tzinfo); + + return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo); } /* Internal helper. @@ -4201,14 +4345,12 @@ datetime_datetime_now_impl(PyTypeObject *type, PyObject *tz) return NULL; self = datetime_best_possible((PyObject *)type, - tz == Py_None ? localtime : gmtime, + tz == Py_None ? _PyTime_localtime : + _PyTime_gmtime, tz); if (self != NULL && tz != Py_None) { /* Convert UTC to tzinfo's zone. */ - PyObject *temp = self; - - self = _PyObject_CallMethodId(tz, &PyId_fromutc, "O", self); - Py_DECREF(temp); + self = _PyObject_CallMethodId(tz, &PyId_fromutc, "N", self); } return self; } @@ -4219,7 +4361,7 @@ datetime_datetime_now_impl(PyTypeObject *type, PyObject *tz) static PyObject * datetime_utcnow(PyObject *cls, PyObject *dummy) { - return datetime_best_possible(cls, gmtime, Py_None); + return datetime_best_possible(cls, _PyTime_gmtime, Py_None); } /* Return new local datetime from timestamp (Python timestamp -- a double). */ @@ -4227,26 +4369,24 @@ static PyObject * datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) { PyObject *self; - double timestamp; + PyObject *timestamp; PyObject *tzinfo = Py_None; static char *keywords[] = {"timestamp", "tz", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, kw, "d|O:fromtimestamp", + if (! PyArg_ParseTupleAndKeywords(args, kw, "O|O:fromtimestamp", keywords, ×tamp, &tzinfo)) return NULL; if (check_tzinfo_subclass(tzinfo) < 0) return NULL; self = datetime_from_timestamp(cls, - tzinfo == Py_None ? localtime : gmtime, + tzinfo == Py_None ? _PyTime_localtime : + _PyTime_gmtime, timestamp, 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); + self = _PyObject_CallMethodId(tzinfo, &PyId_fromutc, "N", self); } return self; } @@ -4255,11 +4395,11 @@ datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) static PyObject * datetime_utcfromtimestamp(PyObject *cls, PyObject *args) { - double timestamp; + PyObject *timestamp; PyObject *result = NULL; - if (PyArg_ParseTuple(args, "d:utcfromtimestamp", ×tamp)) - result = datetime_from_timestamp(cls, gmtime, timestamp, + if (PyArg_ParseTuple(args, "O:utcfromtimestamp", ×tamp)) + result = datetime_from_timestamp(cls, _PyTime_gmtime, timestamp, Py_None); return result; } @@ -4288,27 +4428,32 @@ datetime_strptime(PyObject *cls, PyObject *args) static PyObject * datetime_combine(PyObject *cls, PyObject *args, PyObject *kw) { - static char *keywords[] = {"date", "time", NULL}; + static char *keywords[] = {"date", "time", "tzinfo", NULL}; PyObject *date; PyObject *time; + PyObject *tzinfo = NULL; PyObject *result = NULL; - if (PyArg_ParseTupleAndKeywords(args, kw, "O!O!:combine", keywords, + if (PyArg_ParseTupleAndKeywords(args, kw, "O!O!|O:combine", keywords, &PyDateTime_DateType, &date, - &PyDateTime_TimeType, &time)) { - PyObject *tzinfo = Py_None; - - if (HASTZINFO(time)) - tzinfo = ((PyDateTime_Time *)time)->tzinfo; + &PyDateTime_TimeType, &time, &tzinfo)) { + if (tzinfo == NULL) { + if (HASTZINFO(time)) + tzinfo = ((PyDateTime_Time *)time)->tzinfo; + else + tzinfo = Py_None; + } result = PyObject_CallFunction(cls, "iiiiiiiO", - GET_YEAR(date), - GET_MONTH(date), - GET_DAY(date), - TIME_GET_HOUR(time), - TIME_GET_MINUTE(time), - TIME_GET_SECOND(time), - TIME_GET_MICROSECOND(time), - tzinfo); + GET_YEAR(date), + GET_MONTH(date), + GET_DAY(date), + TIME_GET_HOUR(time), + TIME_GET_MINUTE(time), + TIME_GET_SECOND(time), + TIME_GET_MICROSECOND(time), + tzinfo); + if (result) + DATE_SET_FOLD(result, TIME_GET_FOLD(time)); } return result; } @@ -4376,7 +4521,7 @@ add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta, else return new_datetime(year, month, day, hour, minute, second, microsecond, - HASTZINFO(date) ? date->tzinfo : Py_None); + HASTZINFO(date) ? date->tzinfo : Py_None, 0); } static PyObject * @@ -4469,9 +4614,7 @@ datetime_subtract(PyObject *left, PyObject *right) return NULL; if (offdiff != NULL) { - PyObject *temp = result; - result = delta_subtract(result, offdiff); - Py_DECREF(temp); + Py_SETREF(result, delta_subtract(result, offdiff)); Py_DECREF(offdiff); } } @@ -4521,6 +4664,8 @@ datetime_repr(PyDateTime_DateTime *self) GET_YEAR(self), GET_MONTH(self), GET_DAY(self), DATE_GET_HOUR(self), DATE_GET_MINUTE(self)); } + if (baserepr != NULL && DATE_GET_FOLD(self) != 0) + baserepr = append_keyword_fold(baserepr, DATE_GET_FOLD(self)); if (baserepr == NULL || ! HASTZINFO(self)) return baserepr; return append_keyword_tzinfo(baserepr, self->tzinfo); @@ -4536,25 +4681,55 @@ static PyObject * datetime_isoformat(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) { int sep = 'T'; - static char *keywords[] = {"sep", NULL}; + char *timespec = NULL; + static char *keywords[] = {"sep", "timespec", NULL}; char buffer[100]; - PyObject *result; + PyObject *result = NULL; int us = DATE_GET_MICROSECOND(self); + static char *specs[][2] = { + {"hours", "%04d-%02d-%02d%c%02d"}, + {"minutes", "%04d-%02d-%02d%c%02d:%02d"}, + {"seconds", "%04d-%02d-%02d%c%02d:%02d:%02d"}, + {"milliseconds", "%04d-%02d-%02d%c%02d:%02d:%02d.%03d"}, + {"microseconds", "%04d-%02d-%02d%c%02d:%02d:%02d.%06d"}, + }; + size_t given_spec; - if (!PyArg_ParseTupleAndKeywords(args, kw, "|C:isoformat", keywords, &sep)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "|Cs:isoformat", keywords, &sep, ×pec)) return NULL; - if (us) - result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d.%06d", + + if (timespec == NULL || strcmp(timespec, "auto") == 0) { + if (us == 0) { + /* seconds */ + given_spec = 2; + } + else { + /* microseconds */ + given_spec = 4; + } + } + else { + for (given_spec = 0; given_spec < Py_ARRAY_LENGTH(specs); given_spec++) { + if (strcmp(timespec, specs[given_spec][0]) == 0) { + if (given_spec == 3) { + us = us / 1000; + } + break; + } + } + } + + if (given_spec == Py_ARRAY_LENGTH(specs)) { + PyErr_Format(PyExc_ValueError, "Unknown timespec value"); + return NULL; + } + else { + result = PyUnicode_FromFormat(specs[given_spec][1], GET_YEAR(self), GET_MONTH(self), GET_DAY(self), (int)sep, DATE_GET_HOUR(self), DATE_GET_MINUTE(self), DATE_GET_SECOND(self), us); - else - result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d", - GET_YEAR(self), GET_MONTH(self), - GET_DAY(self), (int)sep, - DATE_GET_HOUR(self), DATE_GET_MINUTE(self), - DATE_GET_SECOND(self)); + } if (!result || !HASTZINFO(self)) return result; @@ -4581,6 +4756,70 @@ datetime_ctime(PyDateTime_DateTime *self) /* Miscellaneous methods. */ static PyObject * +flip_fold(PyObject *dt) +{ + return new_datetime_ex2(GET_YEAR(dt), + GET_MONTH(dt), + GET_DAY(dt), + DATE_GET_HOUR(dt), + DATE_GET_MINUTE(dt), + DATE_GET_SECOND(dt), + DATE_GET_MICROSECOND(dt), + HASTZINFO(dt) ? + ((PyDateTime_DateTime *)dt)->tzinfo : Py_None, + !DATE_GET_FOLD(dt), + Py_TYPE(dt)); +} + +static PyObject * +get_flip_fold_offset(PyObject *dt) +{ + PyObject *result, *flip_dt; + + flip_dt = flip_fold(dt); + if (flip_dt == NULL) + return NULL; + result = datetime_utcoffset(flip_dt, NULL); + Py_DECREF(flip_dt); + return result; +} + +/* PEP 495 exception: Whenever one or both of the operands in + * inter-zone comparison is such that its utcoffset() depends + * on the value of its fold fold attribute, the result is False. + * + * Return 1 if exception applies, 0 if not, and -1 on error. + */ +static int +pep495_eq_exception(PyObject *self, PyObject *other, + PyObject *offset_self, PyObject *offset_other) +{ + int result = 0; + PyObject *flip_offset; + + flip_offset = get_flip_fold_offset(self); + if (flip_offset == NULL) + return -1; + if (flip_offset != offset_self && + delta_cmp(flip_offset, offset_self)) + { + result = 1; + goto done; + } + Py_DECREF(flip_offset); + + flip_offset = get_flip_fold_offset(other); + if (flip_offset == NULL) + return -1; + if (flip_offset != offset_other && + delta_cmp(flip_offset, offset_other)) + result = 1; + done: + Py_DECREF(flip_offset); + return result; +} + +static PyObject * datetime_richcompare(PyObject *self, PyObject *other, int op) { PyObject *result = NULL; @@ -4627,6 +4866,13 @@ datetime_richcompare(PyObject *self, PyObject *other, int op) diff = memcmp(((PyDateTime_DateTime *)self)->data, ((PyDateTime_DateTime *)other)->data, _PyDateTime_DATETIME_DATASIZE); + if ((op == Py_EQ || op == Py_NE) && diff == 0) { + int ex = pep495_eq_exception(self, other, offset1, offset2); + if (ex == -1) + goto done; + if (ex) + diff = 1; + } result = diff_to_bool(diff, op); } else if (offset1 != Py_None && offset2 != Py_None) { @@ -4642,6 +4888,13 @@ datetime_richcompare(PyObject *self, PyObject *other, int op) diff = GET_TD_SECONDS(delta) | GET_TD_MICROSECONDS(delta); Py_DECREF(delta); + if ((op == Py_EQ || op == Py_NE) && diff == 0) { + int ex = pep495_eq_exception(self, other, offset1, offset2); + if (ex == -1) + goto done; + if (ex) + diff = 1; + } result = diff_to_bool(diff, op); } else if (op == Py_EQ) { @@ -4667,9 +4920,26 @@ static Py_hash_t datetime_hash(PyDateTime_DateTime *self) { if (self->hashcode == -1) { - PyObject *offset; - - offset = datetime_utcoffset((PyObject *)self, NULL); + PyObject *offset, *self0; + if (DATE_GET_FOLD(self)) { + self0 = new_datetime_ex2(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), + HASTZINFO(self) ? self->tzinfo : Py_None, + 0, Py_TYPE(self)); + if (self0 == NULL) + return -1; + } + else { + self0 = (PyObject *)self; + Py_INCREF(self0); + } + offset = datetime_utcoffset(self0, NULL); + Py_DECREF(self0); if (offset == NULL) return -1; @@ -4723,76 +4993,80 @@ datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) int ss = DATE_GET_SECOND(self); int us = DATE_GET_MICROSECOND(self); PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; + int fold = DATE_GET_FOLD(self); - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO:replace", + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO$i:replace", datetime_kws, &y, &m, &d, &hh, &mm, &ss, &us, - &tzinfo)) + &tzinfo, &fold)) return NULL; tuple = Py_BuildValue("iiiiiiiO", y, m, d, hh, mm, ss, us, tzinfo); if (tuple == NULL) return NULL; clone = datetime_new(Py_TYPE(self), tuple, NULL); + + if (clone != NULL) { + if (fold != 0 && fold != 1) { + PyErr_SetString(PyExc_ValueError, + "fold must be either 0 or 1"); + return NULL; + } + DATE_SET_FOLD(clone, fold); + } Py_DECREF(tuple); return clone; } static PyObject * -local_timezone(PyDateTime_DateTime *utc_time) +local_timezone_from_timestamp(time_t timestamp) { PyObject *result = NULL; - struct tm *timep; - time_t timestamp; PyObject *delta; - PyObject *one_second; - PyObject *seconds; + struct tm local_time_tm; PyObject *nameo = NULL; const char *zone = NULL; - delta = new_delta(ymd_to_ord(GET_YEAR(utc_time), GET_MONTH(utc_time), - GET_DAY(utc_time)) - 719163, - 60 * (60 * DATE_GET_HOUR(utc_time) + - DATE_GET_MINUTE(utc_time)) + - DATE_GET_SECOND(utc_time), - 0, 0); - if (delta == NULL) + if (_PyTime_localtime(timestamp, &local_time_tm) != 0) return NULL; - one_second = new_delta(0, 1, 0, 0); - if (one_second == NULL) - goto error; - seconds = divide_timedelta_timedelta((PyDateTime_Delta *)delta, - (PyDateTime_Delta *)one_second); - Py_DECREF(one_second); - if (seconds == NULL) - goto error; - Py_DECREF(delta); - timestamp = PyLong_AsLong(seconds); - Py_DECREF(seconds); - if (timestamp == -1 && PyErr_Occurred()) - return NULL; - timep = localtime(×tamp); #ifdef HAVE_STRUCT_TM_TM_ZONE - zone = timep->tm_zone; - delta = new_delta(0, timep->tm_gmtoff, 0, 1); + zone = local_time_tm.tm_zone; + delta = new_delta(0, local_time_tm.tm_gmtoff, 0, 1); #else /* HAVE_STRUCT_TM_TM_ZONE */ { - PyObject *local_time; - 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), - utc_time->tzinfo); - if (local_time == NULL) - goto error; - delta = datetime_subtract(local_time, (PyObject*)utc_time); - /* XXX: before relying on tzname, we should compare delta - to the offset implied by timezone/altzone */ - if (daylight && timep->tm_isdst >= 0) - zone = tzname[timep->tm_isdst % 2]; - else - zone = tzname[0]; + PyObject *local_time, *utc_time; + struct tm utc_time_tm; + char buf[100]; + strftime(buf, sizeof(buf), "%Z", &local_time_tm); + zone = buf; + local_time = new_datetime(local_time_tm.tm_year + 1900, + local_time_tm.tm_mon + 1, + local_time_tm.tm_mday, + local_time_tm.tm_hour, + local_time_tm.tm_min, + local_time_tm.tm_sec, 0, Py_None, 0); + if (local_time == NULL) { + return NULL; + } + if (_PyTime_gmtime(timestamp, &utc_time_tm) != 0) + return NULL; + utc_time = new_datetime(utc_time_tm.tm_year + 1900, + utc_time_tm.tm_mon + 1, + utc_time_tm.tm_mday, + utc_time_tm.tm_hour, + utc_time_tm.tm_min, + utc_time_tm.tm_sec, 0, Py_None, 0); + if (utc_time == NULL) { + Py_DECREF(local_time); + return NULL; + } + delta = datetime_subtract(local_time, utc_time); Py_DECREF(local_time); + Py_DECREF(utc_time); } #endif /* HAVE_STRUCT_TM_TM_ZONE */ + if (delta == NULL) { + return NULL; + } if (zone != NULL) { nameo = PyUnicode_DecodeLocale(zone, "surrogateescape"); if (nameo == NULL) @@ -4805,12 +5079,65 @@ local_timezone(PyDateTime_DateTime *utc_time) return result; } +static PyObject * +local_timezone(PyDateTime_DateTime *utc_time) +{ + time_t timestamp; + PyObject *delta; + PyObject *one_second; + PyObject *seconds; + + delta = datetime_subtract((PyObject *)utc_time, PyDateTime_Epoch); + if (delta == NULL) + return NULL; + one_second = new_delta(0, 1, 0, 0); + if (one_second == NULL) { + Py_DECREF(delta); + return NULL; + } + seconds = divide_timedelta_timedelta((PyDateTime_Delta *)delta, + (PyDateTime_Delta *)one_second); + Py_DECREF(one_second); + Py_DECREF(delta); + if (seconds == NULL) + return NULL; + timestamp = _PyLong_AsTime_t(seconds); + Py_DECREF(seconds); + if (timestamp == -1 && PyErr_Occurred()) + return NULL; + return local_timezone_from_timestamp(timestamp); +} + +static long long +local_to_seconds(int year, int month, int day, + int hour, int minute, int second, int fold); + +static PyObject * +local_timezone_from_local(PyDateTime_DateTime *local_dt) +{ + long long seconds; + time_t timestamp; + seconds = local_to_seconds(GET_YEAR(local_dt), + GET_MONTH(local_dt), + GET_DAY(local_dt), + DATE_GET_HOUR(local_dt), + DATE_GET_MINUTE(local_dt), + DATE_GET_SECOND(local_dt), + DATE_GET_FOLD(local_dt)); + if (seconds == -1) + return NULL; + /* XXX: add bounds check */ + timestamp = seconds - epoch; + return local_timezone_from_timestamp(timestamp); +} + static PyDateTime_DateTime * datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) { PyDateTime_DateTime *result; PyObject *offset; PyObject *temp; + PyObject *self_tzinfo; PyObject *tzinfo = Py_None; static char *keywords[] = {"tz", NULL}; @@ -4821,27 +5148,27 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) if (check_tzinfo_subclass(tzinfo) == -1) return NULL; - if (!HASTZINFO(self) || self->tzinfo == Py_None) - goto NeedAware; + if (!HASTZINFO(self) || self->tzinfo == Py_None) { + self_tzinfo = local_timezone_from_local(self); + if (self_tzinfo == NULL) + return NULL; + } else { + self_tzinfo = self->tzinfo; + Py_INCREF(self_tzinfo); + } /* Conversion to self's own time zone is a NOP. */ - if (self->tzinfo == tzinfo) { + if (self_tzinfo == tzinfo) { + Py_DECREF(self_tzinfo); Py_INCREF(self); return self; } /* Convert self to UTC. */ - offset = datetime_utcoffset((PyObject *)self, NULL); + offset = call_utcoffset(self_tzinfo, (PyObject *)self); + Py_DECREF(self_tzinfo); if (offset == NULL) return NULL; - if (offset == Py_None) { - Py_DECREF(offset); - NeedAware: - PyErr_SetString(PyExc_ValueError, "astimezone() cannot be applied to " - "a naive datetime"); - return NULL; - } - /* result = self - offset */ result = (PyDateTime_DateTime *)add_datetime_timedelta(self, (PyDateTime_Delta *)offset, -1); @@ -4849,6 +5176,32 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) if (result == NULL) return NULL; + /* Make sure result is aware and UTC. */ + if (!HASTZINFO(result)) { + temp = (PyObject *)result; + result = (PyDateTime_DateTime *) + new_datetime_ex2(GET_YEAR(result), + GET_MONTH(result), + GET_DAY(result), + DATE_GET_HOUR(result), + DATE_GET_MINUTE(result), + DATE_GET_SECOND(result), + DATE_GET_MICROSECOND(result), + PyDateTime_TimeZone_UTC, + DATE_GET_FOLD(result), + Py_TYPE(result)); + Py_DECREF(temp); + if (result == NULL) + return NULL; + } + else { + /* Result is already aware - just replace tzinfo. */ + temp = result->tzinfo; + result->tzinfo = PyDateTime_TimeZone_UTC; + Py_INCREF(result->tzinfo); + Py_DECREF(temp); + } + /* Attach new tzinfo and let fromutc() do the rest. */ temp = result->tzinfo; if (tzinfo == Py_None) { @@ -4896,6 +5249,56 @@ datetime_timetuple(PyDateTime_DateTime *self) dstflag); } +static long long +local_to_seconds(int year, int month, int day, + int hour, int minute, int second, int fold) +{ + long long t, a, b, u1, u2, t1, t2, lt; + t = utc_to_seconds(year, month, day, hour, minute, second); + /* Our goal is to solve t = local(u) for u. */ + lt = local(t); + if (lt == -1) + return -1; + a = lt - t; + u1 = t - a; + t1 = local(u1); + if (t1 == -1) + return -1; + if (t1 == t) { + /* We found one solution, but it may not be the one we need. + * Look for an earlier solution (if `fold` is 0), or a + * later one (if `fold` is 1). */ + if (fold) + u2 = u1 + max_fold_seconds; + else + u2 = u1 - max_fold_seconds; + lt = local(u2); + if (lt == -1) + return -1; + b = lt - u2; + if (a == b) + return u1; + } + else { + b = t1 - u1; + assert(a != b); + } + u2 = t - b; + t2 = local(u2); + if (t2 == -1) + return -1; + if (t2 == t) + return u2; + if (t1 == t) + return u1; + /* We have found both offsets a and b, but neither t - a nor t - b is + * a solution. This means t is in the gap. */ + return fold?Py_MIN(u1, u2):Py_MAX(u1, u2); +} + +/* date(1970,1,1).toordinal() == 719163 */ +#define EPOCH_SECONDS (719163LL * 24 * 60 * 60) + static PyObject * datetime_timestamp(PyDateTime_DateTime *self) { @@ -4910,33 +5313,18 @@ datetime_timestamp(PyDateTime_DateTime *self) Py_DECREF(delta); } else { - struct tm time; - time_t timestamp; - memset((void *) &time, '\0', sizeof(struct tm)); - time.tm_year = GET_YEAR(self) - 1900; - time.tm_mon = GET_MONTH(self) - 1; - time.tm_mday = GET_DAY(self); - time.tm_hour = DATE_GET_HOUR(self); - time.tm_min = DATE_GET_MINUTE(self); - time.tm_sec = DATE_GET_SECOND(self); - time.tm_wday = -1; - time.tm_isdst = -1; - timestamp = mktime(&time); - if (timestamp == (time_t)(-1) -#ifndef _AIX - /* Return value of -1 does not necessarily mean an error, - * but tm_wday cannot remain set to -1 if mktime succeeded. */ - && time.tm_wday == -1 -#else - /* on AIX, tm_wday is always sets, even on error */ -#endif - ) - { - PyErr_SetString(PyExc_OverflowError, - "timestamp out of range"); + long long seconds; + seconds = local_to_seconds(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self), + DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_FOLD(self)); + if (seconds == -1) return NULL; - } - result = PyFloat_FromDouble(timestamp + DATE_GET_MICROSECOND(self) / 1e6); + result = PyFloat_FromDouble(seconds - EPOCH_SECONDS + + DATE_GET_MICROSECOND(self) / 1e6); } return result; } @@ -4956,7 +5344,8 @@ datetime_gettime(PyDateTime_DateTime *self) DATE_GET_MINUTE(self), DATE_GET_SECOND(self), DATE_GET_MICROSECOND(self), - Py_None); + Py_None, + DATE_GET_FOLD(self)); } static PyObject * @@ -4966,7 +5355,8 @@ datetime_gettimetz(PyDateTime_DateTime *self) DATE_GET_MINUTE(self), DATE_GET_SECOND(self), DATE_GET_MICROSECOND(self), - GET_DT_TZINFO(self)); + GET_DT_TZINFO(self), + DATE_GET_FOLD(self)); } static PyObject * @@ -5018,7 +5408,7 @@ datetime_utctimetuple(PyDateTime_DateTime *self) * __getstate__ isn't exposed. */ static PyObject * -datetime_getstate(PyDateTime_DateTime *self) +datetime_getstate(PyDateTime_DateTime *self, int proto) { PyObject *basestate; PyObject *result = NULL; @@ -5026,6 +5416,9 @@ datetime_getstate(PyDateTime_DateTime *self) basestate = PyBytes_FromStringAndSize((char *)self->data, _PyDateTime_DATETIME_DATASIZE); if (basestate != NULL) { + if (proto > 3 && DATE_GET_FOLD(self)) + /* Set the first bit of the third byte */ + PyBytes_AS_STRING(basestate)[2] |= (1 << 7); if (! HASTZINFO(self) || self->tzinfo == Py_None) result = PyTuple_Pack(1, basestate); else @@ -5036,9 +5429,19 @@ datetime_getstate(PyDateTime_DateTime *self) } static PyObject * +datetime_reduce_ex(PyDateTime_DateTime *self, PyObject *args) +{ + int proto; + if (!PyArg_ParseTuple(args, "i:__reduce_ex__", &proto)) + return NULL; + + return Py_BuildValue("(ON)", Py_TYPE(self), datetime_getstate(self, proto)); +} + +static PyObject * datetime_reduce(PyDateTime_DateTime *self, PyObject *arg) { - return Py_BuildValue("(ON)", Py_TYPE(self), datetime_getstate(self)); + return Py_BuildValue("(ON)", Py_TYPE(self), datetime_getstate(self, 2)); } static PyMethodDef datetime_methods[] = { @@ -5093,9 +5496,12 @@ static PyMethodDef datetime_methods[] = { {"isoformat", (PyCFunction)datetime_isoformat, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("[sep] -> string in ISO 8601 format, " - "YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM].\n\n" + "YYYY-MM-DDT[HH[:MM[:SS[.mmm[uuu]]]]][+HH:MM].\n" "sep is used to separate the year from the time, and " - "defaults to 'T'.")}, + "defaults to 'T'.\n" + "timespec specifies what components of the time to include" + " (allowed values are 'auto', 'hours', 'minutes', 'seconds'," + " 'milliseconds', and 'microseconds').\n")}, {"utcoffset", (PyCFunction)datetime_utcoffset, METH_NOARGS, PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, @@ -5112,13 +5518,16 @@ static PyMethodDef datetime_methods[] = { {"astimezone", (PyCFunction)datetime_astimezone, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("tz -> convert to local time in new timezone tz\n")}, + {"__reduce_ex__", (PyCFunction)datetime_reduce_ex, METH_VARARGS, + PyDoc_STR("__reduce_ex__(proto) -> (cls, state)")}, + {"__reduce__", (PyCFunction)datetime_reduce, METH_NOARGS, PyDoc_STR("__reduce__() -> (cls, state)")}, {NULL, NULL} }; -static char datetime_doc[] = +static const char datetime_doc[] = PyDoc_STR("datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])\n\ \n\ The year, month and day arguments are required. tzinfo may be None, or an\n\ @@ -5201,7 +5610,9 @@ static PyDateTime_CAPI CAPI = { new_time_ex, new_delta_ex, datetime_fromtimestamp, - date_fromtimestamp + date_fromtimestamp, + new_datetime_ex2, + new_time_ex2 }; @@ -5282,12 +5693,12 @@ PyInit__datetime(void) /* time values */ d = PyDateTime_TimeType.tp_dict; - x = new_time(0, 0, 0, 0, Py_None); + x = new_time(0, 0, 0, 0, Py_None, 0); if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) return NULL; Py_DECREF(x); - x = new_time(23, 59, 59, 999999, Py_None); + x = new_time(23, 59, 59, 999999, Py_None, 0); if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) return NULL; Py_DECREF(x); @@ -5300,12 +5711,12 @@ PyInit__datetime(void) /* 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, Py_None, 0); 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, Py_None, 0); if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) return NULL; Py_DECREF(x); @@ -5347,7 +5758,7 @@ PyInit__datetime(void) /* Epoch */ PyDateTime_Epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0, - PyDateTime_TimeZone_UTC); + PyDateTime_TimeZone_UTC, 0); if (PyDateTime_Epoch == NULL) return NULL; @@ -5382,19 +5793,19 @@ PyInit__datetime(void) /* A 4-year cycle has an extra leap day over what we'd get from * pasting together 4 single years. */ - assert(DI4Y == 4 * 365 + 1); + Py_BUILD_ASSERT(DI4Y == 4 * 365 + 1); assert(DI4Y == days_before_year(4+1)); /* Similarly, a 400-year cycle has an extra leap day over what we'd * get from pasting together 4 100-year cycles. */ - assert(DI400Y == 4 * DI100Y + 1); + Py_BUILD_ASSERT(DI400Y == 4 * DI100Y + 1); assert(DI400Y == days_before_year(400+1)); /* OTOH, a 100-year cycle has one fewer leap day than we'd get from * pasting together 25 4-year cycles. */ - assert(DI100Y == 25 * DI4Y - 1); + Py_BUILD_ASSERT(DI100Y == 25 * DI4Y - 1); assert(DI100Y == days_before_year(100+1)); one = PyLong_FromLong(1); |