naritoブログ

このブログはDjangoとBootstrap4で作成されました
ソースコード

Djangoで、タイムゾーンの変換

プログラミング関連 Django pytz Python 約69日前
2017年3月15日10:58
Python、タイムゾーンの変換にpytzを使う
https://torina.top/main/338/
で、pytzを使った日付の処理を書きました。

Djangoではフレームワークの機能として、タイムゾーンの変換機能がありますので、実際に試してみましょう。

Django1.10
Pythn3.6
プロジェクト名は「tztest」
アプリケーションは一つで「app」

pytzを、インストールしておきます。
pip install pytz


tztest/tztest/settings.py
変更のある場所だけ抜き出しています。
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app',  # 追加
]
...
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'app.middleware.TimezoneMiddleware',  # 追加!
]
...

# とりあえず日本にする
TIME_ZONE = 'Asia/Tokyo'

# ここをFalseとかにはしないように!
USE_TZ = True


INSTALLED_APPSにアプリケーションを、MIDDLEWAREにもこれから作るものを足します。
USE_TZ = Trueになっていないとタイムゾーン関連の機能が止まってしまいます。
Webサイトがただ一つのタイムゾーン(日本のみ)ならFalseでも別に大丈夫ですが、Trueにしてそこまで複雑になる訳でもありません。
TIME_ZONE = 'Asia/Tokyo'は、デフォルトのタイムゾーンになります。

tztest/tztest/urls.py
一覧、更新、新規、タイムゾーン設定ページがあります。
from django.conf.urls import url
from django.contrib import admin
from app import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', views.PostList.as_view(), name='post_list'),
    url(r'^update/(?P<pk>[0-9]+)/$', views.PostUpdate.as_view(), name='post_update'),
    url(r'^create/$', views.PostCreate.as_view(), name='post_create'),
    url(r'^set_timezone/$', views.set_timezone, name='set_timezone'),
]



Djangoでは、テンプレー トとフォームでエンドユーザのタイムゾーンに変換します。
しかし、エンドユーザのタイムゾーンをこちらから取得するのは難しいです。
多言語化とは違い、Accept-LanguageのようなHTTPヘッダがある訳でもありません。
なので、どのタイムゾーンかはユーザに選択してもらい、それをsession等に保管しておきます。

その保管されたタイムゾーンを使い、アプリケーションのタイムゾーンを変更しているのが下記のミドルウェアです。
tztest/app/middleware.py
import pytz

from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin


class TimezoneMiddleware(MiddlewareMixin):
    def process_request(self, request):
        tzname = request.session.get('django_timezone')
        if tzname:
            timezone.activate(pytz.timezone(tzname))
        else:
            timezone.deactivate()


これがセッションからタイムゾーン情報を引き出しています。
tzname = request.session.get('django_timezone')


このactivateで、カレントタイムゾーンを変更しています。
        if tzname:
            timezone.activate(pytz.timezone(tzname))
        else:
            timezone.deactivate()



もう少し内部の話をすると、このactivateでは大まかに、
django.utils.timezone._activeのvalue属性に、もらったタイムゾーンを設定しています。
deactivateは、このvalueを削除するだけです。
Djangoでカレントタイムゾーンを参照する必要があるとき(テンプレー トやフォーム)に、このvalueを参照しているのです。
ユーザがまだタイムゾーンを選択していなかったり、何らかの理由でdeactivateで削除していれば、settings.TIME_ZONEに指定したタイムゾーンが使用されます。
つまり、以下のような処理です。
# _activeのvalue属性を取得、value属性がなければpytz.timezone(settings.TIME_ZONE)
current_timezone = getattr(_active, "value", pytz.timezone(settings.TIME_ZONE))



tztest/app/models.py
タイトルと作成日だけの、シンプルなモデルです。
from django.db import models
from django.utils import timezone


class Post(models.Model):

    title = models.CharField('タイトル', max_length=255)
    created_at = models.DateTimeField('作成日', default=timezone.now)

    def __str__(self):
        return self.title


もはやdatetime.now()を私たちが呼び出す必要はないかもしれません。
timezone.nowは、USE_TZがFalseならば内部でdatetime.now()を呼び出します。
もちろん、USE_TZがTrueならばちゃんと現在日付を取得します。
created_at = models.DateTimeField('作成日', default=timezone.now)


