タイムゾーン

概要

タイムゾーンのサポートが有効のとき、Django は内部的に aware なタイムゾーンオブジェクトを使用して、データベース内に UTC で日時の情報を保持します。そして、テンプレートやフォーム内でエンドユーザのタイムゾーンに変換します。

この機能は、ユーザが複数のタイムゾーンを使用しており、またユーザに対して彼らの時計と同じ日時を表示したいときに有用です。

たとえウェブサイトが 1 つのタイムゾーンでしか使われないとしても、それでもデータベース内に UTC でデータを保持するのは良い実装方法です。主な理由は夏時間 (DST) です。多くの国では DST の制度を採用しており、時計は春に進んで秋に戻ります。もし現地時刻で運用していたら、この変換が起きる年に 2 回、エラーに遭遇することになるでしょう。(pytz ドキュメントが この問題 を大変詳しく論じています。) これはブログにとっては重要な問題ではありませんが、年に 2 回請求金額が 1 時間分多くなったり少なくなったりするのは問題です。この問題の解決策は、コード内では UTC を使い、エンドユーザとやり取りするときだけ現地時刻を使用することです。

Time zone support is disabled by default. To enable it, set USE_TZ = True in your settings file. Time zone support uses pytz, which is installed when you install Django.

Changed in Django 1.11:

Older versions don't require pytz or install it automatically.

注釈

django-admin startproject によって生成されるデフォルトの settings.py ファイル は、利便性のために USE_TZ = True を含んでいます。

注釈

ほかにも、独立はしていますが、関連する USE_L10N 設定もあります。これは、Django が表示形式のローカル化を有効にするかどうかをコントロールします。詳しくは 表示形式のローカル化 を参照してください。

特定の問題と格闘している場合は、time zone FAQ を参照してください。

コンセプト

native と aware の日時オブジェクト

Python の datetime.datetime のオブジェクトには、タイムゾーン情報を保持するために使える tzinfo 属性があり、これは datetime.tzinfo のサブクラスのインスタンスで表されます。 この属性がセットされオフセットを示すとき、datetime オブジェクトは aware となります。それ以外の場合は naive となります。

is_aware() is_naive() を使って、datatime を aware にするか native にするかを決めることもできます。

タイムゾーンサポートが無効化されているときは、Django は現地時刻で native な datetime オブジェクトを使用します。これは、多くのケースでシンプルかつ十分です。このモードでは、現在時刻を取得するには以下のように記述します:

import datetime

now = datetime.datetime.now()

タイムゾーンサポートが有効化 (USE_TZ=True ) されているときは、Django は タイムゾーンを認識する (awareな) datetime オブジェクトを使用します。コード内で datetime オブジェクトを作成した場合も、aware となります。このモードでは、上の例は以下のようになります:

from django.utils import timezone

now = timezone.now()

警告

aware な datetime オブジェクトを扱うのは、直感的ではにことがあります。例えば、標準の datetime コンストラクタの tzinfo 引数は、DST を伴うタイムゾーンでは正しく動作しません。UTC を使えば常に安全です; 他のタイムゾーンを使用している場合は、pytz ドキュメントを熟読してください。

注釈

Python の datetime.time オブジェクトは、tzinfo 属性を持ちます。また PostgreSQL には、これに相当する time with time zone タイプがあります。ただし、PostgreSQL のドキュメントにあるとおり、このタイプは "不確かな有用性に導く特性を示しています"。

Django は naive な time オブジェクトのみをサポートしており、aware な time オブジェクトを保存しようとすると例外を投げます。これは、日付を伴わない時刻に対するタイムゾーンには意味がないからです。

native な datetime オブジェクトの変換

後方互換性を維持するため、setting:USE_TZTrue のときでも、Django は naive な datetime オブジェクトを受け入れます。データベースレイヤがこれを受け取ったとき、default time zone で変換して aware に直そうとし、警告を投げます。

Unfortunately, during DST transitions, some datetimes don't exist or are ambiguous. In such situations, pytz raises an exception. That's why you should always create aware datetime objects when time zone support is enabled.

実際には、これはめったに問題になりません。Django はモデルやーフォームでは aware な datetime オブジェクトを渡し、ほとんどの場合、新しい datetime オブジェクトは timedelta 計算を通じて他の日時から作成されます。唯一アプリケーションのコード内でよく作成される datetime は現在の時刻で、timezone.now() は自動的に適切な処理を行います。

