DBに日時とタイムゾーンの情報を保存することについてはかなり多くの質問がありましたが、全体的なレベルではさらに多くの質問がありました。ここでは、特定のケースに対処したいと思います。
システム仕様
DBでカバーする必要があるビジネスルール
ORDR-13432-Year-Month-Day
のような何らかの識別子)。現時点では正確な計算は重要ではありませんが、テナントのローカル日時に依存していることが重要です最初のアイデア
アプローチ1
ローカルテナントの日付時刻を保存すると、テナントごとにナイスになりますが、次のようなクエリで問題が発生します。
SELECT * FROM ORDERS WHERE OrderDateTime BETWEEN UTCDateTime1 AND UTCDateTime2
このクエリのOrderDateTime
は、テナントに基づいて異なる瞬間を意味するため、問題があります。もちろん、このクエリにはTenants
テーブルへの結合が含まれてローカルの日付/時刻オフセットを取得し、_OrderDateTime
をその場で計算して調整します。それは可能ですが、それを行うのに良い方法であるかどうかはわかりませんか?
アプローチ2
極端な例を見てみましょう。テナントがUTCより6時間進んでおり、ローカルの日時が2017-01-01 02:00
であるとします。 UTCは2016-12-31 20:00
になります。その時点で発注された注文はOrderNumber 'ORDR-13432-2017-1-1'
を取得するはずですが、UTCを保存するとORDR-13432-2016-12-31
を取得します。
この場合、DBでOrderを作成する時点で、UTC日時、テナントオフセットを取得し、再計算されたテナントのローカル時間に基づいてOrderNumberをコンパイルしますが、DateTime列はUTCで保存します。
質問
[UPDATE]
Gerard AshtonとHugoからのコメントに基づきます:
テナントがタイムゾーンを変更できるかどうか、および政治当局がタイムゾーンのプロパティまたはテリトリーのタイムゾーンを変更するとどうなるかについて、最初の質問は詳細に関して明確ではありませんでした。もちろんそれは非常に重要ですが、この問題の中心にはありません。別の質問でそれを取り上げるかもしれません。
この質問のために、テナントが場所を変更しないと仮定しましょう。その場所のタイムゾーンプロパティまたはタイムゾーン自体が変更される場合があり、これらの変更はこの質問とは別にシステムで処理されます。
Hugoの答えはほとんど正しいですが、いくつかの重要なポイントを追加します。
顧客のタイムゾーンを保存するときは、数値のオフセットを保存しないでください。他の人が指摘したように、UTCからのオフセットは1つの時点のみであり、DSTやその他の理由で簡単に変更できます。代わりに、タイムゾーン識別子、できればIANAタイムゾーン識別子を"America/Los_Angeles"
などの文字列として保存する必要があります。詳細は タイムゾーンタグwiki をご覧ください。
OrderDateTime
フィールドは、UTCで時刻を絶対的に表す必要があります。ただし、データベースプラットフォームに応じて、これを保存する方法にはいくつかの選択肢があります。
たとえば、Microsoft SQL Serverを使用する場合、UTCからのオフセットを保持するdatetimeoffset
列にローカル時間を格納するのが適切なアプローチです。その列に作成するインデックスはUTC同等に基づいているため、範囲クエリを実行すると、クエリのパフォーマンスが向上することに注意してください。
他のデータベースプラットフォームを使用している場合は、代わりにtimestamp
フィールドにUTC値を格納することもできます。一部のデータベースにはtimestamp with time zone
もありますが、タイムゾーンまたはオフセットstoresを意味するわけではないことを理解してください。値を保存および取得するときに暗黙的に。常にUTCを表す場合は、多くの場合、timestamp
(タイムゾーンなし)またはdatetime
のみが適切です。
上記のメソッドのいずれかがUTC時間を保存するため、ローカル時間値のインデックスを必要とする操作の実行方法も考慮する必要があります。たとえば、ユーザーのタイムゾーンの日に基づいて、日次レポートを作成する必要がある場合があります。そのためには、現地の日付でグループ化する必要があります。 UTC値からクエリ時にそれを計算しようとすると、テーブル全体をスキャンすることになります。
これに対処するための良い方法は、ローカルdate
(または、場合によってはローカルdatetime
にも個別の列を作成することですが、nota datetimeoffset
またはtimestamp
)。これは、個別に入力する完全に分離された列でも、他の列に基づいて計算/計算された列でもかまいません。この列をインデックスで使用して、ローカル日付でフィルタリングまたはグループ化できます。
計算列アプローチを使用する場合は、データベース内のタイムゾーンを変換する方法を知る必要があります。一部のデータベースには、IANAタイムゾーン識別子を理解するconvert_tz
関数が組み込まれています。
Microsoft SQL Serverを使用している場合、SQL 2016およびAzure SQL DBで新しい AT TIME ZONE
関数を使用できますが、これはMicrosoftタイムゾーン識別子でのみ機能します。 IANAタイムゾーン識別子を使用するには、my SQL Serverタイムゾーンサポート プロジェクトなどのサードパーティソリューションが必要です。
クエリ時には、BETWEEN
ステートメントの使用を避けます。完全に包括的です。日付全体で問題なく動作しますが、時間がかかる場合は、次のような半開範囲のクエリを実行することをお勧めします。
... WHERE OrderDateTime >= @t1 AND OrderDateTime < @t2
たとえば、@t1
が今日の始まりであれば、@t2
は明日の始まりになります。
ユーザーのタイムゾーンが変更されたコメントで説明されているシナリオに関して:
データベースのローカル日付を計算することを選択した場合、心配する必要がある唯一のシナリオは、ロケーションまたはビジネスが「ゾーン分割」が発生することなくタイムゾーンを切り替える場合です。ゾーン分割は、古いルールと新しいルールを含む、変更されたエリアをカバーする新しいタイムゾーン識別子が導入される場合です。
たとえば、これを書いている時点でIANA tzdbに追加された最新のゾーンはAmerica/Punta_Arenas
です。これは、チリ南部がチリの残り(America/Santiago
)がUTCに戻ったときにUTC-3にとどまることにしたゾーン分割でしたDSTの終わりに-4。
ただし、2つのタイムゾーンの境界の小さな地域が、どちらの側に従うかを変更することを決定し、ゾーン分割が保証されなかった場合、古いデータに対して新しいタイムゾーンのルールを使用する可能性があります。
ローカル日付を個別に保存する場合(DBではなくアプリケーションで計算されます)、問題はありません。ユーザーはタイムゾーンを新しいタイムゾーンに変更し、古いデータはすべてそのまま残り、新しいデータは新しいタイムゾーンで保存されます。
内部的には常にUTCを使用し、ユーザーに日付を表示する場合にのみタイムゾーンに変換することをお勧めします。したがって、アプローチ2を好む傾向があります。
テナントの現地の日付/時刻が識別子の一部でなければならないというビジネスルールがある場合は、そうします。ただし、内部的には、注文日をUTCで保持します。
あなたの例を使用して:タイムゾーンがUTC+06:00
にあるテナント。したがって、テナントのローカル時間は2017-01-01 02:00
であり、これはUTCの2016-12-31 20:00
と同等です。
順序識別子はORDR-13432-2017-1-1
になり、順序日付はUTC 2016-12-31 20:00Z
になります。
2つの日付の間のすべての注文を取得するには、このクエリは簡単です。
SELECT * FROM ORDERS WHERE OrderDateTime BETWEEN UTCDateTime1 AND UTCDateTime2
OrderDateTime
はUTCであるためです。
特定のテナントを探している場合は、対応するタイムゾーンを取得し、それに応じて日付を変換して検索できます。上記と同じ例を使用して(テナントのタイムゾーンはUTC+06:00
にあります)、2017-01-01
で行われたすべての注文を取得するには(テナントの現地時間に):
--get tenant timezone
--startUTC=tenant's local 2017-01-01 00:00 converted to UTC (2016-12-31T18:00Z)
--endUTC=tenant's local 2017-01-01 23:59:59.999 converted to UTC (2017-01-01T17:59:59.999)
SELECT * FROM ORDERS WHERE OrderDateTime between startUTC and endUTC
これにより、ORDR-13432-2017-1-1
が正しく取得されます。
異なるタイムゾーンの複数のテナントに対してクエリを実行するには、両方のアプローチで結合が必要になるため、この場合に「より良い」ものはありません。
テナントのローカル日付/時刻で追加の列を作成しない限り(UTCOrderDateTime
はテナントのタイムゾーンに変換されます)。これは冗長になりますが、複数のタイムゾーンで検索するクエリに役立ちます。それが合理的なトレードオフである場合、それらのクエリがどの程度頻繁に行われるかによって異なります。