タイムゾーンをエレガントに扱う方法


140

アプリケーションを使用しているユーザーとは異なるタイムゾーンでホストされているWebサイトがあります。これに加えて、ユーザーは特定のタイムゾーンを持つことができます。他のSOユーザーとアプリケーションがこれにどのように取り組むかと思っていましたか?最も明白な部分は、DB内で日付/時刻がUTCに格納されることです。サーバー上では、すべての日付/時刻はUTCで処理する必要があります。しかし、私が克服しようとしている3つの問題を見つけました。

  1. UTCでの現在の時刻の取得(で簡単に解決DateTime.UtcNow)。

  2. データベースから日付/時刻を取得し、ユーザーに表示します。さまざまなビューで日付を印刷する呼び出しが潜在的にたくさんあります。この問題を解決できるビューとコントローラーの間にあるレイヤーを考えていました。または、カスタム拡張メソッドをオンにしますDateTime(以下を参照)。主な欠点は、ビューで日時を使用するすべての場所で、拡張メソッドを呼び出す必要があることです。

    これにより、のようなものを使用するのも難しくなりJsonResultます。あなたはもはや簡単に呼び出すことができませんでしたJson(myEnumerable)、それはそうでなければなりませんJson(myEnumerable.Select(transformAllDates))。たぶん、AutoMapperはこの状況で役に立ちますか?

  3. ユーザーからの入力の取得(ローカルからUTC)。たとえば、日付を含むフォームをPOSTするには、日付を以前にUTCに変換する必要があります。最初に頭に浮かぶのは、カスタムの作成ModelBinderです。

以下は、ビューで使用することを考えた拡張機能です。

public static class DateTimeExtensions
{
    public static DateTime UtcToLocal(this DateTime source, 
        TimeZoneInfo localTimeZone)
    {
        return TimeZoneInfo.ConvertTimeFromUtc(source, localTimeZone);
    }

    public static DateTime LocalToUtc(this DateTime source, 
        TimeZoneInfo localTimeZone)
    {
        source = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
        return TimeZoneInfo.ConvertTimeToUtc(source, localTimeZone);
    }
}

サーバーのローカル時刻が予想されるタイムゾーンと大きく異なる可能性がある多くのアプリケーションがクラウドベースになっていることを考えると、タイムゾーンの処理は非常に一般的なことだと思います。

これは以前にエレガントに解決されましたか?私が見逃しているものはありますか?アイデアや考えは大歓迎です。

編集:混乱を解消するために、もう少し詳細を追加すると思いました。問題は、今ではありませんどのようにそれがよりUTC->ローカルおよびローカルから> UTCから行くのプロセスについてです、デシベルでUTC時刻を格納します。@Max Zerbiniが指摘するように、ビューにUTC-> Localコードを配置するのは明らかに賢明ですが、DateTimeExtensions実際に答えを使用していますか?ユーザーから入力を受け取るとき、日付をユーザーのローカル時間として受け入れ(JSが使用しているModelBinderため)、それを使用してUTCに変換することは理にかなっていますか?ユーザーのタイムゾーンはDBに保存され、簡単に取得できます。


3
最初にこの優れた投稿を読んでみてください... 夏時間とタイムゾーンのベストプラクティス
dodgy_coder '28

@dodgy_coder-これは常にタイムゾーンの優れたリソースリンクでした。ただし、これは私の問題(特にMVCに関連する)を実際に解決するものではありません。ありがとう。
TheCloudlessSky

code.google.com/p/noda-timeが役に立つかもしれません
Arnis Lapsa '28

選択したソリューションに興味があります。私自身も同様の決定に直面しています。良い質問。
ショーン

@Sean-これまでの解決策はエレガントではありません(そのため、私はまだ回答を受け入れていません)。これは、日付/時刻を印刷し、それらをModelBinder
TheCloudlessSky

回答:


106

