我正在构建一个必须正确全球化的时态表达式库,因此可以在所有可用的时区中工作。
现在我似乎陷入困境,因为我不确定如何在使用任何种类的.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");
}
答案 0 :(得分:0)
此案例的根本问题是没有足够的信息来执行适当的时区转换。简单偏移不足以推断时区,因为对于特定时刻的给定偏移,可能存在多个潜在时区。由于DateTimeOffset对象不捕获和维护有关时区的信息,因此它必须由该类外部的东西负责维护这种关系。
让我摆脱气味的是调用ToLocalTime(),后来我意识到实际上是在计算中引入了隐含的TimeZoneInfo,为机器配置的本地时间,我相信DateTimeOffset必须在内部转换为UTC只需删除配置的偏移量,然后使用构造函数创建一个新的DateTimeOffset类,使用DateTime [UTC]和TimeZoneInfo [来自本地系统]来生成正确的dst感知结果日期。
鉴于此限制,我不再在DateTimeOffset类中看到DateTime和TimeZoneInfo的同样准确且更有价值的组合中的任何值。