【问题标题】:Convert UTC time to local country time that actually works将 UTC 时间转换为实际有效的当地国家时间
【发布时间】:2020-04-28 09:07:24
【问题描述】:

如果我有一些 UTC 时间,我如何在 C# 中获得相应的波兰当地时间?还是在法国?还是在厄瓜多尔? (我知道这对于像俄罗斯这样多次使用的大国来说没有意义)

在大多数现有示例中,人们使用TimeZoneInfo 对象:

var zone = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time");

问题是这还不够。许多国家/地区都有“夏令时”或“daylight saving time”之类的东西,它们根据一年中的某一天甚至确切的时间(在时间变化的当天)使用与 UTC 不同的时间偏移量。

例如,与其知道我是否需要Central Daylight TimeCentral Standard Time 选项,我只会知道目标时间是针对芝加哥的。我希望能够有一个字符串,让平台为我决定这两个偏移中哪一个是正确的。

是否有任何 C# 内置函数可以处理这个问题,或者我是否需要根据所选国家/地区的法律自己处理夏令时开始和结束的所有计算?

【问题讨论】:

  • 例如,您可以使用 CovertTime。请注意,它需要一个 Datetime 参数:您需要一个日期和一个时间来正确转换(因此它可以考虑日光节约等)
  • I want to be able to pick America/Chicago and let it handle that for me. 一个有效的请求,但唯一的方法是使用 NodaTime 而不是内置类型。首先,Windows 不使用 IANA 时区名称。 NodaTime 可以,甚至允许加载一个新的 tzdb 来替换它编译时使用的那个
  • @jdweng 在一个网站上,你有 UTC 时间在服务器上,你想在用户的首选时间呈现,但你只知道他们的城市和国家?
  • @jdweng:这取决于你的架构。十年前的 Web 应用程序将呈现 HTML 供浏览器显示,因此它们必须有一个与用户相关联的时区,服务器可以知道该时区。更现代的 Web 应用程序倾向于使用 JSON 来传输数据,并具有可以呈现模板以生成 HTML 式 DOM 的客户端 JavaScript 代码。 JavaScript 可以访问用户机器上配置的本地时区信息。
  • @jdweng 不一定。想象一个应用程序,您在其中协调跨时区的会议,您在伦敦,但在巴黎或罗马有与会者。无论如何,问题的整个前提是用户希望在 C# 中执行此操作,而不是 javascript。

标签: c# .net datetime timezone


【解决方案1】:

夏令时之类的时间会根据日期而变化。因此,如果您使用时区来转换时间,则必须提供您尝试转换的完整 DateTime,并且 .NET 将处理确定 DST 是否处于活动状态并进行适当的计算。

以下示例强调了这一点:亚利桑那州和犹他州在美国采用“山地时间”,但亚利桑那州不像美国大多数州那样实行夏令时。因此,在冬季,这两个时区具有相同的偏移量,但在夏季,它们具有不同的偏移量。

var arizona = TimeZoneInfo.FindSystemTimeZoneById("US Mountain Standard Time");
var utah = TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time");
var time1 = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var time2 = new DateTime(2020, 7, 1, 0, 0, 0, DateTimeKind.Utc);
Console.WriteLine(TimeZoneInfo.ConvertTime(time1, arizona).ToString());
Console.WriteLine(TimeZoneInfo.ConvertTime(time1, utah).ToString());
Console.WriteLine(TimeZoneInfo.ConvertTime(time2, arizona).ToString());
Console.WriteLine(TimeZoneInfo.ConvertTime(time2, utah).ToString());

输出:

2019 年 12 月 31 日下午 5:00:00
2019 年 12 月 31 日下午 5:00:00
2020 年 6 月 30 日下午 5:00:00
2020 年 6 月 30 日下午 6:00:00

注意:自从我第一次看到您的问题以来,Joel Coehoorn 添加了以下内容:

例如,我不想知道我是否需要Central Daylight TimeCentral Standard Time 选项,而是希望能够选择America/Chicago 并让它为我处理。

我不知道这是否是您的问题的意图。如果是,that's another question entirely

