Skip to content

gh-71587: Establish global state in _datetime #110475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 90 additions & 72 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@

#define PyTimezone_Check(op) PyObject_TypeCheck(op, &PyDateTime_TimeZoneType)

typedef struct {
/* Conversion factors. */
PyObject *us_per_ms; // 1_000
PyObject *us_per_second; // 1_000_000
PyObject *us_per_minute; // 1e6 * 60 as Python int
PyObject *us_per_hour; // 1e6 * 3600 as Python int
PyObject *us_per_day; // 1e6 * 3600 * 24 as Python int
PyObject *us_per_week; // 1e6 * 3600 * 24 * 7 as Python int
PyObject *seconds_per_day; // 3600 * 24 as Python int

/* The interned UTC timezone instance */
PyObject *utc;

/* The interned Epoch datetime instance */
PyObject *epoch;
} datetime_state;

static datetime_state _datetime_global_state;

#define GLOBAL_STATE() (&_datetime_global_state)

/*[clinic input]
module datetime
class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType"
Expand Down Expand Up @@ -1139,11 +1160,6 @@ typedef struct
PyObject *name;
} PyDateTime_TimeZone;

/* The interned UTC timezone instance */
static PyObject *PyDateTime_TimeZone_UTC;
/* The interned Epoch datetime instance */
static PyObject *PyDateTime_Epoch;

/* Create new timezone instance checking offset range. This
function does not check the name argument. Caller must assure
that offset is a timedelta instance and name is either NULL
Expand Down Expand Up @@ -1177,7 +1193,8 @@ new_timezone(PyObject *offset, PyObject *name)
assert(name == NULL || PyUnicode_Check(name));

if (name == NULL && delta_bool((PyDateTime_Delta *)offset) == 0) {
return Py_NewRef(PyDateTime_TimeZone_UTC);
datetime_state *st = GLOBAL_STATE();
return Py_NewRef(st->utc);
}
if ((GET_TD_DAYS(offset) == -1 &&
GET_TD_SECONDS(offset) == 0 &&
Expand Down Expand Up @@ -1390,7 +1407,8 @@ tzinfo_from_isoformat_results(int rv, int tzoffset, int tz_useconds)
if (rv == 1) {
// Create a timezone from offset in seconds (0 returns UTC)
if (tzoffset == 0) {
return Py_NewRef(PyDateTime_TimeZone_UTC);
datetime_state *st = GLOBAL_STATE();
return Py_NewRef(st->utc);
}

PyObject *delta = new_delta(0, tzoffset, tz_useconds, 1);
Expand Down Expand Up @@ -1807,19 +1825,6 @@ cmperror(PyObject *a, PyObject *b)
return NULL;
}

/* ---------------------------------------------------------------------------
* Cached Python objects; these are set by the module init function.
*/

/* Conversion factors. */
static PyObject *us_per_ms = NULL; /* 1000 */
static PyObject *us_per_second = NULL; /* 1000000 */
static PyObject *us_per_minute = NULL; /* 1e6 * 60 as Python int */
static PyObject *us_per_hour = NULL; /* 1e6 * 3600 as Python int */
static PyObject *us_per_day = NULL; /* 1e6 * 3600 * 24 as Python int */
static PyObject *us_per_week = NULL; /* 1e6*3600*24*7 as Python int */
static PyObject *seconds_per_day = NULL; /* 3600*24 as Python int */

/* ---------------------------------------------------------------------------
* Class implementations.
*/
Expand All @@ -1845,7 +1850,8 @@ delta_to_microseconds(PyDateTime_Delta *self)
x1 = PyLong_FromLong(GET_TD_DAYS(self));
if (x1 == NULL)
goto Done;
x2 = PyNumber_Multiply(x1, seconds_per_day); /* days in seconds */
datetime_state *st = GLOBAL_STATE();
x2 = PyNumber_Multiply(x1, st->seconds_per_day); /* days in seconds */
if (x2 == NULL)
goto Done;
Py_SETREF(x1, NULL);
Expand All @@ -1862,7 +1868,7 @@ delta_to_microseconds(PyDateTime_Delta *self)
/* x1 = */ x2 = NULL;

/* x3 has days+seconds in seconds */
x1 = PyNumber_Multiply(x3, us_per_second); /* us */
x1 = PyNumber_Multiply(x3, st->us_per_second); /* us */
if (x1 == NULL)
goto Done;
Py_SETREF(x3, NULL);
Expand Down Expand Up @@ -1917,7 +1923,8 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
PyObject *num = NULL;
PyObject *result = NULL;

tuple = checked_divmod(pyus, us_per_second);
datetime_state *st = GLOBAL_STATE();
tuple = checked_divmod(pyus, st->us_per_second);
if (tuple == NULL) {
goto Done;
}
Expand All @@ -1935,7 +1942,7 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
num = Py_NewRef(PyTuple_GET_ITEM(tuple, 0)); /* leftover seconds */
Py_DECREF(tuple);

