web-dev-qa-db-ja.com

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

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

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

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

    また、これは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時間を保存するhowではなく、UTC-> LocalおよびLocal->から移行するプロセスに関するものです。 UTC。 @Max Zerbiniが指摘しているように、UTC-> Localコードをビューに配置することは明らかに賢明ですが、DateTimeExtensionsを本当に使用していますか?ユーザーから入力を取得する場合、ユーザーのローカル時間として日付を受け入れて(JSが使用しているため)、ModelBinderを使用してUTCに変換するのは理にかなっていますか?ユーザーのタイムゾーンはDBに保存され、簡単に取得できます。

132
TheCloudlessSky

これは推奨事項ではなく、より多くのパラダイムを共有していますが、Webアプリ(ASP.NET MVCに限定されない)でタイムゾーン情報を処理する際に見た最も積極的の方法は以下:

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

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

これまでのところ、かなり標準的な料金ですが、ここで物事が「面白く」なります。

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

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

    次に、javascriptを使用してUTC時間をクライアントのローカル時間に変換します。スクリプトは、すべての<time>要素を見つけ、date-formatデータプロパティを使用して日付をフォーマットし、要素の内容を設定します。

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

私はそれが最良のアプローチだと言っているわけではありませんが、それは私が前に見たことがなかった別のアプローチでした。たぶんあなたはそれからいくつかの興味深いアイデアを収集するでしょう。

101
J. Holmes

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

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 のおかげで、元のソリューションの弱点を指摘し、元の投稿を削除して申し訳ありませんが、正しいコード表示形式を取得する際に問題が発生しました。 「pre code」なので、この問題を取り除きましたが、問題ありませんでした。

13
Nestor

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

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

私がやった主なことが2つありました。まず、常に DateTimeOffset structure を使用します。 UTCからのオフセットを考慮し、クライアントからその情報を取得できれば、生活が少し楽になります。

次に、クライアントがいる場所/タイムゾーンがわかっていると仮定して、変換を実行するときに、 public info time zone database を使用して、UTCから別のタイムゾーンに時間を変換できます(または三角測量します) 、必要に応じて、2つのタイムゾーン間)。 tzデータベース( Olsonデータベース と呼ばれることもある)の素晴らしい点は、履歴全体のタイムゾーンの変更を考慮できることです。オフセットの取得は、オフセットを取得したい日付の関数です( 2005年のエネルギー政策法 which 夏時間が始まる日付を変更した日付を見てください)米国 )。

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

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

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

SOおよびWebサイトでの処理に関しては、対象者と表示しようとしているものによって異なります。気付いた場合、ほとんどのソーシャルWebサイト(およびSO、およびsf4answersのイベントセクション)は、relative時間でイベントを表示します。または、絶対値が使用されている場合、通常はUTCです。

ただし、視聴者が現地時間を想定している場合は、DateTimeOffsetを使用して、変換するタイムゾーンを取得する拡張メソッドを使用するだけで十分です。 SQLデータ型datetimeoffsetは.NET DateTimeOffsetに変換され、 GetUniversalTime method を使用するための世界時を取得できます。そこから、ZoneInfoクラスのメソッドを使用して、UTCから現地時間に変換します(それをDateTimeOffsetに入れるには少し作業が必要ですが、それは簡単です)。

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

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

8
casperOne

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

5
Max Zerbini

出力用に、次のようなディスプレイ/エディターテンプレートを作成します

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

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

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

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

このリンク は、そのパスをたどりたい場合に適切な方向にプッシュすることを望みます。

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

1
dnatoli

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

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

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

0
geoffreys