Skip to content

Commit 2a94dfa

Browse files
committed
* tests/test_dates.py (DatetimeTests, mxDateTimeTests): full test
coverage for datetime and time strings with and without time zone information. * psycopg/typecast_datetime.c (typecast_PYDATETIME_cast): adjust to handle the changes in typecast_parse_time. (typecast_PYTIME_cast): add support for time zone aware time values. * psycopg/typecast_mxdatetime.c (typecast_MXDATE_cast): make sure that values with time zones are correctly processed (even though that means ignoring the time zone value). (typecast_MXTIME_cast): same here. * psycopg/typecast.c (typecast_parse_time): Update method to parse second resolution timezone offsets.
1 parent ba8be43 commit 2a94dfa

File tree

5 files changed

+224
-83
lines changed

5 files changed

+224
-83
lines changed

ChangeLog

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
2009-02-17 James Henstridge <james@jamesh.id.au>
22

3+
* tests/test_dates.py (DatetimeTests, mxDateTimeTests): full test
4+
coverage for datetime and time strings with and without time zone
5+
information.
6+
7+
* psycopg/typecast_datetime.c (typecast_PYDATETIME_cast): adjust
8+
to handle the changes in typecast_parse_time.
9+
(typecast_PYTIME_cast): add support for time zone aware time
10+
values.
11+
12+
* psycopg/typecast_mxdatetime.c (typecast_MXDATE_cast): make sure
13+
that values with time zones are correctly processed (even though
14+
that means ignoring the time zone value).
15+
(typecast_MXTIME_cast): same here.
16+
17+
* psycopg/typecast.c (typecast_parse_time): Update method to parse
18+
second resolution timezone offsets.
19+
320
* psycopg/typecast.c (typecast_parse_time): Fix up handling of
421
negative timezone offsets with a non-zero minutes field.
522

