非区域性不变的 DateTime 值





4.00/5 (8投票s)
2003年9月27日
4分钟阅读

124765

699
演示如何在 C# 中以不受区域性影响的格式表示 DateTime 值。
引言
在使应用程序面向全球时,隔离可本地化资源非常重要,但识别不受区域性影响的资源也同样重要。也就是说,那些无论应用程序在何种区域性下运行都保持相同的资源。让我用一个故事来阐明这一点。
1997 年,我当时正在为油漆零售商开发一款照片级逼真彩色渲染应用程序。然而,当我们在法语或西班牙语版本的 Windows 上测试该应用程序时,所有颜色都渲染成了黑色。调查发现问题出在我们文件数据上。我们将颜色反射率数据以文本格式文件存储(这里只需知道它们包含 16 或 32 个浮点值,例如“(15.362,3.297,...)”)。问题在于我们使用了 VB 的内在库函数来读取这些数据。现在,让我说清楚——问题不在于 VB,而在于我们对这些函数的最初认识。原来 VB 有一些区域性敏感函数,而我们却在使用它们。因此,VB 是根据当前区域性来解析数据的。根本原因是法语和西班牙语使用逗号作为小数点……如果您看看我们的数据格式,就会立刻明白哪里出了错!
我们考虑了两种潜在的解决方案:
- 将分隔符从逗号更改为某种其他字符,这种字符没有区域性会将其用作小数点,例如波浪号(~)。这个解决方案本可以正常工作,因为当区域性特定的小数点失败时,VB 库会优雅地回退到英语小数点。但是,我们不能这样做,因为我们会拥有两种文件格式实现:英语和非英语……这不好。
- 我们编写了自己的字符串解析器。这意味着比本应需要的开发工作量和测试要多得多,但它确保了我们的文件在任何地方都有效。
我讲这个故事是为了说明不解决全球化问题可能带来的痛苦的真实例子。
不受区域性影响的 DateTime
DateTime
值很难以真正一致、可靠的方式进行处理。它们的表示形式最为多变。我不打算深入讨论这些变化,但请看以下列表,其中显示了我撰写本文时当前 LongDateTime
的各种表示形式:
en-GB = 2003 年 9 月 27 日 16:34
en-US = 2003 年 9 月 27 日星期六下午 4:34
fr-FR = 2003 年 9 月 27 日星期六 16 h 34
fr-CA = 2003 年 9 月 27 日 16:34
这里有两个因素需要考虑:
- 如上所示的日期时间值并不完全表达我们需要的所有信息。为什么?因为为了完整地表达信息,我们缺少
TimeZone
数据。我正在英国写这篇文章。这个表示完全取决于我当时处于英国夏令时(BST)时区的事实。如果我在纽约显示这些数据,除非我考虑到时差,否则它会相差 5 个小时而不准确。事实上,如果时间是凌晨 1:34,那么不仅时间会不准确,日期部分(依此类推,月份和年份部分)也会不准确。 - 表示形式是区域性特定的。在上面的示例中,如果您愿意,可以推断出区域性。但是,请考虑经典的美国-英国日期问题:01/02/2003 是 2 月 1 日(英国)还是 1 月 2 日(美国)?
解决这些问题的方法是以一种*不受区域性影响*的方式表示 DateTime
值。您可以通过始终以指定的区域性格式存储该值来实现此目的,例如 en-US。但是,这不符合标准,并且要求您的数据的第三方使用者了解其区域性特异性。对此的答案是使用标准的、不受区域性影响的格式。有许多此类格式,例如 NATO 格式(我认为一个例子是 01 JAN 2003 16:34:00),但最好的选择是使用 ISO 8601 标准。有关此标准的讨论可以在 W3C、剑桥大学和 惠灵顿大学找到。在此标准格式中,日期时间字符串将是 2003-09-27T16:34:00。此外,此标准提供了一种表示时区的机制。使数据完全不受区域性影响的最简单方法是将其转换为格林威治标准时间 (GMT),用“Z”后缀表示。因此,此帖子的日期将表示为 2003-09-27T15:34:00Z。
所以,这听起来像是一项相当大的工作。
但不是!我们正在使用 .NET,所以我们的生活很轻松。
using System.Globalization;
//...
DateTime _datetime = DateTime.Now;
string _formattedDateTime = _datetime.ToUniversalTime().ToString("s",
DateTimeFormatInfo.InvariantInfo) + "Z";
实际上,唯一令人头疼的是,DateTime
类似乎并不原生支持 ISO 8601 的时区要求(除非有人能告诉我怎么做……),这就是为什么我在上面的代码中附加了“Z”。