0

私は、適切にグローバル化され、利用可能なすべてのタイム ゾーンで動作する必要がある一時的表現ライブラリを構築中です。

さまざまな .Add を使用して移行境界を越えたときに、正しい夏時間 (DST) で DateTimeOffset オブジェクトから調整された日付を取得する方法がわからないため、行き詰まっているようです。日、時間など.

興味深いことに、私はローカル システムのタイム ゾーンの回避策を見つけましたが、同じ戦略を任意のタイム ゾーンに適用する方法を見つけられませんでした。

オフセットによってタイム ゾーン情報を逆引きしようとするスニペットを見つけることができました (ソースを保持していませんでした。申し訳ありません!)。ただし、複数の潜在的な結果があり、それぞれが機能しない DST ルールが異なる可能性があります。(いくつかの最適化が利用できるかもしれませんが、基本前提には欠陥があると思います)

public TimeZoneInfo GetTimeZoneInfo(DateTimeOffset Value)
{
    // Search available sytem time zones for a matching one
    foreach (var tzi in TimeZoneInfo.GetSystemTimeZones())
    {
        // Compare value offset with time zone offset
        if (tzi.GetUtcOffset(Value).Equals(Value.Offset))
        {
            return tzi;
        }
    }
}

このことを証明するのは少し面倒なので、コアの問題をいくつかのメソッドと単体テストに抽出しました。これにより、私が直面している問題が実証されることを願っています。

public DateTimeOffset GetNextDay_Wrong(DateTimeOffset FromDateTimeOffset)
{
    // Cannot create a new DateTimeOffset using simply the supplied value's UtcOffset
    // because in PST, for example, it could be -7 or -8 depending on DST
    return new DateTimeOffset(FromDateTimeOffset.Date.AddDays(1), FromDateTimeOffset.Offset);
}

[TestMethod]
public void GetNextDay_WrongTest()
{
    var tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");

    var workingDate = new DateTime(2009, 11, 2, 0, 0, 0);
    var failingDate = new DateTime(2009, 11, 1, 0, 0, 0);

    var workingDate_tz = new DateTimeOffset(workingDate, tz.GetUtcOffset(workingDate));
    var failingDate_tz = new DateTimeOffset(failingDate, tz.GetUtcOffset(failingDate));

    var actual_workingDate_tz = GetNextDay_Wrong(workingDate_tz);
    var actual_failingDate_tz = GetNextDay_Wrong(failingDate_tz);

    var expected_workingDate = new DateTime(2009, 11, 3, 0, 0, 0);
    var expected_failingDate = new DateTime(2009, 11, 2, 0, 0, 0);

    var expected_workingDate_tz = new DateTimeOffset(expected_workingDate, tz.GetUtcOffset(expected_workingDate));
    var expected_failingDate_tz = new DateTimeOffset(expected_failingDate, tz.GetUtcOffset(expected_failingDate));

    Assert.AreEqual(expected_workingDate_tz, actual_workingDate_tz, "Should have found the following day's midnight");
    Assert.AreEqual(expected_failingDate_tz, actual_failingDate_tz, "Failing date does not have the correct offset for it's DST");
}

public DateTimeOffset GetNextDay_LooksRight(DateTimeOffset FromDateTimeOffset)
{
    // Because we cannot create a new DateTimeOffset we simply adjust the one provided!
    var temp = FromDateTimeOffset;
    // Move back to midnight of the current day
    temp = temp.Subtract(new TimeSpan(temp.Hour, temp.Minute, temp.Second));
    // Now move to the next day
    temp = temp.AddDays(1);
    // Let the DateTimeOffset class do it's magic
    temp = temp.ToLocalTime();
    // Check if the time zone has changed
    if (FromDateTimeOffset.Offset != temp.Offset)
    {
        // Calculate the change amount (could be 30 mins or even stranger)
        var delta = FromDateTimeOffset.Offset - temp.Offset;
        // Adjust the temp value by the delta
        temp = temp.Add(delta);
    }
    return temp.ToLocalTime();
}

