summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/datetime.rst46
-rw-r--r--Doc/data/refcounts.dat8
-rw-r--r--Include/datetime.h13
-rw-r--r--Lib/test/datetimetester.py181
-rw-r--r--Misc/NEWS.d/next/C API/2017-12-28-15-22-05.bpo-10381.a1E6aF.rst2
-rw-r--r--Modules/_datetimemodule.c3
-rw-r--r--Modules/_testcapimodule.c87
7 files changed, 329 insertions, 11 deletions
diff --git a/Doc/c-api/datetime.rst b/Doc/c-api/datetime.rst
index 305e990..7872461 100644
--- a/Doc/c-api/datetime.rst
+++ b/Doc/c-api/datetime.rst
@@ -13,6 +13,16 @@ the module initialisation function. The macro puts a pointer to a C structure
into a static variable, :c:data:`PyDateTimeAPI`, that is used by the following
macros.
+Macro for access to the UTC singleton:
+
+.. c:var:: PyObject* PyDateTime_TimeZone_UTC
+
+ Returns the time zone singleton representing UTC, the same object as
+ :attr:`datetime.timezone.utc`.
+
+ .. versionadded:: 3.7
+
+
Type-check macros:
.. c:function:: int PyDate_Check(PyObject *ob)
@@ -79,27 +89,41 @@ Macros to create objects:
.. c:function:: PyObject* PyDate_FromDate(int year, int month, int day)
- Return a ``datetime.date`` object with the specified year, month and day.
+ Return a :class:`datetime.date` object with the specified year, month and day.
.. c:function:: PyObject* PyDateTime_FromDateAndTime(int year, int month, int day, int hour, int minute, int second, int usecond)
- Return a ``datetime.datetime`` object with the specified year, month, day, hour,
+ Return a :class:`datetime.datetime` object with the specified year, month, day, hour,
minute, second and microsecond.
.. c:function:: PyObject* PyTime_FromTime(int hour, int minute, int second, int usecond)
- Return a ``datetime.time`` object with the specified hour, minute, second and
+ Return a :class:`datetime.time` object with the specified hour, minute, second and
microsecond.
.. c:function:: PyObject* PyDelta_FromDSU(int days, int seconds, int useconds)
- Return a ``datetime.timedelta`` object representing the given number of days,
- seconds and microseconds. Normalization is performed so that the resulting
- number of microseconds and seconds lie in the ranges documented for
- ``datetime.timedelta`` objects.
+ Return a :class:`datetime.timedelta` object representing the given number
+ of days, seconds and microseconds. Normalization is performed so that the
+ resulting number of microseconds and seconds lie in the ranges documented for
+ :class:`datetime.timedelta` objects.
+
+.. c:function:: PyObject* PyTimeZone_FromOffset(PyDateTime_DeltaType* offset)
+
+ Return a :class:`datetime.timezone` object with an unnamed fixed offset
+ represented by the *offset* argument.
+
+ .. versionadded:: 3.7
+
+.. c:function:: PyObject* PyTimeZone_FromOffsetAndName(PyDateTime_DeltaType* offset, PyUnicode* name)
+
+ Return a :class:`datetime.timezone` object with a fixed offset represented
+ by the *offset* argument and with tzname *name*.
+
+ .. versionadded:: 3.7
Macros to extract fields from date objects. The argument must be an instance of
@@ -199,11 +223,11 @@ Macros for the convenience of modules implementing the DB API:
.. c:function:: PyObject* PyDateTime_FromTimestamp(PyObject *args)
- Create and return a new ``datetime.datetime`` object given an argument tuple
- suitable for passing to ``datetime.datetime.fromtimestamp()``.
+ Create and return a new :class:`datetime.datetime` object given an argument
+ tuple suitable for passing to :meth:`datetime.datetime.fromtimestamp()`.
.. c:function:: PyObject* PyDate_FromTimestamp(PyObject *args)
- Create and return a new ``datetime.date`` object given an argument tuple
- suitable for passing to ``datetime.date.fromtimestamp()``.
+ Create and return a new :class:`datetime.date` object given an argument
+ tuple suitable for passing to :meth:`datetime.date.fromtimestamp()`.
diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat
index b1cad48..6dc86fc 100644
--- a/Doc/data/refcounts.dat
+++ b/Doc/data/refcounts.dat
@@ -177,6 +177,14 @@ PyDelta_FromDSU:int:days::
PyDelta_FromDSU:int:seconds::
PyDelta_FromDSU:int:useconds::
+PyTimeZone_FromOffset:PyObject*::+1:
+PyTimeZone_FromOffset:PyDateTime_DeltaType*:offset:+1:Reference count not increased if offset is +00:00
+
+PyTimeZone_FromOffsetAndName:PyObject*::+1:
+PyTimeZone_FromOffsetAndName:PyDateTime_DeltaType*:offset:+1:Reference count not increased if offset is +00:00 and name == NULL
+PyTimeZone_FromOffsetAndName:PyUnicode*:name:+1:
+
+
PyDescr_NewClassMethod:PyObject*::+1:
PyDescr_NewClassMethod:PyTypeObject*:type::
PyDescr_NewClassMethod:PyMethodDef*:method::
diff --git a/Include/datetime.h b/Include/datetime.h
index 3bf35cb..059d5ec 100644
--- a/Include/datetime.h
+++ b/Include/datetime.h
@@ -155,12 +155,16 @@ typedef struct {
PyTypeObject *DeltaType;
PyTypeObject *TZInfoType;
+ /* singletons */
+ PyObject *TimeZone_UTC;
+
/* constructors */
PyObject *(*Date_FromDate)(int, int, int, PyTypeObject*);
PyObject *(*DateTime_FromDateAndTime)(int, int, int, int, int, int, int,
PyObject*, PyTypeObject*);
PyObject *(*Time_FromTime)(int, int, int, int, PyObject*, PyTypeObject*);
PyObject *(*Delta_FromDelta)(int, int, int, int, PyTypeObject*);
+ PyObject *(*TimeZone_FromTimeZone)(PyObject *offset, PyObject *name);
/* constructors for the DB API */
PyObject *(*DateTime_FromTimestamp)(PyObject*, PyObject*, PyObject*);
@@ -202,6 +206,9 @@ static PyDateTime_CAPI *PyDateTimeAPI = NULL;
#define PyDateTime_IMPORT \
PyDateTimeAPI = (PyDateTime_CAPI *)PyCapsule_Import(PyDateTime_CAPSULE_NAME, 0)
+/* Macro for access to the UTC singleton */
+#define PyDateTime_TimeZone_UTC PyDateTimeAPI->TimeZone_UTC
+
/* Macros for type checking when not building the Python core. */
#define PyDate_Check(op) PyObject_TypeCheck(op, PyDateTimeAPI->DateType)
#define PyDate_CheckExact(op) (Py_TYPE(op) == PyDateTimeAPI->DateType)
@@ -242,6 +249,12 @@ static PyDateTime_CAPI *PyDateTimeAPI = NULL;
PyDateTimeAPI->Delta_FromDelta(days, seconds, useconds, 1, \
PyDateTimeAPI->DeltaType)
+#define PyTimeZone_FromOffset(offset) \
+ PyDateTimeAPI->TimeZone_FromTimeZone(offset, NULL)
+
+#define PyTimeZone_FromOffsetAndName(offset, name) \
+ PyDateTimeAPI->TimeZone_FromTimeZone(offset, name)
+
/* Macros supporting the DB API. */
#define PyDateTime_FromTimestamp(args) \
PyDateTimeAPI->DateTime_FromTimestamp( \
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index e8ed79e..a0883b9 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -31,6 +31,8 @@ from datetime import timezone
from datetime import date, datetime
import time as _time
+import _testcapi
+
# Needed by test_datetime
import _strptime
#
@@ -5443,6 +5445,185 @@ class ZoneInfoCompleteTest(unittest.TestSuite):
class IranTest(ZoneInfoTest):
zonename = 'Asia/Tehran'
+
+class CapiTest(unittest.TestCase):
+ def setUp(self):
+ # Since the C API is not present in the _Pure tests, skip all tests
+ if self.__class__.__name__.endswith('Pure'):
+ self.skipTest('Not relevant in pure Python')
+
+ # This *must* be called, and it must be called first, so until either
+ # restriction is loosened, we'll call it as part of test setup
+ _testcapi.test_datetime_capi()
+
+ def test_utc_capi(self):
+ for use_macro in (True, False):
+ capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
+
+ with self.subTest(use_macro=use_macro):
+ self.assertIs(capi_utc, timezone.utc)
+
+ def test_timezones_capi(self):
+ est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
+
+ exp_named = timezone(timedelta(hours=-5), "EST")
+ exp_unnamed = timezone(timedelta(hours=-5))
+
+ cases = [
+ ('est_capi', est_capi, exp_named),
+ ('est_macro', est_macro, exp_named),
+ ('est_macro_nn', est_macro_nn, exp_unnamed)
+ ]
+
+ for name, tz_act, tz_exp in cases:
+ with self.subTest(name=name):
+ self.assertEqual(tz_act, tz_exp)
+
+ dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
+ dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
+
+ self.assertEqual(dt1, dt2)
+ self.assertEqual(dt1.tzname(), dt2.tzname())
+
+ dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
+
+ self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
+
+ def test_check_date(self):
+ class DateSubclass(date):
+ pass
+
+ d = date(2011, 1, 1)
+ ds = DateSubclass(2011, 1, 1)
+ dt = datetime(2011, 1, 1)
+
+ is_date = _testcapi.datetime_check_date
+
+ # Check the ones that should be valid
+ self.assertTrue(is_date(d))
+ self.assertTrue(is_date(dt))
+ self.assertTrue(is_date(ds))
+ self.assertTrue(is_date(d, True))
+
+ # Check that the subclasses do not match exactly
+ self.assertFalse(is_date(dt, True))
+ self.assertFalse(is_date(ds, True))
+
+ # Check that various other things are not dates at all
+ args = [tuple(), list(), 1, '2011-01-01',
+ timedelta(1), timezone.utc, time(12, 00)]
+ for arg in args:
+ for exact in (True, False):
+ with self.subTest(arg=arg, exact=exact):
+ self.assertFalse(is_date(arg, exact))
+
+ def test_check_time(self):
+ class TimeSubclass(time):
+ pass
+
+ t = time(12, 30)
+ ts = TimeSubclass(12, 30)
+
+ is_time = _testcapi.datetime_check_time
+
+ # Check the ones that should be valid
+ self.assertTrue(is_time(t))
+ self.assertTrue(is_time(ts))
+ self.assertTrue(is_time(t, True))
+
+ # Check that the subclass does not match exactly
+ self.assertFalse(is_time(ts, True))
+
+ # Check that various other things are not times
+ args = [tuple(), list(), 1, '2011-01-01',
+ timedelta(1), timezone.utc, date(2011, 1, 1)]
+
+ for arg in args:
+ for exact in (True, False):
+ with self.subTest(arg=arg, exact=exact):
+ self.assertFalse(is_time(arg, exact))
+
+ def test_check_datetime(self):
+ class DateTimeSubclass(datetime):
+ pass
+
+ dt = datetime(2011, 1, 1, 12, 30)
+ dts = DateTimeSubclass(2011, 1, 1, 12, 30)
+
+ is_datetime = _testcapi.datetime_check_datetime
+
+ # Check the ones that should be valid
+ self.assertTrue(is_datetime(dt))
+ self.assertTrue(is_datetime(dts))
+ self.assertTrue(is_datetime(dt, True))
+
+ # Check that the subclass does not match exactly
+ self.assertFalse(is_datetime(dts, True))
+
+ # Check that various other things are not datetimes
+ args = [tuple(), list(), 1, '2011-01-01',
+ timedelta(1), timezone.utc, date(2011, 1, 1)]
+
+ for arg in args:
+ for exact in (True, False):
+ with self.subTest(arg=arg, exact=exact):
+ self.assertFalse(is_datetime(arg, exact))
+
+ def test_check_delta(self):
+ class TimeDeltaSubclass(timedelta):
+ pass
+
+ td = timedelta(1)
+ tds = TimeDeltaSubclass(1)
+
+ is_timedelta = _testcapi.datetime_check_delta
+
+ # Check the ones that should be valid
+ self.assertTrue(is_timedelta(td))
+ self.assertTrue(is_timedelta(tds))
+ self.assertTrue(is_timedelta(td, True))
+
+ # Check that the subclass does not match exactly
+ self.assertFalse(is_timedelta(tds, True))
+
+ # Check that various other things are not timedeltas
+ args = [tuple(), list(), 1, '2011-01-01',
+ timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
+
+ for arg in args:
+ for exact in (True, False):
+ with self.subTest(arg=arg, exact=exact):
+ self.assertFalse(is_timedelta(arg, exact))
+
+ def test_check_tzinfo(self):
+ class TZInfoSubclass(tzinfo):
+ pass
+
+ tzi = tzinfo()
+ tzis = TZInfoSubclass()
+ tz = timezone(timedelta(hours=-5))
+
+ is_tzinfo = _testcapi.datetime_check_tzinfo
+
+ # Check the ones that should be valid
+ self.assertTrue(is_tzinfo(tzi))
+ self.assertTrue(is_tzinfo(tz))
+ self.assertTrue(is_tzinfo(tzis))
+ self.assertTrue(is_tzinfo(tzi, True))
+
+ # Check that the subclasses do not match exactly
+ self.assertFalse(is_tzinfo(tz, True))
+ self.assertFalse(is_tzinfo(tzis, True))
+
+ # Check that various other things are not tzinfos
+ args = [tuple(), list(), 1, '2011-01-01',
+ date(2011, 1, 1), datetime(2011, 1, 1)]
+
+ for arg in args:
+ for exact in (True, False):
+ with self.subTest(arg=arg, exact=exact):
+ self.assertFalse(is_tzinfo(arg, exact))
+
def load_tests(loader, standard_tests, pattern):
standard_tests.addTest(ZoneInfoCompleteTest())
return standard_tests
diff --git a/Misc/NEWS.d/next/C API/2017-12-28-15-22-05.bpo-10381.a1E6aF.rst b/Misc/NEWS.d/next/C API/2017-12-28-15-22-05.bpo-10381.a1E6aF.rst
new file mode 100644
index 0000000..2671769
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2017-12-28-15-22-05.bpo-10381.a1E6aF.rst
@@ -0,0 +1,2 @@
+Add C API access to the ``datetime.timezone`` constructor and
+``datetime.timzone.UTC`` singleton.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index d1f48e5..4a33f2d 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -6036,10 +6036,12 @@ static PyDateTime_CAPI CAPI = {
&PyDateTime_TimeType,
&PyDateTime_DeltaType,
&PyDateTime_TZInfoType,
+ NULL, // PyDatetime_TimeZone_UTC not initialized yet
new_date_ex,
new_datetime_ex,
new_time_ex,
new_delta_ex,
+ new_timezone,
datetime_fromtimestamp,
date_fromtimestamp,
new_datetime_ex2,
@@ -6168,6 +6170,7 @@ PyInit__datetime(void)
if (x == NULL || PyDict_SetItemString(d, "utc", x) < 0)
return NULL;
PyDateTime_TimeZone_UTC = x;
+ CAPI.TimeZone_UTC = PyDateTime_TimeZone_UTC;
delta = new_delta(-1, 60, 0, 1); /* -23:59 */
if (delta == NULL)
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index e3be7d3..3f41134 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2220,12 +2220,92 @@ test_datetime_capi(PyObject *self, PyObject *args) {
}
test_run_counter++;
PyDateTime_IMPORT;
+
if (PyDateTimeAPI)
Py_RETURN_NONE;
else
return NULL;
}
+/* Functions exposing the C API type checking for testing */
+#define MAKE_DATETIME_CHECK_FUNC(check_method, exact_method) \
+ PyObject *obj; \
+ int exact = 0; \
+ if (!PyArg_ParseTuple(args, "O|p", &obj, &exact)) { \
+ return NULL; \
+ } \
+ int rv = exact?exact_method(obj):check_method(obj); \
+ if (rv) { \
+ Py_RETURN_TRUE; \
+ } else { \
+ Py_RETURN_FALSE; \
+ }
+
+static PyObject *
+datetime_check_date(PyObject *self, PyObject *args) {
+ MAKE_DATETIME_CHECK_FUNC(PyDate_Check, PyDate_CheckExact)
+}
+
+static PyObject *
+datetime_check_time(PyObject *self, PyObject *args) {
+ MAKE_DATETIME_CHECK_FUNC(PyTime_Check, PyTime_CheckExact)
+}
+
+static PyObject *
+datetime_check_datetime(PyObject *self, PyObject *args) {
+ MAKE_DATETIME_CHECK_FUNC(PyDateTime_Check, PyDateTime_CheckExact)
+}
+
+static PyObject *
+datetime_check_delta(PyObject *self, PyObject *args) {
+ MAKE_DATETIME_CHECK_FUNC(PyDelta_Check, PyDelta_CheckExact)
+}
+
+static PyObject *
+datetime_check_tzinfo(PyObject *self, PyObject *args) {
+ MAKE_DATETIME_CHECK_FUNC(PyTZInfo_Check, PyTZInfo_CheckExact)
+}
+
+
+/* Makes three variations on timezone representing UTC-5:
+ 1. timezone with offset and name from PyDateTimeAPI
+ 2. timezone with offset and name from PyTimeZone_FromOffsetAndName
+ 3. timezone with offset (no name) from PyTimeZone_FromOffset
+*/
+static PyObject *
+make_timezones_capi(PyObject *self, PyObject *args) {
+ PyObject *offset = PyDelta_FromDSU(0, -18000, 0);
+ PyObject *name = PyUnicode_FromString("EST");
+
+ PyObject *est_zone_capi = PyDateTimeAPI->TimeZone_FromTimeZone(offset, name);
+ PyObject *est_zone_macro = PyTimeZone_FromOffsetAndName(offset, name);
+ PyObject *est_zone_macro_noname = PyTimeZone_FromOffset(offset);
+
+ Py_DecRef(offset);
+ Py_DecRef(name);
+
+ PyObject *rv = PyTuple_New(3);
+
+ PyTuple_SET_ITEM(rv, 0, est_zone_capi);
+ PyTuple_SET_ITEM(rv, 1, est_zone_macro);
+ PyTuple_SET_ITEM(rv, 2, est_zone_macro_noname);
+
+ return rv;
+}
+
+static PyObject *
+get_timezone_utc_capi(PyObject* self, PyObject *args) {
+ int macro = 0;
+ if (!PyArg_ParseTuple(args, "|p", &macro)) {
+ return NULL;
+ }
+ if (macro) {
+ return PyDateTime_TimeZone_UTC;
+ } else {
+ return PyDateTimeAPI->TimeZone_UTC;
+ }
+}
+
/* test_thread_state spawns a thread of its own, and that thread releases
* `thread_done` when it's finished. The driver code has to know when the
@@ -4452,6 +4532,13 @@ static PyMethodDef TestMethods[] = {
{"test_config", (PyCFunction)test_config, METH_NOARGS},
{"test_sizeof_c_types", (PyCFunction)test_sizeof_c_types, METH_NOARGS},
{"test_datetime_capi", test_datetime_capi, METH_NOARGS},
+ {"datetime_check_date", datetime_check_date, METH_VARARGS},
+ {"datetime_check_time", datetime_check_time, METH_VARARGS},
+ {"datetime_check_datetime", datetime_check_datetime, METH_VARARGS},
+ {"datetime_check_delta", datetime_check_delta, METH_VARARGS},
+ {"datetime_check_tzinfo", datetime_check_tzinfo, METH_VARARGS},
+ {"make_timezones_capi", make_timezones_capi, METH_NOARGS},
+ {"get_timezone_utc_capi", get_timezone_utc_capi, METH_VARARGS},
{"test_list_api", (PyCFunction)test_list_api, METH_NOARGS},
{"test_dict_iteration", (PyCFunction)test_dict_iteration,METH_NOARGS},
{"dict_getitem_knownhash", dict_getitem_knownhash, METH_VARARGS},