New time/date locale-safe typecasting code.

This commit is contained in:
Federico Di Gregorio 2005-11-14 11:57:45 +00:00
parent 770e7c34d3
commit 164eb32817
6 changed files with 211 additions and 77 deletions

View File

@ -1,3 +1,9 @@
2005-11-14 Federico Di Gregorio <fog@initd.org>
* psycopg/typecast.c: added typecast_parse_date and typecast_parse_time
functions to do locale-safe date/time parsing. This would probably also
speed-up psycopg a little bit.
2005-11-07 Federico Di Gregorio <fog@initd.org> 2005-11-07 Federico Di Gregorio <fog@initd.org>
* psycopg/pqpath.c: fixed problem with uninitialized value (all this was * psycopg/pqpath.c: fixed problem with uninitialized value (all this was

View File

@ -47,6 +47,99 @@ skip_until_space2(char *s, int *len)
return s; return s;
} }
static int
typecast_parse_date(char* s, char** t, int* len,
int* year, int* month, int* day)
{
int acc = -1, cz = 0;
Dprintf("typecast_parse_date: len = %d, s = %s", *len, s);
while (cz < 3 && *len > 0 && *s) {
switch (*s) {
case '-':
case ' ':
case 'T':
if (cz == 0) *year = acc;
else if (cz == 1) *month = acc;
else if (cz == 2) *day = acc;
acc = -1; cz++;
break;
default:
acc = (acc == -1 ? 0 : acc*10) + ((int)*s - (int)'0');
break;
}
s++; (*len)--;
}
if (acc != -1) {
*day = acc;
cz += 1;
}
if (t != NULL) *t = s;
return cz;
}
static int
typecast_parse_time(char* s, char** t, int* len,
int* hh, int* mm, int* ss, int* us, int* tz)
{
int acc = -1, cz = 0;
int tzs = 1, tzhh = 0, tzmm = 0;
/* sets microseconds and timezone to 0 because they may be missing */
*us = *tz = 0;
Dprintf("typecast_parse_time: len = %d, s = %s", *len, s);
while (cz < 5 && *len > 0 && *s) {
switch (*s) {
case ':':
if (cz == 0) *hh = acc;
else if (cz == 1) *mm = acc;
else if (cz == 2) *ss = acc;
else if (cz == 3) *us = acc;
else if (cz == 4) tzhh = acc;
acc = -1; cz++;
break;
case '.':
/* we expect seconds and if we don't get them we return an error */
if (cz != 2) return -1;
*ss = acc;
acc = -1; cz++;
break;
case '+':
case '-':
/* seconds or microseconds here, anything else is an error */
if (cz < 2 || cz > 3) return -1;
if (*s == '-') tzs = -1;
if (cz == 2) *ss = acc;
else if (cz == 3) *us = acc;
else if (cz == 4) *tz = acc;
acc = -1; cz++;
break;
default:
acc = (acc == -1 ? 0 : acc*10) + ((int)*s - (int)'0');
break;
}
s++; (*len)--;
}
if (acc != -1) {
if (cz == 2) { *ss = acc; cz += 1; }
else if (cz == 3) { *us = acc; cz += 1; }
else if (cz == 4) { tzhh = acc; cz += 1; }
else if (cz == 5) tzmm = acc;
}
if (t != NULL) *t = s;
*tz = tzs * tzhh*60 + tzmm;
return cz;
}
/** include casting objects **/ /** include casting objects **/
#include "psycopg/typecast_basic.c" #include "psycopg/typecast_basic.c"

View File

