4

と聞かれましたが、ウェブアプリでタイムゾーンをどう扱うかという概念を掴むのに苦労しています。プロジェクトの進捗状況を追跡するシステムがあります。SQL Server データベースに ProjectStartDate DATE があります。(さらにいくつかのフィールドとテーブルがありますが、1 つに焦点を当てましょう)。

サーバーは米国のどこかにあります。私はオーストラリアに住んでいます。

呼び出しSELECT GETDATE()は「2013-08-11 14:40:50.630」を返します 私のシステム時計は「2013-08-12 07:40」を示しています

私のデータベースでは、すべてのテーブルに「CreateDateTime」列があります。それをC#コード内に保存するときは、次を使用しますCreateDate = DateTime.UtcNow

UTCを使ったほうがいいと聞いたので、それを使っています。

しかし、ユーザーにカレンダー コントロールが表示され、プロジェクトの開始日を選択すると、ユーザーが選択した内容が保存されます。変換はありません...そして、私が言ったように、StartDate はデータベースの DATE 型です。

問題は、プロジェクトが今日開始された場合、サーバーがまだ昨日にあるため、フロント エンドで現在のプロジェクトが開始されていないと表示されることです。

日付を保存しているので、日付を保存する必要があると思います。しかし、どうにかしてユーザーのタイムゾーンを取得し、それを UI レベルで適用する必要があるのでしょうか?

私が見る問題は次のとおりです。

  • ユーザーのタイムゾーンがわかりません。選択できるように何かを追加しますか?
  • プロジェクトのステータスはストアド プロシージャで決定される場合がありますが、いつ変換を適用できますか? proc では、チェックを行い、StartDate <= DateTime.Now?

ほとんどの場合、EntityFramework と Linq を使用してデータを取得します。SQL の意味と .Net の意味の両方から、データを挿入および取得するための戦略が必要です。

これに基づいてユーザーにタイムゾーンを選択させるコードを追加しました。

public List<TimeZoneDto> GetTimeZones()
{
    var zones = TimeZoneInfo.GetSystemTimeZones();
    var result = zones.Select(tz => new TimeZoneDto
        {
            Id = tz.Id, 
            Description = tz.DisplayName
        }).ToList();

    return result;
}

それはその後、プロファイルに保持されます。

以下の回答でアドバイスされているように、すべての日付は UTC として保存されています。

データベースとの間、クライアントとの間で日付を処理する方法については、まだ混乱しています。レコードを保存する方法の例を次に示します。

public int SaveNonAvailibility(PersonNonAvailibilityDto n)
{
    person_non_availibility o;

    if (n.Id == 0)
    {
        o = new person_non_availibility
            {
                PersonId = n.PersonId,
                Reason = n.Reason,
                StartDate = n.StartDate,
                EndDate = n.EndDate,
                NonAvailibilityTypeId = n.NonAvailibilityTypeId,
                CreateUser = _userId,
                CreateDate = DateTime.UtcNow

            };
        _context.person_non_availibility.Add(o);
    }
    else
    {
        o = (from c in _context.person_non_availibility where c.Id == n.Id select c).FirstOrDefault();
        o.StartDate = n.StartDate;
        o.EndDate = n.EndDate;
        o.Reason = n.Reason;
        o.NonAvailibilityTypeId = n.NonAvailibilityTypeId;
        o.LastUpdateDate = DateTime.UtcNow;
        o.LastUpdateUser = _userId;
        o.Deleted = n.Deleted ? DateTime.UtcNow : (DateTime?)null;
    }
    _context.SaveChanges();
    return o.Id;
}

この方法は基本的に、人が仕事に出られないときに節約します。LastUpdateDate の保存方法に注意してください。また、「開始」日と「終了」日。これらの日付は、より「ビジネス」な日付です。

選択してから日付を確認すると、問題が発生します。この例では、NOW に基づいて個人の料金を取得しています。

public decimal CurrentRate
{
    get
    {
        if (ResourceCosts != null)
        {
            var i = ResourceCosts.FirstOrDefault(t => DateTime.UtcNow <= (t.EndDate.HasValue ? t.EndDate.Value : DateTime.UtcNow) && DateTime.UtcNow >= t.StartDate);
            if (i != null) return i.Cost;
            return 0;
        }
        return 0;
    }
}

ここで私がやりたいことは、現在の日付に基づいて、彼の料金を確認したいということです (彼の料金として、1 月 1 日から 1 月 15 日までは 100 ドル、16 日から 1 月 15 日までは 110 ドルになる可能性があります)。 1 月 31 日なので、今日適用可能なレートを探します (存在する場合)。これはタイム ゾーンを超えては機能しません。おそらく、ここで「DateTime.UTCNow」に基づいて日付操作を行う必要がありますか?