デフォルトタイムゾーンとカレントタイムゾーン

デフォルトタイムゾーン は、TIME_ZONE 設定によって定義されるタイムゾーンです。

タイムゾーン は、レンダリングに使われるタイムゾーンです。

activate() を使って、エンドユーザの実際のタイムゾーンに対するカレントタイムゾーンをセットする必要があります。 そうしないと、デフォルトタイムゾーンが使用されます。

注釈

TIME_ZONE のドキュメントで説明されているとおり、Django は環境変数をセットし、プロセスがデフォルトタイムゾーンで動くようにします。 これは、USE_TZ やカレントタイムゾーンの値にかかわらず発生します。

USE_TZTrue のとき、これは現地時刻に未だ依存しているアプリケーションでの後方互換性を維持するために役立ちます。ただし、上記で説明したとおり、これは完全には信用できません。そして、常に コード内では UTC で aware な datetimes を扱う必要があります。、fromtimestamp() を使って、tz パラメータを utc にセットしてください。

カレントタイムゾーンを選択する

カレントタイムゾーンは、翻訳に対する locale に相当します。ただし、Django がユーザのタイムゾーンを自動的に決めるために使える Accept-Language HTTP ヘッダに相当するものはありません。代わりに、Django は タイムゾーン選択関数 を提供しています。これらを使って、あなたにとって有用なタイムゾーン選択ロジックを組み立ててください。

タイムゾーンを扱うほとんどのウェブサイトは、ユーザにどのタイムゾーンに住んでいるかを尋ねて、その情報をユーザのプロフィールに保存します。匿名ユーザに対しては、ウェブサイトが優先する訪問者のタイムゾーンか UTC を使います。pytz には、国ごとのタイムゾーンのリストのようなヘルパーがあり、最も可能性の高いタイムゾーンを事前選択するために使うことができます。

以下は、セッション内にカレントタイムゾーンを保持する例です。(シンプルにするため、全体的にエラー処理を省略しています。)

以下のミドルウェアを MIDDLEWARE に追加します:

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()

カレントタイムゾーンをセットできるビューを作成します:

from django.shortcuts import redirect, render

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

このビューに POST するフォームを template.html に入れます:

