DateTimeOffset添加TimeSpan为其TimeZoneInfo返回无效的UTC偏移量

时间:2010-01-06 15:24:40

标签: offset dst datetimeoffset timezone

我正在构建一个必须正确全球化的时态表达式库,因此可以在所有可用的时区中工作。

现在我似乎陷入困境,因为我不确定如何在使用任何种类的.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");
}

1 个答案:

答案 0 :(得分:0)

此案例的根本问题是没有足够的信息来执行适当的时区转换。简单偏移不足以推断时区,因为对于特定时刻的给定偏移,可能存在多个潜在时区。由于DateTimeOffset对象不捕获和维护有关时区的信息,因此它必须由该类外部的东西负责维护这种关系。

让我摆脱气味的是调用ToLocalTime(),后来我意识到实际上是在计算中引入了隐含的TimeZoneInfo,为机器配置的本地时间,我相信DateTimeOffset必须在内部转换为UTC只需删除配置的偏移量,然后使用构造函数创建一个新的DateTimeOffset类,使用DateTime [UTC]和TimeZoneInfo [来自本地系统]来生成正确的dst感知结果日期。

鉴于此限制,我不再在DateTimeOffset类中看到DateTime和TimeZoneInfo的同样准确且更有价值的组合中的任何值。