Python の sqlite3 で DATETIME 型を日付としてフェッチ

## 概要

Python 標準ライブラリの `sqlite3` モジュールでは、SQL の `DATE` 型及び `TIMESTAMP` 型を DB から Python へフェッチする時、自動的に Python 標準ライブラリの `datetime.date` 及び `datetime.datetime` 型へ変換してくれます。しかし、SQL の `DATETIME` 型は Python の `str` 型としてフェッチされます。

SQL の `DATETIME` 型も `TIMESTAMP` 型と同様に Python 標準ライブラリの `datetime.datetime` 型としてフェッチする方法を示します。

### 対象読者

SQLite3 の `DATETIME` 型のカラムから Python へ文字列ではなく、日付としてシームレスにデータをフェッチしたい方を主な対象読者とします。

## Adapter と Converter

Python 標準ライブラリの `sqlite3` モジュールのディフォルトの動作として SQL の `DATE` 型及び `TIMESTAMP` 型が Python 標準ライブラリの `datetime.date` 及び `datetime.datetime` 型へ変換されるのは、魔法ではなく、Adapter と Converter という機構を利用して実現されています。

Python の日本語訳ライブラリーリファレンスでは、Adapter を適合関数、Converter を変換関数として説明しています。

https://docs.python.org/ja/3/library/sqlite3.html#default-adapters-and-converters

Adapter は、任意の Python の型を　SQL として SQLite3 へ送出する際に変換する関数です。Converter は、SQLite3 からフェッチしたデータを任意の Python の型へ変換する関数です。

ディフォルトで登録されている Adapter と Converter のリストは、次のように確認できます。尚、一部の出力は、読みやすいように加工しています。

```python
>>> import sqlite3
>>> sqlite3.adapters
{
  (<class datetime.date>, <class sqlite3.PrepareProtocol>): <function register_adapters_and_converters.<locals>.adapt_date at 0x104ef3a30>,
  (<class datetime.datetime>, <class sqlite3.PrepareProtocol>): <function register_adapters_and_converters.<locals>.adapt_datetime at 0x104ef36d0>
}
>>> sqlite3.converters
{
  'DATE': <function register_adapters_and_converters.<locals>.convert_date at 0x104f18af0>,
  'TIMESTAMP': <function register_adapters_and_converters.<locals>.convert_timestamp at 0x104f18b80>
}
```

Adapter には Python 標準ライブラリの `datetime.date` 及び `datetime.datetime` 型を変換する関数が登録されていること、Converter には SQL の `DATE` 型及び `TIMESTAMP` 型を変換する関数が登録されていることが、分かると思います。

これは、Python 標準ライブラリの `sqlite3.dbapi2` モジュールで次のように登録されています。

```python
def register_adapters_and_converters():
    def adapt_date(val):
        return val.isoformat()

    def adapt_datetime(val):
        return val.isoformat(" ")

    def convert_date(val):
        return datetime.date(*map(int, val.split(b"-")))

    def convert_timestamp(val):
        datepart, timepart = val.split(b" ")
        year, month, day = map(int, datepart.split(b"-"))
        timepart_full = timepart.split(b".")
        hours, minutes, seconds = map(int, timepart_full[0].split(b":"))
        if len(timepart_full) == 2:
            microseconds = int('{:0<6.6}'.format(timepart_full[1].decode()))
        else:
            microseconds = 0

        val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds)
        return val


    register_adapter(datetime.date, adapt_date)
    register_adapter(datetime.datetime, adapt_datetime)
    register_converter("date", convert_date)
    register_converter("timestamp", convert_timestamp)
```

開発者がカスタムの Adapter と Converter を作成する方法は、Python のライブラリーリファレンスで紹介されています。

https://docs.python.org/ja/3/library/sqlite3.html#how-to-adapt-custom-python-types-to-sqlite-values

Adapter は、SQL の `INSERT` や `UPDATE` 文などを構築する時に使用されます。Python の `datetime.datetime` 型は、前述の `adapt_datetime(val)` ファンクションによって、日付と時間の区切りが "T" ではなく " (空白文字)" の ISO 8601 フォーマットに変換されることが分かります。この変換は、`INSERT` や `UPDATE`　先のカラムの型は問いません。

Converter は、SQL の `SELECT`　文などの実行結果をフェッチする時に使用されます。SQL の `TIMESTAMP` 型は、前述の `convert_timestamp(val)` ファンクションによって、Python の `datetime.datetime` 型へ変換されることが分かります。この変換は、`register_converter` の第一引数で指定したカラムの型に適用されます。

したがって、SQL の `DATETIME` 型を `TIMESTAMP` 型と同様に Python 標準ライブラリの `datetime.datetime` 型としてフェッチするには、カスタムの　Converter を使用すればよいと分かります。

## `DATETIME` 型用の Converter の実装例

SQL の `DATETIME` 型用のカスタムの Converter ですが、`TIMESTAMP` 型と同様の振る舞いでよければ、改めてファンクションを作成する必要はありません。

`sqlite3.connect` を実行する前に `sqlite3.register_converter` で、`TIMESTAMP` 型の Converter を `DATETIME` 型へ適用すれば、実現することができます。

```python
import sqlite3

def get_db():
  sqlite3.register_converter('DATETIME', sqlite3.converters['TIMESTAMP'])
  db = sqlite3.connect(...)
  return db
```

SQLite3 を業務システムで使用することは少ないと思いますが、参考にしてみてください。