/* typecast_datetime.c - date and time typecasting functions to python types * * Copyright (C) 2001-2019 Federico Di Gregorio * Copyright (C) 2020-2021 The Psycopg Team * * This file is part of psycopg. * * psycopg2 is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * In addition, as a special exception, the copyright holders give * permission to link this program with the OpenSSL library (or with * modified versions of OpenSSL that use the same license as OpenSSL), * and distribute linked combinations including the two. * * You must obey the GNU Lesser General Public License in all respects for * all of the code used other than OpenSSL. * * psycopg2 is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. */ #include #include "datetime.h" RAISES_NEG static int typecast_datetime_init(void) { PyDateTime_IMPORT; if (!PyDateTimeAPI) { PyErr_SetString(PyExc_ImportError, "datetime initialization failed"); return -1; } return 0; } /** DATE - cast a date into a date python object **/ static PyObject * typecast_PYDATE_cast(const char *str, Py_ssize_t len, PyObject *curs) { PyObject* obj = NULL; int n, y=0, m=0, d=0; if (str == NULL) { Py_RETURN_NONE; } if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { if (str[0] == '-') { obj = PyObject_GetAttrString( (PyObject*)PyDateTimeAPI->DateType, "min"); } else { obj = PyObject_GetAttrString( (PyObject*)PyDateTimeAPI->DateType, "max"); } } else { n = typecast_parse_date(str, NULL, &len, &y, &m, &d); Dprintf("typecast_PYDATE_cast: " "n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", " "y = %d, m = %d, d = %d", n, len, y, m, d); if (n != 3) { PyErr_SetString(DataError, "unable to parse date"); return NULL; } else { if (y > 9999) y = 9999; obj = PyObject_CallFunction( (PyObject*)PyDateTimeAPI->DateType, "iii", y, m, d); } } return obj; } /* convert the strings -infinity and infinity into a datetime with timezone */ static PyObject * _parse_inftz(const char *str, PyObject *curs) { PyObject *rv = NULL; PyObject *m = NULL; PyObject *tzinfo_factory = NULL; PyObject *tzinfo = NULL; PyObject *args = NULL; PyObject *kwargs = NULL; PyObject *replace = NULL; if (!(m = PyObject_GetAttrString( (PyObject*)PyDateTimeAPI->DateTimeType, (str[0] == '-' ? "min" : "max")))) { goto exit; } tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory; if (tzinfo_factory == Py_None) { rv = m; m = NULL; goto exit; } #if PY_VERSION_HEX < 0x03070000 { PyObject *tzoff; if (!(tzoff = PyDelta_FromDSU(0, 0, 0))) { goto exit; } tzinfo = PyObject_CallFunctionObjArgs(tzinfo_factory, tzoff, NULL); Py_DECREF(tzoff); if (!tzinfo) { goto exit; } } #else tzinfo = PyDateTime_TimeZone_UTC; Py_INCREF(tzinfo); #endif /* m.replace(tzinfo=tzinfo) */ if (!(args = PyTuple_New(0))) { goto exit; } if (!(kwargs = PyDict_New())) { goto exit; } if (0 != PyDict_SetItemString(kwargs, "tzinfo", tzinfo)) { goto exit; } if (!(replace = PyObject_GetAttrString(m, "replace"))) { goto exit; } rv = PyObject_Call(replace, args, kwargs); exit: Py_XDECREF(replace); Py_XDECREF(args); Py_XDECREF(kwargs); Py_XDECREF(tzinfo); Py_XDECREF(m); return rv; } static PyObject * _parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs) { PyObject* rv = NULL; PyObject *tzoff = NULL; PyObject *tzinfo = NULL; PyObject *tzinfo_factory; int n, y=0, m=0, d=0; int hh=0, mm=0, ss=0, us=0, tzsec=0; const char *tp = NULL; Dprintf("typecast_PYDATETIMETZ_cast: s = %s", str); n = typecast_parse_date(str, &tp, &len, &y, &m, &d); Dprintf("typecast_PYDATE_cast: tp = %p " "n = %d, len = " FORMAT_CODE_PY_SSIZE_T "," " y = %d, m = %d, d = %d", tp, n, len, y, m, d); if (n != 3) { PyErr_SetString(DataError, "unable to parse date"); goto exit; } if (len > 0) { n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tzsec); Dprintf("typecast_PYDATETIMETZ_cast: n = %d," " len = " FORMAT_CODE_PY_SSIZE_T "," " hh = %d, mm = %d, ss = %d, us = %d, tzsec = %d", n, len, hh, mm, ss, us, tzsec); if (n < 3 || n > 6) { PyErr_SetString(DataError, "unable to parse time"); goto exit; } } if (ss > 59) { mm += 1; ss -= 60; } if (y > 9999) y = 9999; tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory; if (n >= 5 && tzinfo_factory != Py_None) { /* we have a time zone, calculate minutes and create appropriate tzinfo object calling the factory */ Dprintf("typecast_PYDATETIMETZ_cast: UTC offset = %ds", tzsec); #if PY_VERSION_HEX < 0x03070000 /* Before Python 3.7 the timezone offset had to be a whole number * of minutes, so round the seconds to the closest minute */ tzsec = 60 * (int)round(tzsec / 60.0); #endif if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; } if (!(tzinfo = PyObject_CallFunctionObjArgs( tzinfo_factory, tzoff, NULL))) { goto exit; } } else { Py_INCREF(Py_None); tzinfo = Py_None; } Dprintf("typecast_PYDATETIMETZ_cast: tzinfo: %p, refcnt = " FORMAT_CODE_PY_SSIZE_T, tzinfo, Py_REFCNT(tzinfo)); rv = PyObject_CallFunction( (PyObject*)PyDateTimeAPI->DateTimeType, "iiiiiiiO", y, m, d, hh, mm, ss, us, tzinfo); exit: Py_XDECREF(tzoff); Py_XDECREF(tzinfo); return rv; } /** DATETIME - cast a timestamp into a datetime python object **/ static PyObject * typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs) { if (str == NULL) { Py_RETURN_NONE; } /* check for infinity */ if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { return PyObject_GetAttrString( (PyObject*)PyDateTimeAPI->DateTimeType, (str[0] == '-' ? "min" : "max")); } return _parse_noninftz(str, len, curs); } /** DATETIMETZ - cast a timestamptz into a datetime python object **/ static PyObject * typecast_PYDATETIMETZ_cast(const char *str, Py_ssize_t len, PyObject *curs) { if (str == NULL) { Py_RETURN_NONE; } if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { return _parse_inftz(str, curs); } return _parse_noninftz(str, len, curs); } /** TIME - parse time into a time object **/ static PyObject * typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs) { PyObject* rv = NULL; PyObject *tzoff = NULL; PyObject *tzinfo = NULL; PyObject *tzinfo_factory; int n, hh=0, mm=0, ss=0, us=0, tzsec=0; if (str == NULL) { Py_RETURN_NONE; } n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tzsec); Dprintf("typecast_PYTIME_cast: n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", " "hh = %d, mm = %d, ss = %d, us = %d, tzsec = %d", n, len, hh, mm, ss, us, tzsec); if (n < 3 || n > 6) { PyErr_SetString(DataError, "unable to parse time"); return NULL; } if (ss > 59) { mm += 1; ss -= 60; } tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory; if (n >= 5 && tzinfo_factory != Py_None) { /* we have a time zone, calculate seconds and create appropriate tzinfo object calling the factory */ Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tzsec); #if PY_VERSION_HEX < 0x03070000 /* Before Python 3.7 the timezone offset had to be a whole number * of minutes, so round the seconds to the closest minute */ tzsec = 60 * (int)round(tzsec / 60.0); #endif if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; } if (!(tzinfo = PyObject_CallFunctionObjArgs(tzinfo_factory, tzoff, NULL))) { goto exit; } } else { Py_INCREF(Py_None); tzinfo = Py_None; } rv = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO", hh, mm, ss, us, tzinfo); exit: Py_XDECREF(tzoff); Py_XDECREF(tzinfo); return rv; } /* Attempt parsing a number as microseconds * Redshift is reported returning this stuff, see #558 * * Return a new `timedelta()` object in case of success or NULL and set an error */ static PyObject * interval_from_usecs(const char *str) { PyObject *us = NULL; char *pend; PyObject *rv = NULL; Dprintf("interval_from_usecs: %s", str); if (!(us = PyLong_FromString((char *)str, &pend, 0))) { Dprintf("interval_from_usecs: parsing long failed"); goto exit; } if (*pend != '\0') { /* there are trailing chars, it's not just micros. Barf. */ Dprintf("interval_from_usecs: spurious chars %s", pend); PyErr_Format(PyExc_ValueError, "expected number of microseconds, got %s", str); goto exit; } rv = PyObject_CallFunction( (PyObject*)PyDateTimeAPI->DeltaType, "iiO", 0, 0, us); exit: Py_XDECREF(us); return rv; } /** INTERVAL - parse an interval into a timedelta object **/ static PyObject * typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) { long v = 0, years = 0, months = 0, hours = 0, minutes = 0, micros = 0; PY_LONG_LONG days = 0, seconds = 0; int sign = 1, denom = 1, part = 0; const char *orig = str; if (str == NULL) { Py_RETURN_NONE; } Dprintf("typecast_PYINTERVAL_cast: s = %s", str); while (len-- > 0 && *str) { switch (*str) { case '-': sign = -1; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { long v1; v1 = v * 10 + (*str - '0'); /* detect either a rollover, happening if v is really too short, * or too big value. On Win where long == int the 2nd check * is useless. */ if (v1 < v || v1 > (long)INT_MAX) { /* uhm, oops... but before giving up, maybe it's redshift * returning microseconds? See #558 */ PyObject *rv; if ((rv = interval_from_usecs(orig))) { return rv; } else { PyErr_Clear(); } PyErr_SetString( PyExc_OverflowError, "interval component too big"); return NULL; } v = v1; } if (part == 6) { denom *= 10; } break; case 'y': if (part == 0) { years = v * sign; v = 0; sign = 1; part = 1; str = skip_until_space2(str, &len); } break; case 'm': if (part <= 1) { months = v * sign; v = 0; sign = 1; part = 2; str = skip_until_space2(str, &len); } break; case 'd': if (part <= 2) { days = v * sign; v = 0; sign = 1; part = 3; str = skip_until_space2(str, &len); } break; case ':': if (part <= 3) { hours = v; v = 0; part = 4; } else if (part == 4) { minutes = v; v = 0; part = 5; } break; case '.': if (part == 5) { seconds = v; v = 0; part = 6; } break; case 'P': PyErr_SetString(NotSupportedError, "iso_8601 intervalstyle currently not supported"); return NULL; default: break; } str++; } /* manage last value, be it minutes or seconds or microseconds */ if (part == 4) { minutes = v; } else if (part == 5) { seconds = v; } else if (part == 6) { micros = v; if (denom < 1000000L) { do { micros *= 10; denom *= 10; } while (denom < 1000000L); } else if (denom > 1000000L) { micros = (long)round((double)micros / denom * 1000000.0); } } else if (part == 0) { /* Parsing failed, maybe it's just an integer? Assume usecs */ return interval_from_usecs(orig); } /* add hour, minutes, seconds together and include the sign */ seconds += 60 * (PY_LONG_LONG)minutes + 3600 * (PY_LONG_LONG)hours; if (sign < 0) { seconds = -seconds; micros = -micros; } /* add the days, months years together - they already include a sign */ days += 30 * (PY_LONG_LONG)months + 365 * (PY_LONG_LONG)years; return PyObject_CallFunction((PyObject*)PyDateTimeAPI->DeltaType, "LLl", days, seconds, micros); } /* psycopg defaults to using python datetime types */ #define typecast_DATE_cast typecast_PYDATE_cast #define typecast_TIME_cast typecast_PYTIME_cast #define typecast_INTERVAL_cast typecast_PYINTERVAL_cast #define typecast_DATETIME_cast typecast_PYDATETIME_cast #define typecast_DATETIMETZ_cast typecast_PYDATETIMETZ_cast