考虑到 SummerTime,将 DateTime 转换为 DateTimeOffset

Convert DateTime to DateTimeOffset taking into account SummerTime

考虑下面的代码。当 CEST 从夏季变为冬季时,我有一个时间点的日期列表。我需要将它们转换为 UTC。但是,使用此代码,午夜会丢失,我无法理解如何修复它。

DateTime firstDate = new DateTime(2020, 10, 24, 21, 0, 0);
DateTime lastDate = new DateTime(2020, 10, 25, 3, 0, 0);
DateTime[] dates = Enumerable.Range(0, (lastDate - firstDate).Hours + 1)
    .Select(r => firstDate.AddHours(r))
    .ToArray();

TimeZoneInfo tzo = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
List<DateTimeOffset> offsets = new List<DateTimeOffset>();
foreach(var date in dates)
{
    var timeSpan = tzo.GetUtcOffset(date);
    var offset = new DateTimeOffset(date, timeSpan);
    offsets.Add(offset);
}
10/24/2020 09:00:00 PM +02:00 = 19:00Z
10/24/2020 10:00:00 PM +02:00 = 20:00Z
10/24/2020 11:00:00 PM +02:00 = 21:00Z
10/25/2020 12:00:00 AM +02:00 = 22:00Z
10/25/2020 01:00:00 AM +02:00 = 23:00Z
10/25/2020 02:00:00 AM +01:00 = 01:00Z - What happened to 00:00Z?
10/25/2020 03:00:00 AM +01:00 = 02:00Z

您正在从我称之为“本地 date/time”的值转换为 UTC 值。由于夏令时转换(和其他时区更改),一些本地 date/time 值被跳过,一些被重复。

在您展示的情况下,当地时间在凌晨 2 点(含)和凌晨 3 点(不含)之间发生两次,因为在凌晨 3 点(第一次)时钟回到凌晨 2 点 - 这行:

10/25/2020 02:00:00 AM +01:00 = 01:00Z

... 显示凌晨 2 点的 second 映射。但是在 2020-10-25T00:00:00Z,由于时钟倒退,当地时间 凌晨 2 点。换句话说,您的转换不明确。

TimeZoneInfo documentation 状态:

If dateTime is ambiguous, or if the converted time is ambiguous, this method interprets the ambiguous time as a standard time.

此处,“标准时间”是任何不明确时间的第二次出现(因为它是从夏令时到标准时间的过渡)。

从根本上说,如果您只有本地值,那么您的信息就不完整。如果您想将不明确的值视为夏令时而不是标准时间(或将它们标记为不明确),您始终可以使用 TimeZoneInfo.IsAmbiguousTime(DateTime) 来检测。

问题是 2020 年 10 月 25 日有两个凌晨 2 点 - 02:00 AM +02:0002:00 AM +01:00

由于源 DateTime 没有可用的偏移量,GetUtcOffset 方法无法确定该值指的是哪个凌晨 2 点,因此它默认使用 non-DST 偏移量- 02:00 AM +01:00.

根据您实际尝试做的事情,您可能能够通过将开始时间转换为 UTC 来解决此问题,生成一个 UTC 列表DateTimeOffset 值,然后将它们转换回所需的 time-zone:

DateTime firstDate = new DateTime(2020, 10, 24, 21, 0, 0);
TimeZoneInfo tzo = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
DateTimeOffset firstDateUtc = TimeZoneInfo.ConvertTime(firstDate, tzo, TimeZoneInfo.Utc);
DateTimeOffset[] utcDates = Enumerable.Range(0, 7).Select(r => firstDateUtc.AddHours(r)).ToArray();
DateTimeOffset[] offsets = Array.ConvertAll(utcDates, d => TimeZoneInfo.ConvertTime(d, tzo));

输出:

24/10/2020 21:00:00 +02:00 
24/10/2020 22:00:00 +02:00 
24/10/2020 23:00:00 +02:00 
25/10/2020 00:00:00 +02:00 
25/10/2020 01:00:00 +02:00 
25/10/2020 02:00:00 +02:00 
25/10/2020 02:00:00 +01:00 

或者,您可能想查看 NodaTime,它提供了一个更清晰的日期和时间模型。