これは推奨事項ではなく、パラダイムの共有が増えていますが、Webアプリ(ASP.NET MVCに限定されない)でタイムゾーン情報を処理する最も積極的な方法は次のとおりです。

  • サーバー上のすべての日時はUTCです。それは、あなたが言ったように、を使用することを意味しますDateTime.UtcNow

  • クライアントがサーバーに日付を渡すことをできるだけ信頼しないようにしてください。たとえば、「今」が必要な場合は、クライアントで日付を作成してからサーバーに渡さないでください。GETで日付を作成してViewModelに渡すか、POSTで渡しますDateTime.UtcNow

これまでのところ、かなり標準的な運賃ですが、ここで物事が「興味深い」ものになります。

  • クライアントから日付を受け入れる必要がある場合は、JavaScriptを使用して、サーバーに投稿するデータがUTCであることを確認してください。クライアントは現在のタイムゾーンを知っているため、妥当な精度で時刻をUTCに変換できます。

  • ビューをレンダリングするとき、HTML5 <time>要素を使用していたため、ViewModelで日時を直接レンダリングすることはありませんでした。これはHtmlHelper、のような拡張機能として実装されましたHtml.Time(Model.when)。レンダリングします<time datetime='[utctime]' data-date-format='[datetimeformat]'></time>

    次に、javascriptを使用してUTC時間をクライアントのローカル時間に変換します。スクリプトはすべての<time>要素を検索し、date-formatdataプロパティを使用して日付をフォーマットし、要素のコンテンツを入力します。

これにより、クライアントのタイムゾーンを追跡、保存、管理する必要がなくなりました。サーバーは、クライアントがどのタイムゾーンにいるかを気にせず、タイムゾーンの変換を行う必要もありませんでした。それは単にUTCを吐き出して、クライアントにそれを妥当なものに変換させます。これは、どのタイムゾーンにあるかを知っているため、ブラウザから簡単です。クライアントがタイムゾーンを変更した場合、Webアプリケーションは自動的に更新されます。それらが保存した唯一のものは、ユーザーのロケールの日時フォーマット文字列でした。

それが最善のアプローチであるとは言いませんが、これまで見たことのない別のアプローチでした。多分あなたはそれからいくつかの興味深いアイデアを集めるでしょう。


御返答いただき有難うございます。ユーザーからの日付入力(たとえば、予定のスケジュール)を取得する場合、この入力現在のタイムゾーンにあると想定されていませんか?アクションを入力する前にModelBinderがこの変換を行うことについてどう思いますか(@casperOneへの私のコメントを参照してください。また、この<time>アイデアは非常に優れています。唯一の欠点は、これらの要素についてDOM全体をクエリする必要があることを意味します)。日付を変換するだけではあまりいいとは言えません(無効になったJSについてはどうですか?)ありがとうございます!
TheCloudlessSky

通常、はい、それはローカルタイムゾーンであることを前提としています。しかし、ユーザーから時間を収集するときはいつでも、サーバーに送信される前に、JavaScriptを使用してUTCに変換されるようにしました。彼らの解決策はJSが非常に重く、JSがオフのときはあまりうまく機能しませんでした。周りに利口があるかどうかを確認するのに十分なほど考えていません。しかし、私が言ったように、それはあなたにいくつかのアイデアを与えるでしょう。
J.ホームズ

2
その後、現地の日付を必要とする別のプロジェクトに取り組み、これが私が使用した方法でした。日付/時刻のフォーマットをするためにmoment.jsを使用しています...それは甘いです。ありがとう!
TheCloudlessSky 2013年

アプリケーションのapp.config / web.cofigでアプリケーションスコープのタイムゾーンを定義し、すべてのDateTime.Now値をDateTime.UtcNowに変換する簡単な方法はありませんか?(社内のプログラマーが誤ってDateTime.Nowを使用するような状況は避けたいと思います)
Uri Abramson 2013

3
私が見ることができるこのアプローチの不利な点の1つは、PDFや電子メールなどを作成するために他のことに時間を使用している場合、それらの時間要素がないため、手動で変換する必要があることです。それ以外の場合、非常にきちんとしたソリューション
シェンク