tuple = checked_divmod(num, seconds_per_day);
tuple = checked_divmod(num, st->seconds_per_day);
if (tuple == NULL)
goto Done;
Py_DECREF(num);
Expand Down Expand Up @@ -2535,28 +2542,29 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
y = accum("microseconds", x, us, _PyLong_GetOne(), &leftover_us);
CLEANUP;
}
datetime_state *st = GLOBAL_STATE();
if (ms) {
y = accum("milliseconds", x, ms, us_per_ms, &leftover_us);
y = accum("milliseconds", x, ms, st->us_per_ms, &leftover_us);
CLEANUP;
}
if (second) {
y = accum("seconds", x, second, us_per_second, &leftover_us);
y = accum("seconds", x, second, st->us_per_second, &leftover_us);
CLEANUP;
}
if (minute) {
y = accum("minutes", x, minute, us_per_minute, &leftover_us);
y = accum("minutes", x, minute, st->us_per_minute, &leftover_us);
CLEANUP;
}
if (hour) {
y = accum("hours", x, hour, us_per_hour, &leftover_us);
y = accum("hours", x, hour, st->us_per_hour, &leftover_us);
CLEANUP;
}
if (day) {
y = accum("days", x, day, us_per_day, &leftover_us);
y = accum("days", x, day, st->us_per_day, &leftover_us);
CLEANUP;
}
if (week) {
y = accum("weeks", x, week, us_per_week, &leftover_us);
y = accum("weeks", x, week, st->us_per_week, &leftover_us);
CLEANUP;
}
if (leftover_us) {
Expand Down Expand Up @@ -2711,7 +2719,8 @@ delta_total_seconds(PyObject *self, PyObject *Py_UNUSED(ignored))
if (total_microseconds == NULL)
return NULL;

total_seconds = PyNumber_TrueDivide(total_microseconds, us_per_second);
datetime_state *st = GLOBAL_STATE();
total_seconds = PyNumber_TrueDivide(total_microseconds, st->us_per_second);

Py_DECREF(total_microseconds);
return total_seconds;
Expand Down Expand Up @@ -3943,8 +3952,10 @@ timezone_repr(PyDateTime_TimeZone *self)
to use Py_TYPE(self)->tp_name here. */
const char *type_name = Py_TYPE(self)->tp_name;

if (((PyObject *)self) == PyDateTime_TimeZone_UTC)
datetime_state *st = GLOBAL_STATE();
if (((PyObject *)self) == st->utc) {
return PyUnicode_FromFormat("%s.utc", type_name);
}

if (self->name == NULL)
return PyUnicode_FromFormat("%s(%R)", type_name, self->offset);
Expand All @@ -3964,11 +3975,14 @@ timezone_str(PyDateTime_TimeZone *self)
if (self->name != NULL) {
return Py_NewRef(self->name);
}
if ((PyObject *)self == PyDateTime_TimeZone_UTC ||
datetime_state *st = GLOBAL_STATE();
if ((PyObject *)self == st->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 = '-';
Expand Down Expand Up @@ -6134,7 +6148,8 @@ local_timezone(PyDateTime_DateTime *utc_time)
PyObject *one_second;
PyObject *seconds;

delta = datetime_subtract((PyObject *)utc_time, PyDateTime_Epoch);
datetime_state *st = GLOBAL_STATE();
delta = datetime_subtract((PyObject *)utc_time, st->epoch);
if (delta == NULL)
return NULL;
one_second = new_delta(0, 1, 0, 0);
Expand Down Expand Up @@ -6246,6 +6261,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
if (result == NULL)
return NULL;

datetime_state *st = GLOBAL_STATE();
/* Make sure result is aware and UTC. */
if (!HASTZINFO(result)) {
temp = (PyObject *)result;
Expand All @@ -6257,7 +6273,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
DATE_GET_MINUTE(result),
DATE_GET_SECOND(result),
DATE_GET_MICROSECOND(result),
PyDateTime_TimeZone_UTC,
st->utc,
DATE_GET_FOLD(result),
Py_TYPE(result));
Py_DECREF(temp);
Expand All @@ -6266,7 +6282,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
}
else {
/* Result is already aware - just replace tzinfo. */
Py_SETREF(result->tzinfo, Py_NewRef(PyDateTime_TimeZone_UTC));
Py_SETREF(result->tzinfo, Py_NewRef(st->utc));
}

/* Attach new tzinfo and let fromutc() do the rest. */
Expand Down Expand Up @@ -6370,8 +6386,9 @@ datetime_timestamp(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored))
PyObject *result;

if (HASTZINFO(self) && self->tzinfo != Py_None) {
datetime_state *st = GLOBAL_STATE();
PyObject *delta;
delta = datetime_subtract((PyObject *)self, PyDateTime_Epoch);
delta = datetime_subtract((PyObject *)self, st->epoch);
if (delta == NULL)
return NULL;
result = delta_total_seconds(delta, NULL);
Expand Down Expand Up @@ -6692,10 +6709,11 @@ get_datetime_capi(void)
capi->Date_FromTimestamp = datetime_date_fromtimestamp_capi;
capi->DateTime_FromDateAndTimeAndFold = new_datetime_ex2;
capi->Time_FromTimeAndFold = new_time_ex2;
// Make sure this function is called after PyDateTime_TimeZone_UTC has
// Make sure this function is called after utc has
// been initialized.
assert(PyDateTime_TimeZone_UTC != NULL);
capi->TimeZone_UTC = PyDateTime_TimeZone_UTC; // borrowed ref
datetime_state *st = GLOBAL_STATE();
assert(st->utc != NULL);
capi->TimeZone_UTC = st->utc; // borrowed ref
return capi;
}

Expand Down Expand Up @@ -6782,17 +6800,18 @@ _datetime_exec(PyObject *module)
return -1;
}