@ -41,7 +41,6 @@ typecast_PYDATE_cast(char *str, int len, PyObject *curs)
if (str == NULL) {Py_INCREF(Py_None); return Py_None;} if (str == NULL) {Py_INCREF(Py_None); return Py_None;}
/* check for infinity */
if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) {
if (str[0] == '-') { if (str[0] == '-') {
obj = PyObject_GetAttrString(pyDateTypeP, "min"); obj = PyObject_GetAttrString(pyDateTypeP, "min");
@ -52,8 +51,10 @@ typecast_PYDATE_cast(char *str, int len, PyObject *curs)
} }
else { else {
n = sscanf(str, "%d-%d-%d", &y, &m, &d); n = typecast_parse_date(str, NULL, &len, &y, &m, &d);
Dprintf("typecast_PYDATE_cast: "
"n = %d, len = %d, y = %d, m = %d, d = %d",
n, len, y, m, d);
if (n != 3) { if (n != 3) {
PyErr_SetString(DataError, "unable to parse date"); PyErr_SetString(DataError, "unable to parse date");
} }
@ -71,10 +72,8 @@ typecast_PYDATETIME_cast(char *str, int len, PyObject *curs)
{ {
PyObject* obj = NULL; PyObject* obj = NULL;
int n, y=0, m=0, d=0; int n, y=0, m=0, d=0;
int hh=0, mm=0; int hh=0, mm=0, ss=0, us=0, tz=0;
int tzh=0, tzm=0; char *tp = NULL;
double ss=0.0;
char tzs=0;
if (str == NULL) {Py_INCREF(Py_None); return Py_None;} if (str == NULL) {Py_INCREF(Py_None); return Py_None;}
@ -90,39 +89,45 @@ typecast_PYDATETIME_cast(char *str, int len, PyObject *curs)
else { else {
Dprintf("typecast_PYDATETIME_cast: s = %s", str); Dprintf("typecast_PYDATETIME_cast: s = %s", str);
n = sscanf(str, "%d-%d-%d %d:%d:%lf%c%d:%d", n = typecast_parse_date(str, &tp, &len, &y, &m, &d);
&y, &m, &d, &hh, &mm, &ss, &tzs, &tzh, &tzm); Dprintf("typecast_PYDATE_cast: tp = %p "
Dprintf("typecast_PYDATETIME_cast: date parsed, %d components", n); "n = %d, len = %d, y = %d, m = %d, d = %d",
tp, n, len, y, m, d);
if (n != 3 && n != 6 && n <= 7) { if (n != 3) {
PyErr_SetString(DataError, "unable to parse date"); PyErr_SetString(DataError, "unable to parse date");
} }
else {
double micro = (ss - floor(ss)) * 1000000.0; if (len > 0) {
int sec = (int)floor(ss); n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tz);
if (sec > 59) { Dprintf("typecast_PYDATETIME_cast: n = %d, len = %d, "
mm += 1; "hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
sec -= 60; n, len, hh, mm, ss, us, tz);
if (n < 3 || n > 5) {
PyErr_SetString(DataError, "unable to parse time");
} }
if (tzs && ((cursorObject*)curs)->tzinfo_factory != Py_None) { }
if (ss > 59) {
mm += 1;
ss -= 60;
}
if (n == 5 && ((cursorObject*)curs)->tzinfo_factory != Py_None) {
/* we have a time zone, calculate minutes and create /* we have a time zone, calculate minutes and create
appropriate tzinfo object calling the factory */ appropriate tzinfo object calling the factory */
PyObject *tzinfo; PyObject *tzinfo;
tzm += tzh*60; Dprintf("typecast_PYDATETIME_cast: UTC offset = %dm", tz);
if (tzs == '-') tzm = -tzm;
Dprintf("typecast_PYDATETIME_cast: UTC offset = %dm", tzm);
tzinfo = PyObject_CallFunction( tzinfo = PyObject_CallFunction(
((cursorObject*)curs)->tzinfo_factory, "i", tzm); ((cursorObject*)curs)->tzinfo_factory, "i", tz);
obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiiiO", obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiiiO",
y, m, d, hh, mm, sec, (int)round(micro), tzinfo); y, m, d, hh, mm, ss, us, tzinfo);
Dprintf("typecast_PYDATETIME_cast: tzinfo: %p, refcnt = %d", Dprintf("typecast_PYDATETIME_cast: tzinfo: %p, refcnt = %d",
tzinfo, tzinfo->ob_refcnt); tzinfo, tzinfo->ob_refcnt);
Py_XDECREF(tzinfo); Py_XDECREF(tzinfo);
} }
else { else {
obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiii", obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiii",
y, m, d, hh, mm, sec, (int)round(micro)); y, m, d, hh, mm, ss, us);
}
} }
} }
return obj; return obj;
@ -134,25 +139,24 @@ static PyObject *
typecast_PYTIME_cast(char *str, int len, PyObject *curs) typecast_PYTIME_cast(char *str, int len, PyObject *curs)
{ {
PyObject* obj = NULL; PyObject* obj = NULL;
int n, hh=0, mm=0; int n, hh=0, mm=0, ss=0, us=0, tz=0;
double ss=0.0;
if (str == NULL) {Py_INCREF(Py_None); return Py_None;} if (str == NULL) {Py_INCREF(Py_None); return Py_None;}
n = sscanf(str, "%d:%d:%lf", &hh, &mm, &ss); n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tz);
Dprintf("typecast_PYTIME_cast: n = %d, len = %d, "
"hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
n, len, hh, mm, ss, us, tz);
if (n != 3) { if (n < 3 || n > 5) {
PyErr_SetString(DataError, "unable to parse time"); PyErr_SetString(DataError, "unable to parse time");
} }
else { else {
double micro = (ss - floor(ss)) * 1000000.0; if (ss > 59) {
int sec = (int)floor(ss);
if (sec > 59) {
mm += 1; mm += 1;
sec -= 60; ss -= 60;
} }
obj = PyObject_CallFunction(pyTimeTypeP, "iiii", obj = PyObject_CallFunction(pyTimeTypeP, "iiii", hh, mm, ss, us);
hh, mm, sec, (int)round(micro));
} }
return obj; return obj;
} }

