0

さまざまなタイムゾーンにいる可能性のあるユーザーがいて、その日の始まりと月のUTC値を決定しようとしています。オブジェクトの中に、それを行おうとするメソッドがあります。次のようになります。

private void SetUserStartTimesUTC()
{
    DateTime TheNow = DateTime.UtcNow.ConvertUTCTimeToUserTime(this.UserTimezoneID);

    DateTime TheUserStartDateUserTime = TheNow.Date;
    DateTime TheUserStartMonthUserTime = new DateTime(TheNow.Year, TheNow.Month, 1);
    DateTime TheUserEndMonthUserTime = TheUserStartMonthUserTime.AddMonths(1);

    this.UserStartOfDayUTC = TheUserStartDateUserTime.ConvertUserTimeToUTCTime(this.UserTimezoneID);
    this.UserStartOfMonthUTC = TheUserStartMonthUserTime.ConvertUserTimeToUTCTime(this.UserTimezoneID);
    this.UserEndOfMonthUTC = TheUserEndMonthUserTime.ConvertUserTimeToUTCTime(this.UserTimezoneID);
}

そして、この方法は、ユーザーの時刻とUTC時刻の間の変換を行う他の2つの拡張方法に依存しています

public static DateTime ConvertUserTimeToUTCTime(this DateTime TheUserTime, string TheTimezoneID)
{
    TimeZoneInfo TheTZ = TimeZoneInfo.FindSystemTimeZoneById(TheTimezoneID);
    DateTime TheUTCTime = new DateTime();

    if (TheTZ != null)
    {
        DateTime UserTime = new DateTime(TheUserTime.Year, TheUserTime.Month, TheUserTime.Day, TheUserTime.Hour, TheUserTime.Minute, TheUserTime.Second);
        TheUTCTime = TimeZoneInfo.ConvertTimeToUtc(UserTime, TheTZ);
    }

    return TheUTCTime;
}

public static DateTime ConvertUTCTimeToUserTime(this DateTime TheUTCTime, string TheTimezoneID)
{
    TimeZoneInfo TheTZ = TimeZoneInfo.FindSystemTimeZoneById(TheTimezoneID);
    DateTime TheUserTime = new DateTime();

    if (TheTZ != null)
    {
        DateTime UTCTime = new DateTime(TheUTCTime.Year, TheUTCTime.Month, TheUTCTime.Day, TheUTCTime.Hour, TheUTCTime.Minute, 0, DateTimeKind.Utc);
        TheUserTime = TimeZoneInfo.ConvertTime(UTCTime, TheTZ);
    }

    return TheUserTime;
}

今、私はしばらくの間タイムゾーンの問題に取り組んできましたが、タイムゾーンの問題は、検出が難しい可能性のある1つずつバグを引き起こす可能性があることを知っています。

タイムゾーンの実装は正しいように見えますか、それともある種のオフバイワンバグを作成するエッジケースがありますか?

あなたの提案をありがとう。

4

1 に答える 1

5

正直なところ、あなたの方法は不必要に複雑に見えます。

なぜパラメータを呼び出しTheUTCTimeて、そのUTCバージョンを作成するのでしょうか。すでにKindUTCを持っているべきではありませんか?そうでない場合でも、使用したほうがよいでしょう。DateTime.SpecifyKind現在、一方の方法で変換する場合は秒を消去し、もう一方の方法で変換する場合はそうではありません...どちらの場合も、秒未満の値を消去します。

また:

  • TimeZoneInfo.FindSystemTimeZoneById二度と戻らないnull
  • new DateTime()タイムゾーンが見つからない場合に戻る(つまり、西暦0001年1月1日)のは、エラーを示すには非常に貧弱な方法のようです。
  • 変換メソッドにローカル変数を含める必要はありません。ConvertTime直接呼び出した結果を返すだけです
  • あなたの「月末」は実際には「翌月の始まり」です。それはあなたが望むものかもしれませんが、それは明確ではありません。

DateTime個人的には、これらすべてのBCLを完全に回避することを強くお勧めします。私は主な作者であることに完全に偏っていますが、少なくとも野田タイムがより快適に作業できることを願っています...それは「時間のない日付」、「時間のない時間」の概念を分離します日付コンポーネント」、「特定のタイムゾーンのないローカルの日付と時刻」、「特定のタイムゾーンの日付と時刻」...したがって、型システムは、賢明なことだけを行うのに役立ちます。

編集:あなたが本当にBCLタイプ内でこれをしなければならないなら、私はそれをこのように書くでしょう:

private void SetUserStartTimesUTC()
{
    DateTime nowUtc = DateTime.UtcNow;
    TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(UserTimeZoneID);

    // User-local values, all with a Kind of Unspecified.
    DateTime now = TimeZoneInfo.ConvertTime(nowUtc, zone);
    DateTime today = now.Date;
    DateTime startOfThisMonth = todayUser.AddDays(1 - today.Day);
    DateTime startOfNextMonth = startOfThisMonth.AddMonths(1);

    // Now convert back to UTC... see note below
    UserStartOfDayUTC = TimeZoneInfo.ConvertTimeToUtc(today, zone);
    UserStartOfMonthUTC = TimeZoneInfo.ConvertTimeToUtc(startOfThisMonth, zone);
    UserEndOfMonthUTC = TimeZoneInfo.ConvertTimeToUtc(startOfNextMonth, zone);
}

ご覧のとおり、追加した拡張メソッドは実際にはあまりメリットがありません。

さて、コードは「メモ」に言及しています-あなたは現在、真夜中が常に存在し、明確であると常に想定しています。これは、すべてのタイムゾーンに当てはまるわけではありません。たとえば、ブラジルでは、夏時間が進むと、時刻が深夜から午前1時にスキップされるため、基本的に深夜自体は無効になります。

Noda Timeでは、これを修正することで修正しますDateTimeZone.AtStartOfDay(LocalDate)が、BCLではそれほど簡単ではありません。

比較のために、同等のNodaTimeコードは次のようになります。

private void SetUserStartTimesUTC()
{
    // clock would be a dependency; you *could* use SystemClock.Instance.Now,
    // but the code would be much more testable if you injected it.
    Instant now = clock.Now;

    // You can choose to use TZDB or the BCL time zones
    DateTimeZone zone = zoneProvider.FindSystemTimeZoneById(UserTimeZoneID);

    LocalDateTime userLocalNow = now.InZone(zone);

    LocalDate today = userLocalNow.Date;
    LocalDate startOfThisMonth = today.PlusDays(1 - today.Day);
    LocalDate startOfNextMonth = startOfThisMonth.PlusMonths(1);

    UserStartOfDayUTC = zone.AtStartOfDay(today);
    UserStartOfMonthUTC = zone.AtStartOfDay(startOfThisMonth);
    UserEndOfMonthUTC = zone.AtStartOfDay(startOfNextMonth);
}

...プロパティのタイプZonedDateTime(タイムゾーンを記憶している)。必要に応じて、プロパティセッターごとに呼び出しをチェーンするだけで、タイプInstant(特定の時点)に変更できます。ToInstant

于 2013-01-21T17:43:12.323 に答える