[TestMethod]
public void GetNextDay_LooksRightTest()
{
    // Everything is looking good and the test passes now, so we're home free yeah?

    // { To work this needs to match your system's configured Local Time Zone, I'm in PST }
    var tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");

    var workingDate = new DateTime(2009, 11, 2, 0, 0, 0);
    var failingDate = new DateTime(2009, 11, 1, 0, 0, 0);

    var workingDate_tz = new DateTimeOffset(workingDate, tz.GetUtcOffset(workingDate));
    var failingDate_tz = new DateTimeOffset(failingDate, tz.GetUtcOffset(failingDate));

    var actual_workingDate_tz = GetNextDay_LooksRight(workingDate_tz);
    var actual_failingDate_tz = GetNextDay_LooksRight(failingDate_tz);

    var expected_workingDate = new DateTime(2009, 11, 3, 0, 0, 0);
    var expected_failingDate = new DateTime(2009, 11, 2, 0, 0, 0);

    var expected_workingDate_tz = new DateTimeOffset(expected_workingDate, tz.GetUtcOffset(expected_workingDate));
    var expected_failingDate_tz = new DateTimeOffset(expected_failingDate, tz.GetUtcOffset(expected_failingDate));

    Assert.AreEqual(expected_workingDate_tz, actual_workingDate_tz, "Should have found the following day's midnight");
    Assert.AreEqual(expected_failingDate_tz, actual_failingDate_tz, "Failing date does not have the correct offset for it's DST");
}

[TestMethod]
public void GetNextDay_LooksRight_FAILTest()
{
    // Here is where the frustrating part is... aparantly the "magic" that DateTimeOffset provides only works for your systems Local Time Zone...

    // { To properly fail this cannot match your system's configured Local Time Zone, I'm in PST so I use EST }
    var tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

    var workingDate = new DateTime(2009, 11, 2, 0, 0, 0);
    var failingDate = new DateTime(2009, 11, 1, 0, 0, 0);

    var workingDate_tz = new DateTimeOffset(workingDate, tz.GetUtcOffset(workingDate));
    var failingDate_tz = new DateTimeOffset(failingDate, tz.GetUtcOffset(failingDate));

    var actual_workingDate_tz = GetNextDay_LooksRight(workingDate_tz);
    var actual_failingDate_tz = GetNextDay_LooksRight(failingDate_tz);

    var expected_workingDate = new DateTime(2009, 11, 3, 0, 0, 0);
    var expected_failingDate = new DateTime(2009, 11, 2, 0, 0, 0);

    var expected_workingDate_tz = new DateTimeOffset(expected_workingDate, tz.GetUtcOffset(expected_workingDate));
    var expected_failingDate_tz = new DateTimeOffset(expected_failingDate, tz.GetUtcOffset(expected_failingDate));

    Assert.AreEqual(expected_workingDate_tz, actual_workingDate_tz, "Should have found the following day's midnight");
    Assert.AreEqual(expected_failingDate_tz, actual_failingDate_tz, "Failing date does not have the correct offset for it's DST");
}
4

1 に答える 1

0

この場合の根本的な問題は、適切なタイムゾーン変換を実行するための十分な情報がないことです。特定の瞬間の特定のオフセットには複数の潜在的なタイムゾーンが存在する可能性があるため、単純なオフセットはタイムゾーンを推測するのに十分なほど具体的ではありません。DateTimeOffsetオブジェクトは、作成されたタイムゾーンに関する情報をキャプチャおよび維持しないため、この関係を維持するには、そのクラスの外部にあるものの責任である必要があります。

私を香りから遠ざけたのは、後で気付いたToLocalTime()の呼び出しでした。これは、実際には、マシンに構成された現地時間の暗黙のTimeZoneInfoを計算に導入することであり、内部的にはDateTimeOffsetがUTCに変換されている必要があると思います。構成されたオフセットを削除し、コンストラクターを使用して新しいDateTimeOffsetクラスを作成し、DateTime[UTC]およびTimeZoneInfo[ローカルシステムから]を取得して、正しいdst対応の結果の日付を生成します。

この制限があるため、DateTimeとTimeZoneInfoの同等に正確でより価値のある組み合わせに対して、DateTimeOffsetクラスに値が表示されなくなりました。

于 2010-01-11T22:02:25.277 に答える