View File

@ -31,11 +31,13 @@ static PyObject *
typecast_MXDATE_cast(char *str, int len, PyObject *curs) typecast_MXDATE_cast(char *str, int len, PyObject *curs)
{ {
int n, y=0, m=0, d=0; int n, y=0, m=0, d=0;
int hh=0, mm=0; int hh=0, mm=0, ss=0, us=0, tz=0;
double ss=0.0; char *tp = NULL;
if (str == NULL) {Py_INCREF(Py_None); return Py_None;} if (str == NULL) {Py_INCREF(Py_None); return Py_None;}
Dprintf("typecast_MXDATE_cast: s = %s", str);
/* check for infinity */ /* check for infinity */
if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) {
if (str[0] == '-') { if (str[0] == '-') {
@ -46,15 +48,27 @@ typecast_MXDATE_cast(char *str, int len, PyObject *curs)
} }
} }
Dprintf("typecast_MXDATE_cast: s = %s", str); n = typecast_parse_date(str, &tp, &len, &y, &m, &d);
n = sscanf(str, "%d-%d-%d %d:%d:%lf", &y, &m, &d, &hh, &mm, &ss); Dprintf("typecast_MXDATE_cast: tp = %p n = %d, len = %d, "
Dprintf("typecast_MXDATE_cast: date parsed, %d components", n); "y = %d, m = %d, d = %d", tp, n, len, y, m, d);
if (n != 3) {
if (n != 3 && n != 6) {
PyErr_SetString(DataError, "unable to parse date"); PyErr_SetString(DataError, "unable to parse date");
return NULL;
} }
return mxDateTimeP->DateTime_FromDateAndTime(y, m, d, hh, mm, ss);
if (len > 0) {
n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tz);
Dprintf("typecast_MXDATE_cast: n = %d, len = %d, "
"hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
n, len, hh, mm, ss, us, tz);
if (n < 3 || n > 5) {
PyErr_SetString(DataError, "unable to parse time");
}
}
Dprintf("typecast_MXDATE_cast: fractionary seconds: %lf",
(double)ss + (double)us/(double)1000000.0);
return mxDateTimeP->DateTime_FromDateAndTime(y, m, d, hh, mm,
(double)ss + (double)us/(double)1000000.0);
} }
/** TIME - parse time into an mx.DateTime object **/ /** TIME - parse time into an mx.DateTime object **/
@ -62,23 +76,26 @@ typecast_MXDATE_cast(char *str, int len, PyObject *curs)
static PyObject * static PyObject *
typecast_MXTIME_cast(char *str, int len, PyObject *curs) typecast_MXTIME_cast(char *str, int len, PyObject *curs)
{ {
int n, hh=0, mm=0; int n, hh=0, mm=0, ss=0, us=0, tz=0;
double ss=0.0;
if (str == NULL) {Py_INCREF(Py_None); return Py_None;} if (str == NULL) {Py_INCREF(Py_None); return Py_None;}
Dprintf("typecast_MXTIME_cast: s = %s", str); Dprintf("typecast_MXTIME_cast: s = %s", str);
n = sscanf(str, "%d:%d:%lf", &hh, &mm, &ss); n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tz);
Dprintf("typecast_MXTIME_cast: time parsed, %d components", n); Dprintf("typecast_MXTIME_cast: time parsed, %d components", n);
Dprintf("typecast_MXTIME_cast: hh = %d, mm = %d, ss = %f", hh, mm, ss); Dprintf("typecast_MXTIME_cast: hh = %d, mm = %d, ss = %d, us = %d",
hh, mm, ss, us);
if (n != 3) { if (n < 3 || n > 5) {
PyErr_SetString(DataError, "unable to parse time"); PyErr_SetString(DataError, "unable to parse time");
return NULL; return NULL;
} }
return mxDateTimeP->DateTimeDelta_FromTime(hh, mm ,ss); Dprintf("typecast_MXTIME_cast: fractionary seconds: %lf",
(double)ss + (double)us/(double)1000000.0);
return mxDateTimeP->DateTimeDelta_FromTime(hh, mm,
(double)ss + (double)us/(double)1000000.0);
} }
/** INTERVAL - parse an interval into an mx.DateTimeDelta **/ /** INTERVAL - parse an interval into an mx.DateTimeDelta **/

