c#daylight savings重复小时转换为UTC

我正在使用TimeZoneInfo在客户端wallclock’Eastern Time’和UTC之间进行转换。 我的问题是在秋季DST更改期间发生的“重复”小时。

在从UTC转换到东部期间:
2010-11-07 06:00 UTC – > 2010-11-07T 01 :00:00-03:30
2010-11-07 07:00 UTC – > 2010-11-07T 01 :00:00-03:30
我怎么知道哪个是第一个小时,哪个是第二个小时? DateTime.IsDaylightSavingTime()在两个小时都返回false,但是它不应该在第一个小时返回true吗?

同样,我如何存储2010-11-07 01:00:00 -03:30? 我的应用程序如何转换为UTC,因为它可能是2010-11-07 06:00或2010-11-07 07:00

对于那些需要代码的人,我骑自行车穿过一个带有UTC日期时间列的数据表,尝试使用’DupHr’列转换为东部第二个重复小时,但我总是在01:00时结束’DupHr’= 1。

TimeZoneInfo est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); DateTime EasternTime; DateTime DuplicateHour = new DateTime(2010, 11, 7, 1, 0, 0); // hard coded for this example TimeZoneInfo.AdjustmentRule[] rules = est.GetAdjustmentRules(); foreach (DataRow row in dt.Rows) { row["DupHr"] = 0; // by default not duplicate hour EasternTime = TimeZoneInfo.ConvertTimeFromUtc((DateTime)row[UTCColumnName], est); if (!EasternTime.IsDaylightSavingTime()) { if (EasternTime.Equals(DuplicateHour )) { row["DupHr"] = 1; // This is the second duplicate hour ! } } else EasternTime.Add(rules[1].DaylightDelta); // Add DST offset from rule #1 row[newESTColumnName] = EasternTime; } 

谢谢!

重要的是要知道重复小时的“模棱两可”。 小时必须是唯一的(第一和第二)。 考虑一个收费站货币柜台应用程序,它必须全天候运行并累计每小时的收集。 每小时收集的钱必须是可识别的。 第一小时1:00至1:59与第二小时1:00至1:59小时不同。 以下例程isSecondHour仅在通过时间为秋季DST变化的第二小时时才返回true。 用户界面可以适当地显示该标志。

  // Get the DST rule for the year and zone (rules may change from year to year as in 2004) public static TimeZoneInfo.AdjustmentRule GetDSTrule(int Year, TimeZoneInfo zone) { TimeZoneInfo.AdjustmentRule[] rules = zone.GetAdjustmentRules(); foreach (TimeZoneInfo.AdjustmentRule rul in rules) { if (rul.DateStart  new DateTime(Year, 1, 1)) { return rul; } } return null; } // Determine if 'localtime' is in the second duplicate DST hour. public static Boolean isSecondHour(TimeZoneInfo localzone, DateTime localtime, DateTime UTCtime) { if (localzone.IsAmbiguousTime(localtime)) { TimeZoneInfo.AdjustmentRule rul = GetDSTrule(localtime.Year, localzone); return UTCtime.Add(localzone.GetUtcOffset(localtime)) == localtime; } else return false; } static void Main(string[] args) { var est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); var times = new DateTime[] { new DateTime (2010, 11, 7, 3, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 4, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 5, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 5, 30, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 6, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 6, 30, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 7, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 8, 0, 0, DateTimeKind.Unspecified) }; DateTime EasternTime; Console.WriteLine("UTC Time | Est Time | IsDaylightSaving | IsAmbiguousTime | isSecondHour "); foreach (var utc in times) { // Get Eastern Time from UTC using standard convert routine. EasternTime = TimeZoneInfo.ConvertTimeFromUtc(utc, est); Console.WriteLine("{0:HH:mm} | {1:HH:mm} | {2,11} | {3,5} | {4,5}", utc,EasternTime, est.IsDaylightSavingTime(EasternTime), est.IsAmbiguousTime(EasternTime),isSecondHour(est,EasternTime, utc)); } 

结果

 UTC Time | Est Time | IsDaylightSaving | IsAmbiguousTime | isSecondHour 03:00 | 23:00 | True | False | False 04:00 | 00:00 | True | False | False 05:00 | 01:00 | False | True | False 05:30 | 01:30 | False | True | False 06:00 | 01:00 | False | True | True 06:30 | 01:30 | False | True | True 07:00 | 02:00 | False | False | False 08:00 | 03:00 | False | False | False 

摘要

您无法知道,因为没有存储偏移量,您丢失了一条重要信息,即时间最初所在的时区,正如您所指出的那样,可能是东部标准时间或东部夏令时。

检测模糊的时间

TimeZoneInfo提供方法IsAmbiguousTime来检查是否可能是这种情况。

