DjangoのORMで日付データを扱っていたら錯乱した話

(当たり前のようにDjangoの話ですが)仕事で日付データを扱った実装をしていた時のこと、混乱状態に陥ったので次に備えて自分の中で何が起きていたかを振り返ります。仕事の内容は書けないので、今回は架空のカレンダーサービスを例に話をすすめます。hogehoge calendar。

カレンダーのサービスに必要不可欠な要素といえば色々ありますが、その中の1つに「予定」がありますね。「どこ」で、「だれ」と、「いつ」会うかといった情報が設定できるべきです。その中の「いつ」は、「予定の開始日時」と「予定の終了日時」を設定できるようにすべきでしょう。したがってDjangoで以下のようなフィールドをhogehoge calendarの Event モデルに実装します。

start_period = models.DateTimeField("予定開始日時", null=True)
end_period = models.DateTimeField("予定終了日時", null=True)

これで予定開始日時と予定終了日時を設定できます。やったね!偉大な一歩!

さて、もしとある予定の予定開始時刻を過ぎた場合、予定終了時刻までカレンダー上でその予定をイカした色に変色させて「この予定がアクティブです」と知らせたいとします。とすると、「現在時刻が予定期間内であるかどうか」というのを確認しなくちゃいけませんよね。僕が混乱したのこのポイントでした。一見、なんのこっちゃない風に見えますがね(´・ω・`)

以下のような設定で話をすすめます。ようはアクティブな予定があって、start_periodend_period はその予定の予定開始時刻と予定終了時刻のdatetimeのデータが入っています。

# 現在時刻 2019/01/13 00:00:00
now = datetime(2019, 1, 13, 12, 00, 00, 000000)

# 予定開始時刻 2019/01/12 00:00:00
start_period = datetime(2019, 1, 12, 12, 00, 00, 000000)

# 予定終了時刻 2019/01/14 00:00:00
end_period = datetime(2019, 1, 14, 12, 00, 00, 000000)

普通にPythonのif文を使って予定がアクティブかどうか確かめるなら、愚直にこういう風に書けます。

if start_period <= now and now <= end_period:
   print('この予定はアクティブです')

うっひょ〜簡単。でも、DBにEventデータがあるので、ORMを使って書いたほうが楽そう。じゃあ、書きましょう(`・ω・´)

「えっと……start_periodに対してlteにして、end_periodに対してはgteを設定……っと(カタカタ」

qs = Event.objects.all()
qs = qs.filter(start_period__gte=now, end_period__lte=now)

「実行結果は……」

<QuerySet []>

「よしよし。ちゃんと……0件……だと……」

「あれ、Djangoも間違えることってあるんだ。もう一回チャンスあげたろ(再試行」

<QuerySet []>

「あれ……あかん……orz」

if start_period <= now and now <= end_period:

「上のコードをそのままORMに置き換えたつもりだったのに、なんでだろう(´・ω・`)」と考える。そのあと、ORMでの絞り込みついて考える。「あれ?gtelteが逆なのかな。でも、それだとstart_period >= today and now >= todayになるんじゃないかな。それって期間外の予定。というか、そんな予定存在しないんじゃないか」となる。だんだんと混乱してきて、最終的に「時間ってなんだろう……」となる。

kaiはめのまえがまっくらになった。

いま考えても混乱しかけた。あぶないあぶない。結論から言うと、実際はgtelteが逆でした。

qs.filter(start_period__lte=now, end_period__gte=now)

どうして混乱していたのか

Pythonで普通に書くときとは頭の中で描かれる図が違うので、混乱してました。感覚で申し訳ないですが、以下の感じが僕の頭の中の2つの違いです。

Python

そのままのPythonは、期間の中にnowが包まれてる感じ。

if start_period <= now and now <= end_period:

上の様に書くけど、頭のなかだとstart_period <= now <= end_period という式が成り立ってる。

f:id:kai1233:20190113230608p:plain
Pythonの時の考え方

Django

Djangoの場合は、now という一点があってそこを中心に考えているイメージ。「nowから見て、start_periodend_periodはどこにあるの?」といった感じ。

qs.filter(start_period__lte=today, end_period__gte=today)

f:id:kai1233:20190113225212p:plain
DjangoのORMの時の考え方

こういう風に僕の頭の中で、実際は違うもので認識されてます。なのになかなかPythonの方の考え方から抜けられず、混乱していました。頭の中で思い浮かべる理解と実際の動作の違いに苦しみました。こういう時は頭の中の図を書き出してみるのが良さそうですね。実際の処理内容を図解してみるのも理解に繋がりました。

こういうのって、わかっていても一度ハマったらなかなか抜け出せないんですよね。一度見方を与えられちゃったらそこから抜け出せない目の錯覚。みたいな。直感から逃れられない。これからもこういうことが沢山あるでしょうし、自分の理解を疑って掛かることを知ってることが大切なことかもしれません。良い経験になりました。