C# DateTime - 将 DateTimeOffset 转换为另一个时区

C# DateTime - converting a DateTimeOffset to another TimeZone

将 DateTimeOffset 转换为另一个 TimeZone 时,OffSet 不正确。

我阅读了很多文章并试验了太多小时,但看不出我在这里遗漏了什么:

// It's June in the UK and we're in British Summer Time, which is 1 hour ahead of UTC (GMT)
var UKoffsetUtc = new TimeSpan(1, 0, 0);

// It's 4pm - declare local time as a DateTimeOffset
var UKdateTimeOffset = new DateTimeOffset(2020, 6, 17, 16, 0, 0, UKoffsetUtc);

// Convert to UTC as a date
var utc = DateTime.SpecifyKind(UKdateTimeOffset.UtcDateTime, DateTimeKind.Utc);

// Get Aus TimeZoneInfo
var AUSTimeZone = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");

// Check the Aus offset from UTC
var AUSOffset = AUSTimeZone.GetUtcOffset(utc);
Console.WriteLine(AUSOffset); // Output is 10 as expected

// Declare Aus Time as DateTimeOffset
var AUSDateTimeOffset = TimeZoneInfo.ConvertTimeFromUtc(utc, AUSTimeZone);

// The Aus Offset from UTC is not correct 
Console.WriteLine(AUSDateTimeOffset.ToString("dd MM yyyy HH:mm zzz"));

输出为 18 06 2020 01:00 +01:00

澳大利亚比世界标准时间早 10 小时(比格林威治标准时间早 9 小时)所以日期和时间是正确的,但不是偏移量。

如何在 AUSDateTimeOffset 中获得正确的偏移量?

错误在这部分:

var AUSDateTimeOffset = TimeZoneInfo.ConvertTimeFromUtc(utc, AUSTimeZone);

在该代码中,utc 是一个 DateTime,因此结果 AUSDateTimeOffset 实际上是一个 DateTime。它的 Kind 将是 DateTimeKind.Unspecified.

转换将正确完成,因此您会在结果中看到正确的日期和时间。但是,偏移量是错误的,因为它不是 DateTime 的一部分。 The documentation about the zzz specifier 说:

With DateTime values, the "zzz" custom format specifier represents the signed offset of the local operating system's time zone from UTC, measured in hours and minutes. It doesn't reflect the value of an instance's DateTime.Kind property. For this reason, the "zzz" format specifier is not recommended for use with DateTime values.

因此,+01:00 来自您当地的时区,而不是目标时区。

有几种方法可以解决此问题:

  • 您可以使用正确的偏移量使 AUSDateTimeOffset 成为 DateTimeOffset

    DateTime AUSDateTime = TimeZoneInfo.ConvertTimeFromUtc(utc, AUSTimeZone);
    TimeSpan AUSOffset = AUSTimeZone.GetUtcOffset(utc);
    DateTimeOffset AUSDateTimeOffset = new DateTimeOffset(AUSDateTime, AUSOffset);
    
  • 您可以使用基于 UTC 的 DateTimeOffset 而不是基于 UTC 的 DateTime:

    DateTimeOffset utc = UKdateTimeOffset.ToUniversalTime();
    DateTimeOffset AUSDateTimeOffset = TimeZoneInfo.ConvertTime(utc, AUSTimeZone);
    
  • 您可以只转换原始 DateTimeOffset,因为不需要先转换为 UTC:

    DateTimeOffset AUSDateTimeOffset = TimeZoneInfo.ConvertTime(UKdateTimeOffset, AUSTimeZone);
    
  • 正如 Jimi 在评论中指出的那样,您甚至可以在根本不构造 TimeZoneInfo 对象的情况下进行转换:

    DateTimeOffset AUSDateTimeOffset = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(UKdateTimeOffset, "AUS Eastern Standard Time");
    

以上任何一项都会给出相同的正确响应。

您可以创建新的偏移量并使用它 -

    // Create new offset for UTC
    var AUSOffset = new DateTimeOffset(utc, TimeSpan.Zero);

    // Declare Aus Time as DateTimeOffset
    var AUSDateTimeOffset = UKdateTimeOffset.ToOffset(AUSTimeZone.GetUtcOffset(AUSOffset));                              
    Console.WriteLine(AUSDateTimeOffset.ToString("dd MM yyyy HH:mm zzz"));

或:

按照 Jimi 在评论中的建议使用 ConvertTimeBySystemTimeZoneId

    var finalDate = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(UKdateTimeOffset, "AUS Eastern Standard Time");
    Console.WriteLine(finalDate.ToString("dd MM yyyy HH:mm zzz"));

出行方便:

var fromTime = DateTimeOffset.Parse("2020-06-17T16:00:00+01:00");
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");//TZConvert.GetTimeZoneInfo("Australia/Sydney");//for Mac
var toTime = fromTime.ToOffset(timeZoneInfo.GetUtcOffset(fromTime.UtcDateTime));
Console.Write(toTime.ToString("yyyy-MM-ddTHH:mm:sszzz")); //2020-06-18T01:00:00+10:00