PyObject *x = create_timezone(delta, NULL);
PyObject *utc = create_timezone(delta, NULL);
Py_DECREF(delta);
if (x == NULL) {
if (utc == NULL) {
return -1;
}
if (PyDict_SetItemString(d, "utc", x) < 0) {
Py_DECREF(x);
if (PyDict_SetItemString(d, "utc", utc) < 0) {
Py_DECREF(utc);
return -1;
}

PyDateTime_TimeZone_UTC = x;
datetime_state *st = GLOBAL_STATE();
st->utc = utc;

/* bpo-37642: These attributes are rounded to the nearest minute for backwards
* compatibility, even though the constructor will accept a wider range of
Expand All @@ -6802,7 +6821,7 @@ _datetime_exec(PyObject *module)
return -1;
}

x = create_timezone(delta, NULL);
PyObject *x = create_timezone(delta, NULL);
Py_DECREF(delta);
DATETIME_ADD_MACRO(d, "min", x);

Expand All @@ -6816,9 +6835,8 @@ _datetime_exec(PyObject *module)
DATETIME_ADD_MACRO(d, "max", x);

/* Epoch */
PyDateTime_Epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0,
PyDateTime_TimeZone_UTC, 0);
if (PyDateTime_Epoch == NULL) {
st->epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0, st->utc, 0);
if (st->epoch == NULL) {
return -1;
}

Expand All @@ -6844,7 +6862,7 @@ _datetime_exec(PyObject *module)
return -1;
}

if (PyModule_AddObjectRef(module, "UTC", PyDateTime_TimeZone_UTC) < 0) {
if (PyModule_AddObjectRef(module, "UTC", st->utc) < 0) {
return -1;
}

Expand All @@ -6866,54 +6884,54 @@ _datetime_exec(PyObject *module)
static_assert(DI100Y == 25 * DI4Y - 1, "DI100Y");
assert(DI100Y == days_before_year(100+1));

us_per_ms = PyLong_FromLong(1000);
if (us_per_ms == NULL) {
st->us_per_ms = PyLong_FromLong(1000);
if (st->us_per_ms == NULL) {
goto error;
}
us_per_second = PyLong_FromLong(1000000);
if (us_per_second == NULL) {
st->us_per_second = PyLong_FromLong(1000000);
if (st->us_per_second == NULL) {
goto error;
}
us_per_minute = PyLong_FromLong(60000000);
if (us_per_minute == NULL) {
st->us_per_minute = PyLong_FromLong(60000000);
if (st->us_per_minute == NULL) {
goto error;
}
seconds_per_day = PyLong_FromLong(24 * 3600);
if (seconds_per_day == NULL) {
st->seconds_per_day = PyLong_FromLong(24 * 3600);
if (st->seconds_per_day == NULL) {
goto error;
}

/* The rest are too big for 32-bit ints, but even
* us_per_week fits in 40 bits, so doubles should be exact.
*/
us_per_hour = PyLong_FromDouble(3600000000.0);
if (us_per_hour == NULL) {
st->us_per_hour = PyLong_FromDouble(3600000000.0);
if (st->us_per_hour == NULL) {
goto error;
}
us_per_day = PyLong_FromDouble(86400000000.0);
if (us_per_day == NULL) {
st->us_per_day = PyLong_FromDouble(86400000000.0);
if (st->us_per_day == NULL) {
goto error;
}
us_per_week = PyLong_FromDouble(604800000000.0);
if (us_per_week == NULL) {
st->us_per_week = PyLong_FromDouble(604800000000.0);
if (st->us_per_week == NULL) {
goto error;
}

return 0;

error:
Py_XDECREF(us_per_ms);
Py_XDECREF(us_per_second);
Py_XDECREF(us_per_minute);
Py_XDECREF(us_per_hour);
Py_XDECREF(us_per_day);
Py_XDECREF(us_per_week);
Py_XDECREF(seconds_per_day);
Py_CLEAR(st->us_per_ms);
Py_CLEAR(st->us_per_second);
Py_CLEAR(st->us_per_minute);
Py_CLEAR(st->us_per_hour);
Py_CLEAR(st->us_per_day);
Py_CLEAR(st->us_per_week);
Py_CLEAR(st->seconds_per_day);
return -1;
}

static struct PyModuleDef datetimemodule = {
PyModuleDef_HEAD_INIT,
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "_datetime",
.m_doc = "Fast implementation of the datetime type.",
.m_size = -1,
Expand Down
Loading