psycopg/typecast.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
9696
int* hh, int* mm, int* ss, int* us, int* tz)
9797
{
9898
int acc = -1, cz = 0;
99-
int tzs = 1, tzhh = 0, tzmm = 0;
99+
int tzsign = 1, tzhh = 0, tzmm = 0, tzss = 0;
100100
int usd = 0;
101101

102102
/* sets microseconds and timezone to 0 because they may be missing */
@@ -105,14 +105,15 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
105105
Dprintf("typecast_parse_time: len = " FORMAT_CODE_PY_SSIZE_T ", s = %s",
106106
*len, s);
107107

108-
while (cz < 6 && *len > 0 && *s) {
108+
while (cz < 7 && *len > 0 && *s) {
109109
switch (*s) {
110110
case ':':
111111
if (cz == 0) *hh = acc;
112112
else if (cz == 1) *mm = acc;
113113
else if (cz == 2) *ss = acc;
114114
else if (cz == 3) *us = acc;
115115
else if (cz == 4) tzhh = acc;
116+
else if (cz == 5) tzmm = acc;
116117
acc = -1; cz++;
117118
break;
118119
case '.':
@@ -125,7 +126,7 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
125126
case '-':
126127
/* seconds or microseconds here, anything else is an error */
127128
if (cz < 2 || cz > 3) return -1;
128-
if (*s == '-') tzs = -1;
129+
if (*s == '-') tzsign = -1;
129130
if (cz == 2) *ss = acc;
130131
else if (cz == 3) *us = acc;
131132
acc = -1; cz = 4;
@@ -151,11 +152,12 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
151152
else if (cz == 2) { *ss = acc; cz += 1; }
152153
else if (cz == 3) { *us = acc; cz += 1; }
153154
else if (cz == 4) { tzhh = acc; cz += 1; }
154-
else if (cz == 5) tzmm = acc;
155+
else if (cz == 5) { tzmm = acc; cz += 1; }
156+
else if (cz == 6) tzss = acc;
155157
}
156158
if (t != NULL) *t = s;
157159

158-
*tz = tzs * (tzhh * 60 + tzmm);
160+
*tz = tzsign * (3600 * tzhh + 60 * tzmm + tzss);
159161

160162
if (*us != 0) {
161163
while (usd++ < 6) *us *= 10;

psycopg/typecast_datetime.c

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ static PyObject *
7474
typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
7575
{
7676
PyObject* obj = NULL;
77+
PyObject *tzinfo = NULL;
78+
PyObject *tzinfo_factory;
7779
int n, y=0, m=0, d=0;
7880
int hh=0, mm=0, ss=0, us=0, tz=0;
7981
const char *tp = NULL;
@@ -108,7 +110,7 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
108110
" len = " FORMAT_CODE_PY_SSIZE_T ","
109111
" hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
110112
n, len, hh, mm, ss, us, tz);
111-
if (n < 3 || n > 5) {
113+
if (n < 3 || n > 6) {
112114
PyErr_SetString(DataError, "unable to parse time");
113115
return NULL;
114116
}
@@ -121,24 +123,34 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
121123
if (y > 9999)
122124
y = 9999;
123125

124-
if (n == 5 && ((cursorObject*)curs)->tzinfo_factory != Py_None) {
126+
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
127+
if (n >= 5 && tzinfo_factory != Py_None) {
125128
/* we have a time zone, calculate minutes and create
126129
appropriate tzinfo object calling the factory */
127-
PyObject *tzinfo;
128-
Dprintf("typecast_PYDATETIME_cast: UTC offset = %dm", tz);
129-
tzinfo = PyObject_CallFunction(
130-
((cursorObject*)curs)->tzinfo_factory, "i", tz);
130+
Dprintf("typecast_PYDATETIME_cast: UTC offset = %ds", tz);
131+
132+
/* The datetime module requires that time zone offsets be
133+
a whole number of minutes, so fail if we have a time
134+
zone with a seconds offset.
135+
*/
136+
if (tz % 60 != 0) {
137+
PyErr_Format(PyExc_ValueError, "time zone offset %d is not "
138+
"a whole number of minutes", tz);
139+
return NULL;
140+
}
141+
tzinfo = PyObject_CallFunction(tzinfo_factory, "i", tz / 60);
142+
} else {
143+
Py_INCREF(Py_None);
144+
tzinfo = Py_None;
145+
}
146+
if (tzinfo != NULL) {
131147
obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiiiO",
132148
y, m, d, hh, mm, ss, us, tzinfo);
133149
Dprintf("typecast_PYDATETIME_cast: tzinfo: %p, refcnt = "
134150
FORMAT_CODE_PY_SSIZE_T,
135151
tzinfo, tzinfo->ob_refcnt
136152
);
137-
Py_XDECREF(tzinfo);
138-
}
139-
else {
140-
obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiii",
141-
y, m, d, hh, mm, ss, us);
153+
Py_DECREF(tzinfo);
142154
}
143155
}
144156
return obj;
@@ -150,6 +162,8 @@ static PyObject *
150162
typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
151163
{
152164
PyObject* obj = NULL;
165+
PyObject *tzinfo = NULL;
166+
PyObject *tzinfo_factory;
153167
int n, hh=0, mm=0, ss=0, us=0, tz=0;
154168

155169
if (str == NULL) {Py_INCREF(Py_None); return Py_None;}
@@ -159,16 +173,38 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
159173
"hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
160174
n, len, hh, mm, ss, us, tz);
161175

162-
if (n < 3 || n > 5) {
176+
if (n < 3 || n > 6) {
163177
PyErr_SetString(DataError, "unable to parse time");
164178
return NULL;
165179
}
166-
else {
167-
if (ss > 59) {
168-
mm += 1;
169-
ss -= 60;
180+
if (ss > 59) {
181+
mm += 1;
182+
ss -= 60;
183+
}
184+
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
185+
if (n >= 5 && tzinfo_factory != Py_None) {
186+
/* we have a time zone, calculate minutes and create
187+
appropriate tzinfo object calling the factory */
188+
Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tz);
189+
190+
/* The datetime module requires that time zone offsets be
191+
a whole number of minutes, so fail if we have a time
192+
zone with a seconds offset.
193+
*/
194+
if (tz % 60 != 0) {
195+
PyErr_Format(PyExc_ValueError, "time zone offset %d is not "
196+
"a whole number of minutes", tz);
197+
return NULL;
170198
}
171-
obj = PyObject_CallFunction(pyTimeTypeP, "iiii", hh, mm, ss, us);
199+
tzinfo = PyObject_CallFunction(tzinfo_factory, "i", tz / 60);
200+
} else {
201+
Py_INCREF(Py_None);
202+
tzinfo = Py_None;
203+
}
204+
if (tzinfo != NULL) {
205+
obj = PyObject_CallFunction(pyTimeTypeP, "iiiiO",
206+
hh, mm, ss, us, tzinfo);
207+
Py_DECREF(tzinfo);
172208
}
173209
return obj;
174210
}

psycopg/typecast_mxdatetime.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ typecast_MXDATE_cast(const char *str, Py_ssize_t len, PyObject *curs)
6363
" len = " FORMAT_CODE_PY_SSIZE_T ","
6464
" hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
6565
n, len, hh, mm, ss, us, tz);
66-
if (n != 0 && (n < 3 || n > 5)) {
66+
if (n != 0 && (n < 3 || n > 6)) {
6767
PyErr_SetString(DataError, "unable to parse time");
6868
return NULL;
6969
}
@@ -91,7 +91,7 @@ typecast_MXTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
9191
Dprintf("typecast_MXTIME_cast: hh = %d, mm = %d, ss = %d, us = %d",
9292
hh, mm, ss, us);
9393

94-
if (n < 3 || n > 5) {
94+
if (n < 3 || n > 6) {
9595
PyErr_SetString(DataError, "unable to parse time");
9696
return NULL;
9797
}

0 commit comments

Comments
 (0)