您检测到这个模糊时间的问题是您正在尝试使用IsDaylightSavings ,它会在不明确的时间返回false,如下例所示:

 var est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); var times = new DateTime[] { new DateTime (2010, 11, 7, 4, 0, 0, DateTimeKind.Utc), new DateTime (2010, 11, 7, 5, 0, 0, DateTimeKind.Utc), new DateTime (2010, 11, 7, 5, 30, 0, DateTimeKind.Utc), new DateTime (2010, 11, 7, 6, 0, 0, DateTimeKind.Utc), }; Console.WriteLine(" Time | IsDaylightSaving | IsAmbiguousTime"); foreach (var t in times) { var time = TimeZoneInfo.ConvertTimeFromUtc(t, est); Console.WriteLine (" {0:HH:mm} | {1,11} | {2,5}", time, est.IsDaylightSavingTime(time), est.IsAmbiguousTime(time)); } 

结果:

  Time | IsDaylightSaving | IsAmbiguousTime 00:00 | True | False 01:00 | False | True 01:30 | False | True 01:00 | False | True 

所以你想要使用est.IsAmbiguousTime(EasternTime) 。 然后就不需要DuplicateHour因为这将覆盖当天模糊的全部时间范围。 DateTimeOffset由于显式存储偏移量而不会遇到此问题。

将EST转换为UTC并存储在数据库中

对于从EST到UTC的初始转换,数据库中的现有数据将希望存储偏移量以供将来使用。 对于非模糊时间,可以从时区检索。 但是,正如您所指出的那样,由于模糊不清,这些信息将无法使用。 对于这些时间,您将不得不假设使用哪个偏移量并将数据库中的时间标记为可疑,因此UI在显示这些时间时可以做出相应的反应。

根据受影响的数据量,可能不值得更改UI并简单地忽略问题,特别是如果时间超过一小时对用户来说真的不重要(因为在用户的上面)屏幕在那个时区它仍将显示为凌晨1点)。 如果您以后改变主意,数据库仍会记录时间是可疑的。

从UTC转换为EST并检测模糊时间

首先,使用DateTimeOffset,因为这可以区分美国东部时间凌晨1点和美国东部标准时间凌晨1点之间的区别。 此时TimeZoneInfo.IsAmbiguousTime(DateTimeOffset)可用于突出显示屏幕上的重复次数, TimeZoneInfo.IsDaylightSavings(DateTimeOffset)也将正确返回true或false。

 var est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); var times = new DateTimeOffset[] { new DateTimeOffset (2010, 11, 7, 4, 00, 0, TimeSpan.Zero), new DateTimeOffset (2010, 11, 7, 5, 00, 0, TimeSpan.Zero), new DateTimeOffset (2010, 11, 7, 5, 30, 0, TimeSpan.Zero), new DateTimeOffset (2010, 11, 7, 6, 00, 0, TimeSpan.Zero), }; Console.WriteLine(" Time | IsDaylightSaving | IsAmbiguousTime"); foreach (var t in times) { var time = TimeZoneInfo.ConvertTime (t, est); Console.WriteLine (" {0:HH:mm} | {1,11} | {2,5}", time, est.IsDaylightSavingTime(time), est.IsAmbiguousTime(time)); } 

结果:

  Time | IsDaylightSaving | IsAmbiguousTime 00:00 | True | False 01:00 | True | True 01:30 | True | True 01:00 | False | True 

未来的考虑因素

用户界面问题

当向用户显示时,本地时间是否模糊(重复小时)无关紧要。 您只需将UTC时间转换为其时区并将其格式化为字符串即可。 您可能需要检查IsAmbiguousTime以向用户显示提示他们可能两次看到“1am”的原因。 按日期对信息进行排序应使用UTC完成。 从UTC到本地时间永远不应该是模棱两可的,因为每个时间点在UTC中只存在一次,没有重复的小时数。

因此,现在唯一的问题是,如果用户输入时间并且您需要解释他们的意思时间,因为用户不太可能输入偏移量或甚至不关心此类详细信息。 遗憾的是,没有简单的方法可以解决这个问题,并且没有尝试向用户讲述偏移,他们会犯错并输入错误的时间。 例如,他们可能会在午夜4点半之后进入凌晨4点,忘记那天晚上还有1小时/更少。 或者,当时钟在凌晨3点前进时,它们可以在凌晨3点进入,在那一天是一个根本不存在的时间。

幸运的是,时钟改变的时间旨在最大限度地减少用户输入问题,因为大多数人都睡着了。 所以系统可以采取最好的猜测,并接受有时一小时的外出。 如果确实很重要,那么您可以检查当天是否有夏令时并显示具有警告/提示的不同UI。

存储和转移

如果将时间存储在MSSQL服务器中,则应首选datetimeoffset,因为这可以处理存储时间和偏移量。 使用此类型时,MSSQL服务器可以正确处理具有不同偏移的时间。

对于不支持此类型的数据库,您可以将时间以UTC格式存储在数据库中,并将该时间的偏移量存储在单独的列中。 这样您就可以准确地知道记录的当地时间。