【讨论】:

    【解决方案2】:

    是否有任何 C# 内置函数可以处理这个问题?

    很遗憾,没有。

    我是否需要根据所选国家/地区的法律自行处理夏令时开始和结束的所有计算?

    谢天谢地,也没有。您可以使用名为NodaTime 的高质量开源库,您可以通过NuGet 将其包含在您的项目中。

    不过,这确实要求您能够为每个位置找到正确的 IANA timezone name(请参阅链接表中的“Tz 数据库名称”列)。

    【讨论】:

    • 你能添加一些源代码来展示 NodaTime 将如何解决这个问题吗?
    • @StriplingWarrior 我本来打算的,但该项目几乎在首页上展示了这一点。
    • 我认为 StackOverflow 鼓励在可行的情况下直接在答案中包含相关的代码示例和信息。
    • 你答案的第一部分不正确。内置的 TimeZoneInfo 对象正确处理所有 DST 计算。混淆可能在于其中包含“标准”一词的标识符名称,尽管它适用于整个时区。
    • 不。偏移量将根据从同一 TimeZoneInfo 对象传递给每个函数的日期而有所不同。见dotnetfiddle.net/rakVrI
    【解决方案3】:

    如果我有一些 UTC 时间,我如何在 C# 中获得相应的波兰当地时间?还是在法国?还是在厄瓜多尔?

    在 Windows 上:

    var poland = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Central European Standard Time");
    var france = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Romance Standard Time");
    var ecuador1 = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "SA Pacific Standard Time"); // (Mainland)
    var ecuador2 = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Central America Standard Time"); // (Galapagos Is.)
    

    在 Linux、OSX 和其他 .NET Core 平台上:

    var poland = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Europe/Warsaw");
    var france = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Europe/Paris");
    var ecuador1 = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "America/Guayaquil"); // (Mainland)
    var ecuador2 = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Pacific/Galapagos"); // (Galapagos Is.)
    

    (注意Ecuador has two time zones,大陆一个,加拉帕戈斯群岛一个。)

    CLDR windowsZones.xml file 是确定要使用哪个 Windows ID 的一个很好的参考,它是在 Windows 和 IANA 时区标识符之间转换的真实来源。

    (我知道这对于像俄罗斯这样多次使用的大国来说没有意义)

    确实,俄罗斯有许多个不同的时区标识符。人们必须知道他们对俄罗斯的哪个部分感兴趣。

    在大多数现有示例中,人们使用TimeZoneInfo 对象:

    var zone = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time");
    

    问题是这还不够。很多国家都有东西 比如“夏令时”或“daylight saving time”,他们 根据一年中的哪一天使用与 UTC 不同的时间偏移量 甚至是准确的时间(在时间变化的当天)。

    例如,我不想知道我是否需要Central Daylight TimeCentral Standard Time 选项,而是希望能够选择 America/Chicago 让它为我处理。

    不,这不是问题。您可能对该要求感到困惑,因为“标准”一词在标识符的名称中。尽管如此,TimeZoneInfo 对象仍代表整个时区,包括标准时间和夏令时。

    为了澄清,TimeZoneInfo.FindSystemTimeZoneById 方法需要一个标识符。结果对象的Id 属性将包含该标识符。

    • "America/Chicago" 是 IANA 时区标识符。
    • "Central Standard Time" 既是 Windows 时区标识符,又是同一时区的英文本地化“标准名称”(在 Windows 上)。
    • "Central Daylight Time" 是该区域的英文本地化“日光名称”(在 Windows 上)。它不是有效的时区标识符。

    是否有任何 C# 内置函数可以处理这个问题,或者我需要 处理夏令时开始和结束的所有计算 根据自己选择的国家的法律?

    所有内置计算都处理这个问题。只传递有效的标识符,框架将确定与 UTC 的正确偏移量,无论是标准时间还是夏令时/夏令时有效。

    这是一个演示这些概念的working example

    TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time"); // Use "America/Chicago" on non-Windows systems
    
    Console.WriteLine("Id:             " + tzi.Id);
    Console.WriteLine("DisplayName:    " + tzi.DisplayName);
    Console.WriteLine("StandardName:   " + tzi.StandardName);
    Console.WriteLine("DaylightName:   " + tzi.DaylightName);
    Console.WriteLine();
    
    DateTime winterDate = new DateTime(2020, 1, 1);
    DateTime summerDate = new DateTime(2020, 7, 1);
    
    TimeSpan winterOffset = tzi.GetUtcOffset(winterDate);
    TimeSpan summerOffset = tzi.GetUtcOffset(summerDate);
    
    bool winterIsDst = tzi.IsDaylightSavingTime(winterDate);
    bool summerIsDst = tzi.IsDaylightSavingTime(summerDate);
    
    Console.WriteLine("Winter Offset:  " + winterOffset + "  IsDST:  " + winterIsDst);
    Console.WriteLine("Summer Offset:  " + summerOffset + "  IsDST:  " + summerIsDst);
    

    输出:

    Id:             Central Standard Time
    DisplayName:    (UTC-06:00) Central Time (US & Canada)
    StandardName:   Central Standard Time
    DaylightName:   Central Daylight Time
    
    Winter Offset:  -06:00:00  IsDST:  False
    Summer Offset:  -05:00:00  IsDST:  True
    

    最后,如果您希望能够在任何操作系统上使用任何一组标识符,您可以通过两种不同的方式来处理它:

    • 您可以使用我的TimeZoneConverter 库检索TimeZoneInfo。例如:

      // Either of these will work on any platform:
      TimeZoneInfo tzi = TZConvert.GetTimeZoneInfo("America/Chicago");
      TimeZoneInfo tzi = TZConvert.GetTimeZoneInfo("Central Standard Time");
      
    • 您可以使用Noda Time 代替TimeZoneInfoZonedDateTimeProviders 用于 Bcl (Windows) 和 Tzdb (IANA) 标识符。

      // This will work on any platform:
      DateTimeZone dtz = DateTimeZoneProviders.Tzdb["America/Chicago"];
      
      // This will work with Windows on .NET Framework only
      DateTimeZone dtz = DateTimeZoneProviders.Bcl["Central Standard Time"];
      

    您可能还想在 Stack Overflow 上阅读 the timezone tag wiki。尤其是标题为“时区数据库”的部分。

    【讨论】:

    • 俄罗斯也经常更改时区,这使得 NodaTime 和 tzdb far 比试图找出今年这个或那个机场的时区更稳定。如果它们在 2 周后发生变化,请注意埃及几年前的做法,您只需将新的 tzdb 加载到 NodaTime
    • 确实short notice changes are problematic 用于两个数据集。没错,Noda Time 让开发人员可以更好地控制何时更新。使用TimeZoneInfo,数据来自底层操作系统,并且无法始终控制应用这些更新的时间(例如 Azure 应用服务)。
    • 我要为您提供如此清晰完整的答案而鼓掌。
    • 感谢您的详尽解答,并指出野田时间图书馆。
    猜你喜欢
    • 2021-03-20
    • 1970-01-01
    • 2016-02-22
    • 2015-11-22
    • 2015-05-20
    • 2015-09-26
    • 1970-01-01
    • 2011-03-25
    • 2022-12-05
    相关资源
    最近更新 更多