15

いくつかのフィードバックの後、これが私の最終的な解決策です。これはクリーンでシンプルで、夏時間の問題をカバーしています。

1-モデルレベルで変換を処理します。したがって、Modelクラスでは、次のように記述します。

    public class Quote
    {
        ...
        public DateTime DateCreated
        {
            get { return CRM.Global.ToLocalTime(_DateCreated); }
            set { _DateCreated = value.ToUniversalTime(); }
        }
        private DateTime _DateCreated { get; set; }
        ...
    }

2-グローバルヘルパーで、カスタム関数「ToLocalTime」を作成します。

    public static DateTime ToLocalTime(DateTime utcDate)
    {
        var localTimeZoneId = "China Standard Time";
        var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
        var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
        return localTime;
    }

3-各ユーザープロファイルにタイムゾーンIDを保存することで、これをさらに改善できます。定数「中国標準時」を使用する代わりに、ユーザークラスから取得できます。

public class Contact
{
    ...
    public string TimeZone { get; set; }
    ...
}

4-ここで、ドロップダウンボックスから選択するようにユーザーに表示するタイムゾーンのリストを取得できます。

public class ListHelper
{
    public IEnumerable<SelectListItem> GetTimeZoneList()
    {
        var list = from tz in TimeZoneInfo.GetSystemTimeZones()
                   select new SelectListItem { Value = tz.Id, Text = tz.DisplayName };

        return list;
    }
}

したがって、現在中国では午前9時25分、米国でホストされているWebサイト、データベースのUTCに保存された日付、これが最終結果です。

5/9/2013 6:25:58 PM (Server - in USA) 
5/10/2013 1:25:58 AM (Database - Converted UTC)
5/10/2013 9:25:58 AM (Local - in China)

編集

元のソリューションの弱点を指摘してくれたMatt Johnsonに感謝します。元の投稿を削除して申し訳ありませんが、正しいコード表示形式を取得する際に問題が発生しました...エディタに「箇条書き」と「プリコード」を混在させることに問題があることがわかりました。雄牛を削除し、それは大丈夫でした。


n層アーキテクチャを持たない人やWebライブラリが共有されている人は、うまくいくようです。タイムゾーンIDをデータレイヤーに共有する方法。
Vikash Kumar

9

ユーザーはsf4answersイベントセクションで、イベントのアドレス、開始日、オプションで終了日を入力します。これらの時間はdatetimeoffset、UTCからのオフセットを考慮したSQLサーバーにれます。

これはあなたが直面しているのと同じ問題です(ただし、を使用しているという点で、別のアプローチを取っていますDateTime.UtcNow)。場所があり、あるタイムゾーンから別のタイムゾーンに時間を変換する必要がある。

私がうまくいった主なことが2つあります。まず、常にDateTimeOffset構造体を使用します。UTCからのオフセットを考慮し、クライアントからその情報を取得できる場合は、生活が少し楽になります。

次に、クライアントがいる場所/タイムゾーンがわかっていると仮定して、変換を実行するときに、公開情報タイムゾーンデータベースを使用して、UTCから別のタイムゾーンに時刻を変換できます(または、2つの間で三角測量します)時間帯)。tzデータベース(オルソンデータベースと呼ばれることもあります)の優れた点は、履歴全体のタイムゾーンの変化を考慮に入れていることです。オフセットの取得は、オフセットを取得する日付の関数です(米国で夏時間が実施される日付変更した2005年のエネルギー政策法を参照してください)。

データベースが手元にあれば、ZoneInfo(tzデータベース/ Olsonデータベース).NET APIを使用できます。バイナリ配布はないことに注意してください。最新バージョンをダウンロードして自分でコンパイルする必要があります。