在与外部系统交换时,理想情况下将时间转换为本地格式为yyyy-MM-dd HH:mm:sszzzz (例如2010-11-07 01:00:00-03:30 ),以便时间和偏移都可以保存。 否则UTC通常是最好的选择,但理想情况下应该以’Z’或’+00:00’为后缀,以使其显而易见。

在内存中,DateTimeOffset类是更好的选择,因为它可以表示与DateTime相比的任何仲裁偏移,而DateTime只能表示UTC或系统的本地时间。


请注意,TimeZoneInfo夏令时的准确性取决于所应用的操作系统版本,Service Pack和Windows更新。

此外,重要的是如何应用日光节约。 如果操作系统使用“自动调整夏令时”来应用它们,则可以正确调整偏移。 如果管理员已禁用此function并通过添加/减去一小时来手动调整时间,则操作系统将不会意识到这一点,并且将使用错误的偏移量进行操作。 有关此OS设置的其他说明,请参阅TimeZoneInfo.Local 。

所以在讨论之后,我现在有两个例程,一个用于指示UTC时间是否是我的本地时区的’Duplicate’小时以及将Eastern转换为UTC的例程。 检索数据时,客户端应用程序可以利用isSecondHour ‘例程来适当地显示时间。 同样,在将时间保存到服务器时,客户端必须提供second_DST_hour标志以及本地时间,以便将它们转换为UTC。

  // Determine if 'localtime' is in the second duplicate DST hour. public static Boolean isSecondHour(TimeZoneInfo localzone, DateTime localtime, DateTime UTCtime) { if (localzone.IsAmbiguousTime(localtime)) { // UTC time + UTC offset = second hour time (not first hour) return UTCtime.Add(localzone.GetUtcOffset(localtime)) == localtime; } else return false; } // Convert Local time to UTC, with 'SecondDST' indicating if hour is the second hour of autumn DST change. public static DateTime Convert_to_UTC(TimeZoneInfo localzone, DateTime localtime, Boolean SecondDST) { DateTime newUTC = TimeZoneInfo.ConvertTimeToUtc(localtime, localzone); if (localzone.IsAmbiguousTime(localtime) && !SecondDST) { TimeZoneInfo.AdjustmentRule rul = GetDSTrule(localtime.Year, localzone); return newUTC.Add(-rul.DaylightDelta); } else return newUTC; } // Get the DST rule for the year and zone (rules may change from year to year as in 2004) public static TimeZoneInfo.AdjustmentRule GetDSTrule(int Year, TimeZoneInfo zone) { TimeZoneInfo.AdjustmentRule[] rules = zone.GetAdjustmentRules(); foreach (TimeZoneInfo.AdjustmentRule rul in rules) { if (rul.DateStart < new DateTime(Year, 1, 1) && rul.DateEnd > new DateTime(Year, 1, 1)) { return rul; } } return null; } 

然后使用这些:

  var est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); var times = new DateTime[] { new DateTime (2010, 11, 7, 3, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 4, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 5, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 5, 30, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 6, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 6, 30, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 7, 0, 0, DateTimeKind.Unspecified), new DateTime (2010, 11, 7, 8, 0, 0, DateTimeKind.Unspecified) }; // ------------------ UTC to Eastern DateTime EasternTime; Console.WriteLine("UTC Time | Est Time | IsDaylightSaving | IsAmbiguousTime | isSecondHour "); foreach (var utc in times) { // Get Eastern Time from UTC using standard convert routine. EasternTime = TimeZoneInfo.ConvertTimeFromUtc(utc, est); Console.WriteLine("{0:HH:mm} | {1:HH:mm} | {2,11} | {3,5} | {4,5}", utc,EasternTime, est.IsDaylightSavingTime(EasternTime), est.IsAmbiguousTime(EasternTime),isSecondHour(est,EasternTime, utc)); } // ------------------ Eastern to UTC DateTime testTime; Console.WriteLine("UTC Time | Est Time | IsDaylightSaving | IsAmbiguousTime | isSecondHour "); EasternTime = new DateTime(2010, 11, 7, 1, 30, 0, DateTimeKind.Unspecified); // First Hour of DST testTime = Convert_to_UTC (est, EasternTime,false); Console.WriteLine("{0:HH:mm} | {1:HH:mm} | {2,11} | {3,5} | {4,5}", testTime, EasternTime, est.IsDaylightSavingTime(EasternTime), est.IsAmbiguousTime(EasternTime), isSecondHour(est, EasternTime, testTime)); // Second Hour of DST testTime = Convert_to_UTC(est, EasternTime, true); Console.WriteLine("{0:HH:mm} | {1:HH:mm} | {2,11} | {3,5} | {4,5}", testTime, EasternTime, est.IsDaylightSavingTime(EasternTime), est.IsAmbiguousTime(EasternTime), isSecondHour(est, EasternTime, testTime));