少し前に、タイムゾーンのサポートを必要とする新しい市場にシステムを適応させ始めました。初期の研究は前の記事で説明されました。現在、このアプローチは現実の影響を受けてわずかに進化しています。この記事では、ディスカッション中に発生した問題と、実装される最終決定について説明します。
TL; DR
- 用語を区別する必要があります:
- UTCは、+ 00:00ゾーンの現地時間であり、DSTの影響はありません
- DateTimeOffset – UTCからの現地時間オフセット±NN:NN。ここで、オフセットは、DST効果のないUTCからのベースオフセットです(C#TimeZoneInfo.BaseUtcOffset内)
- DateTime –タイムゾーンに関する情報のない現地時間(Kind属性は無視します)
- 使用を外部と内部に分割します:
- API、メッセージ、ファイルのエクスポート/インポートを介した入力および出力データは、厳密にUTC(DateTimeタイプ)である必要があります
- システム内では、データはオフセット(DateTimeOffsetタイプ)とともに保存されます
- 古いコードでの使用を非DBコード(C#、JS)とDBに分割します:
- 非DBコードは、ローカル値(DateTimeタイプ)でのみ動作します
- データベースはローカル値+オフセット(DateTimeOffsetタイプ)で動作します
- 新しいプロジェクト(コンポーネント)はDateTimeOffsetを使用します。
- データベースでは、DateTimeタイプは単にDateTimeOffsetに変更されます:
- テーブルフィールドタイプ
- ストアドプロシージャのパラメータ
- 互換性のない構造はコードで修正されています
- オフセット情報は、受信した値に添付されます(単純な連結)
- 非DBコードに戻る前に、値はローカルに変換されます
- DB以外のコードへの変更はありません
- DSTは、CLRストアドプロシージャを使用して解決されます(SQL Server 2016の場合、AT TIME ZONEを使用できます)。
さて、克服された困難についてさらに詳しく説明します。
IT業界の「根深い」標準
日付を現地時間でオフセットして保存することへの恐れから人々を解放するのにかなりの時間がかかりました。少し前に、経験豊富なプログラマーに「タイムゾーンをサポートするにはどうすればよいですか?」と尋ねると、 –唯一のオプションは、「UTCを使用し、デモンストレーションの直前に現地時間に変換する」でした。通常のワークフローでは、オフセットやタイムゾーンの名前などの追加情報が必要であるという事実は、実装の内部に隠されていました。 DateTimeOffsetの登場により、そのような詳細が明らかになりましたが、「プログラミングエクスペリエンス」の慣性により、別の事実にすぐに同意することはできません。「基本的なUTCオフセットを使用してローカル日付を保存する」はUTCを保存することと同じです。どこでもDateTimeOffsetを使用することの別の利点により、.NETFrameworkおよびSQLServerのタイムゾーンの遵守に対する制御を委任し、システムからのデータ入力および出力の瞬間のみを人間が制御できるようにすることができます。人間による制御は、日付/時刻の値を操作するためにプログラマーによって作成されたコードです。
この恐れを克服するために、私は説明、例、概念実証を含む複数のセッションを開催する必要がありました。例が単純で、プロジェクトで解決されるタスクに近いほど、優れています。 「一般的な」議論から始めると、理解が複雑になり、時間が無駄になります。簡単に言えば、理論が少なく、実践が多い。 UTCとDateTimeOffsetに対する引数は、次の2つのカテゴリに関連付けることができます。
- 「常にUTC」が標準であり、残りは機能しません
- UTCはDSTの問題を解決します
UTCもDateTimeOffsetも、C#のTimeZoneInfoクラスを介して利用できるゾーン間の変換のルールに関する情報を使用せずにDSTの問題を解決しないことに注意してください。
簡略化されたモデル
上で述べたように、古いコードでは、変更はデータベースでのみ発生します。これは、簡単な例を使用して評価できます。
T-SQLのモデルの例
// 1) data storage // input data in the user's locale, as he sees them declare @input_user1 datetime = '2017-10-27 10:00:00' // there is information about the zone in the user configuration declare @timezoneOffset_user1 varchar(10) = '+03:00' declare @storedValue datetimeoffset // upon receiving values, attach the user’s offset set @storedValue = TODATETIMEOFFSET(@input_user1, @timezoneOffset_user1) // this value will be saved select @storedValue 'stored' // 2) display of information // a different time zone is specified in the second user’s configuration, declare @timezoneOffset_user2 varchar(10) = '-05:00' // before returning to the client code, values are reduced to local ones // this is how the data will look like in the database and on users’ displays select @storedValue 'stored value', CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user1)) 'user1 Moscow', CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user2)) 'user2 NY' // 3) now the second user saves the data declare @input_user2 datetime // input local values are received, as the user sees them in New York set @input_user2 = '2017-10-27 02:00:00.000' // link to the offset information set @storedValue = TODATETIMEOFFSET(@input_user2, @timezoneOffset_user2) select @storedValue 'stored' // 4) display of information select @storedValue 'stored value', CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user1)) 'user1 Moscow', CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user2)) 'user2 NY'
スクリプト実行の結果は次のようになります。
この例は、このモデルではデータベースにのみ変更を加えることができるため、欠陥のリスクが大幅に軽減されることを示しています。
日付/時刻の値を処理するための関数の例
// When receiving values from the non-DB code in DateTimeOffset, they will be local, // but with offset +00:00, so you must attach a user’s offset, but you cannot convert between // time zones. To do this, we translate the value into DateTime and then back with the indication of the offset // DateTime is converted to DateTimeOffset without problems, // so you do not need to change the call of the stored procedures in the client code create function fn_ConcatinateWithTimeOffset(@dto datetimeoffset, @userId int) returns DateTimeOffset as begin declare @user_time_zone varchar(10) set @user_time_zone = '-05:00' // from the user's settings @userId return todatetimeoffset(convert(datetime, @dto), @user_time_zone) end // Client code cannot read DateTimeOffset into variables of the DateTime type, // so you need to not only convert to a correct time zone but also reduce to DateTime, // otherwise, there will be an error create function fn_GetUserDateTime(@dto datetimeoffset, @userId int) returns DateTime as begin declare @user_time_zone varchar(10) set @user_time_zone = '-05:00' // from the user's settings @userId return convert(datetime, switchoffset(@dto, @user_time_zone)) end
小さなアーティファクト
SQLコードの調整中に、DateTimeで機能するものの、DateTimeOffsetと互換性のないものがいくつか見つかりました:
GETDATE()+ 1はDATEADD(day、1、SYSDATETIMEOFFSET())に置き換える必要があります
DEFAULTキーワードはDateTimeOffsetと互換性がないため、SYSDATETIMEOFFSET()
を使用する必要があります。ISNULL(date_field、NULL)> 0″構文はDateTimeで機能しますが、DateTimeOffsetは「date_fieldISNOTNULL」に置き換える必要があります
結論またはUTCとDateTimeOffset
UTCのアプローチと同様に、データの送受信時に変換を処理することに気付く人もいるかもしれません。それでは、十分に試行された実用的なソリューションがあるのに、なぜこれらすべてが必要なのですか?これにはいくつかの理由があります:
- DateTimeOffsetを使用すると、SQLServerの場所を忘れることができます。
- これにより、作業の一部をシステムに移すことができます。
- DateTimeOffsetをあらゆる場所で使用し、データを表示したり外部システムに出力したりする前にのみ実行すると、変換を最小限に抑えることができます。
このアプローチを使用しているため、これらの理由は私には不可欠であるように思われました。
ご不明な点がございましたら、コメントをお寄せください。