.Net中TimeZoneInfo和DST的问题

时间:2015-06-29 20:04:49

标签: c# .net mono timezone

我正在一个简单的应用程序中将一些Unix时间戳日期转换为本地时间。我打印两者,UTC时间和" E.南美标准时间" - > (格林尼治标准时间-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

另一个Windows 7盒子(.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

对我失踪的事情有任何建议吗?

2 个答案:

答案 0 :(得分:3)

嗯,您可能只是错过了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)

答案 1 :(得分:1)

我同意Jon的观点,Noda Time对于这种情况要好得多。我强烈建议你继续他的实施。

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

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

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

  • 当您使用z应用DateTime格式说明符时,会评估Kind。对于Utc种,它会发出"+0"。对于Local种类,它会为计算机运行的本地时区发出偏移量。对于Unspecified种类,它被视为 local

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

MSDN says this about the z specifier

  

使用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