mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-25 10:23:43 +03:00
1d3a89a0bb
ag -l Copyright | xargs sed -i \ "s/\(.*copyright (C) [0-9]\+\)\(-[0-9]\+\)\?\(.*Psycopg Team.*\)/\1-$(date +%Y)\3/I"
487 lines
14 KiB
C
487 lines
14 KiB
C
/* typecast_datetime.c - date and time typecasting functions to python types
|
|
*
|
|
* Copyright (C) 2001-2019 Federico Di Gregorio <fog@debian.org>
|
|
* 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 <math.h>
|
|
#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
|