A step on the way to making tzinfo classes writable by mortals: get rid
of the timetz case. A tzinfo method will always see a datetimetz arg, or None, now. In the former case, it's still possible that it will get a datetimetz argument belonging to a different timezone. That will get fixed next.
This commit is contained in:
parent
567332abc4
commit
bad8ff089a
|
@ -1,5 +1,5 @@
|
|||
% XXX what order should the types be discussed in?
|
||||
|
||||
|
||||
\section{\module{datetime} ---
|
||||
Basic date and time types}
|
||||
|
||||
|
@ -785,13 +785,13 @@ Instance attributes (read-only):
|
|||
\begin{memberdesc}{hour}
|
||||
In \code{range(24)}.
|
||||
\end{memberdesc}
|
||||
\begin{memberdesc}{minute}
|
||||
\begin{memberdesc}{minute}
|
||||
In \code{range(60)}.
|
||||
\end{memberdesc}
|
||||
\begin{memberdesc}{second}
|
||||
\begin{memberdesc}{second}
|
||||
In \code{range(60)}.
|
||||
\end{memberdesc}
|
||||
\begin{memberdesc}{microsecond}
|
||||
\begin{memberdesc}{microsecond}
|
||||
In \code{range(1000000)}.
|
||||
\end{memberdesc}
|
||||
|
||||
|
@ -844,7 +844,7 @@ Instance methods:
|
|||
should not be instantiated directly. You need to derive a concrete
|
||||
subclass, and (at least) supply implementations of the standard
|
||||
\class{tzinfo} methods needed by the \class{datetime} methods you
|
||||
use. The \module{datetime} module does not supply any concrete
|
||||
use. The \module{datetime} module does not supply any concrete
|
||||
subclasses of \class{tzinfo}.
|
||||
|
||||
An instance of (a concrete subclass of) \class{tzinfo} can be passed
|
||||
|
@ -854,21 +854,17 @@ The latter objects view their fields as being in local time, and the
|
|||
from UTC, the name of the time zone, and DST offset, all relative to a
|
||||
date or time object passed to them.
|
||||
|
||||
Special requirement for pickling: A tzinfo subclass must have an
|
||||
Special requirement for pickling: A \class{tzinfo} subclass must have an
|
||||
\method{__init__} method that can be called with no arguments, else it
|
||||
can be pickled but possibly not unpickled again. This is a technical
|
||||
requirement that may be relaxed in the future.
|
||||
|
||||
A concrete subclass of \class{tzinfo} may need to implement the
|
||||
following methods. Exactly which methods are needed depends on the
|
||||
uses made of aware \module{datetime} objects; if in doubt, simply
|
||||
implement all of them. The methods are called by a \class{datetimetz}
|
||||
or \class{timetz} object, passing itself as the argument. A
|
||||
\class{tzinfo} subclass's methods should be prepared to accept a dt
|
||||
argument of \code{None} or of type \class{timetz} or
|
||||
\class{datetimetz}.
|
||||
uses made of aware \module{datetime} objects. If in doubt, simply
|
||||
implement all of them.
|
||||
|
||||
\begin{methoddesc}{utcoffset}{dt}
|
||||
\begin{methoddesc}{utcoffset}{self, dt}
|
||||
Return offset of local time from UTC, in minutes east of UTC. If
|
||||
local time is west of UTC, this should be negative. Note that this
|
||||
is intended to be the total offset from UTC; for example, if a
|
||||
|
@ -878,10 +874,14 @@ argument of \code{None} or of type \class{timetz} or
|
|||
an integer, in the range -1439 to 1439 inclusive (1440 = 24*60;
|
||||
the magnitude of the offset must be less than one day), or a
|
||||
\class{timedelta} object representing a whole number of minutes
|
||||
in the same range.
|
||||
in the same range. Most implementations of \method{utcoffset()}
|
||||
will probably look like:
|
||||
\begin{verbatim}
|
||||
return CONSTANT # fixed-offset class
|
||||
return CONSTANT + self.dst(dt) # daylight-aware class
|
||||
\end{methoddesc}
|
||||
|
||||
\begin{methoddesc}{tzname}{dt}
|
||||
\begin{methoddesc}{tzname}{self, dt}
|
||||
Return the timezone name corresponding to the \class{datetime} represented
|
||||
by dt, as a string. Nothing about string names is defined by the
|
||||
\module{datetime} module, and there's no requirement that it mean anything
|
||||
|
@ -893,7 +893,7 @@ argument of \code{None} or of type \class{timetz} or
|
|||
of dt passed, especially if the \class{tzinfo} class is accounting for DST.
|
||||
\end{methoddesc}
|
||||
|
||||
\begin{methoddesc}{dst}{dt}
|
||||
\begin{methoddesc}{dst}{self, dt}
|
||||
Return the DST offset, in minutes east of UTC, or \code{None} if
|
||||
DST information isn't known. Return 0 if DST is not in effect.
|
||||
If DST is in effect, return the offset as an integer or
|
||||
|
@ -907,6 +907,26 @@ argument of \code{None} or of type \class{timetz} or
|
|||
\member{tm_isdst} flag should be set.
|
||||
\end{methoddesc}
|
||||
|
||||
These methods are called by a \class{datetimetz} or \class{timetz} object,
|
||||
in response to their methods of the same names. A \class{datetimetz}
|
||||
object passes itself as the argument, and a \class{timetz} object passes
|
||||
\code{None} as the argument. A \class{tzinfo} subclass's methods should
|
||||
therefore be prepared to accept a \var{dt} argument of \code{None}, or of
|
||||
class \class{datetimetz}.
|
||||
|
||||
When \code{None} is passed, it's up to the class designer to decide the
|
||||
best response. For example, returning \code{None} is appropriate if the
|
||||
class wishes to say that timetz objects don't participate in the
|
||||
\class{tzinfo} protocol. In other applications, it may be more useful
|
||||
for \code{utcoffset(None}} to return the standard UTC offset.
|
||||
|
||||
When a \class{datetimetz} object is passed in response to a
|
||||
\class{datetimetz} method, \code{dt.tzinfo} is the same object as
|
||||
\var{self}. \class{tzinfo} methods can rely on this, unless
|
||||
user code calls \class{tzinfo} methods directly. The intent is that
|
||||
the \class{tzinfo} methods interpret \var{dt} as being in local time,
|
||||
and not need to worry about objects in other timezones.
|
||||
|
||||
Example \class{tzinfo} classes:
|
||||
|
||||
\verbatiminput{tzinfo-examples.py}
|
||||
|
|
|
@ -1557,6 +1557,23 @@ class TestTime(unittest.TestCase):
|
|||
# must be legit (which is true for timetz and datetimetz).
|
||||
class TZInfoBase(unittest.TestCase):
|
||||
|
||||
def test_argument_passing(self):
|
||||
cls = self.theclass
|
||||
# A datetimetz passes itself on, a timetz passes None.
|
||||
class introspective(tzinfo):
|
||||
def tzname(self, dt): return dt and "real" or "none"
|
||||
def utcoffset(self, dt): return dt and 42 or -42
|
||||
dst = utcoffset
|
||||
|
||||
obj = cls(1, 2, 3, tzinfo=introspective())
|
||||
|
||||
expected = cls is timetz and "none" or "real"
|
||||
self.assertEqual(obj.tzname(), expected)
|
||||
|
||||
expected = timedelta(minutes=(cls is timetz and -42 or 42))
|
||||
self.assertEqual(obj.utcoffset(), expected)
|
||||
self.assertEqual(obj.dst(), expected)
|
||||
|
||||
def test_bad_tzinfo_classes(self):
|
||||
cls = self.theclass
|
||||
self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
|
||||
|
@ -1677,22 +1694,26 @@ class TZInfoBase(unittest.TestCase):
|
|||
self.assertEqual(got, expected)
|
||||
|
||||
# However, if they're different members, uctoffset is not ignored.
|
||||
d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
|
||||
d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
|
||||
d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
|
||||
for x in d0, d1, d2:
|
||||
for y in d0, d1, d2:
|
||||
got = cmp(x, y)
|
||||
if (x is d0 or x is d1) and (y is d0 or y is d1):
|
||||
expected = 0
|
||||
elif x is y is d2:
|
||||
expected = 0
|
||||
elif x is d2:
|
||||
expected = -1
|
||||
else:
|
||||
assert y is d2
|
||||
expected = 1
|
||||
self.assertEqual(got, expected)
|
||||
# Note that a timetz can't actually have an operand-depedent offset,
|
||||
# though (and timetz.utcoffset() passes None to tzinfo.utcoffset()),
|
||||
# so skip this test for timetz.
|
||||
if cls is not timetz:
|
||||
d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
|
||||
d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
|
||||
d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
|
||||
for x in d0, d1, d2:
|
||||
for y in d0, d1, d2:
|
||||
got = cmp(x, y)
|
||||
if (x is d0 or x is d1) and (y is d0 or y is d1):
|
||||
expected = 0
|
||||
elif x is y is d2:
|
||||
expected = 0
|
||||
elif x is d2:
|
||||
expected = -1
|
||||
else:
|
||||
assert y is d2
|
||||
expected = 1
|
||||
self.assertEqual(got, expected)
|
||||
|
||||
|
||||
class TestTimeTZ(TestTime, TZInfoBase):
|
||||
|
@ -2535,7 +2556,7 @@ class USTimeZone(tzinfo):
|
|||
return self.stdoffset + self.dst(dt)
|
||||
|
||||
def dst(self, dt):
|
||||
if dt is None or isinstance(dt, time) or dt.tzinfo is None:
|
||||
if dt is None or dt.tzinfo is None:
|
||||
# An exception instead may be sensible here, in one or more of
|
||||
# the cases.
|
||||
return ZERO
|
||||
|
|
|
@ -564,22 +564,22 @@ check_tzinfo_subclass(PyObject *p)
|
|||
return -1;
|
||||
}
|
||||
|
||||
/* Return tzinfo.methname(self), without any checking of results.
|
||||
/* Return tzinfo.methname(tzinfoarg), without any checking of results.
|
||||
* If tzinfo is None, returns None.
|
||||
*/
|
||||
static PyObject *
|
||||
call_tzinfo_method(PyObject *self, PyObject *tzinfo, char *methname)
|
||||
call_tzinfo_method(PyObject *tzinfo, char *methname, PyObject *tzinfoarg)
|
||||
{
|
||||
PyObject *result;
|
||||
|
||||
assert(self && tzinfo && methname);
|
||||
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", self);
|
||||
result = PyObject_CallMethod(tzinfo, methname, "O", tzinfoarg);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -612,8 +612,8 @@ replace_tzinfo(PyObject *self, PyObject *newtzinfo)
|
|||
((PyDateTime_DateTimeTZ *)self)->tzinfo = newtzinfo;
|
||||
}
|
||||
|
||||
/* Internal helper.
|
||||
* Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the
|
||||
|
||||
/* 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 a Python int or long or timedelta, TypeError is raised and this
|
||||
|
@ -635,7 +635,7 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
|
|||
assert(tzinfoarg != NULL);
|
||||
|
||||
*none = 0;
|
||||
u = call_tzinfo_method(tzinfoarg, tzinfo, name);
|
||||
u = call_tzinfo_method(tzinfo, name, tzinfoarg);
|
||||
if (u == NULL)
|
||||
return -1;
|
||||
|
||||
|
@ -702,18 +702,21 @@ call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
|
|||
|
||||
static PyObject *new_delta(int d, int sec, int usec, int normalize);
|
||||
|
||||
/* Call tzinfo.name(self) and return the offset as a timedelta or None. */
|
||||
/* Call tzinfo.name(tzinfoarg), and return the offset as a timedelta or None.
|
||||
*/
|
||||
static PyObject *
|
||||
offset_as_timedelta(PyObject *self, PyObject *tzinfo, char *name) {
|
||||
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, self, &none);
|
||||
int offset = call_utc_tzinfo_method(tzinfo, name, tzinfoarg,
|
||||
&none);
|
||||
if (offset < 0 && PyErr_Occurred())
|
||||
return NULL;
|
||||
if (none) {
|
||||
|
@ -740,26 +743,26 @@ call_dst(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
|
|||
return call_utc_tzinfo_method(tzinfo, "dst", tzinfoarg, none);
|
||||
}
|
||||
|
||||
/* Call tzinfo.tzname(self), and return the result. tzinfo must be
|
||||
/* Call tzinfo.tzname(tzinfoarg), and return the result. tzinfo must be
|
||||
* an instance of the tzinfo class or None. If tzinfo isn't None, and
|
||||
* tzname() doesn't return None ora string, TypeError is raised and this
|
||||
* tzname() doesn't return None or a string, TypeError is raised and this
|
||||
* returns NULL.
|
||||
*/
|
||||
static PyObject *
|
||||
call_tzname(PyObject *self, PyObject *tzinfo)
|
||||
call_tzname(PyObject *tzinfo, PyObject *tzinfoarg)
|
||||
{
|
||||
PyObject *result;
|
||||
|
||||
assert(self != NULL);
|
||||
assert(tzinfo != NULL);
|
||||
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", self);
|
||||
result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg);
|
||||
|
||||
if (result != NULL && result != Py_None && ! PyString_Check(result)) {
|
||||
PyErr_Format(PyExc_TypeError, "tzinfo.tzname() must "
|
||||
|
@ -816,7 +819,9 @@ classify_utcoffset(PyObject *op, int *offset)
|
|||
return (PyTime_Check(op) || PyDate_Check(op)) ?
|
||||
OFFSET_NAIVE : OFFSET_UNKNOWN;
|
||||
}
|
||||
*offset = call_utcoffset(tzinfo, op, &none);
|
||||
*offset = call_utcoffset(tzinfo,
|
||||
PyTimeTZ_Check(op) ? Py_None : op,
|
||||
&none);
|
||||
if (*offset == -1 && PyErr_Occurred())
|
||||
return OFFSET_ERROR;
|
||||
return none ? OFFSET_NAIVE : OFFSET_AWARE;
|
||||
|
@ -951,9 +956,12 @@ format_utcoffset(char *buf, size_t buflen, const char *sep,
|
|||
* so this imports the module and calls it. All the hair is due to
|
||||
* giving special meanings to the %z and %Z format codes via a preprocessing
|
||||
* step on the format string.
|
||||
* tzinfoarg is the argument to pass to the object's tzinfo method, if
|
||||
* needed.
|
||||
*/
|
||||
static PyObject *
|
||||
wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple)
|
||||
wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
|
||||
PyObject *tzinfoarg)
|
||||
{
|
||||
PyObject *result = NULL; /* guilty until proved innocent */
|
||||
|
||||
|
@ -1031,11 +1039,12 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple)
|
|||
zreplacement = PyString_FromString("");
|
||||
if (zreplacement == NULL) goto Done;
|
||||
if (tzinfo != Py_None && tzinfo != NULL) {
|
||||
assert(tzinfoarg != NULL);
|
||||
if (format_utcoffset(buf,
|
||||
sizeof(buf),
|
||||
"",
|
||||
tzinfo,
|
||||
object) < 0)
|
||||
tzinfoarg) < 0)
|
||||
goto Done;
|
||||
Py_DECREF(zreplacement);
|
||||
zreplacement = PyString_FromString(buf);
|
||||
|
@ -1053,8 +1062,9 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple)
|
|||
Zreplacement = PyString_FromString("");
|
||||
if (Zreplacement == NULL) goto Done;
|
||||
if (tzinfo != Py_None && tzinfo != NULL) {
|
||||
PyObject *temp = call_tzname(object,
|
||||
tzinfo);
|
||||
PyObject *temp;
|
||||
assert(tzinfoarg != NULL);
|
||||
temp = call_tzname(tzinfo, tzinfoarg);
|
||||
if (temp == NULL) goto Done;
|
||||
if (temp != Py_None) {
|
||||
assert(PyString_Check(temp));
|
||||
|
@ -2424,7 +2434,8 @@ date_strftime(PyDateTime_Date *self, PyObject *args, PyObject *kw)
|
|||
tuple = PyObject_CallMethod((PyObject *)self, "timetuple", "()");
|
||||
if (tuple == NULL)
|
||||
return NULL;
|
||||
result = wrap_strftime((PyObject *)self, format, tuple);
|
||||
result = wrap_strftime((PyObject *)self, format, tuple,
|
||||
(PyObject *)self);
|
||||
Py_DECREF(tuple);
|
||||
return result;
|
||||
}
|
||||
|
@ -3652,7 +3663,7 @@ time_strftime(PyDateTime_Time *self, PyObject *args, PyObject *kw)
|
|||
if (tuple == NULL)
|
||||
return NULL;
|
||||
assert(PyTuple_Size(tuple) == 9);
|
||||
result = wrap_strftime((PyObject *)self, format, tuple);
|
||||
result = wrap_strftime((PyObject *)self, format, tuple, Py_None);
|
||||
Py_DECREF(tuple);
|
||||
return result;
|
||||
}
|
||||
|
@ -4140,18 +4151,17 @@ timetz_dealloc(PyDateTime_TimeTZ *self)
|
|||
/* These are all METH_NOARGS, so don't need to check the arglist. */
|
||||
static PyObject *
|
||||
timetz_utcoffset(PyDateTime_TimeTZ *self, PyObject *unused) {
|
||||
return offset_as_timedelta((PyObject *)self, self->tzinfo,
|
||||
"utcoffset");
|
||||
return offset_as_timedelta(self->tzinfo, "utcoffset", Py_None);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) {
|
||||
return offset_as_timedelta((PyObject *)self, self->tzinfo, "dst");
|
||||
return offset_as_timedelta(self->tzinfo, "dst", Py_None);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
timetz_tzname(PyDateTime_TimeTZ *self, PyObject *unused) {
|
||||
return call_tzname((PyObject *)self, self->tzinfo);
|
||||
return call_tzname(self->tzinfo, Py_None);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -4181,7 +4191,7 @@ timetz_isoformat(PyDateTime_TimeTZ *self)
|
|||
|
||||
/* We need to append the UTC offset. */
|
||||
if (format_utcoffset(buf, sizeof(buf), ":", self->tzinfo,
|
||||
(PyObject *)self) < 0) {
|
||||
Py_None) < 0) {
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -4234,7 +4244,7 @@ timetz_nonzero(PyDateTime_TimeTZ *self)
|
|||
}
|
||||
offset = 0;
|
||||
if (self->tzinfo != Py_None) {
|
||||
offset = call_utcoffset(self->tzinfo, (PyObject *)self, &none);
|
||||
offset = call_utcoffset(self->tzinfo, Py_None, &none);
|
||||
if (offset == -1 && PyErr_Occurred())
|
||||
return -1;
|
||||
}
|
||||
|
@ -4543,18 +4553,18 @@ datetimetz_dealloc(PyDateTime_DateTimeTZ *self)
|
|||
/* These are all METH_NOARGS, so don't need to check the arglist. */
|
||||
static PyObject *
|
||||
datetimetz_utcoffset(PyDateTime_DateTimeTZ *self, PyObject *unused) {
|
||||
return offset_as_timedelta((PyObject *)self, self->tzinfo,
|
||||
"utcoffset");
|
||||
return offset_as_timedelta(self->tzinfo, "utcoffset",
|
||||
(PyObject *)self);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) {
|
||||
return offset_as_timedelta((PyObject *)self, self->tzinfo, "dst");
|
||||
return offset_as_timedelta(self->tzinfo, "dst", (PyObject *)self);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
datetimetz_tzname(PyDateTime_DateTimeTZ *self, PyObject *unused) {
|
||||
return call_tzname((PyObject *)self, self->tzinfo);
|
||||
return call_tzname(self->tzinfo, (PyObject *)self);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in New Issue