EMP
EMP

Reputation: 61971

How to read datetime back from sqlite as a datetime instead of string in Python?

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?

Upvotes: 93

Views: 65920

Answers (4)

pieca
pieca

Reputation: 2563

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())

Upvotes: 1

Richard Li
Richard Li

Reputation: 528

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.)

Upvotes: 1

EMP
EMP

Reputation: 61971

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.

Upvotes: 31

Alex Martelli
Alex Martelli

Reputation: 881555

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.

Upvotes: 127

Related Questions