この記事の執筆時点では、現在、最新のデータ配布のすべてのファイルを解析しています(実際には、9月25日にftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gzファイルに対して実行しました。 2011年、2017年3月には、https://iana.org/time-zonesまたはftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gzから入手できます)。

そのため、sf4answersでは、住所を取得した後、緯度/経度の組み合わせにジオコーディングされ、サードパーティのWebサービスに送信されて、tzデータベースのエントリに対応するタイムゾーンを取得します。そこから、開始時間と終了時間が次のように変換されます。DateTimeOffset適切なUTCオフセットを持つインスタンスにされ、データベースに格納されます。

SOやWebサイトでの対処については、オーディエンスと表示しようとしている内容によって異なります。気づいたら、ほとんどのソーシャルWebサイト(およびSO、およびsf4answersのイベントセクション)は、イベントを相対的に表示します時間で。または、絶対値が使用されている場合、通常はUTCです。

ただし、オーディエンスが現地時間を期待している場合DateTimeOffsetは、タイムゾーンを変換する拡張メソッドと一緒に使用するだけで十分です。SQLデータ型datetimeoffsetは.NETに変換されDateTimeOffsetGetUniversalTimeメソッドを使用するための世界時を取得できます。そこから、ZoneInfoクラスのメソッドを使用してUTCから現地時間に変換するだけです(これをに変換するには少し作業を行う必要がありますが、実行するDateTimeOffsetのは簡単です)。

どこで変換を行うのですか?それはどこかで支払わなければならないコストであり、「最善の」方法はありません。ただし、ビューに提示されるビューモデルの一部としてタイムゾーンオフセットを使用して、ビューを選択します。そうすれば、ビューの要件が変わっても、変更に対応するためにビューモデルを変更する必要はありません。にJsonResultは、オフセットを含むモデルが含まれます。IEnumerable<T>

入力側では、モデルバインダーを使用していますか?絶対に方法はないと思います。すべての日付(現在または将来)がこの方法で変換される必要があることを保証することはできません。これは、このアクションを実行するコントローラーの明示的な関数である必要があります。繰り返しになりますが、要件が変更された場合でも、ModelBinderビジネスロジックを調整するために1つまたは多くのインスタンスを微調整する必要はありません。これビジネスロジックです。つまり、コントローラー内にある必要があります。


詳しい回答ありがとうございます。無礼に出くわさないように願っていますが、これはASP.NET MVCに関する私の懸念の答えにはなりませんでした。ユーザー入力はどうですか(カスタムモデルバインダーを使用すれば十分でしょうか)。そして最も重要なのは、これらの日付を(ユーザーのローカルタイムゾーンで)表示することです。拡張メソッドは、不要なように見える私のビューに多くの重みを追加するように感じています。
TheCloudlessSky

1
@TheCloudlessSky:編集した応答の最後の2つの段落を参照してください。個人的には、変換を行う場所の詳細はマイナーだと思います。主な問題は、日時データの実際の変換と保存です(ところで、datetimeoffsetSQL ServerとDateTimeOffset.NETでの使用を十分に強調することはできません。非常に単純化されています).NETは適切に処理しません上で概説した理由のために。2003年に入力された日付がNYCにあり、それを2011年にLAの日付に変換したい場合、この場合、.NETは失敗します。
casperOne 2011

モデルバインダー(またはアクションの前のレイヤー)を使用しない場合の問題は、日付を操作するときすべてのコントローラーをテストすることが非常に困難になることです(すべてのコントローラーが日付変換に依存します)。これにより、ユニットテストの日付をUTCで記述できます。私のアプリケーションには、プロファイルに関連付けられているユーザーがいます(医師/秘書がその業務に関連付けられていると考えてください)。プロファイルはタイムゾーン情報を保持します。したがって、現在のユーザーのプロファイルのタイムゾーンを取得してバインド中に変換するのはかなり簡単です。これに対して他の議論がありますか?ご協力ありがとうございます。
TheCloudlessSky

それに対する反対はあなたのテストがテストケースを正確に反映しないということです。あなたの入力がされていない UTCにするつもりなので、あなたのテストケースは、それを使用するように細工してはなりません。場所とすべてを含む実際の日付を使用するDateTimeOffset必要があります(ただし、これを使用すると、IMOを大幅に軽減できます)。
casperOne 2011

はい-しかし、これは日付を扱うすべてのテストがこれを考慮しなければならないことを意味します。日時を扱うすべてのアクションは、常に UTCに変換されます。私にとって、これはモデルバインダーの第一候補であり、モデルバインダーをテストします。
TheCloudlessSky

5

これは私の意見ですが、MVCアプリケーションはデータプレゼンテーションの問題をデータモデルの管理から分離する必要があると思います。データベースはローカルサーバー時間でデータを格納できますが、ローカルユーザーのタイムゾーンを使用して日時をレンダリングするのはプレゼンテーションレイヤーの義務です。これは、国によってはI18Nおよび数値形式と同じ問題のようです。あなたのケースでは、アプリケーションはCultureユーザーのタイムゾーンを検出し、さまざまなテキスト、数値、およびdatimeプレゼンテーションを表示するビューを変更する必要がありますが、保存されたデータは同じ形式にすることができます。


御返答いただき有難うございます。ええ、これは私が基本的に説明したものです。MVCでこれをエレガントに実現するにはどうすればよいのかと思っています。アプリは、ユーザーのタイムゾーンをアカウント作成の一部として保存します(タイムゾーンを選択します)。問題は、私がのために作成したメソッドで(うまくいけば)散らかさなくても、各ビューで日付をレンダリングする方法に関係していますDateTimeExtensions
TheCloudlessSky

これには多くの方法があります。1つは提案したヘルパーメソッドを使用する方法で、もう1つはおそらくより複雑ですがエレガントな方法は、要求と応答を処理して日時を変換するフィルターを使用する方法です。別の方法は、ビューモデルのDateTimeタイプのフィールドに注釈を付けるカスタムDisplay属性を開発することです。
Massimo Zerbini、2011

そういうものを探していました。私のOPを見ると、ビュー/ jsonの結果に移動するに、ただしアクションが実行された後で、AutoMapperを使用して処理を実行することにも言及しました。
TheCloudlessSky 2009

1

出力用に、次のような表示/編集テンプレートを作成します

@inherits System.Web.Mvc.WebViewPage<System.DateTime>
@Html.Label(Model.ToLocalTime().ToLongTimeString()))

