【问题标题】:Problems with TimeZoneInfo and DST in .Net.Net 中 TimeZoneInfo 和 DST 的问题
【发布时间】:2015-06-29 20:04:48
【问题描述】:

我正在使用一个简单的应用程序将一些 Unix 时间戳日期转换为本地时间。我正在打印 UTC 时间和“E. South America Standard Time” -> (GMT-03:00) Brasilia。下面的代码运行良好,但似乎与 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(单声道):

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

对我缺少的东西有什么建议吗?

【问题讨论】:

  • 不是您问题的直接答案,但您看过 NodaTime 吗? (nodatime.org) 我发现它通常更容易使用并且更可靠地处理与本地化时间之间的转换。此外,这些不正确的结果是否涉及最近更改了 DST 规则的位置?可能是一个过时的时区数据库。
  • @Nathan:我只是自己添加的 :)

标签: c# .net mono timezone


【解决方案1】:

好吧,您可能只是错过了这样一个事实,即 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)

【讨论】:

    【解决方案2】:

    我同意 Jon 的观点,即 Noda Time 更适合这种情况。我强烈建议您使用他的实现。

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

    • 在最后一行中,您将dt 变量格式化为字符串。这个变量是DateTime类型,它的.KindDateTimeKind.Unspecified

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

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

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

    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
    

    【讨论】:

    • 按照建议,我将遵循 Jon 的解决方案,但感谢您准确指出我错过的地方!
    猜你喜欢
    • 2021-03-10
    • 2018-10-19
    • 2013-03-05
    • 2017-03-24
    • 1970-01-01
    • 2012-04-27
    • 1970-01-01
    • 2014-01-17
    • 1970-01-01
    相关资源
    最近更新 更多