93

I'm using the sqlite3 module in Python 2.6.4 to store a datetime in a SQLite database. Inserting it is very easy, because sqlite automatically converts the date to a string. The problem is, when reading it it comes back as a string, but I need to reconstruct the original datetime object. How do I do this?

4 Answers 4

127

If you declare your column with a type of timestamp, you're in clover:

>>> db = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES)
>>> c = db.cursor()
>>> c.execute('create table foo (bar integer, baz timestamp)')
<sqlite3.Cursor object at 0x40fc50>
>>> c.execute('insert into foo values(?, ?)', (23, datetime.datetime.now()))
<sqlite3.Cursor object at 0x40fc50>
>>> c.execute('select * from foo')
<sqlite3.Cursor object at 0x40fc50>
>>> c.fetchall()
[(23, datetime.datetime(2009, 12, 1, 19, 31, 1, 40113))]

See? both int (for a column declared integer) and datetime (for a column declared timestamp) survive the round-trip with the type intact.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks, Alex, it works! I'm surprised by this, because there is no mention of the TIMESTAMP type at all on sqlite.org/datatype3.html
@Evgeny, see docs.python.org/library/… -- it's not documented at sqlite.org because it's actually implemented in the Python layer, not in sqlite itself!
I just find out it is the detect_types=sqlite3.PARSE_DECLTYPES parameter of the connect function that makes fetchall returns a datetime. If you omit it you will have a unicode object.
Seems like this fails for timezone aware values like u'2012-01-01 18:35:24.617954-02:00'
What is the rationale for TIMESTAMP working but not DATETIME?
|
31

It turns out that sqlite3 can do this and it's even documented, kind of - but it's pretty easy to miss or misunderstand.

What I had to do is:

  • Pass the sqlite3.PARSE_COLNAMES option in the .connect() call, eg.
conn = sqlite3.connect(dbFilePath, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
  • Put the type I wanted into the query - and for datetime, it's not actually "datetime", but "timestamp":

    sql = 'SELECT jobid, startedTime as "[timestamp]" FROM job'
    
    cursor = conn.cursor()
    try:
        cursor.execute(sql)
        return cursor.fetchall()
    finally:
        cursor.close()
    

If I pass in "datetime" instead it's silently ignored and I still get a string back. Same if I omit the quotes.

5 Comments

note that it's definitely "[timestamp]", [timestamp] is silently ignored, and .fetchone also won't convert the types. What an odd library.
It seemed what really count is the double quote characters, neither the alias you explicitly specified nor the as keyword
I have now filed bugs.python.org/issue35145 'sqlite3: "select *" should autoconvert datetime fields'.
^bug issue has been migrated here: github.com/python/cpython/issues/79326
do note here that in 3.12 github.com/python/cpython/issues/90016 that datetime converters are deprecated
2

Proper way to do it as of Python 3.12+ is to register a converter, see docs. Example:

from contextlib import closing
from datetime import datetime, UTC
import sqlite3

# https://docs.python.org/3/library/sqlite3.html#sqlite3-adapter-converter-recipes
def adapt_datetime_iso(val):
    return val.isoformat()

def convert_datetime(val):
    return datetime.fromisoformat(val.decode())

sqlite3.register_adapter(datetime, adapt_datetime_iso)
sqlite3.register_converter("datetime", convert_datetime)

with (
    sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) as conn,
    closing(conn.cursor()) as cursor
):
    cursor.execute("create table if not exists test (date datetime);")
    cursor.execute("insert into test values (:date);", {"date": datetime.now(tz=UTC)})
    cursor.execute("select * from test;")
    print(cursor.fetchall())

Comments

1

Note: In Python3, I had to change the SQL to something like:

SELECT jobid, startedTime as "st [timestamp]" FROM job

(I had to explicitly name the column.)

1 Comment

As @AdamBaxter said, what really count is the double quote characters, neither the alias you explicitly specified nor the as keyword. e.g., this works fine: SELECT jobid, startedTime "[timestamp]" FROM job

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.