Commit 73ca440e authored by Alexander Belopolsky's avatar Alexander Belopolsky

Issue #5288: Eliminated round-trips between timdelta and int offsets

parent 6ef08a0e
......@@ -88,8 +88,11 @@
/* p is a pointer to a time or a datetime object; HASTZINFO(p) returns
* p->hastzinfo.
*/
#define HASTZINFO(p) (((_PyDateTime_BaseTZInfo *)(p))->hastzinfo)
#define HASTZINFO(p) (((_PyDateTime_BaseTZInfo *)(p))->hastzinfo)
#define GET_TIME_TZINFO(p) (HASTZINFO(p) ? \
((PyDateTime_Time *)(p))->tzinfo : Py_None)
#define GET_DT_TZINFO(p) (HASTZINFO(p) ? \
((PyDateTime_DateTime *)(p))->tzinfo : Py_None)
/* M is a char or int claiming to be a valid month. The macro is equivalent
* to the two-sided Python test
* 1 <= M <= 12
......@@ -839,25 +842,6 @@ check_tzinfo_subclass(PyObject *p)
return -1;
}
/* Return tzinfo.methname(tzinfoarg), without any checking of results.
* If tzinfo is None, returns None.
*/
static PyObject *
call_tzinfo_method(PyObject *tzinfo, char *methname, PyObject *tzinfoarg)
{
PyObject *result;
assert(tzinfo && methname && tzinfoarg);
assert(check_tzinfo_subclass(tzinfo) >= 0);
if (tzinfo == Py_None) {
result = Py_None;
Py_INCREF(result);
}
else
result = PyObject_CallMethod(tzinfo, methname, "O", tzinfoarg);
return result;
}
/* If self has a tzinfo member, return a BORROWED reference to it. Else
* return NULL, which is NOT AN ERROR. There are no error returns here,
* and the caller must not decref the result.
......@@ -875,69 +859,53 @@ get_tzinfo_member(PyObject *self)
return tzinfo;
}
/* Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the
* result. tzinfo must be an instance of the tzinfo class. If the method
* returns None, this returns 0 and sets *none to 1. If the method doesn't
* return None or timedelta, TypeError is raised and this returns -1. If it
* returnsa timedelta and the value is out of range or isn't a whole number
* of minutes, ValueError is raised and this returns -1.
* Else *none is set to 0 and the integer method result is returned.
/* Call getattr(tzinfo, name)(tzinfoarg), and check the result. tzinfo must
* be an instance of the tzinfo class. If the method returns None, this
* returns None. If the method doesn't return None or timedelta, TypeError is
* raised and this returns NULL. If it returns a timedelta and the value is
* out of range or isn't a whole number of minutes, ValueError is raised and
* this returns NULL. Else result is returned.
*/
static int
call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
int *none)
static PyObject *
call_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg)
{
PyObject *u;
int result = -1;
PyObject *offset;
assert(tzinfo != NULL);
assert(PyTZInfo_Check(tzinfo));
assert(PyTZInfo_Check(tzinfo) || tzinfo == Py_None);
assert(tzinfoarg != NULL);
*none = 0;
u = call_tzinfo_method(tzinfo, name, tzinfoarg);
if (u == NULL)
return -1;
else if (u == Py_None) {
result = 0;
*none = 1;
}
else if (PyDelta_Check(u)) {
const int days = GET_TD_DAYS(u);
if (days < -1 || days > 0)
result = 24*60; /* trigger ValueError below */
else {
/* next line can't overflow because we know days
* is -1 or 0 now
*/
int ss = days * 24 * 3600 + GET_TD_SECONDS(u);
result = divmod(ss, 60, &ss);
if (ss || GET_TD_MICROSECONDS(u)) {
PyErr_Format(PyExc_ValueError,
"tzinfo.%s() must return a "
"whole number of minutes",
name);
result = -1;
}
if (tzinfo == Py_None)
Py_RETURN_NONE;
offset = PyObject_CallMethod(tzinfo, name, "O", 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) {
Py_DECREF(offset);
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
" representing a whole number of minutes");
return NULL;
}
if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
Py_DECREF(offset);
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
" strictly between -timedelta(hours=24) and"
" timedelta(hours=24).");
return NULL;
}
}
else {
Py_DECREF(offset);
PyErr_Format(PyExc_TypeError,
"tzinfo.%s() must return None or "
"timedelta, not '%s'",
name, Py_TYPE(u)->tp_name);
"timedelta, not '%.200s'",
name, Py_TYPE(offset)->tp_name);
return NULL;
}
Py_DECREF(u);
if (result < -1439 || result > 1439) {
PyErr_Format(PyExc_ValueError,
"tzinfo.%s() returned %d; must be in "
"-1439 .. 1439",
name, result);
result = -1;
}
return result;
return offset;
}
/* Call tzinfo.utcoffset(tzinfoarg), and extract an integer from the
......@@ -948,37 +916,10 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
* # of minutes), ValueError is raised and this returns -1. Else *none is
* set to 0 and the offset is returned (as int # of minutes east of UTC).
*/
static int
call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
{
return call_utc_tzinfo_method(tzinfo, "utcoffset", tzinfoarg, none);
}
/* Call tzinfo.name(tzinfoarg), and return the offset as a timedelta or None.
*/
static PyObject *
offset_as_timedelta(PyObject *tzinfo, char *name, PyObject *tzinfoarg) {
PyObject *result;
assert(tzinfo && name && tzinfoarg);
if (tzinfo == Py_None) {
result = Py_None;
Py_INCREF(result);
}
else {
int none;
int offset = call_utc_tzinfo_method(tzinfo, name, tzinfoarg,
&none);
if (offset < 0 && PyErr_Occurred())
return NULL;
if (none) {
result = Py_None;
Py_INCREF(result);
}
else
result = new_delta(0, offset * 60, 0, 1);
}
return result;
call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg)
{
return call_tzinfo_method(tzinfo, "utcoffset", tzinfoarg);
}
/* Call tzinfo.dst(tzinfoarg), and extract an integer from the
......@@ -989,10 +930,10 @@ offset_as_timedelta(PyObject *tzinfo, char *name, PyObject *tzinfoarg) {
* ValueError is raised and this returns -1. Else *none is set to 0 and
* the offset is returned (as an int # of minutes east of UTC).
*/
static int
call_dst(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
static PyObject *
call_dst(PyObject *tzinfo, PyObject *tzinfoarg)
{
return call_utc_tzinfo_method(tzinfo, "dst", tzinfoarg, none);
return call_tzinfo_method(tzinfo, "dst", tzinfoarg);
}
/* Call tzinfo.tzname(tzinfoarg), and return the result. tzinfo must be
......@@ -1010,102 +951,23 @@ call_tzname(PyObject *tzinfo, PyObject *tzinfoarg)
assert(check_tzinfo_subclass(tzinfo) >= 0);
assert(tzinfoarg != NULL);
if (tzinfo == Py_None) {
result = Py_None;
Py_INCREF(result);
}
else
result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg);
if (result != NULL && result != Py_None) {
if (!PyUnicode_Check(result)) {
PyErr_Format(PyExc_TypeError, "tzinfo.tzname() must "
"return None or a string, not '%s'",
Py_TYPE(result)->tp_name);
Py_DECREF(result);
result = NULL;
}
}
return result;
}
if (tzinfo == Py_None)
Py_RETURN_NONE;
typedef enum {
/* an exception has been set; the caller should pass it on */
OFFSET_ERROR,
/* type isn't date, datetime, or time subclass */
OFFSET_UNKNOWN,
/* date,
* datetime with !hastzinfo
* datetime with None tzinfo,
* datetime where utcoffset() returns None
* time with !hastzinfo
* time with None tzinfo,
* time where utcoffset() returns None
*/
OFFSET_NAIVE,
/* time or datetime where utcoffset() doesn't return None */
OFFSET_AWARE
} naivety;
/* Classify an object as to whether it's naive or offset-aware. See
* the "naivety" typedef for details. If the type is aware, *offset is set
* to minutes east of UTC (as returned by the tzinfo.utcoffset() method).
* If the type is offset-naive (or unknown, or error), *offset is set to 0.
* tzinfoarg is the argument to pass to the tzinfo.utcoffset() method.
*/
static naivety
classify_utcoffset(PyObject *op, PyObject *tzinfoarg, int *offset)
{
int none;
PyObject *tzinfo;
result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg);
assert(tzinfoarg != NULL);
*offset = 0;
tzinfo = get_tzinfo_member(op); /* NULL means no tzinfo, not error */
if (tzinfo == Py_None)
return OFFSET_NAIVE;
if (tzinfo == NULL) {
/* note that a datetime passes the PyDate_Check test */
return (PyTime_Check(op) || PyDate_Check(op)) ?
OFFSET_NAIVE : OFFSET_UNKNOWN;
}
*offset = call_utcoffset(tzinfo, tzinfoarg, &none);
if (*offset == -1 && PyErr_Occurred())
return OFFSET_ERROR;
return none ? OFFSET_NAIVE : OFFSET_AWARE;
}
/* Classify two objects as to whether they're naive or offset-aware.
* This isn't quite the same as calling classify_utcoffset() twice: for
* binary operations (comparison and subtraction), we generally want to
* ignore the tzinfo members if they're identical. This is by design,
* so that results match "naive" expectations when mixing objects from a
* single timezone. So in that case, this sets both offsets to 0 and
* both naiveties to OFFSET_NAIVE.
* The function returns 0 if everything's OK, and -1 on error.
*/
static int
classify_two_utcoffsets(PyObject *o1, int *offset1, naivety *n1,
PyObject *tzinfoarg1,
PyObject *o2, int *offset2, naivety *n2,
PyObject *tzinfoarg2)
{
if (get_tzinfo_member(o1) == get_tzinfo_member(o2)) {
*offset1 = *offset2 = 0;
*n1 = *n2 = OFFSET_NAIVE;
}
else {
*n1 = classify_utcoffset(o1, tzinfoarg1, offset1);
if (*n1 == OFFSET_ERROR)
return -1;
*n2 = classify_utcoffset(o2, tzinfoarg2, offset2);
if (*n2 == OFFSET_ERROR)
return -1;
if (result == NULL || result == Py_None)
return result;
if (!PyUnicode_Check(result)) {
PyErr_Format(PyExc_TypeError, "tzinfo.tzname() must "
"return None or a string, not '%s'",
Py_TYPE(result)->tp_name);
Py_DECREF(result);
result = NULL;
}
return 0;
return result;
}
/* repr is like "someclass(arg1, arg2)". If tzinfo isn't None,
......@@ -1157,6 +1019,8 @@ format_ctime(PyDateTime_Date *date, int hours, int minutes, int seconds)
GET_YEAR(date));
}
static PyObject *delta_negative(PyDateTime_Delta *self);
/* Add an hours & minutes UTC offset string to buf. buf has no more than
* buflen bytes remaining. The UTC offset is gotten by calling
* tzinfo.uctoffset(tzinfoarg). If that returns None, \0 is stored into
......@@ -1171,28 +1035,41 @@ static int
format_utcoffset(char *buf, size_t buflen, const char *sep,
PyObject *tzinfo, PyObject *tzinfoarg)
{
int offset;
int hours;
int minutes;
PyObject *offset;
int hours, minutes, seconds;
char sign;
int none;
assert(buflen >= 1);
offset = call_utcoffset(tzinfo, tzinfoarg, &none);
if (offset == -1 && PyErr_Occurred())
offset = call_utcoffset(tzinfo, tzinfoarg);
if (offset == NULL)
return -1;
if (none) {
if (offset == Py_None) {
Py_DECREF(offset);
*buf = '\0';
return 0;
}
sign = '+';
if (offset < 0) {
/* Offset is normalized, so it is negative if days < 0 */
if (GET_TD_DAYS(offset) < 0) {
PyObject *temp = offset;
sign = '-';
offset = - offset;
offset = delta_negative((PyDateTime_Delta *)offset);
Py_DECREF(temp);
if (offset == NULL)
return -1;
}
else {
sign = '+';
}
hours = divmod(offset, 60, &minutes);
/* Offset is not negative here. */
seconds = GET_TD_SECONDS(offset);
Py_DECREF(offset);
minutes = divmod(seconds, 60, &seconds);
hours = divmod(minutes, 60, &minutes);
assert(seconds == 0);
/* XXX ignore sub-minute data, curently not allowed. */
PyOS_snprintf(buf, buflen, "%c%02d%s%02d", sign, hours, sep, minutes);
return 0;
}
......@@ -1434,7 +1311,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
format = PyUnicode_FromString(PyBytes_AS_STRING(newfmt));
if (format != NULL) {
result = PyObject_CallMethod(time, "strftime", "OO",
format, timetuple);
format, timetuple, NULL);
Py_DECREF(format);
}
Py_DECREF(time);
......@@ -1937,17 +1814,24 @@ delta_subtract(PyObject *left, PyObject *right)
return result;
}
static int
delta_cmp(PyObject *self, PyObject *other)
{
int diff = GET_TD_DAYS(self) - GET_TD_DAYS(other);
if (diff == 0) {
diff = GET_TD_SECONDS(self) - GET_TD_SECONDS(other);
if (diff == 0)
diff = GET_TD_MICROSECONDS(self) -
GET_TD_MICROSECONDS(other);
}
return diff;
}
static PyObject *
delta_richcompare(PyObject *self, PyObject *other, int op)
{
if (PyDelta_Check(other)) {
int diff = GET_TD_DAYS(self) - GET_TD_DAYS(other);
if (diff == 0) {
diff = GET_TD_SECONDS(self) - GET_TD_SECONDS(other);
if (diff == 0)
diff = GET_TD_MICROSECONDS(self) -
GET_TD_MICROSECONDS(other);
}
int diff = delta_cmp(self, other);
return diff_to_bool(diff, op);
}
else {
......@@ -3119,76 +3003,73 @@ tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt)
return tzinfo_nogo("dst");
}
static PyObject *add_datetime_timedelta(PyDateTime_DateTime *date,
PyDateTime_Delta *delta,
int factor);
static PyObject *datetime_utcoffset(PyObject *self, PyObject *);
static PyObject *datetime_dst(PyObject *self, PyObject *);
static PyObject *
tzinfo_fromutc(PyDateTime_TZInfo *self, PyDateTime_DateTime *dt)
tzinfo_fromutc(PyDateTime_TZInfo *self, PyObject *dt)
{
int y, m, d, hh, mm, ss, us;
PyObject *result;
int off, dst;
int none;
int delta;
PyObject *result = NULL;
PyObject *off = NULL, *dst = NULL;
PyDateTime_Delta *delta = NULL;
if (! PyDateTime_Check(dt)) {
if (!PyDateTime_Check(dt)) {
PyErr_SetString(PyExc_TypeError,
"fromutc: argument must be a datetime");
return NULL;
}
if (! HASTZINFO(dt) || dt->tzinfo != (PyObject *)self) {
if (GET_DT_TZINFO(dt) != (PyObject *)self) {
PyErr_SetString(PyExc_ValueError, "fromutc: dt.tzinfo "
"is not self");
return NULL;
}
off = call_utcoffset(dt->tzinfo, (PyObject *)dt, &none);
if (off == -1 && PyErr_Occurred())
off = datetime_utcoffset(dt, NULL);
if (off == NULL)
return NULL;
if (none) {
if (off == Py_None) {
PyErr_SetString(PyExc_ValueError, "fromutc: non-None "
"utcoffset() result required");
return NULL;
goto Fail;
}
dst = call_dst(dt->tzinfo, (PyObject *)dt, &none);
if (dst == -1 && PyErr_Occurred())
return NULL;
if (none) {
dst = datetime_dst(dt, NULL);
if (dst == NULL)
goto Fail;
if (dst == Py_None) {
PyErr_SetString(PyExc_ValueError, "fromutc: non-None "
"dst() result required");
return NULL;
goto Fail;
}
y = GET_YEAR(dt);
m = GET_MONTH(dt);
d = GET_DAY(dt);
hh = DATE_GET_HOUR(dt);
mm = DATE_GET_MINUTE(dt);
ss = DATE_GET_SECOND(dt);
us = DATE_GET_MICROSECOND(dt);
delta = off - dst;
mm += delta;
if ((mm < 0 || mm >= 60) &&
normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
return NULL;
result = new_datetime(y, m, d, hh, mm, ss, us, dt->tzinfo);
delta = (PyDateTime_Delta *)delta_subtract(off, dst);
if (delta == NULL)
goto Fail;
result = add_datetime_timedelta((PyDateTime_DateTime *)dt, delta, 1);
if (result == NULL)
return result;
dst = call_dst(dt->tzinfo, result, &none);
if (dst == -1 && PyErr_Occurred())
goto Fail;
if (none)
goto Inconsistent;
if (dst == 0)
return result;
mm += dst;
if ((mm < 0 || mm >= 60) &&
normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
Py_DECREF(dst);
dst = call_dst(GET_DT_TZINFO(dt), result);
if (dst == NULL)
goto Fail;
Py_DECREF(result);
result = new_datetime(y, m, d, hh, mm, ss, us, dt->tzinfo);
if (dst == Py_None)
goto Inconsistent;
if (delta_bool(delta) != 0) {
PyObject *temp = result;
result = add_datetime_timedelta((PyDateTime_DateTime *)result,
(PyDateTime_Delta *)dst, 1);
Py_DECREF(temp);
if (result == NULL)
goto Fail;
}
Py_DECREF(delta);
Py_DECREF(dst);
Py_DECREF(off);
return result;
Inconsistent:
......@@ -3197,7 +3078,10 @@ Inconsistent:
/* fall thru to failure */
Fail:
Py_DECREF(result);
Py_XDECREF(off);
Py_XDECREF(dst);
Py_XDECREF(delta);
Py_XDECREF(result);
return NULL;
}
......@@ -3463,19 +3347,15 @@ timezone_dst(PyObject *self, PyObject *dt)
Py_RETURN_NONE;
}
static PyObject *
add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta,
int factor);
static PyObject *
timezone_fromutc(PyDateTime_TimeZone *self, PyDateTime_DateTime *dt)
{
if (! PyDateTime_Check(dt)) {
if (!PyDateTime_Check(dt)) {
PyErr_SetString(PyExc_TypeError,
"fromutc: argument must be a datetime");
return NULL;
}
if (! HASTZINFO(dt) || dt->tzinfo != (PyObject *)self) {
if (!HASTZINFO(dt) || dt->tzinfo != (PyObject *)self) {
PyErr_SetString(PyExc_ValueError, "fromutc: dt.tzinfo "
"is not self");
return NULL;
......@@ -3689,21 +3569,18 @@ time_dealloc(PyDateTime_Time *self)
/* These are all METH_NOARGS, so don't need to check the arglist. */
static PyObject *
time_utcoffset(PyDateTime_Time *self, PyObject *unused) {
return offset_as_timedelta(HASTZINFO(self) ? self->tzinfo : Py_None,
"utcoffset", Py_None);
time_utcoffset(PyObject *self, PyObject *unused) {
return call_utcoffset(GET_TIME_TZINFO(self), Py_None);
}
static PyObject *
time_dst(PyDateTime_Time *self, PyObject *unused) {
return offset_as_timedelta(HASTZINFO(self) ? self->tzinfo : Py_None,
"dst", Py_None);
time_dst(PyObject *self, PyObject *unused) {
return call_dst(GET_TIME_TZINFO(self), Py_None);
}
static PyObject *
time_tzname(PyDateTime_Time *self, PyObject *unused) {
return call_tzname(HASTZINFO(self) ? self->tzinfo : Py_None,
Py_None);
return call_tzname(GET_TIME_TZINFO(self), Py_None);
}
/*
......@@ -3758,7 +3635,7 @@ time_isoformat(PyDateTime_Time *self, PyObject *unused)
TIME_GET_MINUTE(self),
TIME_GET_SECOND(self));
if (result == NULL || ! HASTZINFO(self) || self->tzinfo == Py_None)
if (result == NULL || !HASTZINFO(self) || self->tzinfo == Py_None)
return result;
/* We need to append the UTC offset. */
......@@ -3809,98 +3686,108 @@ time_strftime(PyDateTime_Time *self, PyObject *args, PyObject *kw)
static PyObject *
time_richcompare(PyObject *self, PyObject *other, int op)
{
PyObject *result = NULL;
PyObject *offset1, *offset2;
int diff;
naivety n1, n2;
int offset1, offset2;
if (! PyTime_Check(other)) {
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
if (classify_two_utcoffsets(self, &offset1, &n1, Py_None,
other, &offset2, &n2, Py_None) < 0)
if (GET_TIME_TZINFO(self) == GET_TIME_TZINFO(other)) {
diff = memcmp(((PyDateTime_Time *)self)->data,
((PyDateTime_Time *)other)->data,
_PyDateTime_TIME_DATASIZE);
return diff_to_bool(diff, op);
}
offset1 = time_utcoffset(self, NULL);
if (offset1 == NULL)
return NULL;
assert(n1 != OFFSET_UNKNOWN && n2 != OFFSET_UNKNOWN);
offset2 = time_utcoffset(other, NULL);
if (offset2 == NULL)
goto done;
/* If they're both naive, or both aware and have the same offsets,
* we get off cheap. Note that if they're both naive, offset1 ==
* offset2 == 0 at this point.
* offset2 == Py_None at this point.
*/
if (n1 == n2 && offset1 == offset2) {
if ((offset1 == offset2) ||
(PyDelta_Check(offset1) && PyDelta_Check(offset2) &&
delta_cmp(offset1, offset2) == 0)) {
diff = memcmp(((PyDateTime_Time *)self)->data,
((PyDateTime_Time *)other)->data,
_PyDateTime_TIME_DATASIZE);
return diff_to_bool(diff, op);
}
if (n1 == OFFSET_AWARE && n2 == OFFSET_AWARE) {
assert(offset1 != offset2); /* else last "if" handled it */
/* Convert everything except microseconds to seconds. These
* can't overflow (no more than the # of seconds in 2 days).
*/
offset1 = TIME_GET_HOUR(self) * 3600 +
(TIME_GET_MINUTE(self) - offset1) * 60 +
TIME_GET_SECOND(self);
offset2 = TIME_GET_HOUR(other) * 3600 +
(TIME_GET_MINUTE(other) - offset2) * 60 +
TIME_GET_SECOND(other);
diff = offset1 - offset2;
result = diff_to_bool(diff, op);
}
/* The hard case: both aware with different UTC offsets */
else if (offset1 != Py_None && offset2 != Py_None) {
int offsecs1, offsecs2;
assert(offset1 != offset2); /* else last "if" handled it */
offsecs1 = TIME_GET_HOUR(self) * 3600 +
TIME_GET_MINUTE(self) * 60 +
TIME_GET_SECOND(self) -
GET_TD_DAYS(offset1) * 86400 -
GET_TD_SECONDS(offset1);
offsecs2 = TIME_GET_HOUR(other) * 3600 +
TIME_GET_MINUTE(other) * 60 +
TIME_GET_SECOND(other) -
GET_TD_DAYS(offset2) * 86400 -
GET_TD_SECONDS(offset2);
diff = offsecs1 - offsecs2;
if (diff == 0)
diff = TIME_GET_MICROSECOND(self) -
TIME_GET_MICROSECOND(other);
return diff_to_bool(diff, op);
result = diff_to_bool(diff, op);
}
assert(n1 != n2);
PyErr_SetString(PyExc_TypeError,
"can't compare offset-naive and "
"offset-aware times");
return NULL;
else {
PyErr_SetString(PyExc_TypeError,
"can't compare offset-naive and "
"offset-aware times");
}
done:
Py_DECREF(offset1);
Py_XDECREF(offset2);
return result;
}
static long
time_hash(PyDateTime_Time *self)
{
if (self->hashcode == -1) {
naivety n;
int offset;
PyObject *temp;
PyObject *offset;
n = classify_utcoffset((PyObject *)self, Py_None, &offset);
assert(n != OFFSET_UNKNOWN);
if (n == OFFSET_ERROR)
offset = time_utcoffset((PyObject *)self, NULL);
if (offset == NULL)
return -1;
/* Reduce this to a hash of another object. */
if (offset == 0) {
if (offset == Py_None)
self->hashcode = generic_hash(
(unsigned char *)self->data, _PyDateTime_TIME_DATASIZE);
return self->hashcode;
}
else {
int hour;
int minute;
assert(n == OFFSET_AWARE);
PyObject *temp1, *temp2;
int seconds, microseconds;
assert(HASTZINFO(self));
hour = divmod(TIME_GET_HOUR(self) * 60 +
TIME_GET_MINUTE(self) - offset,
60,
&minute);
if (0 <= hour && hour < 24)
temp = new_time(hour, minute,
TIME_GET_SECOND(self),
TIME_GET_MICROSECOND(self),
Py_None);
else
temp = Py_BuildValue("iiii",
hour, minute,
TIME_GET_SECOND(self),
TIME_GET_MICROSECOND(self));
}
if (temp != NULL) {
self->hashcode = PyObject_Hash(temp);
Py_DECREF(temp);
seconds = TIME_GET_HOUR(self) * 3600 +
TIME_GET_MINUTE(self) * 60 +
TIME_GET_SECOND(self);
microseconds = TIME_GET_MICROSECOND(self);
temp1 = new_delta(0, seconds, microseconds, 1);
if (temp1 == NULL) {
Py_DECREF(offset);
return -1;
}
temp2 = delta_subtract(temp1, offset);
Py_DECREF(temp1);
if (temp2 == NULL) {
Py_DECREF(offset);
return -1;
}
self->hashcode = PyObject_Hash(temp2);
Py_DECREF(temp2);
}
Py_DECREF(offset);
}
return self->hashcode;
}
......@@ -3929,10 +3816,10 @@ time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw)
}
static int
time_bool(PyDateTime_Time *self)
time_bool(PyObject *self)
{
int offset;
int none;
PyObject *offset, *tzinfo;
int offsecs = 0;
if (TIME_GET_SECOND(self) || TIME_GET_MICROSECOND(self)) {
/* Since utcoffset is in whole minutes, nothing can
......@@ -3940,13 +3827,15 @@ time_bool(PyDateTime_Time *self)
*/
return 1;
}
offset = 0;
if (HASTZINFO(self) && self->tzinfo != Py_None) {
offset = call_utcoffset(self->tzinfo, Py_None, &none);
if (offset == -1 && PyErr_Occurred())
tzinfo = GET_TIME_TZINFO(self);
if (tzinfo != Py_None) {
offset = call_utcoffset(tzinfo, Py_None);
if (offset == NULL)
return -1;
offsecs = GET_TD_DAYS(offset)*86400 + GET_TD_SECONDS(offset);
Py_DECREF(offset);
}
return (TIME_GET_MINUTE(self) - offset + TIME_GET_HOUR(self)*60) != 0;
return (TIME_GET_MINUTE(self)*60 - offsecs + TIME_GET_HOUR(self)*3600) != 0;
}
/* Pickle support, a simple use of __reduce__. */
......@@ -4455,21 +4344,18 @@ datetime_dealloc(PyDateTime_DateTime *self)
/* These are all METH_NOARGS, so don't need to check the arglist. */
static PyObject *
datetime_utcoffset(PyDateTime_DateTime *self, PyObject *unused) {
return offset_as_timedelta(HASTZINFO(self) ? self->tzinfo : Py_None,
"utcoffset", (PyObject *)self);
datetime_utcoffset(PyObject *self, PyObject *unused) {
return call_utcoffset(GET_DT_TZINFO(self), self);
}
static PyObject *
datetime_dst(PyDateTime_DateTime *self, PyObject *unused) {
return offset_as_timedelta(HASTZINFO(self) ? self->tzinfo : Py_None,
"dst", (PyObject *)self);
datetime_dst(PyObject *self, PyObject *unused) {
return call_dst(GET_DT_TZINFO(self), self);
}
static PyObject *
datetime_tzname(PyDateTime_DateTime *self, PyObject *unused) {
return call_tzname(HASTZINFO(self) ? self->tzinfo : Py_None,
(PyObject *)self);
datetime_tzname(PyObject *self, PyObject *unused) {
return call_tzname(GET_DT_TZINFO(self), self);
}
/*
......@@ -4536,21 +4422,43 @@ datetime_subtract(PyObject *left, PyObject *right)
/* datetime - ??? */
if (PyDateTime_Check(right)) {
/* datetime - datetime */
naivety n1, n2;
int offset1, offset2;
PyObject *offset1, *offset2, *offdiff = NULL;
int delta_d, delta_s, delta_us;
if (classify_two_utcoffsets(left, &offset1, &n1, left,
right, &offset2, &n2,
right) < 0)
return NULL;
assert(n1 != OFFSET_UNKNOWN && n2 != OFFSET_UNKNOWN);
if (n1 != n2) {
PyErr_SetString(PyExc_TypeError,
"can't subtract offset-naive and "
"offset-aware datetimes");
return NULL;
if (GET_DT_TZINFO(left) == GET_DT_TZINFO(right)) {
offset2 = offset1 = Py_None;
Py_INCREF(offset1);
Py_INCREF(offset2);
}
else {
offset1 = datetime_utcoffset(left, NULL);
if (offset1 == NULL)
return NULL;
offset2 = datetime_utcoffset(right, NULL);
if (offset2 == NULL) {
Py_DECREF(offset1);
return NULL;
}
if ((offset1 != Py_None) != (offset2 != Py_None)) {
PyErr_SetString(PyExc_TypeError,
"can't subtract offset-naive and "
"offset-aware datetimes");
Py_DECREF(offset1);
Py_DECREF(offset2);
return NULL;
}
}
if ((offset1 != offset2) &&
delta_cmp(offset1, offset2) != 0) {
offdiff = delta_subtract(offset1, offset2);
if (offdiff == NULL) {
Py_DECREF(offset1);
Py_DECREF(offset2);
return NULL;
}
}
Py_DECREF(offset1);
Py_DECREF(offset2);
delta_d = ymd_to_ord(GET_YEAR(left),
GET_MONTH(left),
GET_DAY(left)) -
......@@ -4569,11 +4477,13 @@ datetime_subtract(PyObject *left, PyObject *right)
DATE_GET_SECOND(right));
delta_us = DATE_GET_MICROSECOND(left) -
DATE_GET_MICROSECOND(right);
/* (left - offset1) - (right - offset2) =
* (left - right) + (offset2 - offset1)
*/
delta_s += (offset2 - offset1) * 60;
result = new_delta(delta_d, delta_s, delta_us, 1);
if (offdiff != NULL) {
PyObject *temp = result;
result = delta_subtract(result, offdiff);
Py_DECREF(temp);
Py_DECREF(offdiff);
}
}
else if (PyDelta_Check(right)) {
/* datetime - delta */
......@@ -4683,9 +4593,9 @@ datetime_ctime(PyDateTime_DateTime *self)
static PyObject *
datetime_richcompare(PyObject *self, PyObject *other, int op)
{
PyObject *result = NULL;
PyObject *offset1, *offset2;
int diff;
naivety n1, n2;
int offset1, offset2;
if (! PyDateTime_Check(other)) {
if (PyDate_Check(other)) {
......@@ -4706,85 +4616,99 @@ datetime_richcompare(PyObject *self, PyObject *other, int op)
return Py_NotImplemented;
}
if (classify_two_utcoffsets(self, &offset1, &n1, self,
other, &offset2, &n2, other) < 0)
if (GET_DT_TZINFO(self) == GET_DT_TZINFO(other)) {
diff = memcmp(((PyDateTime_DateTime *)self)->data,
((PyDateTime_DateTime *)other)->data,
_PyDateTime_DATETIME_DATASIZE);
return diff_to_bool(diff, op);
}
offset1 = datetime_utcoffset(self, NULL);
if (offset1 == NULL)
return NULL;
assert(n1 != OFFSET_UNKNOWN && n2 != OFFSET_UNKNOWN);
offset2 = datetime_utcoffset(other, NULL);
if (offset2 == NULL)
goto done;
/* If they're both naive, or both aware and have the same offsets,
* we get off cheap. Note that if they're both naive, offset1 ==
* offset2 == 0 at this point.
* offset2 == Py_None at this point.
*/
if (n1 == n2 && offset1 == offset2) {
if ((offset1 == offset2) ||
(PyDelta_Check(offset1) && PyDelta_Check(offset2) &&
delta_cmp(offset1, offset2) == 0)) {
diff = memcmp(((PyDateTime_DateTime *)self)->data,
((PyDateTime_DateTime *)other)->data,
_PyDateTime_DATETIME_DATASIZE);
return diff_to_bool(diff, op);
result = diff_to_bool(diff, op);
}
if (n1 == OFFSET_AWARE && n2 == OFFSET_AWARE) {
else if (offset1 != Py_None && offset2 != Py_None) {
PyDateTime_Delta *delta;
assert(offset1 != offset2); /* else last "if" handled it */
assert(offset1 != offset2); /* else last "if" handled it */
delta = (PyDateTime_Delta *)datetime_subtract((PyObject *)self,
other);
if (delta == NULL)
return NULL;
goto done;
diff = GET_TD_DAYS(delta);
if (diff == 0)
diff = GET_TD_SECONDS(delta) |
GET_TD_MICROSECONDS(delta);
Py_DECREF(delta);
return diff_to_bool(diff, op);
result = diff_to_bool(diff, op);
}
assert(n1 != n2);
PyErr_SetString(PyExc_TypeError,
"can't compare offset-naive and "
"offset-aware datetimes");
return NULL;
else {
PyErr_SetString(PyExc_TypeError,
"can't compare offset-naive and "
"offset-aware datetimes");
}
done:
Py_DECREF(offset1);
Py_XDECREF(offset2);
return result;
}
static long
datetime_hash(PyDateTime_DateTime *self)
{
if (self->hashcode == -1) {
naivety n;
int offset;
PyObject *temp;
n = classify_utcoffset((PyObject *)self, (PyObject *)self,
&offset);
assert(n != OFFSET_UNKNOWN);
if (n == OFFSET_ERROR)
PyObject *offset;
offset = datetime_utcoffset((PyObject *)self, NULL);
if (offset == NULL)
return -1;
/* Reduce this to a hash of another object. */
if (n == OFFSET_NAIVE) {
if (offset == Py_None)
self->hashcode = generic_hash(
(unsigned char *)self->data, _PyDateTime_DATETIME_DATASIZE);
return self->hashcode;
}
else {
int days;
int seconds;
PyObject *temp1, *temp2;
int days, seconds;
assert(n == OFFSET_AWARE);
assert(HASTZINFO(self));
days = ymd_to_ord(GET_YEAR(self),
GET_MONTH(self),
GET_DAY(self));
seconds = DATE_GET_HOUR(self) * 3600 +
(DATE_GET_MINUTE(self) - offset) * 60 +
DATE_GET_MINUTE(self) * 60 +
DATE_GET_SECOND(self);
temp = new_delta(days,
seconds,
DATE_GET_MICROSECOND(self),
1);
}
if (temp != NULL) {
self->hashcode = PyObject_Hash(temp);
Py_DECREF(temp);
temp1 = new_delta(days, seconds,
DATE_GET_MICROSECOND(self),
1);
if (temp1 == NULL) {
Py_DECREF(offset);
return -1;
}
temp2 = delta_subtract(temp1, offset);
Py_DECREF(temp1);
if (temp2 == NULL) {
Py_DECREF(offset);
return -1;
}
self->hashcode = PyObject_Hash(temp2);
Py_DECREF(temp2);
}
Py_DECREF(offset);
}
return self->hashcode;
}
......@@ -4819,10 +4743,9 @@ datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
static PyObject *
datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
{
int y, m, d, hh, mm, ss, us;
PyObject *result;
int offset, none;
PyObject *offset;
PyObject *temp;
PyObject *tzinfo;
static char *keywords[] = {"tz", NULL};
......@@ -4840,39 +4763,35 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
}
/* Convert self to UTC. */
offset = call_utcoffset(self->tzinfo, (PyObject *)self, &none);
if (offset == -1 && PyErr_Occurred())
offset = datetime_utcoffset((PyObject *)self, NULL);
if (offset == NULL)
return NULL;
if (none)
goto NeedAware;
if (offset == Py_None) {
Py_DECREF(offset);
NeedAware:
PyErr_SetString(PyExc_ValueError, "astimezone() cannot be applied to "
"a naive datetime");
return NULL;
}
y = GET_YEAR(self);
m = GET_MONTH(self);
d = GET_DAY(self);
hh = DATE_GET_HOUR(self);
mm = DATE_GET_MINUTE(self);
ss = DATE_GET_SECOND(self);
us = DATE_GET_MICROSECOND(self);
mm -= offset;
if ((mm < 0 || mm >= 60) &&
normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
/* result = self - offset */
result = add_datetime_timedelta(self,
(PyDateTime_Delta *)offset, -1);
Py_DECREF(offset);
if (result == NULL)
return NULL;
/* Attach new tzinfo and let fromutc() do the rest. */
result = new_datetime(y, m, d, hh, mm, ss, us, tzinfo);
if (result != NULL) {
PyObject *temp = result;
temp = ((PyDateTime_DateTime *)result)->tzinfo;
((PyDateTime_DateTime *)result)->tzinfo = tzinfo;
Py_INCREF(tzinfo);
Py_DECREF(temp);
result = PyObject_CallMethod(tzinfo, "fromutc", "O", temp);
Py_DECREF(temp);
}
return result;
temp = result;
result = PyObject_CallMethod(tzinfo, "fromutc", "O", temp);
Py_DECREF(temp);
NeedAware:
PyErr_SetString(PyExc_ValueError, "astimezone() cannot be applied to "
"a naive datetime");
return NULL;
return result;
}
static PyObject *
......@@ -4881,17 +4800,15 @@ datetime_timetuple(PyDateTime_DateTime *self)
int dstflag = -1;
if (HASTZINFO(self) && self->tzinfo != Py_None) {
int none;
PyObject * dst;
dstflag = call_dst(self->tzinfo, (PyObject *)self, &none);
if (dstflag == -1 && PyErr_Occurred())
dst = call_dst(self->tzinfo, (PyObject *)self);
if (dst == NULL)
return NULL;
if (none)
dstflag = -1;
else if (dstflag != 0)
dstflag = 1;
if (dst != Py_None)
dstflag = delta_bool((PyDateTime_Delta *)dst);
Py_DECREF(dst);
}
return build_struct_time(GET_YEAR(self),
GET_MONTH(self),
......@@ -4927,41 +4844,47 @@ datetime_gettimetz(PyDateTime_DateTime *self)
DATE_GET_MINUTE(self),
DATE_GET_SECOND(self),
DATE_GET_MICROSECOND(self),
HASTZINFO(self) ? self->tzinfo : Py_None);
GET_DT_TZINFO(self));
}
static PyObject *
datetime_utctimetuple(PyDateTime_DateTime *self)
{
int y = GET_YEAR(self);
int m = GET_MONTH(self);
int d = GET_DAY(self);
int hh = DATE_GET_HOUR(self);
int mm = DATE_GET_MINUTE(self);
int ss = DATE_GET_SECOND(self);
int us = 0; /* microseconds are ignored in a timetuple */
int offset = 0;
if (HASTZINFO(self) && self->tzinfo != Py_None) {
int none;
int y, m, d, hh, mm, ss;
PyObject *tzinfo;
PyDateTime_DateTime *utcself;
offset = call_utcoffset(self->tzinfo, (PyObject *)self, &none);
if (offset == -1 && PyErr_Occurred())
return NULL;
tzinfo = GET_DT_TZINFO(self);
if (tzinfo == Py_None) {
utcself = self;
Py_INCREF(utcself);
}
/* Even if offset is 0, don't call timetuple() -- tm_isdst should be
* 0 in a UTC timetuple regardless of what dst() says.
*/
if (offset) {
/* Subtract offset minutes & normalize. */
int stat;
mm -= offset;
stat = normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us);
/* OverflowError may be raised in the edge cases. */
if (stat < 0)
else {
PyObject *offset;
offset = call_utcoffset(tzinfo, (PyObject *)self);
if (offset == NULL)
return NULL;
if (offset == Py_None) {
Py_DECREF(offset);
utcself = self;
Py_INCREF(utcself);
}
else {
utcself = (PyDateTime_DateTime *)add_datetime_timedelta(self,
(PyDateTime_Delta *)offset, -1);
Py_DECREF(offset);
if (utcself == NULL)
return NULL;
}
}
y = GET_YEAR(utcself);
m = GET_MONTH(utcself);
d = GET_DAY(utcself);
hh = DATE_GET_HOUR(utcself);
mm = DATE_GET_MINUTE(utcself);
ss = DATE_GET_SECOND(utcself);
Py_DECREF(utcself);
return build_struct_time(y, m, d, hh, mm, ss, 0);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment