diff --git a/ChangeLog b/ChangeLog index 08d5ba30..5f665358 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2005-11-14 Federico Di Gregorio + + * 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 * psycopg/pqpath.c: fixed problem with uninitialized value (all this was diff --git a/psycopg/typecast.c b/psycopg/typecast.c index c2cf5488..4ea839b8 100644 --- a/psycopg/typecast.c +++ b/psycopg/typecast.c @@ -47,6 +47,99 @@ skip_until_space2(char *s, int *len) 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 "psycopg/typecast_basic.c" diff --git a/psycopg/typecast_datetime.c b/psycopg/typecast_datetime.c index 7f963d9b..3a09f07a 100644 --- a/psycopg/typecast_datetime.c +++ b/psycopg/typecast_datetime.c @@ -41,7 +41,6 @@ typecast_PYDATE_cast(char *str, int len, PyObject *curs) if (str == NULL) {Py_INCREF(Py_None); return Py_None;} - /* check for infinity */ if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { if (str[0] == '-') { obj = PyObject_GetAttrString(pyDateTypeP, "min"); @@ -52,8 +51,10 @@ typecast_PYDATE_cast(char *str, int len, PyObject *curs) } 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) { PyErr_SetString(DataError, "unable to parse date"); } @@ -71,10 +72,8 @@ typecast_PYDATETIME_cast(char *str, int len, PyObject *curs) { PyObject* obj = NULL; int n, y=0, m=0, d=0; - int hh=0, mm=0; - int tzh=0, tzm=0; - double ss=0.0; - char tzs=0; + int hh=0, mm=0, ss=0, us=0, tz=0; + char *tp = NULL; if (str == NULL) {Py_INCREF(Py_None); return Py_None;} @@ -90,39 +89,45 @@ typecast_PYDATETIME_cast(char *str, int len, PyObject *curs) else { Dprintf("typecast_PYDATETIME_cast: s = %s", str); - n = sscanf(str, "%d-%d-%d %d:%d:%lf%c%d:%d", - &y, &m, &d, &hh, &mm, &ss, &tzs, &tzh, &tzm); - Dprintf("typecast_PYDATETIME_cast: date parsed, %d components", n); - - if (n != 3 && n != 6 && n <= 7) { + n = typecast_parse_date(str, &tp, &len, &y, &m, &d); + Dprintf("typecast_PYDATE_cast: tp = %p " + "n = %d, len = %d, y = %d, m = %d, d = %d", + tp, n, len, y, m, d); + if (n != 3) { PyErr_SetString(DataError, "unable to parse date"); } + + if (len > 0) { + n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tz); + Dprintf("typecast_PYDATETIME_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"); + } + } + + 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 + appropriate tzinfo object calling the factory */ + PyObject *tzinfo; + Dprintf("typecast_PYDATETIME_cast: UTC offset = %dm", tz); + tzinfo = PyObject_CallFunction( + ((cursorObject*)curs)->tzinfo_factory, "i", tz); + obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiiiO", + y, m, d, hh, mm, ss, us, tzinfo); + Dprintf("typecast_PYDATETIME_cast: tzinfo: %p, refcnt = %d", + tzinfo, tzinfo->ob_refcnt); + Py_XDECREF(tzinfo); + } else { - double micro = (ss - floor(ss)) * 1000000.0; - int sec = (int)floor(ss); - if (sec > 59) { - mm += 1; - sec -= 60; - } - if (tzs && ((cursorObject*)curs)->tzinfo_factory != Py_None) { - /* we have a time zone, calculate minutes and create - appropriate tzinfo object calling the factory */ - PyObject *tzinfo; - tzm += tzh*60; - if (tzs == '-') tzm = -tzm; - Dprintf("typecast_PYDATETIME_cast: UTC offset = %dm", tzm); - tzinfo = PyObject_CallFunction( - ((cursorObject*)curs)->tzinfo_factory, "i", tzm); - obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiiiO", - y, m, d, hh, mm, sec, (int)round(micro), tzinfo); - Dprintf("typecast_PYDATETIME_cast: tzinfo: %p, refcnt = %d", - tzinfo, tzinfo->ob_refcnt); - Py_XDECREF(tzinfo); - } - else { - obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiii", - y, m, d, hh, mm, sec, (int)round(micro)); - } + obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiii", + y, m, d, hh, mm, ss, us); } } return obj; @@ -134,25 +139,24 @@ static PyObject * typecast_PYTIME_cast(char *str, int len, PyObject *curs) { PyObject* obj = NULL; - int n, hh=0, mm=0; - double ss=0.0; + int n, hh=0, mm=0, ss=0, us=0, tz=0; if (str == NULL) {Py_INCREF(Py_None); return Py_None;} - n = sscanf(str, "%d:%d:%lf", &hh, &mm, &ss); - - if (n != 3) { + 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 || n > 5) { PyErr_SetString(DataError, "unable to parse time"); } else { - double micro = (ss - floor(ss)) * 1000000.0; - int sec = (int)floor(ss); - if (sec > 59) { + if (ss > 59) { mm += 1; - sec -= 60; + ss -= 60; } - obj = PyObject_CallFunction(pyTimeTypeP, "iiii", - hh, mm, sec, (int)round(micro)); + obj = PyObject_CallFunction(pyTimeTypeP, "iiii", hh, mm, ss, us); } return obj; } @@ -256,7 +260,7 @@ typecast_PYINTERVAL_cast(char *str, int len, PyObject *curs) seconds += hundredths + minutes*60 + hours*3600; } - /* calculates days */ + /* calculates days */ days += years*365 + months*30; micro = (seconds - floor(seconds)) * 1000000.0; diff --git a/psycopg/typecast_mxdatetime.c b/psycopg/typecast_mxdatetime.c index b71a15e7..52b1ce7f 100644 --- a/psycopg/typecast_mxdatetime.c +++ b/psycopg/typecast_mxdatetime.c @@ -31,10 +31,12 @@ static PyObject * typecast_MXDATE_cast(char *str, int len, PyObject *curs) { int n, y=0, m=0, d=0; - int hh=0, mm=0; - double ss=0.0; - + int hh=0, mm=0, ss=0, us=0, tz=0; + char *tp = NULL; + if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + + Dprintf("typecast_MXDATE_cast: s = %s", str); /* check for infinity */ if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { @@ -46,15 +48,27 @@ typecast_MXDATE_cast(char *str, int len, PyObject *curs) } } - Dprintf("typecast_MXDATE_cast: s = %s", str); - n = sscanf(str, "%d-%d-%d %d:%d:%lf", &y, &m, &d, &hh, &mm, &ss); - Dprintf("typecast_MXDATE_cast: date parsed, %d components", n); - - if (n != 3 && n != 6) { + n = typecast_parse_date(str, &tp, &len, &y, &m, &d); + Dprintf("typecast_MXDATE_cast: tp = %p n = %d, len = %d, " + "y = %d, m = %d, d = %d", tp, n, len, y, m, d); + if (n != 3) { 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 **/ @@ -62,23 +76,26 @@ typecast_MXDATE_cast(char *str, int len, PyObject *curs) static PyObject * typecast_MXTIME_cast(char *str, int len, PyObject *curs) { - int n, hh=0, mm=0; - double ss=0.0; + int n, hh=0, mm=0, ss=0, us=0, tz=0; if (str == NULL) {Py_INCREF(Py_None); return Py_None;} 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: 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"); 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 **/ diff --git a/sandbox/test.py b/sandbox/test.py index 17e96d01..e3d31a75 100644 --- a/sandbox/test.py +++ b/sandbox/test.py @@ -6,10 +6,24 @@ import psycopg2 #print d.days, d.seconds, d.microseconds #print psycopg.adapt(d).getquoted() -conn = psycopg2.connect("dbname=test_unicode") -conn.set_client_encoding("xxx") +conn = psycopg2.connect("dbname=test") +#conn.set_client_encoding("xxx") 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.fetchall() @@ -17,20 +31,20 @@ def sleep(curs): while not curs.isready(): print "." time.sleep(.1) - + #curs.execute(""" # DECLARE zz INSENSITIVE SCROLL CURSOR WITH HOLD FOR # SELECT now(); # FOR READ ONLY;""", async = 1) -curs.execute("SELECT now() AS foo", async=1); -sleep(curs) -print curs.fetchall() +#curs.execute("SELECT now() AS foo", async=1); +#sleep(curs) +#print curs.fetchall() #curs.execute(""" # FETCH FORWARD 1 FROM zz;""", async = 1) -curs.execute("SELECT now() AS bar", async=1); -print curs.fetchall() +#curs.execute("SELECT now() AS bar", async=1); +#print curs.fetchall() -curs.execute("SELECT now() AS bar"); -sleep(curs) +#curs.execute("SELECT now() AS bar"); +#sleep(curs) diff --git a/setup.cfg b/setup.cfg index f278e998..54a2edf4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [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_DISPLAY_SIZE enable display size calculation (a little slower) # HAVE_PQFREEMEM should be defined on PostgreSQL >= 7.4