注、タイムゾーンを保存している上記のコードに基づいて、ユーザーのタイムゾーンがわかりました。ここでそれを何とか使用できますか?おそらく、ユーザーがログインすると、プロファイルから日付情報 (Zimezone 情報) を取得し、UTC 日付から時間を追加または削除することに基づいて、ユーザーの日時を返すグローバル共有関数を使用します...そしてそれを使用します私はどこで DateTime.UTCNow をやっていますか?

誰かが私を案内してくれることを願っています。

4

2 に答える 2

9

これらすべてを処理する「正しい」方法は 1 つではありません。質問で説明したいくつかの異なる問題には、複数のアプローチがあります。いくつかの点を明確にしようと思います。

  • まず、サーバーの現地時間について考えようとしないでください。コードとデータは、展開する場所に基づいて変更する必要はありません。サーバーは米国にあるとおっしゃいましたが、考慮すべきタイム ゾーンが複数あり、物理的な場所に関係なく、多くのサーバーのタイム ゾーンが UTC に設定されています。

    SQL Server ではGETDATE()orを避ける必要があります。SYSDATETIME()SQL で現在のタイムスタンプが必要な場合は、GETUTCDATE()またはを使用しますSYSUTCDATETIME()。何らかの理由でサーバーのタイムゾーンが重要な場合は、SYSDATETIMEOFFSET()代わりに使用してください。

    同様に、DateTime.Nowサーバー側のコードから in .Net を使用することは避けてください。DateTime.UtcNowまたはDateTimeOffset.UtcNow を UTC タイムスタンプに使用するかDateTimeOffset.Now、何らかの理由でサーバーのタイム ゾーンが重要な場合に使用します。

    詳細については、私のブログ投稿: The Case Against DateTime.Nowを参照してください。

  • 次に、使用しているデータ型について話しましょう。SQL Serverのdate型には日付のみが格納されます。それでおしまい。時間も、オフセットも、タイムゾーンもありません。例は次のようになります2013-08-11本当にカレンダーの日付全体を意味する場合に使用する必要があります。「今日」という世界的に統一された文脈はありません。代わりに、誰もが自分のタイムゾーンに基づいて独自の意味を持っています. また、すべての暦日の長さが 24 時間であるとは限りません。特定のタイム ゾーンで夏時間がどのように適用されているか、および DST 移行の日を評価している場合に応じて、1 日の長さは 23、23.5、24、24.5、または 25 時間になります。

    .Net にはDate型がないため、SQLは時刻が真夜中 ( ) に設定され、種類が に設定されdateた に変換されます。しかし、だまされてはいけません。時刻が突然真夜中になったわけではありません。時刻をゼロで埋めているだけです。これにより、多くのエラーと混乱が生じる可能性があります。(それを避けたい場合は、この目的のための型を持つNoda Timeを試すことができます。)DateTime00:00:00UnspecifiedLocalDate

  • あなたが本当に考える必要があり、あなたの質問で定義していないのはこれです:

    プロジェクトが開始される正確な瞬間は?

    今あなたが言っている2013-08-11のは、特定の瞬間を指しているわけではありません。特定のタイムゾーンでのその日の始まりを意味しますか? それとも、ユーザーのタイムゾーンによると、その日の始まりを意味しますか? それらは同じものではないかもしれません。話している瞬間が分からない限り、誰かの「今」(UTC、ローカル、またはその他)と比較することはできません。

    プロジェクトが世界中の正確な時刻に開始する場合、最も簡単な方法は、その正確な時刻を UTC で含むdatetime(または) 型を格納することです。datetime2つまり、プロジェクトの開始2013-08-10T14:00:00Z時刻は、オーストラリアのシドニーで 8 月 11 日の午前 0 時です。.Net では、 をに設定してDateTime型を使用します。.KindUtc

    これを表す別の方法datetimeoffsetは、値が2013-08-11T00:00:00+10:00- の型を格納することです。これは同じ時点ですが、オフセットを使用して事前に変換された値を提供します。(シドニーはその日の UTC+10 です)。DateTimeOffset.Net でこれを操作するには、型を使用します。

    しかし、プロジェクトがユーザーによって異なる時間に開始される場合、それは正確な時間ではありません。それは「フローティング」スタートのようなものです。世界中のさまざまな場所のユーザーが同じプロジェクトに割り当てられている場合、一部のユーザーが他のユーザーよりも先に開始する可能性があります。それがあなたの意図でdateあれば、すべてのプロジェクトが真夜中に開始する場合はタイプを使用できます。または、プロジェクトが異なる時間に開始する可能性がある場合はdatetimeor ( ) タイプを使用できます。datetime2.Net コードでは、を に設定したDateTimeタイプを使用します。.KindUnspecified

  • ユーザーのタイム ゾーンの取得に関しては、ユーザーに尋ねるのが最善の方法です。よくある誤解にもかかわらず、ブラウザーから取得することはできません。ブラウザからわかるのは、現在のオフセットが何であるかだけです。( timezone tag wikiの「TimeZone != Offset」セクションを読んでください)。

    ユーザーにタイム ゾーンを尋ねるときに、Windows タイム ゾーンを使用する場合は、TimeZoneInfo.GetSystemTimeZonesメソッドからドロップダウン リストを作成できます。は.Idデータベースに保存するキーであり.DisplayName、ユーザーに表示します。後でメソッドを使用して、変換に使用できるオブジェクトTimeZoneInfo.FindSystemTimeZoneByIdを取得できます。TimeZoneInfo

    より正確にしたい場合は、Windows タイム ゾーンの代わりに IANA タイム ゾーンを使用できます。そのためには、このような地図ベースのタイムゾーン ピッカー コントロールを使用することをお勧めします。jsTimeZoneDetectを使用して、コントロールのデフォルト値を推測することもできます。サーバーでは、Noda Timeを使用してタイム ゾーンの変換を実行します。

  • タイムゾーンの変換を必要としないアプローチがあります。基本的に、すべてを UTC で行います。これには、時刻を UTC でブラウザーに送信することが含まれます。その後、JavaScript を使用してユーザーの現在時刻を UTCで取得し、それと比較できます。

    Date必要に応じて、JavaScriptクラスのさまざまな関数を使用してこれを行うことができます。ただし、 moment.jsなどのライブラリを使用する方が簡単な場合があります。

    このアプローチは多くの場合に有効ですが、セキュリティはその 1 つではありません。ユーザーは、コンピューターの時計を簡単に変更して、これを回避できます。

  • 別のアプローチは、サーバー側を UTC と比較することです。データベースに正確な UTC 開始時刻がある場合DateTime.UtcNowは、.Net コードをチェックインし、それを使用して何をすべきかを決定できます。この比較を行うためにユーザーのタイム ゾーンは必要ありませんが、現地時間での意味をユーザーに示したい場合は必要になります。

