我正在构建一个时间表达式库,该库必须正确全球化,因此可以在所有可用的时区工作。
现在我似乎被卡住了,因为我不确定如何在正确的夏令时 (DST) 中从 DateTimeOffset 对象中检索调整后的日期,当使用任何类型的 .Add 移动天数、小时数等越过过渡边界时。
有趣的是,我想出了一个解决本地系统时区的方法,但还没有找到任何方法将相同的策略应用于任何任意时区。
我能够找到一个片段(没有保留来源,抱歉!)它试图通过偏移量反向查找时区信息,但由于有多个潜在结果,每个结果都可能具有不同的 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");
}