ちなみにですが、USE_TZ=Trueの現在日付は以下のようにして取得されています。UTCですね。
UTCを基本的に使い、必要な場所でだけローカル時間を使います。
datetime.utcnow().replace(tzinfo=utc)



tztest/app/admin.py
from django.contrib import admin
from .models import Post
admin.site.register(Post)


tztest/app/views.py
from django.utils import timezone
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import redirect, render
from django.views import generic
from .models import Post
import pytz


class PostList(generic.ListView):
    model = Post


class PostCreate(generic.CreateView):
    model = Post
    fields = '__all__'
    success_url = reverse_lazy('post_list')


class PostUpdate(generic.UpdateView):
    model = Post
    fields = '__all__'
    success_url = reverse_lazy('post_list')


def set_timezone(request):
    if request.method == 'POST':
        request.session['django_timezone'] = request.POST['timezone']
        return redirect('post_list')
    else:
        return render(request, 'app/timezone.html', {'timezones': pytz.common_timezones})


ListViewとCreateView、UpdateViewの3つは何もいう事はないでしょう。
class PostList(generic.ListView):
    model = Post


class PostCreate(generic.CreateView):
    model = Post
    fields = '__all__'
    success_url = reverse_lazy('post_list')


class PostUpdate(generic.UpdateView):
    model = Post
    fields = '__all__'
    success_url = reverse_lazy('post_list')



ユーザが選択したタイムゾーンを、セッションに保存するビューです。
pytz.common_timezonesは、タイムゾーンの一覧のリストです。
def set_timezone(request):
    if request.method == 'POST':
        request.session['django_timezone'] = request.POST['timezone']
        return redirect('post_list')
    else:
        return render(request, 'app/timezone.html', {'timezones': pytz.common_timezones})



tztest/app/templates/app/base.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
  </head>
  <body>
      {% block content %}{% endblock %}
  </body>
</html>


tztest/app/templates/app/post_list.html
一覧です。
{% extends 'app/base.html' %}
{% block content %}
<a href="{% url 'post_create' %}">新規作成</a>
<a href="{% url 'set_timezone' %}">タイムゾーンの変更</a>
<hr>
  {% for post in post_list %}
    {{ post.title }},
    {{ post.created_at }},
    <a href="{% url 'post_update' post.pk %}">更新</a>
    <hr>
  {% endfor %}
{% endblock %}


tztest/app/templates/app/post_form.html
作成・更新です。
{% extends 'app/base.html' %}
{% block content %}
  {{ post.created_at }}
  <form action="" method="POST">
    {{ form }}
    {% csrf_token %}
    <input type="submit">
  </form>
{% endblock %}


tztest/app/templates/app/timzone.html
タイムゾーンの選択用ページです。
テンプレートで使えるタグやフィルタがいくつかありますので、色々見てみるとよいでしょう。
https://docs.djangoproject.com/en/1.10/topics/i18n/timezones/
{% extends 'app/base.html' %}
{% load tz %}
{% block content %}
  {% get_current_timezone as TIME_ZONE %}
  <form action="{% url 'set_timezone' %}" method="POST">
      {% csrf_token %}
      <label for="timezone">Time zone:</label>
      <select name="timezone">
          {% for tz in timezones %}
          <option value="{{ tz }}"{% if tz == TIME_ZONE %} selected="selected"{% endif %}>{{ tz }}</option>
          {% endfor %}
      </select>
      <input type="submit" value="Set" />
  </form>
{% endblock %}


まず、管理画面から見ていきます。現在日本時間の10:42分で、ちゃんと一致しています。


トップ画面にきました。ちゃんと日付が日本時間です。


更新するフォームや、その上の表示もちゃんと一致していますね。



それでは、タイムゾーンを変更してみましょう。US/Alaskaにしてみます。


管理画面で作成しようとしてみると、なにやら17:45分になっています。
ノート: あなたの環境はサーバー時間より、17時間進んでいます。 とか表示されてますね。
これはちゃんと、アラスカの時刻になっています。


他ページや、データを作成してみてもちゃんとアラスカ時刻で表示されています。




このように、タイムゾーンを変更すると、各データの日付がちゃんと変換されました。
以下にタイムゾーン関連のコードがあります。pytzの使い方など、色々と参考になるのではないでしょうか。
https://github.com/django/django/blob/master/django/utils/timezone.py