これで混乱が解消され、悪化しないことを願っています! :) さらに懸念がある場合は、質問を編集するか、コメントで質問してください。

アップデート

更新された質問への回答として、次のことを試すことをお勧めします。

var timeZoneId = "Eastern Standard Time"; // from your user's selection
var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
var nowInTimeZone = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, timeZone);
var todayInTimeZone = nowInTimeZone.Date;

var i = ResourceCosts.FirstOrDefault(t => t.StartDate <= todayInTimeZone &&
            (!t.EndDate.HasValue || t.EndDate >= todayInTimeZone));

もちろん、これはStartDateandEndDateフィールドにこれらを UTC として保存するのではなく、ユーザーに関連する「営業日」として保存することを意味します。これらは、タイム ゾーンを適用する特定の時点にのみ一致するため、ユーザーのタイム ゾーンによっては、同じ UTC タイムスタンプが異なる日付になる可能性があります。

また、完全に包括的な範囲を使用していますが、通常、これらの種類のカレンダーの日付範囲では問題ありません。ただし、重複する可能性があることを認識してください。したがって、 と がある場合、両方の範囲にある日があり2013-01-01 - 2013-02-01ます。2013-02-01 - 2013-03-012013-02-01

この問題を回避する一般的な方法は、半開区間 を使用すること[start,end)です。つまり、start <= now && end > now. ただし、これは、日付だけでなく完全な日付と時刻を使用する場合により一般的です。これを行う必要はないかもしれませんが、少なくとも特定のシナリオではそれについて考える必要があります。

于 2013-08-12T01:09:18.970 に答える
2

ブラウザからユーザーのタイムゾーンを取得できます。これは、コンピューターで設定した値のみを取得します。したがって、彼らは自分たちの利益のためにこれを操作できます。たとえば、現地時間の午前 0 時までアクセスを制限していた場合、彼らは時間を変更して「早期」アクセスを得ることができます。ですから、それを覚えておいてください。

次にやりたいことは、すべての時刻を UTC でデータベースに保存することです。GetDate() を使用する代わりに、GetUTCDate() を使用できます。

次に、タイムゾーンまたは UTC からのオフセットをデータベースに保存できます。UTC オフセットを保存するだけで危険にさらされます。夏時間を観察するタイムゾーンは、1 年の異なる時期に異なるオフセットを持つためです。

しかし、これら 2 つの情報を使用して、ユーザーの "ローカル" 時間を計算できます。保存された日付に時間数を追加してから、それをローカル サーバー時間 (これも UTC から調整されます) と比較することができます。

あまり気にしない場合は、時間を UTC でデータベースに保存し、サーバーで DateTime.UTCNow を比較して、アクセスできるかどうかを判断します。

于 2013-08-11T21:57:40.057 に答える