View File

@ -6,10 +6,24 @@ import psycopg2
#print d.days, d.seconds, d.microseconds #print d.days, d.seconds, d.microseconds
#print psycopg.adapt(d).getquoted() #print psycopg.adapt(d).getquoted()
conn = psycopg2.connect("dbname=test_unicode") conn = psycopg2.connect("dbname=test")
conn.set_client_encoding("xxx") #conn.set_client_encoding("xxx")
curs = conn.cursor() curs = conn.cursor()
#curs.execute("SELECT 1.0 AS foo") curs.execute("SELECT '2005-2-12'::date AS foo")
print curs.fetchall()
curs.execute("SELECT '10:23:60'::time AS foo")
print curs.fetchall()
curs.execute("SELECT '10:23:59.895342'::time AS foo")
print curs.fetchall()
curs.execute("SELECT '0:0:12.31423'::time with time zone AS foo")
print curs.fetchall()
curs.execute("SELECT '0:0:12+01:30'::time with time zone AS foo")
print curs.fetchall()
curs.execute("SELECT '2005-2-12 10:23:59.895342'::timestamp AS foo")
print curs.fetchall()
curs.execute("SELECT '2005-2-12 10:23:59.895342'::timestamp with time zone AS foo")
print curs.fetchall()
#print curs.fetchmany(2) #print curs.fetchmany(2)
#print curs.fetchall() #print curs.fetchall()
@ -22,15 +36,15 @@ def sleep(curs):
# DECLARE zz INSENSITIVE SCROLL CURSOR WITH HOLD FOR # DECLARE zz INSENSITIVE SCROLL CURSOR WITH HOLD FOR
# SELECT now(); # SELECT now();
# FOR READ ONLY;""", async = 1) # FOR READ ONLY;""", async = 1)
curs.execute("SELECT now() AS foo", async=1); #curs.execute("SELECT now() AS foo", async=1);
sleep(curs) #sleep(curs)
print curs.fetchall() #print curs.fetchall()
#curs.execute(""" #curs.execute("""
# FETCH FORWARD 1 FROM zz;""", async = 1) # FETCH FORWARD 1 FROM zz;""", async = 1)
curs.execute("SELECT now() AS bar", async=1); #curs.execute("SELECT now() AS bar", async=1);
print curs.fetchall() #print curs.fetchall()
curs.execute("SELECT now() AS bar"); #curs.execute("SELECT now() AS bar");
sleep(curs) #sleep(curs)

View File

@ -1,5 +1,5 @@
[build_ext] [build_ext]
define=PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3 define=PSYCOPG_DEBUG,PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3
# PSYCOPG_EXTENSIONS enables extensions to PEP-249 (you really want this) # PSYCOPG_EXTENSIONS enables extensions to PEP-249 (you really want this)
# PSYCOPG_DISPLAY_SIZE enable display size calculation (a little slower) # PSYCOPG_DISPLAY_SIZE enable display size calculation (a little slower)
# HAVE_PQFREEMEM should be defined on PostgreSQL >= 7.4 # HAVE_PQFREEMEM should be defined on PostgreSQL >= 7.4