タイムゾーンのサポートが有効のとき、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.
Support for non-pytz
timezone implementations was added.
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 を参照してください。
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 オブジェクトを保存しようとすると例外を投げます。これは、日付を伴わない時刻に対するタイムゾーンには意味がないからです。
後方互換性を維持するため、 USE_TZ
が True
のときでも、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_TZ
が True
のとき、これは現地時刻に未だ依存しているアプリケーションでの後方互換性を維持するために役立ちます。ただし、上記で説明したとおり、これは完全には信用できません。そして、常に コード内では 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>
タイムゾーンサポートを有効にしたとき、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.
タイムゾーンサポートを有効にしているとき、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 %}
以下のフィルタは、aware と native の日時の両表を受け入れます。転換するために、これらは native な日時はデフォルトタイムゾーンにあると仮定します。これらは常に aware な日時を返します。
以下は、Django がタイムゾーンをサポートする以前に開始したプロジェクトを移行する方法です。
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 = False
と USE_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" 形式に転換する必要があります。
loaddata
と dumpdata
でフィクスチャを再生成できます。または、それらが十分に小さい場合、シリアル化された各日時に TIME_ZONE
に一致する UTC オフセットを追加するように編集できます。
複数のタイムゾーンを必要としません。タイムゾーンサポートを有効化するべきですか?
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.
タイムゾーンサポートを有効化しました。私は安全ですか?
おそらく。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.
現地時刻で日時を保持しているデータベースと、どのようにやり取りすればいいですか?
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
.
私のアプリケーションが 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 つを比較している可能性が高いです:
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.
多くの 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.
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)
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.
文字列 "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'))
どうやってカレントタイムゾーンで現在時刻を取得しますか?
そうですね。最初に質問させてください。それは本当に必要ですか?
人間とやり取りするときだけ、現地時刻を使うべきです。そして、テンプレートレイヤは日時をあなたが選択したタイムゾーンに転換するための フィルタとタグ タグを提供しています。
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"
.
全ての利用可能なタイムゾーンを参照するにはどうすればいいですか?
zoneinfo.available_timezones()
provides the set of all valid keys for
IANA time zones available to your system. See the docs for usage
considerations.
2022年6月01日