時間帯
概要
タイムゾーンのサポートが有効になっている場合、Djangoは日時情報をUTCでデータベースに保存し、タイムゾーン対応の日時オブジェクトを内部で使用して、テンプレートとフォームでエンドユーザーのタイムゾーンに変換します。
これは、ユーザーが複数のタイムゾーンに住んでいて、各ユーザーの掛け時計に従って日時情報を表示する場合に便利です。
Webサイトが1つのタイムゾーンでしか利用できない場合でも、データをUTCでデータベースに保存することをお勧めします。 主な理由は夏時間(DST)です。 多くの国では、時計が春に前進し、秋に後退するDSTのシステムがあります。 現地時間で作業している場合、移行が発生すると、年に2回エラーが発生する可能性があります。 ( pytz のドキュメントでは、これらの問題について詳しく説明しています。)これはブログではおそらく問題ではありませんが、顧客に過大請求または過少請求する場合は問題になります。 1時間、年に2回、毎年。 この問題の解決策は、コードでUTCを使用し、エンドユーザーと対話する場合にのみ現地時間を使用することです。
タイムゾーンのサポートはデフォルトで無効になっています。 有効にするには、 :setting: `USE_TZ = True ` 設定ファイルで。 タイムゾーンのサポートでは、Djangoのインストール時にインストールされる pytz を使用します。
ノート
デフォルトsettings.py
によって作成されたファイル :djadmin: `django-admin startproject ` 含まれています :setting: `USE_TZ = True ` 便宜上。
ノート
Djangoがフォーマットのローカリゼーションをアクティブ化するかどうかを制御する、独立しているが関連する:setting: `USE_L10N` 設定もあります。 詳細については、フォーマットのローカリゼーションを参照してください。
特定の問題に取り組んでいる場合は、タイムゾーンに関するFAQ から始めてください。
コンセプト
ナイーブで認識している日時オブジェクト
Pythonのdatetime.datetime
オブジェクトには、datetime.tzinfo
のサブクラスのインスタンスとして表されるタイムゾーン情報を格納するために使用できるtzinfo
属性があります。 この属性が設定され、オフセットを記述する場合、日時オブジェクトは認識です。 それ以外の場合は、ナイーブです。
is_aware()および is_naive()を使用して、日時が認識されているかナイーブであるかを判別できます。
タイムゾーンのサポートが無効になっている場合、Djangoは現地時間でナイーブな日時オブジェクトを使用します。 これは単純で、多くのユースケースに十分です。 このモードでは、現在の時刻を取得するには、次のように記述します。
import datetime
now = datetime.datetime.now()
タイムゾーンサポートが有効になっている場合( :setting: `USE_TZ = True ` )、Djangoはタイムゾーン対応の日時オブジェクトを使用します。 コードで日時オブジェクトを作成する場合は、それらも認識している必要があります。 このモードでは、上記の例は次のようになります。
from django.utils import timezone
now = timezone.now()
警告
認識された日時オブジェクトの処理は、常に直感的であるとは限りません。 たとえば、標準の日時コンストラクターのtzinfo
引数は、DSTのあるタイムゾーンでは確実に機能しません。 UTCの使用は一般的に安全です。 他のタイムゾーンを使用している場合は、 pytz のドキュメントを注意深く確認する必要があります。
ノート
Pythonのdatetime.time
オブジェクトもtzinfo
属性を備えており、PostgreSQLには対応するtime with time zone
タイプがあります。 ただし、PostgreSQLのドキュメントに記載されているように、このタイプは「疑わしい有用性につながるプロパティを示します」。
Djangoはナイーブな時間オブジェクトのみをサポートし、認識された時間オブジェクトを保存しようとすると例外が発生します。日付が関連付けられていない時間のタイムゾーンは意味がないためです。
ナイーブな日時オブジェクトの解釈
:setting: `USE_TZ` がTrue
の場合でも、下位互換性を維持するために、Djangoはナイーブな日時オブジェクトを受け入れます。 データベース層はそれを受け取ると、デフォルトのタイムゾーンでそれを解釈することによってそれを認識させようとし、警告を発します。
残念ながら、DST移行中は、一部の日時が存在しないか、あいまいです。 このような状況では、 pytz は例外を発生させます。 そのため、タイムゾーンのサポートが有効になっている場合は、常に対応する日時オブジェクトを作成する必要があります。
実際には、これが問題になることはめったにありません。 Djangoは、モデルとフォームで日時オブジェクトを認識します。ほとんどの場合、新しい日時オブジェクトは、timedelta
演算によって既存のオブジェクトから作成されます。 アプリケーションコードで頻繁に作成される唯一の日時は現在の時刻であり、 timezone.now()は自動的に正しい処理を実行します。
デフォルトのタイムゾーンと現在のタイムゾーン
デフォルトのタイムゾーンは、:setting: `TIME_ZONE` 設定で定義されたタイムゾーンです。
現在のタイムゾーンは、レンダリングに使用されるタイムゾーンです。
activate()を使用して、現在のタイムゾーンをエンドユーザーの実際のタイムゾーンに設定する必要があります。 それ以外の場合は、デフォルトのタイムゾーンが使用されます。
ノート
:setting: `TIME_ZONE` のドキュメントで説明されているように、Djangoは、プロセスがデフォルトのタイムゾーンで実行されるように環境変数を設定します。 これは、:setting: `USE_TZ` の値と現在のタイムゾーンに関係なく発生します。
:setting: `USE_TZ` がTrue
の場合、これは、現地時間に依然依存しているアプリケーションとの下位互換性を維持するのに役立ちます。 ただし、上記で説明したように、これは完全に信頼できるわけではないため、独自のコードでUTCの認識された日時を常に使用する必要があります。 たとえば、fromtimestamp()
を使用し、tz
パラメーターを utc に設定します。
現在のタイムゾーンの選択
現在のタイムゾーンは、翻訳の現在の locale と同等です。 ただし、Djangoがユーザーのタイムゾーンを自動的に決定するために使用できるAccept-Language
HTTPヘッダーに相当するものはありません。 代わりに、Djangoはタイムゾーン選択機能を提供します。 それらを使用して、自分にとって意味のあるタイムゾーン選択ロジックを構築します。
タイムゾーンを気にするほとんどのWebサイトは、ユーザーにどのタイムゾーンに住んでいるかを尋ね、この情報をユーザーのプロファイルに保存します。 匿名ユーザーの場合、プライマリオーディエンスまたはUTCのタイムゾーンを使用します。 pytz は、国ごとのタイムゾーンのリストのように、ヘルパーを提供します。これを使用して、最も可能性の高い選択肢を事前に選択できます。
これは、セッションに現在のタイムゾーンを保存する例です。 (簡単にするために、エラー処理を完全にスキップします。)
次のミドルウェアを:setting: `MIDDLEWARE` に追加します:
import pytz
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(pytz.timezone(tzname))
else:
timezone.deactivate()
return self.get_response(request)
現在のタイムゾーンを設定できるビューを作成します。
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>
フォームでのタイムゾーン対応の入力
タイムゾーンのサポートを有効にすると、Djangoは現在のタイムゾーンのフォームに入力された日時を解釈し、cleaned_data
の認識された日時オブジェクトを返します。
現在のタイムゾーンで、存在しない、またはDST遷移に該当するためにあいまいな日時の例外が発生した場合( pytz によって提供されるタイムゾーンがこれを行います)、そのような日時は無効な値として報告されます。
テンプレートでのタイムゾーン対応の出力
タイムゾーンのサポートを有効にすると、Djangoは、認識されている日時オブジェクトがテンプレートにレンダリングされるときに、現在のタイムゾーンに変換します。 これは、フォーマットローカリゼーションと非常によく似た動作をします。
警告
Djangoは、ナイーブな日時オブジェクトを変換しません。これは、オブジェクトがあいまいになる可能性があり、タイムゾーンのサポートが有効になっている場合、コードがナイーブな日時を生成してはならないためです。 ただし、以下で説明するテンプレートフィルターを使用して強制的に変換することができます。
現地時間への変換が常に適切であるとは限りません。人間ではなくコンピューター用に出力を生成している可能性があります。 tz
テンプレートタグライブラリによって提供される次のフィルターとタグを使用すると、タイムゾーンの変換を制御できます。
テンプレートフィルター
これらのフィルターは、認識された日時と単純な日時の両方を受け入れます。 変換の目的で、ナイーブな日時がデフォルトのタイムゾーンにあると想定しています。 彼らは常に気づいた日時を返します。
localtime
単一の値を現在のタイムゾーンに強制的に変換します。
例えば:
{% load tz %}
{{ value|localtime }}
utc
単一の値をUTCに強制的に変換します。
例えば:
{% load tz %}
{{ value|utc }}
timezone
単一の値を任意のタイムゾーンに強制的に変換します。
引数は、tzinfo
サブクラスのインスタンスまたはタイムゾーン名である必要があります。
例えば:
{% load tz %}
{{ value|timezone:"Europe/Paris" }}
移行ガイド
Djangoがタイムゾーンをサポートする前に開始されたプロジェクトを移行する方法は次のとおりです。
データベース
PostgreSQL
PostgreSQLバックエンドは日時をtimestamp with time zone
として保存します。 実際には、これは、日時を接続のタイムゾーンからストレージのUTCに変換し、UTCから取得の接続のタイムゾーンに変換することを意味します。
その結果、PostgreSQLを使用している場合は、USE_TZ = False
とUSE_TZ = True
を自由に切り替えることができます。 データベース接続のタイムゾーンはそれぞれ:setting: `TIME_ZONE` またはUTC
に設定されるため、Djangoはすべての場合に正しい日時を取得します。 データ変換を実行する必要はありません。
その他のデータベース
他のバックエンドは、タイムゾーン情報なしで日時を保存します。 USE_TZ = False
からUSE_TZ = True
に切り替える場合は、データを現地時間からUTCに変換する必要があります。これは現地時間がDSTである場合は決定論的ではありません。
コード
最初のステップは追加することです :setting: `USE_TZ = True ` 設定ファイルに。 この時点で、物事はほとんど機能するはずです。 コードでナイーブな日時オブジェクトを作成すると、Djangoは必要に応じてそれらを認識させます。
ただし、これらの変換はDST移行の前後で失敗する可能性があります。つまり、タイムゾーンサポートのメリットをまだ十分に享受できていません。 また、ナイーブな日時と認識された日時を比較することは不可能であるため、いくつかの問題が発生する可能性があります。 Djangoが認識可能な日時を提供するようになったため、モデルまたはフォームからの日時をコードで作成した単純な日時と比較すると、例外が発生します。
したがって、2番目のステップは、日時オブジェクトをインスタンス化する場所でコードをリファクタリングして、それらを認識させることです。 これは段階的に行うことができます。 django.utils.timezone は、互換性コードの便利なヘルパーを定義しています: now()、 is_aware()、 is_naive()、[ X140X] make_aware()、および make_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',
)
備品
認識された日時をシリアル化する場合、UTCオフセットは次のように含まれます。
"2011-09-01T13:20:30+03:00"
素朴な日時の場合、それは明らかにそうではありません:
"2011-09-01T13:20:30"
DateTimeField を使用するモデルの場合、この違いにより、タイムゾーンのサポートがある場合とない場合の両方で機能するフィクスチャを作成できなくなります。
USE_TZ = False
で、またはDjango 1.4より前で生成されたフィクスチャは、「ナイーブ」形式を使用します。 プロジェクトにそのようなフィクスチャが含まれている場合、タイムゾーンのサポートを有効にした後、それらをロードするとRuntimeWarning
が表示されます。 警告を取り除くには、フィクスチャを「認識」形式に変換する必要があります。
:djadmin: `loaddata` 、次に:djadmin:` dumpdata` を使用してフィクスチャを再生成できます。 または、それらが十分に小さい場合は、それらを編集して、:setting: `TIME_ZONE` に一致するUTCオフセットを各シリアル化された日時に追加できます。
よくある質問
設定
複数のタイムゾーンは必要ありません。 タイムゾーンサポートを有効にする必要がありますか?
はい。 タイムゾーンのサポートが有効になっている場合、Djangoはより正確な現地時間のモデルを使用します。 これにより、夏時間(DST)の移行に関する微妙で再現不可能なバグから保護されます。
タイムゾーンのサポートを有効にすると、Djangoが認識している日時を期待している単純な日時を使用しているため、いくつかのエラーが発生します。 このようなエラーはテストの実行時に表示され、簡単に修正できます。 無効な操作を回避する方法をすぐに学びます。
一方、タイムゾーンのサポートがないために発生するバグは、防止、診断、修正がはるかに困難です。 スケジュールされたタスクや日時の計算を伴うものはすべて、年に1〜2回しか噛まない微妙なバグの候補です。
これらの理由により、新しいプロジェクトではタイムゾーンのサポートがデフォルトで有効になっています。そうしないという非常に正当な理由がない限り、タイムゾーンのサポートを維持する必要があります。
タイムゾーンのサポートを有効にしました。 私は安全ですか?
多分。 DST関連のバグからの保護は強化されていますが、単純な日時を不注意に認識された日時に、またはその逆に変更することで、自分の足を撃ち抜くことができます。
アプリケーションが他のシステムに接続する場合(たとえば、Webサービスにクエリを実行する場合)、日時が適切に指定されていることを確認してください。 日時を安全に送信するには、それらの表現にUTCオフセットを含めるか、値をUTC(またはその両方)にする必要があります。
最後に、私たちのカレンダーシステムには、コンピューター用の興味深いトラップが含まれています。
>>> 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
(この機能を実装するには、2012-02-29から1年を引いたものが2011-02-28であるか2011-03-01であるかを決定する必要があります。これは、ビジネス要件によって異なります。)
日時を現地時間で保存するデータベースを操作するにはどうすればよいですか?
をセットする :setting: `TIME_ZONE ` このデータベースの適切なタイムゾーンへのオプション :setting: `データベース` 設定。
これは、タイムゾーンをサポートしておらず、:setting: `USE_TZ` が
True
の場合、Djangoによって管理されていないデータベースに接続する場合に便利です。
トラブルシューティング
私のアプリケーションはでクラッシュします
TypeError: can't compare offset-naive
and offset-aware datetimes
- どうしたの?ナイーブな日時と認識されている日時を比較して、このエラーを再現してみましょう。
>>> 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つを比較している可能性があります。
Djangoによって提供される日時-たとえば、フォームまたはモデルフィールドから読み取られた値。 タイムゾーンのサポートを有効にしたので、それは認識しています。
あなたのコードによって生成された日時。これはナイーブです(またはこれを読んでいないでしょう)。
一般的に、正しい解決策は、代わりに認識された日時を使用するようにコードを変更することです。
:setting: `USE_TZ` の値とは独立して動作することが期待されるプラグ可能なアプリケーションを作成している場合は、 django.utils.timezone.now()が役立つ場合があります。 この関数は、現在の日付と時刻を、
USE_TZ = False
の場合は単純な日時として、USE_TZ = True
の場合は認識された日時として返します。 必要に応じてdatetime.timedelta
を足したり引いたりできます。たくさん見ます
RuntimeWarning: DateTimeField received a naive datetime
(YYYY-MM-DD HH:MM:SS)
while time zone support is active
–それは悪いですか?タイムゾーンのサポートが有効になっている場合、データベースレイヤーは、コードから認識された日時のみを受信することを想定しています。 この警告は、ナイーブな日時を受信したときに発生します。 これは、タイムゾーンサポートのためのコードの移植が完了していないことを示しています。 このプロセスのヒントについては、移行ガイドを参照してください。
それまでの間、下位互換性のために、日時はデフォルトのタイムゾーンにあると見なされます。これは、通常、予想される時間帯です。
now.date()
昨日です! (または明日)常にナイーブな日時を使用している場合は、
date()
メソッドを呼び出すことで日時を日付に変換できると思われるかもしれません。 また、date
は、精度が低いことを除けば、datetime
によく似ていると考えています。これは、タイムゾーンを意識した環境では当てはまりません。
>>> 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>)
この例が示すように、同じ日時は、それが表されているタイムゾーンに応じて異なる日付になります。 しかし、本当の問題はもっと根本的なものです。
日時は、時点を表します。 それは絶対的です:それは何にも依存しません。 それどころか、日付は予定表の概念です。 これは、その範囲が日付が考慮されるタイムゾーンに依存する期間です。 ご覧のとおり、これら2つの概念は根本的に異なり、日時を日付に変換することは決定論的な操作ではありません。
これは実際にはどういう意味ですか?
通常、
datetime
をdate
に変換することは避けてください。 たとえば、:tfilter: `date` テンプレートフィルターを使用して、日時の日付部分のみを表示できます。 このフィルターは、フォーマットする前に日時を現在のタイムゾーンに変換し、結果が正しく表示されるようにします。本当に自分で変換を行う必要がある場合は、最初に日時が適切なタイムゾーンに変換されていることを確認する必要があります。 通常、これは現在のタイムゾーンになります。
>>> 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)
エラーが発生します「
Are time zone definitions for your database installed?
」MySQLを使用している場合、タイムゾーン定義をロードする手順については、MySQLノートのタイムゾーン定義セクションを参照してください。
使用法
私はひもを持っています
"2012-02-21 10:28:45"
そして私はそれがにあることを知っています"Europe/Helsinki"
タイムゾーン。 どうすればそれを認識可能な日時に変えることができますか?これがまさに 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>)
localize
はtzinfo
APIのpytz拡張であることに注意してください。 また、pytz.InvalidTimeError
をキャッチすることもできます。 pytzのドキュメントには、その他の例が含まれています。 認識された日時を操作する前に、それを確認する必要があります。現在のタイムゾーンの現地時間を取得するにはどうすればよいですか?
さて、最初の質問は、あなたは本当にする必要がありますか?
人間とやり取りする場合にのみ現地時間を使用する必要があります。テンプレートレイヤーには、日時を選択したタイムゾーンに変換するためのフィルターとタグが用意されています。
さらに、Pythonは、必要に応じてUTCオフセットを考慮に入れて、認識されている日時を比較する方法を知っています。 すべてのモデルを記述し、コードをUTCで表示する方がはるかに簡単です(おそらく高速です)。 したがって、ほとんどの場合、 django.utils.timezone.now()によって返されるUTCの日時で十分です。
ただし、完全を期すために、現在のタイムゾーンの現地時間を本当に必要とする場合は、次の方法で取得できます。
>>> 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>)
この例では、現在のタイムゾーンは
"Europe/Paris"
です。利用可能なすべてのタイムゾーンを確認するにはどうすればよいですか?
pytz は、ヘルパーを提供します。これには、現在のタイムゾーンのリストと利用可能なすべてのタイムゾーンのリストが含まれます。