将UTC/GMT时间转换为本地时间

350

我们正在开发一个C#应用程序,用于Web服务客户端。该程序将在Windows XP个人电脑上运行。

Web服务返回的字段之一是DateTime字段。服务器以GMT格式返回字段,即在末尾带有“Z”。

然而,我们发现.NET似乎进行了某种隐式转换,时间总是相差12小时。

以下代码示例在某种程度上解决了这个问题,即12小时的差异已经消失,但它没有考虑到新西兰夏令时。

CultureInfo ci = new CultureInfo("en-NZ");
string date = "Web service date".ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            

根据此日期网站

UTC/GMT 偏移量

标准时区:UTC/GMT +12小时
夏令时:+1小时
当前时区偏移量:UTC/GMT +13小时

如何调整这个额外的一小时?这可以通过程序自动完成吗?还是需要在电脑上进行某种设置?


2
“Z”时间指的是UTC时间,而非GMT时间。两者之间可能相差高达0.9秒。 - mc0e
14个回答

414

对于类似 2012-09-19 01:27:30.000 的字符串,DateTime.Parse 无法确定日期和时间的时区。

DateTime 有一个 Kind 属性,可以选择三个时区选项:

  • 未指定的
  • 本地的
  • 协调世界时(UTC)

注意:如果您希望表示除 UTC 或本地时区之外的日期/时间,则应使用DateTimeOffset


因此,对于您问题中的代码:

DateTime convertedDate = DateTime.Parse(dateStr);

var kind = convertedDate.Kind; // will equal DateTimeKind.Unspecified

你说你知道它是哪种类型的,那就告诉我吧。

DateTime convertedDate = DateTime.SpecifyKind(
    DateTime.Parse(dateStr),
    DateTimeKind.Utc);

var kind = convertedDate.Kind; // will equal DateTimeKind.Utc

现在,一旦系统知道它处于UTC时间,您可以直接调用ToLocalTime

DateTime dt = convertedDate.ToLocalTime();
这将会给你所需的结果。

19
另一种指定日期类型的方法:DateTime convertedTime = new DateTime(DateTime.Parse(dateStr).Ticks, DateTimeKind.Utc); - Brad
2
这个解决方案考虑了夏令时吗?我尝试了一下,发现时间少了一个小时。 - Bob Horn
9
DateTime 对象的 KindUnspecified 改为 UTC 的步骤是不必要的。在 ToLocalTime 方法中,Unspecified 被默认为 UTC:http://msdn.microsoft.com/zh-cn/library/system.datetime.tolocaltime.aspx - CJ7
17
@CJ7:是的,但明确表达对于可能需要维护代码的其他开发人员特别有帮助。 - Ryan
如果在您的第二个代码片段中指定DateTimeKind.Local会怎样? - Faisal Mq
显示剩余3条评论

129

如果你在使用.NET 3.5,那么我建议你考虑使用System.TimeZoneInfo类。请参阅http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx。这应该会正确地处理夏令时更改。

// Coordinated Universal Time string from 
// DateTime.Now.ToUniversalTime().ToString("u");
string date = "2009-02-25 16:13:00Z"; 
// Local .NET timeZone.
DateTime localDateTime = DateTime.Parse(date); 
DateTime utcDateTime = localDateTime.ToUniversalTime();

// ID from: 
// "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zone"
// See http://msdn.microsoft.com/en-us/library/system.timezoneinfo.id.aspx
string nzTimeZoneKey = "New Zealand Standard Time";
TimeZoneInfo nzTimeZone = TimeZoneInfo.FindSystemTimeZoneById(nzTimeZoneKey);
DateTime nzDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, nzTimeZone);

如果您在自己的时区工作(在这种情况下是en-NZ),那么您就不需要处理TimeZoneInfo。这只会增加不必要的复杂性。请参阅我的答案以获取更多详细信息。 - Drew Noakes
15
这是需要翻译的内容:如果有人需要,这里是我找到的TimeZoneInfo.FindSystemTimeZoneById可用的时区列表 - http://www.codeproject.com/Messages/3867850/Csharp-NET-List-of-timezones-for-TimeZoneInfo-Find.aspx - nikib3ro

67
TimeZone.CurrentTimeZone.ToLocalTime(date);

9
只有当系统知道要转换的日期处于UTC时,这才有效。请查看我的回答。 - Drew Noakes
3
UTC是默认时区,对吧?因此它适用于"未指定",就像CJ7的答案所说的那样。 - NickG

33

DateTime对象默认具有UnspecifiedKind,对于ToLocalTime,它被认为是UTC

要获取Unspecified DateTime对象的本地时间,您只需要执行以下操作:

convertedDate.ToLocalTime();

DateTimeKindUnspecified 改为 UTC 的步骤是不必要的。对于 ToLocalTime 方法而言,Unspecified 被认为是 UTC。详情请参阅:http://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime.aspx


8
反之亦然:convertedDate.FromLocalTime();将会转换为UTC - R. Schreurs

16

我知道这是一个较旧的问题,但我遇到了类似的情况,我想分享一下我找到的东西,供未来的搜索者参考,包括我自己 :).

DateTime.Parse() 可能有些棘手 -- 例如在 这里

如果 DateTime 来自于 Web 服务或其他已知格式的来源,你可能需要考虑使用类似

DateTime.ParseExact(dateString, 
                   "MM/dd/yyyy HH:mm:ss", 
                   CultureInfo.InvariantCulture, 
                   DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)

或者,甚至更好的是:

DateTime.TryParseExact(...)
AssumeUniversal标志告诉解析器日期/时间已经是UTC;AssumeUniversalAdjustToUniversal的组合告诉解析器不要将结果转换为“本地”时间,这是其默认尝试做的。 (我个人在业务/应用程序/服务层中仅使用UTC。但绕过转换为本地时间也可以加快速度 - 在我的测试中至少提高了50%,请参见下文。)

以下是我们以前的做法:

DateTime.Parse(dateString, new CultureInfo("en-US"))

我们对应用程序进行了分析,发现 DateTime.Parse 在 CPU 使用率中占据了相当大的比例。(顺便说一句,CultureInfo 构造函数不是 CPU 使用率的重要贡献者。)

因此,我设置了一个控制台应用程序以各种方式解析日期/时间字符串 10000 次。底线是:
Parse() 10 秒
ParseExact()(转换为本地时区)20-45 毫秒
ParseExact()(不转换为本地时区)10-15 毫秒
... 是的,Parse() 的结果单位是,而其他结果的单位是毫秒


15

我想提醒一下大家要小心。

如果你只是从计算机的内部时钟获取当前时间以便在显示或报告中添加日期/时间,那么就没有问题。但是如果你需要保存日期/时间信息以供以后参考,或者需要进行计算,请注意!

假设你确定一艘游轮于2007年12月20日15:00 UTC抵达了檀香山。而你想知道当地时间是什么。
1. 可能涉及至少三个“当地人”。当地可能指檀香山,也可能指你计算机所在的位置,或者可能是你客户所在的位置。
2. 如果你使用内置函数进行转换,它很可能是错误的。这是因为夏令时(可能)目前在你的计算机上生效,但在12月份不生效。但是Windows并不知道这一点... 它只有一个标志来确定当前是否生效夏令时。如果当前生效夏令时,则即使是12月的日期,它也会愉快地添加一小时。
3. 夏令时在各个政治行政区划中实施方式不同(或根本不实施)。不要认为其他国家会在特定日期更改,只因为你的国家会更改。


8
实际上,#2并不完全正确。 实际上,在您的电脑所在的每个时区都有关于夏令时的规定,如果已经安装(并更新)信息,您的电脑将知道这些规定。 对于许多地区,这些规则是固定的,而其他地区实行“动态夏令时”。对我而言,“巴西”就是一个让我很恼火的例子。总之,假设现在到12月期间没有法律变化,您的电脑可以计算出您当地时间是否为夏令时。 - Roger Willcocks
即使您不住在巴西,夏令时也是“动态的”,因为政治家可以随时更改它(就像几年前在美国所做的那样)。由于大多数软件都是考虑未来使用而编写的,因此重要的是要意识到没有实际、可预测甚至理论上的方法来知道哪些夏令时规则将生效。您可以接近,但放弃完美可以节省一些沮丧。 - DaveWalley

14
@TimeZoneInfo.ConvertTimeFromUtc(timeUtc, TimeZoneInfo.Local)

或者转换到特定的时区(与Daniel Ballinger的答案相结合): TimeZoneInfo.ConvertTimeFromUtc(timeUtc, TimeZoneInfo.FindSystemTimeZoneById("新西兰标准时间")); - Patrick Koorevaar

5

如果您已经有一个DateTime对象,但不确定它是UTC还是本地时间,可以直接使用对象上的方法:

DateTime convertedDate = DateTime.Parse(date);
DateTime localDate = convertedDate.ToLocalTime();

我们如何调整这个额外的小时?

除非特别指定,.net将使用本地PC设置。我建议阅读:http://msdn.microsoft.com/en-us/library/system.globalization.daylighttime.aspx

看起来代码可能是这样的:

DaylightTime daylight = TimeZone.CurrentTimeZone.GetDaylightChanges( year );

正如上面提到的,要仔细检查您的服务器所在的时区设置。有一些文章可以介绍如何安全地影响IIS中的更改。


系统将会为您处理这个复杂性,只要您告诉系统您的日期是哪一种类型(本地时间/UTC时间/未指定时间)。 - Drew Noakes

4

回答Dana的建议:

现在代码示例看起来像这样:

string date = "Web service date"..ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            
DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(convertedDate);

原始日期为20/08/08,类型为UTC。

"convertedDate"和"dt"是相同的:

21/08/08 10:00:26,类型为本地时间


请查看我的答案以了解此事的解释。 - Drew Noakes

1
我在处理Twitter API返回的UTC时间(status上的created_at字段)时遇到问题,需要将它们转换为DateTime。这个页面上的所有答案和代码示例都无法阻止我出现“字符串未被识别为有效的DateTime”错误,但这是我在StackOverflow上找到的离正确答案最近的一次.

如果有人遇到类似的问题,我在这里发布一个链接,希望能帮到大家 - 我找到了自己所需的答案在这篇博客文章:http://www.wduffy.co.uk/blog/parsing-dates-when-aspnets-datetimeparse-doesnt-work/ - 基本上使用DateTime.ParseExact和格式字符串而不是DateTime.Parse即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接