Djangoのtimezone.nowを使っていてモヤモヤした。特に、「なぜ、UTCの時刻を返すのか」と思ったので、調べてみた。 その記録。
背景
Djangoにはtimezoneのutilsがあり、nowという関数を持っている。 これを実行すると次のようになる。
>>> from django.utils import timezone >>> timezone.now() datetime.datetime(2024, 3, 27, 20, 49, 31, 114299, tzinfo=datetime.timezone.utc)
また、Djangoプロジェクトにはsettingsでタイムゾーンが設定できる。 例えば次のような感じ。
TIME_ZONE = "Asia/Tokyo"
しかし、タイムゾーンを設定していても timezone.now()
はUTC時刻を返す。
設定したタイムゾーンでの時刻にする場合は、 timezone.localtime()
を使えることを知っていた。
なので、次に私はこんな風に書いてみた。
>>> timezone.localtime(timezone.now()) datetime.datetime(2024, 3, 28, 5, 55, 30, 810238, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
めんどくさい。というか、私は何度も timezone.now()
で設定したタイムゾーンの時刻が返ってくると勘違いしている。
「あれ?なんでUTCの時刻返ってきてるんだ?あ、そうか。localtimeという概念あった。」
みたいなことを毎回考えている。モヤモヤするので、もうちょっとこの辺の経緯に関する情報を調べてみる。
ドキュメントを読む
とりあえず、Djangoのtimezone.nowに関するドキュメントを読んでみよう。 5.0でのドキュメントである。
Django ユーティリティ (django.utils) | Django ドキュメント | Django
次の記載がある。
USE_TZ が False の場合、システムのローカルタイムゾーンで現在の時刻を表す、関連するタイムゾーンのない naive な日時(タイムゾーンの関連がない日時)になります。
翻訳前
If USE_TZ is True, this will be an aware datetime representing the current time in UTC. Note that now() will always return times in UTC regardless of the value of TIME_ZONE; you can use localtime() to get the time in the current time zone.
USE_TZという概念が関係ありそうだ。
そもそも、 timezone.localtime()
のデフォルト
また、ドキュメントを読んでいる時点で
timezone.localtime()
はデフォルトでnowを返すことに気づいた。ドキュメントにそう書いてある。
value が省略された場合, デフォルトは now() になります。
Django ユーティリティ (django.utils) | Django ドキュメント | Django
つまり、タイムゾーンを意識した現在時刻は、これで取得すれば良い。
>>> timezone.localtime() datetime.datetime(2024, 3, 28, 6, 8, 8, 700251, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
具体的なコードも見てみたが、本当にそのままnowが実行されている。
django/django/utils/timezone.py at 617bcf611f3daa796e4054ba041089ece30a32fc · django/django · GitHub
というわけで、私は timezone.now()
ではなく timezone.localtime()
をもっと意識して使うと良さそうだ。
USE_TZについて
ここまで調べてみて、最初のめんどくささはある程度低減された。 ただ、せっかくなので、タイトルの問をもう少し深掘りしたい。
timezone.now()
のドキュメントで USE_TZ
の話が出てくるので、この設定値が関係してそうだ。
その辺をもう少し調べてみる。
USE_TZ
デフォルトで日時がタイムゾーンを意識するかどうかを指定する真偽値です。これが True に設定されている場合、Django は内部的にタイムゾーンを意識した日時を使用します。
Settings | Django documentation | Django
この記載を見て思うこととして、そもそも、この設定は必要なのか?(わざわざ、タイムゾーンを意識しない、という設定をすることがあり得るのか?)と思った。というのも、私は、Djangoを使い始めてから USE_TZ
の値を変更したことがない。「デフォルト値から変更しない設定値」という印象で使ってきた。
なので、この設定が導入された経緯を追いかけてみよう。
UZE_TZはいつ導入されたのか
ドキュメントに USE_TZ
の記載があるので、バージョンいくつから導入されているのかを確認してみよう
Djangoのドキュメントで閲覧できる最も古いバージョンは、1.8
である。そのドキュメントを見てみると、すでにUSE_TZがある。
Settings | Django documentation | Django
そこで次のことがわかった
- UZE_TZが指定されていない場合、 Falseで扱われる(つまり、タイムゾーンを意識せずに扱う)
django-admin startproject
でプロジェクトを作るとUSE_TZ = True
が追加される。
というわけで、 1.8
以前に導入されていたことが見えるし、上記の記載からなんとなく以下の状況が推測できた。
- 最初は、USE_TZがなかった。そして、タイムゾーンを意識せずに扱っていた。
- データベースに日時を入れたり、結局のところ、タイムゾーンを意識する必要性が高まってきた。というか常にタイムゾーンを意識した方が望ましい状況になってきた。
- 後方互換性を意識して、新規プロジェクトではデフォルトでタイムゾーンを意識させるように調整が入った。
これは事実かどうかの裏どりはしていない。が、面倒になってきたので、私の調査はここで終えるとする。
timezone.nowがUTCを返す理由もこの辺の兼ね合いなんだろうなあ、と想像した。
USE_TZの今後
少し余談だが、Django 5.0のドキュメントに USE_TZ
のデフォルト値はTrueで、古いバージョンではFalseにだった。との情報があった。
Changed in Django 5.0: In older versions, the default value is False.
Settings | Django documentation | Django
これも推測の裏どりをしていないが、やはりそもそもの話として、 USE_TZ
という設定値必要か?という話があるんだろうな、と想像した。
デフォルト値がTrueになったことで、 デフォルトの settings.pyから USE_TZ
は姿を消すことになるような気がする。
そうすると、この記事に書いたような疑問を解消するのは、少しハードルが上がるようにも思う。 だから、どうという話ではないですが。
終わり
今回は、ふと思ったことを調べてみました。
ではでは〜