naritoブログ

【お知らせ】
新ブログができました。今後そちらで更新し、このサイトは更新されません(ウェブサイト自体は残しておきます)
このブログの内容に関してコメントしたい場合は、新ブログのフリースペースに書き込んでください

このブログの内容を新ブログに移行中です。このブログで見つからない記事は、新ブログにありま

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

約609日前 2017年3月15日10:58
プログラミング関連
Django pytz Python
Python、タイムゾーンの変換にpytzを使う

https://torina.top/detail/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