タイムゾーン

概要

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

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

Even if your website is available in only one time zone, it's still good practice to store data in UTC in your database. The main reason is daylight saving time (DST). Many countries have a system of DST, where clocks are moved forward in spring and backward in autumn. If you're working in local time, you're likely to encounter errors twice a year, when the transitions happen. This probably doesn't matter for your blog, but it's a problem if you over bill or under bill your customers by one hour, twice a year, every year. The solution to this problem is to use UTC in the code and use local time only when interacting with end users.

Time zone support is disabled by default. To enable it, set USE_TZ = True in your settings file.

注釈

In Django 5.0, time zone support will be enabled by default.

Time zone support uses zoneinfo, which is part of the Python standard library from Python 3.9. The backports.zoneinfo package is automatically installed alongside Django if you are using Python 3.8.

Changed in Django 3.2:

Support for non-pytz timezone implementations was added.

Changed in Django 4.0:

zoneinfo was made the default timezone implementation. You may continue to use pytz during the 4.x release cycle via the USE_DEPRECATED_PYTZ setting.

注釈

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

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

コンセプト

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

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

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

When time zone support is disabled, Django uses naive datetime objects in local time. This is sufficient for many use cases. In this mode, to obtain the current time, you would write:

import datetime

now = datetime.datetime.now()

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

from django.utils import timezone

now = timezone.now()

警告

Dealing with aware datetime objects isn't always intuitive. For instance, the tzinfo argument of the standard datetime constructor doesn't work reliably for time zones with DST. Using UTC is generally safe; if you're using other time zones, you should review the zoneinfo documentation carefully.

注釈

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

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

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

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

Unfortunately, during DST transitions, some datetimes don't exist or are ambiguous. That's why you should always create aware datetime objects when time zone support is enabled. (See the Using ZoneInfo section of the zoneinfo docs for examples using the fold attribute to specify the offset that should apply to a datetime during a DST transition.)

実際には、これはめったに問題になりません。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 は タイムゾーン選択関数 を提供しています。これらを使って、あなたにとって有用なタイムゾーン選択ロジックを組み立ててください。

Most websites that care about time zones ask users in which time zone they live and store this information in the user's profile. For anonymous users, they use the time zone of their primary audience or UTC. zoneinfo.available_timezones() provides a set of available timezones that you can use to build a map from likely locations to time zones.

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

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

import zoneinfo

from django.utils import timezone

class TimezoneMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        tzname = request.session.get('django_timezone')
        if tzname:
            timezone.activate(zoneinfo.ZoneInfo(tzname))
        else:
            timezone.deactivate()
        return self.get_response(request)

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

from django.shortcuts import redirect, render

# Prepare a map of common locations to timezone choices you wish to offer.
common_timezones = {
    'London': 'Europe/London',
    'Paris': 'Europe/Paris',
    'New York': 'America/New_York',
}

def set_timezone(request):
    if request.method == 'POST':
        request.session['django_timezone'] = request.POST['timezone']
        return redirect('/')
    else:
        return render(request, 'template.html', {'timezones': 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 city, tz in timezones %}
        <option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ city }}</option>
        {% endfor %}
    </select>
    <input type="submit" value="Set">
</form>

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

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

Converted datetimes that don't exist or are ambiguous because they fall in a DST transition will be reported as invalid values.

テンプレートでのタイムゾーン 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

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

例:

{% load tz %}

{{ value|localtime }}

utc

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

例:

{% load tz %}

{{ value|utc }}

timezone

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

引数は tzinfo サブクラスのインスタンスか、タイムゾーンの名前である必要があります。

例:

{% 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.

その結果、PostgreSQLを使用している場合、USE_TZ = FalseUSE_TZ = True を自由に切り替えることができます。データベース接続のタイムゾーンは、それぞれ TIME_ZONE または UTC に設定されるため、Django はすべての場合に正しい日時を取得します。データ変換を実行する必要はありません。

他のデータベース

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

コード

最初のステップは、設定ファイルに USE_TZ = True を追加することです。この時点では、だいたいのことがうまく動くはずです。コード内で naive datetime objects が生成している場合、Django は必要に応じて aware にします。

ただし、これらの変換では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"

While for a naive datetime, it isn't:

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

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

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

loaddatadumpdata でフィクスチャを再生成できます。または、それらが十分に小さい場合、シリアル化された各日時に TIME_ZONE に一致する UTC オフセットを追加するように編集できます。

FAQ

セットアップ

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

    Yes. When time zone support is enabled, Django uses a more accurate model of local time. This shields you from subtle and unreproducible bugs around daylight saving time (DST) transitions.

    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. 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 edge cases. For example, you can't always subtract one year directly from a given date:

    >>> import datetime
    >>> def one_year_before(value):  # Wrong example.
    ...     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 such a function correctly, 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 な日時を比較することによって、このエラーを再現してみましょう:

    >>> from django.utils import timezone
    >>> aware = timezone.now()
    >>> naive = timezone.make_naive(aware)
    >>> 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 zoneinfo
    >>> paris_tz = zoneinfo.ZoneInfo("Europe/Paris")
    >>> new_york_tz = zoneinfo.ZoneInfo("America/New_York")
    >>> paris = datetime.datetime(2012, 3, 3, 1, 30, tzinfo=paris_tz)
    # This is the correct way to convert between time zones.
    >>> new_york = 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=zoneinfo.ZoneInfo(key='Europe/Paris'))
    >>> new_york
    datetime.datetime(2012, 3, 2, 19, 30, tzinfo=zoneinfo.ZoneInfo(key='America/New_York'))
    

    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(zoneinfo.ZoneInfo("Asia/Singapore"))
    # For this example, we 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()
    >>> local = paris.astimezone(current_tz)
    >>> local
    datetime.datetime(2012, 3, 3, 8, 30, tzinfo=zoneinfo.ZoneInfo(key='Asia/Singapore'))
    >>> 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 な日時に変換しますか?

    Here you need to create the required ZoneInfo instance and attach it to the naïve datetime:

    >>> import zoneinfo
    >>> from django.utils.dateparse import parse_datetime
    >>> naive = parse_datetime("2012-02-21 10:28:45")
    >>> naive.replace(tzinfo=zoneinfo.ZoneInfo("Europe/Helsinki"))
    datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=zoneinfo.ZoneInfo(key='Europe/Helsinki'))
    
  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=zoneinfo.ZoneInfo(key='Europe/Paris'))
    

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

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

    zoneinfo.available_timezones() provides the set of all valid keys for IANA time zones available to your system. See the docs for usage considerations.