{% load tz %}
{% 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{% endif %}>{{ tz }}</option>
        {% endfor %}
    </select>
    <input type="submit" value="Set" />
</form>

フォームでのタイムゾーン aware な入力

タイムゾーンサポートを有効にしたとき、Django はフォーム内に入力された日時を カレントタイムゾーン で変換し、cleaned_data 内で aware な datetime オブジェクトを返します。

カレントタイムゾーンが、(pytz によるタイムゾーンが行う) DST 変換でうまくいかなかったために、存在しなかったり曖昧な日時に対して例外を投げた場合、この日時は無効な値としてレポートされます。

テンプレートでのタイムゾーン aware な出力

タイムゾーンサポートを有効にしているとき、Django はテンプレートでレンダリングする際に aware な datetime オブジェクトを カレントタイムゾーン に連関します。この動作は、 表示形式のローカル化 と非常によく似ています。

警告

Django は naive な日時オブジェクトは転換しません。これは、曖昧であることと、タイムゾーンサポートが有効化されているときは naive な日時を生成するべきではないからです。ただし、後述するテンプレートフィルタを使って強制的に転換させることができます。

現地時刻への転換は、適切ではないことがあります -- 人手はなくコンピュータに対してアウトプットを生成するときなどです。以下のフィルタやタグ (tz テンプレートタグライブラリによって提供されています) で、タイムゾーンの転換をコントロールできます。

テンプレートタグ

localtime

ブロック内で、aware な datetime オブジェクトのカレントライムゾーンへの転換を有効化または無効化します。

このタグは、テンプレートエンジンに関する限り、USE_TZ 設定とまったく同じ効果を持ちます。これを使うと、より細かい粒度の転換をコントロールできます。

テンプレートブロックに対して転換を有効化ないし無効化するには、以下を使ってください:

{% load tz %}

{% localtime on %}
    {{ value }}
{% endlocaltime %}

{% localtime off %}
    {{ value }}
{% endlocaltime %}

注釈

USE_TZ の値は、{% localtime %} ブロックの中では尊重されません。

timezone

ブロック内でカレントタイムゾーンをセットまたは解除します。カレントタイムゾーンがセットされていないときは、デフォルトタイムゾーンが適用されます。

{% load tz %}

{% timezone "Europe/Paris" %}
    Paris time: {{ value }}
{% endtimezone %}

{% timezone None %}
    Server time: {{ value }}
{% endtimezone %}

get_current_timezone

get_current_timezone タグを使って、カレントタイムゾーンの名前を取得することができます:

{% get_current_timezone as TIME_ZONE %}

これ以外に、tz() コンテキストプロセッサを有効化して TIME_ZONE コンテキスト変数を使うことができます。

テンプレートフィルタ

以下のフィルタは、aware と native の日時の両表を受け入れます。転換するために、これらは native な日時はデフォルトタイムゾーンにあると仮定します。これらは常に aware な日時を返します。

localtime

単一の値からカレントタイムゾーンへの変換を強制します。

For example:

{% load tz %}

{{ value|localtime }}

utc

単一の値から UTC への転換を強制します。

For example:

{% load tz %}

{{ value|utc }}

timezone

単一の値から指定したタイムゾーンへの転換を強制します。

The argument must be an instance of a tzinfo subclass or a time zone name.

For example:

{% load tz %}

{{ value|timezone:"Europe/Paris" }}

移行ガイド

以下は、Django がタイムゾーンをサポートする以前に開始したプロジェクトを移行する方法です。

データベース

PostgreSQL

The PostgreSQL backend stores datetimes as timestamp with time zone. In practice, this means it converts datetimes from the connection's time zone to UTC on storage, and from UTC to the connection's time zone on retrieval.

As a consequence, if you're using PostgreSQL, you can switch between USE_TZ = False and USE_TZ = True freely. The database connection's time zone will be set to TIME_ZONE or UTC respectively, so that Django obtains correct datetimes in all cases. You don't need to perform any data conversions.

他のデータベース

他のバックエンドは、タイムゾーン情報なしで日時を保持します。USE_TZ = False から USE_TZ = True に切り替えた場合、現地時刻から UTC に転換する必要があります -- あなたの現地時刻に DST がある場合、これは確実に 1 つに決まるわけではありません。

コード

The first step is to add USE_TZ = True to your settings file. At this point, things should mostly work. If you create naive datetime objects in your code, Django makes them aware when necessary.

ただし、これらの変換ではDSTの移行が失敗する可能性があります。これは、まだ対ー無ゾーンサポートの利点が完全には得られていないことを意味します。 また、naive な日時と aware な日時を比較することは不可能なため、いくつかの問題に遭遇する可能性があります。 Django はaware な日時を提供するので、モデルやフォームから来た datetime と、コード内に作成した naive な datetime を比較すると例外が発生します。

したがって、第2のステップは、datetime オブジェクトをインスタンス化している部分を aware に変更するため、コードをリファクタリングすることです。 これは段階的に行うことができます。django.utils.timezone は、互換性のための便利なヘルパーをいくつ定義しています now()is_aware()is_naive()make_aware() そして make_naive() です。

最後に、アップグレードが必要なコードを特定しやすいように、naive な日時をデータベースに保存しようとすると、Django は警告を出します:

RuntimeWarning: DateTimeField ModelName.field_name received a naive
datetime (2012-01-01 00:00:00) while time zone support is active.

開発中、以下を設定ファイルに追加することで、これらの警告を例外として発生するように変更し、トラックバックを得ることができます:

import warnings
warnings.filterwarnings(
    'error', r"DateTimeField .* received a naive datetime",
    RuntimeWarning, r'django\.db\.models\.fields',
)

フィクスチャー

aware な日時をシリアライズするとき、以下のように UTC オフセットが含まれます:

"2011-09-01T13:20:30+03:00"

naive な日時に対しては、当然ながら含まれません:

"2011-09-01T13:20:30"

DateTimeField を使ったモデルに対しては、この違いにより、タイムゾーンサポートのありとなし両方に対して動作するフィクスチャーを記述することができません。

USE_TZ = False もしくは Django 1.4 以前 で生成されたフィクスチャーは、"naive" 形式を使用します。このようなフィクス茶がプロジェクトに含まれる場合、タイムゾーンサポートを有効化した後に、これらをロードする際に RuntimeWarning を参照してください。警告を破棄するには、これらのフィクスチャーを "aware" 形式に転換する必要があります。

loaddatadumpdata を使って、フィクスチャーを際生成できます。もしくは、これらが十分小さい場合には、TIME_ZONE がシリアライズされたそれぞれの日時と一致する UTC オフセットをこれらに単に追加できます。

FAQ

セットアップ

  1. 複数のタイムゾーンを必要としません。タイムゾーンサポートを有効化するべきですか?

    はい。タイムゾーンサポートが有効のとき、Django は現地時刻のより正確なモデルを使用します。これは、夏時間 (DST) 変換まわりの微細で再現不可能なバグからあなたを守ります。

    When you enable time zone support, you'll encounter some errors because you're using naive datetimes where Django expects aware datetimes. Such errors show up when running tests and they're easy to fix. You'll quickly learn how to avoid invalid operations.

    On the other hand, bugs caused by the lack of time zone support are much harder to prevent, diagnose and fix. Anything that involves scheduled tasks or datetime arithmetic is a candidate for subtle bugs that will bite you only once or twice a year.

    For these reasons, time zone support is enabled by default in new projects, and you should keep it unless you have a very good reason not to.

  2. タイムゾーンサポートを有効化しました。私は安全ですか?

    おそらく。DST 関連のバグからは守られるようになりましたが、それでも不用意な変換 ( naive な日時を aware な日時に変更する、およびその逆)ことによって自ら墓穴を掘る可能性があります。

    If your application connects to other systems -- for instance, if it queries a Web service -- make sure datetimes are properly specified. To transmit datetimes safely, their representation should include the UTC offset, or their values should be in UTC (or both!).

    Finally, our calendar system contains interesting traps for computers:

    >>> import datetime
    >>> def one_year_before(value):       # DON'T DO THAT!
    ...     return value.replace(year=value.year - 1)
    >>> one_year_before(datetime.datetime(2012, 3, 1, 10, 0))
    datetime.datetime(2011, 3, 1, 10, 0)
    >>> one_year_before(datetime.datetime(2012, 2, 29, 10, 0))
    Traceback (most recent call last):
    ...
    ValueError: day is out of range for month
    

    (To implement this function, you must decide whether 2012-02-29 minus one year is 2011-02-28 or 2011-03-01, which depends on your business requirements.)

  3. 現地時刻で日時を保持しているデータベースと、どのようにやり取りすればいいですか?

    TIME_ZONE オプションを、DATABASES 設定内のデータベースに適したタイムゾーンにセットしてください。

    This is useful for connecting to a database that doesn't support time zones and that isn't managed by Django when USE_TZ is True.

トラブルシューティング

  1. 私のアプリケーションが TypeError: can't compare offset-naive and offset-aware datetimes とともにクラッシュします -- 何がいけませんか?

    naive と aware な日時を比較することによって、このエラーを再現してみましょう:

    >>> import datetime
    >>> from django.utils import timezone
    >>> naive = datetime.datetime.utcnow()
    >>> aware = timezone.now()
    >>> naive == aware
    Traceback (most recent call last):
    ...
    TypeError: can't compare offset-naive and offset-aware datetimes
    

    このエラーに遭遇した場合、あなたのコードはこれら 2 つを比較している可能性が高いです:

    • a datetime provided by Django -- for instance, a value read from a form or a model field. Since you enabled time zone support, it's aware.
    • a datetime generated by your code, which is naive (or you wouldn't be reading this).

    Generally, the correct solution is to change your code to use an aware datetime instead.

    If you're writing a pluggable application that's expected to work independently of the value of USE_TZ, you may find django.utils.timezone.now() useful. This function returns the current date and time as a naive datetime when USE_TZ = False and as an aware datetime when USE_TZ = True. You can add or subtract datetime.timedelta as needed.

  2. 多くの RuntimeWarning: DateTimeField received a naive datetime (YYYY-MM-DD HH:MM:SS) while time zone support is active が発生します。 -- これは悪いことですか?

    When time zone support is enabled, the database layer expects to receive only aware datetimes from your code. This warning occurs when it receives a naive datetime. This indicates that you haven't finished porting your code for time zone support. Please refer to the migration guide for tips on this process.

    In the meantime, for backwards compatibility, the datetime is considered to be in the default time zone, which is generally what you expect.

  3. now.date() が昨日 (もしくは明日) になります!

    If you've always used naive datetimes, you probably believe that you can convert a datetime to a date by calling its date() method. You also consider that a date is a lot like a datetime, except that it's less accurate.

    None of this is true in a time zone aware environment:

    >>> import datetime
    >>> import pytz
    >>> paris_tz = pytz.timezone("Europe/Paris")
    >>> new_york_tz = pytz.timezone("America/New_York")
    >>> paris = paris_tz.localize(datetime.datetime(2012, 3, 3, 1, 30))
    # This is the correct way to convert between time zones with pytz.
    >>> new_york = new_york_tz.normalize(paris.astimezone(new_york_tz))
    >>> paris == new_york, paris.date() == new_york.date()
    (True, False)
    >>> paris - new_york, paris.date() - new_york.date()
    (datetime.timedelta(0), datetime.timedelta(1))
    >>> paris
    datetime.datetime(2012, 3, 3, 1, 30, tzinfo=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>)
    >>> new_york
    datetime.datetime(2012, 3, 2, 19, 30, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
    

    As this example shows, the same datetime has a different date, depending on the time zone in which it is represented. But the real problem is more fundamental.

    A datetime represents a point in time. It's absolute: it doesn't depend on anything. On the contrary, a date is a calendaring concept. It's a period of time whose bounds depend on the time zone in which the date is considered. As you can see, these two concepts are fundamentally different, and converting a datetime to a date isn't a deterministic operation.

    What does this mean in practice?

    Generally, you should avoid converting a datetime to date. For instance, you can use the date template filter to only show the date part of a datetime. This filter will convert the datetime into the current time zone before formatting it, ensuring the results appear correctly.

    If you really need to do the conversion yourself, you must ensure the datetime is converted to the appropriate time zone first. Usually, this will be the current timezone:

    >>> from django.utils import timezone
    >>> timezone.activate(pytz.timezone("Asia/Singapore"))
    # For this example, we just set the time zone to Singapore, but here's how
    # you would obtain the current time zone in the general case.
    >>> current_tz = timezone.get_current_timezone()
    # Again, this is the correct way to convert between time zones with pytz.
    >>> local = current_tz.normalize(paris.astimezone(current_tz))
    >>> local
    datetime.datetime(2012, 3, 3, 8, 30, tzinfo=<DstTzInfo 'Asia/Singapore' SGT+8:00:00 STD>)
    >>> local.date()
    datetime.date(2012, 3, 3)
    
  4. I get an error "Are time zone definitions for your database installed?"

    If you are using MySQL, see the Time zone definitions section of the MySQL notes for instructions on loading time zone definitions.

使い方

  1. 文字列 "2012-02-21 10:28:45" があり、 "Europe/Helsinki" タイムゾーンであることが分かっています。どうやって aware な日時に変換しますか?

    これは、まさに pytz が存在する理由です。

    >>> from django.utils.dateparse import parse_datetime
    >>> naive = parse_datetime("2012-02-21 10:28:45")
    >>> import pytz
    >>> pytz.timezone("Europe/Helsinki").localize(naive, is_dst=None)
    datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=<DstTzInfo 'Europe/Helsinki' EET+2:00:00 STD>)
    

    Note that localize is a pytz extension to the tzinfo API. Also, you may want to catch pytz.InvalidTimeError. The documentation of pytz contains more examples. You should review it before attempting to manipulate aware datetimes.

  2. どうやってカレントタイムゾーンで現在時刻を取得しますか?

    そうですね。最初に質問させてください。それは本当に必要ですか?

    人間とやり取りするときだけ、現地時刻を使うべきです。そして、テンプレートレイヤは日時をあなたが選択したタイムゾーンに転換するための フィルタとタグ タグを提供しています。

    Furthermore, Python knows how to compare aware datetimes, taking into account UTC offsets when necessary. It's much easier (and possibly faster) to write all your model and view code in UTC. So, in most circumstances, the datetime in UTC returned by django.utils.timezone.now() will be sufficient.

    For the sake of completeness, though, if you really want the local time in the current time zone, here's how you can obtain it:

    >>> from django.utils import timezone
    >>> timezone.localtime(timezone.now())
    datetime.datetime(2012, 3, 3, 20, 10, 53, 873365, tzinfo=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>)
    

    In this example, the current time zone is "Europe/Paris".

  3. 全ての利用可能なタイムゾーンを参照するにはどうすればいいですか?

    pytz はヘルパーを提供しており、これはカレントタイムゾーンのリストと全ての利用可能なタイムゾーンのリストを含んでいます -- そのうちのいくつかは単に歴史的な関連です。

Back to Top