DjangoBrothers BLOG ✍️

2019/07/25

このエントリーをはてなブックマークに追加
Python Django datetime

【Django】タイムゾーンの扱い方

Pythonのdatetimeモジュール

Pythonのdatetimeオブジェクトには2種類のオブジェクトがあります。

  • awareオブジェクト: タイムゾーンの情報を持っている
  • naiveオブジェクト : タイムゾーンの情報を持っておらず、協定世界時刻(UTC)と現地時間を区別しない

つまり、naiveオブジェクトは単純に時刻そのものを扱うためのオブジェクトと言えます。

そのため、Pythonのプログラムでdatetimeオブジェクトを扱う際には、そのオブジェクトのタイムゾーンに気をつける必要があります。

Djangoでのdatetimeの取り扱い

Djangoでdatetimeを利用する際には、プロジェクト全体のタイムゾーンを設定することで、現地時間に合わせた時刻の利用をすることができます。

Djangoでタイムゾーンを指定するにはsettings.pyで以下のように指定します。こちらは日本標準時に合わせる設定です。

settings.py

# 東京のタイムゾーンに設定
TIME_ZONE = 'Asia/Tokyo'

# タイムゾーンを使用するかどうか
USE_TZ = True

settings.pyでUSE_TZ = Trueとなっていれば、プロジェクト内で使われるdatetimeオブジェクトはawareオブジェクトになっており、必要なタイミングで適切なタイムゾーンへの変換が行われるようになります。

例えば、データベースへのデータの保存時や、テンプレートへの日付の表示の際に、設定したタイムゾーンの時刻に変換してくれます。

以下が東京の現在時刻をテンプレート表示するサンプルです。

views.py

from django.shortcuts import render
from django.utils import timezone

def index(request):
    now = timezone.now()
    context = { 'now': now }
    return render(request, 'app/index.html', context)

app/index.html

<!-- 指定したタイムゾーンで時刻表示される -->
<!-- 2019年7月25日19:45 -->
<p>{{ now }}</p>

datetimeオブジェクトはコード内ではまだローカライズされていない

Djangoのdjango.utils.timezoneを利用する際の注意点として、コード内でdatetimeを利用する時にはまだローカライズされていないという点が挙げられます。

少し分かりづらいですが、例えば、テンプレートで表示されるまではUTCを基準にしたawareなdatetimeオブジェクトを扱っており、それが表示されるタイミングで適切なローカルタイムに変換されているということです。

先ほどのコードをつかって実験してみます。 以下のようにnow()で取得した値を、テンプレートに表示する前にprintで表示してみます。

views.py

from django.shortcuts import render
from django.utils import timezone

def index(request):
    now = timezone.now()
    print(now)  # コード内でnowをprintしてみる
    context = { 'now': now }
    return render(request, 'app/index.html', context)

app/index.html

<!-- ローカライズされた時刻表示される -->
<!-- 2019年7月25日19:45 -->
<p>{{ now }}</p>

console

# UTCの時間が表示される(9時間前)
>>> 2019-07-25 10:45:08.329681+00:00

テンプレートではきちんとローカライズされた時刻が使用されているのに対して、Djangoプロジェクトのコード内(views.py)ではUTCになっているのがわかります。

これはDjangoが柔軟にdatetimeをローカライズできるような設計になっているためですが、このことを知らないと思わぬ不具合が出てしまうこともあります。

例えば、ブログの予約投稿機能があるとします。

日本時間で7月1日午前0時に記事が公開される設定をしているのに、コード内で公開設定日時とtimezone.now()の時刻を比較していたら、9時間分のずれが生じてしまい、7月1日の午前9時になるまでブログ記事は公開されなくなります。

これを防ぐためには、コード内でもローカライズされた時刻を利用することができれば良いです。

django.utils.timezone.localtimeでコード内で時刻をローカライズ

django.utils.timezone.localtimeを使うことで、コード内でもローカライズされた時刻に変換することができるようになります。

先ほどのサンプルを修正してみましょう。

views.py

from django.shortcuts import render
from django.utils import timezone
from django.utils.timezone import localtime # 追加

def index(request):
    now = localtime(timezone.now())
    print(now)  # コード内でnowをprintしてみる
    context = { 'now': now }
    return render(request, 'app/index.html', context)

app/index.html

<!-- ローカライズされた時刻表示される -->
<!-- 2019年7月25日19:45 -->
<p>{{ now }}</p>

console

# コード内でも日本時間で表示される
>>> 2019-07-25 19:45:08.329681+00:00

今度は、コード内の時刻表示もローカライズされた時刻になっています。

参考