特定のモデルのみがこれらのテンプレートを使用するようにする場合は、モデルの属性に基づいてバインドできます。

参照してくださいここここにカスタムエディタテンプレートの作成の詳細については。

あるいは、入力と出力の両方で機能させたいので、コントロールを拡張するか、独自のコントロールを作成することをお勧めします。これにより、入力と出力の両方をインターセプトし、必要に応じてテキスト/値を変換できます。

このリンクをたどると、正しい方向に進むことができます。

どちらにしても、エレガントなソリューションが必要な場合は、少し手間がかかります。明るい面としては、一度実行すると、将来使用するためにコードライブラリに保存できます。


カスタムディスプレイ/エディターテンプレートとバインダーは、実装すると「機能する」ため、最もエレガントなソリューションだと思います。いいえ、開発者は、権利だけを使用する作業の日付を取得するには何も特別なを知っておく必要がありませんDisplayForEditorFor、それはすべての時間を動作します。+1
ケビン・ストライカー

17
これは、ブラウザの時間ではなく、アプリケーションサーバーの時間をレンダリングしませんか?
Alex

0

これはおそらく、ナットをクラックするための大槌ですが、UIとビジネスレイヤーの間にレイヤーを挿入して、日時を透過的に、返されたオブジェクトグラフの現地時間に、入力日時パラメーターのUTCに変換できます。

これはPostSharpまたはコントロールコンテナーの反転を使用して実現できると思います。

個人的には、UIで日時を明示的に変換するだけです...

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.