中的TimeZoneInfo和DST存在问题网

本文关键字:问题 存在 DST TimeZoneInfo 中的 | 更新日期: 2023-09-27 18:00:33

我正在一个简单的应用程序中工作,将一些Unix时间戳日期转换为本地时间。我正在打印UTC时间和"东南美洲标准时间"->(GMT-03:00)巴西利亚。下面的代码运行良好,但似乎与DST:有关

    public static void Main (string[] args)
    {
        long[] timestamps = {1413685800L, 1413689400L, 1424568600L, 1424572200L, 1424575800L};
        string formatUtc = "{0:dd MMM yyyy HH:mm:ss}";
        string formatLocal = "{0:dd MMM yyyy HH:mm:ss z}";
        TimeZoneInfo tzBr = null;
        tzBr = TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time");
        DateTime dt;
        Console.WriteLine("UTC't't't'tAmerica/Sao_Paulo");                     
        Console.WriteLine("---------------------------------------------------------");

        foreach (long ts in timestamps) {
            dt = new DateTime(1970,1,1,0,0,0,0,System.DateTimeKind.Utc).AddSeconds(ts);
            Console.Write(string.Format(formatUtc, dt));
            dt = TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Utc, tzBr);
            Console.WriteLine("'t't" + string.Format(formatLocal, dt));
        }
    }

我在三台不同的机器上测试了这个代码,得到了以下结果:

Windows 7(.Net):

    UTC                         America/Sao_Paulo
---------------------------------------------------------
19 out 2014 02:30:00            18 out 2014 23:30:00 -3
19 out 2014 03:30:00            19 out 2014 01:30:00 -2
22 fev 2015 01:30:00            21 fev 2015 23:30:00 -3 <- Wrong!
22 fev 2015 02:30:00            21 fev 2015 23:30:00 -3
22 fev 2015 03:30:00            22 fev 2015 00:30:00 -3

另一个Windows7盒子(.Net):

UTC                             America/Sao_Paulo
---------------------------------------------------------
19 out 2014 02:30:00 -3         18 out 2014 23:30:00 -3
19 out 2014 03:30:00 -3         19 out 2014 01:30:00 -3 <- Wrong!
22 fev 2015 01:30:00 -3         21 fev 2015 23:30:00 -3 <- Wrong!
22 fev 2015 02:30:00 -3         21 fev 2015 23:30:00 -3
22 fev 2015 03:30:00 -3         22 fev 2015 00:30:00 -3

Linux Fedora 22(Mono):

UTC                             America/Sao_Paulo
---------------------------------------------------------
19 out 2014 02:30:00            18 out 2014 23:30:00 -3
19 out 2014 03:30:00            19 out 2014 01:30:00 -2
22 fev 2015 01:30:00            21 fev 2015 22:30:00 -2 <- Wrong!
22 fev 2015 02:30:00            21 fev 2015 23:30:00 -2 <- Wrong!
22 fev 2015 03:30:00            22 fev 2015 00:30:00 -3

Java应用程序的预期结果(BRT表示-3,BRST表示-2):

UTC                             America/Sao_Paulo
---------------------------------------------------------
19 Out 2014 02:30:00 UTC        18 Out 2014 23:30:00 BRT
19 Out 2014 03:30:00 UTC        19 Out 2014 01:30:00 BRST
22 Fev 2015 01:30:00 UTC        21 Fev 2015 23:30:00 BRST
22 Fev 2015 02:30:00 UTC        21 Fev 2015 23:30:00 BRT
22 Fev 2015 03:30:00 UTC        22 Fev 2015 00:30:00 BRT

对我遗漏的东西有什么建议吗?

中的TimeZoneInfo和DST存在问题网

您可能只是忽略了这样一个事实,即Windows时区数据与Java使用的IANA数据不同,并且您的两个Windows 7框可能应用了一组不同的Windows更新。恐怕我不想猜测Mono到底在用什么。

你可能想考虑的一个选项是使用我的Noda Time库,它使用IANA数据(并允许你使用你想要的任何版本的数据),以及通常更好的API,IMO

using System;
using NodaTime;
using NodaTime.Text;
class Test
{
    public static void Main (string[] args)
    {
        long[] timestamps = {1413685800L, 1413689400L, 1424568600L, 1424572200L, 1424575800L};
        var zone = DateTimeZoneProviders.Tzdb["America/Sao_Paulo"];
        var instantPattern = InstantPattern.CreateWithInvariantCulture("dd MMM yyyy HH:mm:ss");
        var zonedPattern = ZonedDateTimePattern.CreateWithInvariantCulture
            ("dd MMM yyyy HH:mm:ss o<g> (x)", null);
        foreach (long ts in timestamps) {
            var instant = Instant.FromSecondsSinceUnixEpoch(ts);
            var zonedDateTime = instant.InZone(zone);            
            Console.WriteLine("{0} UTC - {1}",                              
                instantPattern.Format(instant),
                zonedPattern.Format(zonedDateTime));
        }
    }
}

输出:

19 Oct 2014 02:30:00 UTC - 18 Oct 2014 23:30:00 -03 (BRT)
19 Oct 2014 03:30:00 UTC - 19 Oct 2014 01:30:00 -02 (BRST)
22 Feb 2015 01:30:00 UTC - 21 Feb 2015 23:30:00 -02 (BRST)
22 Feb 2015 02:30:00 UTC - 21 Feb 2015 23:30:00 -03 (BRT)
22 Feb 2015 03:30:00 UTC - 22 Feb 2015 00:30:00 -03 (BRT)

我同意Jon的观点,Noda Time在这种情况下要好得多。我强烈建议你配合他的实施。

然而,只是为了解释你的结果:

  • 在最后一行中,将dt变量格式化为字符串。此变量为DateTime类型,其.KindDateTimeKind.Unspecified

  • formatLocal格式化程序包含用于返回时区偏移量的z令牌。

  • z格式说明符与DateTime一起应用时,将计算Kind。对于Utc类,它发射"+0"。对于Local类型,它发出计算机运行的本地时区的偏移量。对于Unspecified类,它被视为局部

因此,偏移量不一定来自您转换到的时区,而是来自您本地计算机的时区!

MSDN这样评价z说明符:

DateTime值的情况下;z";自定义格式说明符表示本地操作系统时区与协调世界时(UTC)的带符号偏移量,以小时为单位。它不反映实例的DateTime.Kind属性的值因此;z";不建议将格式说明符与DateTime值一起使用

对于DateTimeOffset values,此格式说明符表示DateTimeOffset值相对于UTC的偏移量(以小时为单位)。

这个措辞有点不正确,因为DateTimeKind.Utc确实返回了"+0",但我认为你明白了。您应该使用DateTimeOffset

DateTimeOffset epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero);
foreach (long ts in timestamps)
{
    DateTimeOffset dto = epoch.AddSeconds(ts);
    Console.Write(formatUtc, dto);
    dto = TimeZoneInfo.ConvertTime(dto, tzBr);
    Console.WriteLine("'t't" + formatLocal, dto);
}
UTC                             America/Sao_Paulo
---------------------------------------------------------
19 Oct 2014 02:30:00            18 Oct 2014 23:30:00 -3
19 Oct 2014 03:30:00            19 Oct 2014 01:30:00 -2
22 Feb 2015 01:30:00            21 Feb 2015 23:30:00 -2
22 Feb 2015 02:30:00            21 Feb 2015 23:30:00 -3
22 Feb 2015 03:30:00            22 Feb 2015 00:30:00 -3