-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sqlite3 timestamp converter cannot handle timezone #73285
Comments
Current convert_timestamp function raises exception when parsing timestamps that contain timezone information and have microsecond set to zero. Patch for this behavior is included. Example script that demonstrates this problem: import sys
import datetime
import sqlite3
import traceback
import contextlib
def main():
db = sqlite3.connect('test.db', detect_types=sqlite3.PARSE_DECLTYPES)
db.executescript("""
CREATE TABLE IF NOT EXISTS test (
timestamp TIMESTAMP)
""")
t = datetime.datetime.now(tz=datetime.timezone.utc)
t = t.replace(microsecond=0)
db.executemany("INSERT INTO test VALUES (:timestamp)", [{'timestamp': t}])
db.commit()
with contextlib.closing(db.cursor()) as c:
try:
c.execute('SELECT * FROM test')
c.fetchall()
print('original implementation success')
except Exception as e:
print('original implementation failed')
traceback.print_exc()
c.close()
sqlite3.register_converter("timestamp", _sqlite_convert_timestamp)
with contextlib.closing(db.cursor()) as c:
try:
c.execute('SELECT * FROM test')
c.fetchall()
print('patched implementation success')
except Exception as e:
print('patched implementation failed')
traceback.print_exc()
c.close()
def _sqlite_convert_timestamp(val):
datepart, timepart = val.split(b" ")
# this is the patch
timepart = timepart.split(b'+', 1)[0].split(b'-', 1)[0]
if __name__ == '__main__':
sys.exit(main()) |
Thanks for your report Bozo. I think this is due to timestamp converter doesn't handle timezone now. Besides this ValueError, if I understand correctly, even if you pass a datetime object with tzinfo, with microsecond set, the returned datetime object is a wrong one since the tzinfo is discarded. |
Yes, that is correct. Timezone information is discarded. Submitted patch only resolves occurrence of ValueError. Is additional patch that enables parsing of timezone required? |
I would prefer add the support for timezone since the current behaviour seems not correct to me. If you'd like to work on it, don't forget to add a test case and sign the CLA. But I am not sure this should be treated as a bug or new feature. |
Could you provide a unittest? |
import sqlite3, datetime
c = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
cur = c.cursor()
cur.execute('create table test(t timestamp)')
t = datetime.datetime.now(tz=datetime.timezone.utc)
cur.execute("insert into test(t) values (?)", (t,))
cur.execute('select t from test')
l = cur.fetchone()[0]
t == l # the result not equal to the original one |
I'm providing new patch that adds support for parsing timezone information. Tests are included. |
In general the patch LGTM. Added comments on Rietveld. |
LGTM generally. :-) |
LGTM except that arguments of assertEqual() should be swapped. |
Thanks Serhiy! I'll take care of that. One problem remained is what to do with 2.7. There is no timezone object in 2.7. My preference is just pointing out the limitation that only naive datetime object is supported in the doc. |
Agreed. And maybe raise more informative error for datetimes with a timezone. I'm also not sure about 3.5 and 3.6. Datetimes with timezones were never supported. Currently all returned ditetime objects are naive. Getting an aware datetime can confuse applications. |
I don't think this can be considered a bug fix, since it changes behavior for applications that read data from SQLite databases which were not created by Python. Those application may now see datetime values with tz infos and will likely not be prepared to handle all the problems associated with this. Is it possible to make the support optional for 3.5 and 3.6 and only enable it for 3.7 (with the possibility of disabling it again) ? |
I asked whether this should be treated as an enhancement or bug fix. Now with your concerns I think it fits as enhancement more.
How about just make it just into 3.7 and just document only naive datetime objects are supported in 3.5, 3.6 and 2.7. |
The naive datetime converter is registered under the name "timestamp". The aware datetime converter or the universal datetime converter (if it is needed) can be registered under different names. |
On 11.01.2017 17:08, Serhiy Storchaka wrote:
This sounds like a good idea. Perhaps use "timestamptz" or something similar. |
On 11.01.2017 17:04, Xiang Zhang wrote:
Best practice is to store all date/time values using UTC (or some The TZ info then becomes redundant and only results in more Of course, there are use cases, where you'd still want to work |
timestamptz.patch implements a new converter that's able to convert aware datetime objects. |
I think the timestamptz converter should either interpret strings without timezone as UTC (and perhaps understand the "Z" suffix as sqlite3 date() function) or raises an error. It should never return naive datetime. The timestamp converter needs better error reporting when get an input with a timezone. |
Currently timestamptz just convert back what the user passed in, no matter naive or aware objects. What to do with them is left to the app. If we raise an error, could users use naive and aware objects together? And interpreting strings without timezone as UTC seems will mistranslate the object. For example, pass in datetime.datetime.now() and translate it back as UTC may not be right.
I thought about it but our intention to introduce a new converter is not to break old code. Doesn't add error reporting violate the intention? Users' code may not catch the error now. |
Naive and aware objects should not be mixed. Their actually are different types. The converter doesn't return str or bytes when can't parse an input as a datetime, and it shouldn't return a naive datetime if can't find a timezone. SQLite date and time functions imply UTC if timezone is omitted. This is a reason for returning aware UTC datetime objects. On other side, Python is more powerful programming language, it distinguish naive and aware datetime objects, and it is unlikely that these two types are mixed in one database column. It is better to raise an error that silently return possible wrong result. It exceptional case user can write special converter or just call SQLite datetime() for unifying data format. I think the old code unlikely will be broken if preserve an exception type and don't change conditions for raising an error. New error message can contain full input string and suggest to use the timestamptz converter if it looks as a datetime with timezone. |
Currently there could be no error when the object gets a timezone. The timezone is simply discarded and only when microseconds comes to 0 there is a ValueError. I don't think the user code would prepare to catch the ValueError. I don't oppose make timestamp more strict but just not sure if it's suitable. |
timestamptz-2.patch make timestamp and timestamptz specilized for their own objects. |
I think ProgrammingError is not correct type of an exception. It is used for programming errors such as using uninitialized or closed objects or supplying incorrect number of bindings. But this error can be caused by invalid data in a database created not by Python. Currently converters raise ValueError or TypeError for invalid data. ValueError is raised for datetime without microseconds but with timezone offset. I think ValueError is more appropriate type for such errors. Perhaps even TypeError should be converted to ValueError.
|
But this is different issue of course. |
I am okay with ValueError(actually I use it in the patch originally) but I am not in favour of catching the errors of the parser. The parser raises errors due to invalid data, not timezone. I think propagate it to users could make the reason more obvious. |
timestamptz-3.patch uses ValueError instead of ProgrammingError and treat suffix Z as UTC timezone. |
Ping for review for timestamptz-3.patch. |
Added few minor comments on Rietveld. Technically the patch LGTM. But the behavior change of the timestamp converter for input without timezone offset perhaps needs wider discussion. We have several options:
Any option can break some code. I prefer option 3, but want to hear thoughts of other core developers. Maybe discuss this on Python-Dev? |
It's fine. Could you compose a mail instead of me? I am not good at that. :-( |
I'm even worse at that. :-( |
LoL. Okay I'll do that. :-) |
This still isn't fixed as of 3.8 (or in master I think). I can understand why you wouldn't want to allow serializing and deserializing time zones, since tzinfo objects cannot be accurately serialized with a simple UTC offset, but you should at least get an error when trying to insert an aware object. Anything is better than it is now, where you get no warning or error when inserting the object, and get a hard to interpret error ("invalid literal for int() with base 10") when trying to retrieve it. For deserialization, the datetime class now (since 3.7) includes a fromisoformat() method that could be used as a counterpart to the isoformat() method used when serializing. At least it would be consistent then. |
Xiang Zhang, was there a discussion on Python-Dev? |
As per discussion on Discourse, we will deprecate the built-in, default adapters and converters. Thus, this issue is now superseded by gh-90016. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: