洪星的博客(原创版,新闻除外)

信息技术 软件开发 电信 移动通信(欢迎和我交流:QQ219402,15152399197)

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  12 随笔 :: 0 文章 :: 62 评论 :: 0 引用

BUG:在 ToString("u") 方法中,当 DateTime 的 Kind 属性为 Local 时,不能自动转换为 UTC 时间,而导致返回的末尾跟“Z”的字符串表示为本地时间,末尾跟“Z”的字符串应当表示为 UTC 时间

当 DateTime 的 Kind 属性为 Utc 时,则正常。

 

GB/T 7408-2005《数据元和交换格式 信息交换 日期和时间表示法》摘要:

5.1.2 标志符所用的字符
[Z] 协调世界时(UTC)标志符,直接跟在一数据元后面(没有间隔),用协调世界时表示该日的时间。

 

DateTime
1 DateTime n = DateTime.Now;
2 Console.WriteLine(n);//2010/3/15 14:07:41
3  Console.WriteLine(n.Kind);//Local
4  Console.WriteLine();
5
6  string s = n.ToString("u");
7 Console.WriteLine(s);//2010-03-15 14:07:41Z
8  DateTime t = DateTime.Parse(s);
9 Console.WriteLine(t);//2010/3/15 22:07:41
10  Console.WriteLine(t.Kind);//Local
11  Console.WriteLine();
12
13 string ss = n.ToString("o");
14 Console.WriteLine(ss);//2010-03-15T14:07:41.8178047+08:00
15 DateTime tt = DateTime.Parse(ss);
16 Console.WriteLine(tt);//2010/3/15 14:07:41
17 Console.WriteLine(tt.Kind);//Local

 

ToString("u") 后,末尾跟“Z”的字符串表示为本地时间

Parse 时,认为末尾跟“Z”的字符串表示为 UTC 时间

同一类中,不同方法中的对同一字符串的时间表示却不同!

其他格式都可以正常往返(“o”格式可以保留 Kind 属性,不降低精度),只有“u”格式相差8个小时,请问这是微软的 BUG 吗?请高手解答!
 

更多格式:http://hi.baidu.com/hongcing/blog/item/736bb6437b412f1972f05d70.html

我的博客:http://www.cnblogs.com/hongcing
我的签名:
posted on 2010-03-14 20:20 洪星 阅读(2474) 评论(22) 编辑 收藏

评论

#1楼 2010-03-14 21:05       
北京时间是东八区 和 格林威治差8小时
 回复 引用 查看   

#2楼 2010-03-14 21:07       
问题应该在DateTime.Parse里面,有个输入参数。

System.Globalization.DateTimeStyles.AdjustToUniversal

 回复 引用 查看   

#3楼[楼主] 2010-03-14 21:16 洪星      
@

你没有明白我的意思,大部分人都知道北京在+8区。

关键是,格式化后的字符串末尾有“Z”,“Z”表示 UTC 时间,而字符串里的时间却表示为本地时间,这是问题的关键。
Parse 又认为末尾有“Z”的是 UTC 时间。
 回复 引用 查看   

#4楼[楼主] 2010-03-14 21:18 洪星      
@

使用 DateTimeStyles.AdjustToUniversal 参数,
会将时间转换为 UTC 时间 2010年3月14日 20:09:15,而实际是本地时间,也就是北京时间。
 回复 引用 查看   

#5楼 2010-03-14 23:04 Anders Liu      
string s = DateTime.Now.ToString("u");//2010-03-14 20:09:15Z
DateTime t = DateTime.Parse(s);//2010-3-15 4:09:15

你怎么打印出来的t的值?如果是t.ToString(),得到的当然是本地时间的表示方式啊。必须也用t.ToString("u")啊。
 回复 引用 查看   

#6楼 2010-03-15 09:03 不死鸟之魂      
这个不是bug。原理很简单:
1、.net 认为所有的 DateTime都是本地时间。
2、用ToString("U") 输出的只是本地时间的一个UTC对应时间的字符串(可以理解为image)。
3、用Parse转换的时候,DateTime其实人的后面的Z,但是你看到和使用的都是本地时间。你可以试试这时候还用ToString("U")的话,得到的和原来的一样。

 回复 引用 查看   

#7楼 2010-03-15 09:04 不死鸟之魂      
如果要是有bug的话,你用Parse得到的,就会使把UTC时间当成本地时间来用,这样就真的错了。
 回复 引用 查看   

#8楼 2010-03-15 09:36 Teamleader      
北京时间是东八区 和 格林威治差8小时 ,地理上也学过,如果你用过Json系列化时间的话,通过js去反系列化json对象,你就知道改加8个小时转化为北京时间了
 回复 引用 查看   

#9楼 2010-03-15 10:24 Bob-wei      
首先,Parse方法与ToString方法是逆运算关系吗?
然后,博主可以试着用DateTimeOffset对象做一遍。
 回复 引用 查看   

#10楼 2010-03-15 11:02 Ivony...      
问题出在你的DateTime对象是怎么来的。

因为绝大多数DateTime对象是不知道自己是本地时间还是UTC时间的,即DateTime.Kind是Unspecified。

如果你将从UTC时间Parse来的DateTime再转回去应该就没问题了。



在对DateTime进行序列化保存时,一定要明确DateTime.Kind属性。

给LZ一段参考代码:
      frameData.Add
      (
        new XAttribute( "GUID", Guid.NewGuid() ),
        new XAttribute( "PublishDate", DateTime.UtcNow.ToString( "u" ) ),
        new XElement( "Author", document.GetDocument().Meta.Author ),


可以看出来,在这段代码里,明确使用了DateTime.UtcNow来获取当前的UTC时间,然后再ToString( "u" ) )。

如果你知道DateTime.Kind应该是本地时间,应使用DateTime.SpecifyKind 方法明确设置。
 回复 引用 查看   

#11楼 2010-03-15 11:04 Ivony...      
.NET Framework 2.0增加了DateTimeOffset来协助解决这个问题。
 回复 引用 查看   

#12楼[楼主] 2010-03-15 14:35 洪星      
引用Bob-wei:
首先,Parse方法与ToString方法是逆运算关系吗?
然后,博主可以试着用DateTimeOffset对象做一遍。

我们是在讨论 DateTime 的 BUG!
Parse方法与ToString方法是逆运算关系吗?
答:
有。
但是会损失精度,和/或丢失 Kind 属性。
 回复 引用 查看   

#13楼[楼主] 2010-03-15 14:36 洪星      
引用Ivony...:
问题出在你的DateTime对象是怎么来的。

因为绝大多数DateTime对象是不知道自己是本地时间还是UTC时间的,即DateTime.Kind是Unspecified。

如果你将从UTC时间Parse来的DateTime再转回去应该就没问题了。



在对DateTime进行序列化保存时,一定要明确DateTime.Kind属性。

给LZ一段参考代码:
[code=csharp]
frameData.Add
(
new XAttribute( "GUID", Guid.NewGuid() ),
...



DateTime.Now 的 Kind 属性为 Local。
 回复 引用 查看   

#14楼[楼主] 2010-03-15 14:37 洪星      
引用Teamleader:北京时间是东八区 和 格林威治差8小时


小学课本里好像有你所说的内容。
 回复 引用 查看   

#15楼[楼主] 2010-03-15 14:39 洪星      
引用不死鸟之魂:
这个不是bug。原理很简单:
1、.net 认为所有的 DateTime都是本地时间。
2、用ToString("U") 输出的只是本地时间的一个UTC对应时间的字符串(可以理解为image)。
3、用Parse转换的时候,DateTime其实人的后面的Z,但是你看到和使用的都是本地时间。你可以试试这时候还用ToString("U")的话,得到的和原来的一样。



注意,文章中讨论的是小写的“u”。
 回复 引用 查看   

#16楼 2010-03-15 15:25 Bob-wei      
引用洪星:
引用Bob-wei:
首先,Parse方法与ToString方法是逆运算关系吗?
然后,博主可以试着用DateTimeOffset对象做一遍。

我们是在讨论 DateTime 的 BUG!
Parse方法与ToString方法是逆运算关系吗?
答:
有。
但是会损失精度,和/或丢失 Kind 属性。

MSDN里有说他们是逆运算关系?它们只是两个不同功能的函数,甚至一个是静态的,要说共同点都支持格式化接口。又怎么会看作是BUG?
遇到时区的问题,建议使用DateTimeOffse类型。
 回复 引用 查看   

#17楼 2010-03-15 15:48 Ivony...      
引用洪星:
引用Ivony...:
问题出在你的DateTime对象是怎么来的。

因为绝大多数DateTime对象是不知道自己是本地时间还是UTC时间的,即DateTime.Kind是Unspecified。

如果你将从UTC时间Parse来的DateTime再转回去应该就没问题了。



在对DateTime进行序列化保存时,一定要明确DateTime.Kind属性。

给LZ一段参考代码:
[code=csharp]
frameData.Add
(
new XAttribute( "GUID", Guid.NewGuid() ),
...



DateTime.Now 的 Kind 属性为 Local。



我明白你的意思了。。。。

这个问题的确可以认为是一个缺陷,我之前也被那啥过,如果你的DateTime的Kind属性是正确的话,正确的写法是:
DateTime.ToUniversalTime().ToString("u");

当然我们可以问微软为什么不自动干这件事情,还有ToUniversalTime()也没有一个TimeZone或TimeZoneInfo参数的重载。反正我没想明白。


不过刚看帮助说Unspecified也会被认为是Local,哎,我觉得还是从源头开始,所有的时间都是Utc的最好,否则一个DateTime在时区是A的代码中创建,到时区是B的代码中转换,还是会出问题。

或者干脆用DateTimeOffset,这一块的确很混乱。。。。
 回复 引用 查看   

#18楼[楼主] 2010-03-15 16:15 洪星      
引用Ivony...:
引用洪星:
引用Ivony...:
问题出在你的DateTime对象是怎么来的。

因为绝大多数DateTime对象是不知道自己是本地时间还是UTC时间的,即DateTime.Kind是Unspecified。

如果你将从UTC时间Parse来的DateTime再转回去应该就没问题了。



在对DateTime进行序列化保存时,一定要明确DateTime.Kind属性。

给LZ一段参考代码:
[code=csharp]
frameData.Add
(
new XAttribute( "GUID"...

你是高手!
在 ToString("u") 方法中,问题就是 DateTime 的 Kind 属性为 Local 时,却不返回末尾跟“Z”表示为 UTC 时间的字符串,而是返回末尾跟“Z”表示为本地时间的字符串。
 回复 引用 查看   

#19楼 2010-03-15 16:33 Ivony...      
引用洪星:
你是高手!
在 ToString("u") 方法中,问题就是 DateTime 的 Kind 属性为 Local 时,却不返回末尾跟“Z”表示为 UTC 时间的字符串,而是返回末尾跟“Z”表示为本地时间的字符串。



这个,其实可以解释为向下兼容。。。。。
因为那啥,.NET Framework 1.x没有Kind属性,所以DateTime不知道自己是什么东东,所以ToString就只能这样,但.NET Framework 2.0增加了Kind属性,但如果行为不同显然会造成更大的问题,所以没改。

不过你说的的确是有道理的,即使.NET Framework 1.x没有Kind属性,但根据设计思想,例如有ToUniversalTime方法,而这个方法总是会把DateTime当作本地时间,从这个方法的行为推断,微软总是将DateTime认为是本地时间的。

但还是有问题,因为DateTime.UtcNow在.NET Framework 1.x也是存在的,当然DateTime.Parse也能从UTC格式中还原DateTime。所以,微软还是可以说自己搞不清DateTime到底是本地时间。

其实研究到这里我们差不多可以知道问题出在哪里了。在.NET Framework 1.x的时候,微软存在一个设计上的失误,这个失误将导致如下代码得到令人费解的结果:

DateTime.UtcNow.ToUniversalTime()


从这些属性和方法的行为可知,DateTime.UtcNow将会返回当前的UTC时间,而ToUniversalTime又会把这个时间当作本地时间来玩。
换言之ToUniversalTime要求用户自己清醒的知道自己在干什么,我认为这是一个很容易把用户玩晕的设计。


当然这个失误在.NET Framework 2.0得到了修正。由于Kind属性的引入,现在ToUniversalTime方法不会转换Kind为Utc的DateTime。


尽管.NET Framework 2.0带来了部分的修正,但我们还是要忍受这样复杂的语法:

dateTime.ToUniversalTime().ToString("u");


而且,大多数的DateTime都是Unspecified的(例如从数据库读取)。
哎,这真是一个麻烦事儿,还是用DateTimeOffset吧。。。。


所以结论也可以得到了。
因为.NET Framework 1.x时代,所有的DateTime的Kind都是Unspecified的,也就是搞不清是本地时间还是哪里的时间。因为.NET Framework 1.x连Kind属性都没有。

这种情况下:
DateTime.ToUniversalTime().ToString("u");


DateTime.ToString("u");//隐式调用ToUniversalTime


都不能算是什么完美的写法。
前者在
DateTime.UtcNow.ToUniversalTime().ToString("u");

容易造成误解
后者在
DateTime.Parse("2010-03-15 14:07:41Z").ToString("u");

容易造成误解

当然罪魁是糟糕的设计。


而.NET Framework 2.0要来修补这个问题的时候,发现已经糟糕到没办法修补了,毕竟修改ToString("u")的行为比修改ToUniversalTime的行为影响大的多。所以微软选择了后者。
 回复 引用 查看   

#20楼[楼主] 2010-03-15 17:07 洪星      
引用Ivony...:
引用洪星:
你是高手!
在 ToString("u") 方法中,问题就是 DateTime 的 Kind 属性为 Local 时,却不返回末尾跟“Z”表示为 UTC 时间的字符串,而是返回末尾跟“Z”表示为本地时间的字符串。



这个,其实可以解释为向下兼容。。。。。
因为那啥,.NET Framework 1.x没有Kind属性,所以DateTime不知道自己是什么东东,所以ToString就只能这样,但.NET Framework 2.0增加了Kind属性,但如果行为不同显然会造成更大的问题,所以没改。

不过你说的...


很感谢你的回答!
但是我还是有个疑问:
如果是为了考虑兼容,可以对 Unspecified 不转换,但是为 Local 和 Utc 时,可以在 ToString("u") 时转换为 Utc,因为 .NET 1.X 的 DateTime 没有 Kind,也就不会有 Local 和 Utc 的兼容性问题。
 回复 引用 查看   

#21楼 2010-03-15 17:16 Ivony...      
引用洪星:
引用Ivony...:
引用洪星:
你是高手!
在 ToString("u") 方法中,问题就是 DateTime 的 Kind 属性为 Local 时,却不返回末尾跟“Z”表示为 UTC 时间的字符串,而是返回末尾跟“Z”表示为本地时间的字符串。



这个,其实可以解释为向下兼容。。。。。
因为那啥,.NET Framework 1.x没有Kind属性,所以DateTime不知道自己是什么东东,所以ToString就只能这样,但.NET Framework 2.0增加了Kind属性,但如果行为不同显然会造成更大的问题,所以没改。

不过你说的...


很感谢你的回答!
但是我还是有个疑问:
如果是为了考虑兼容,可以对 Unspecified 不转换,但是为 Local 和 Utc 时,可以在 ToString("u") 时转换为 Utc,因为 .NET 1.X 的 DateTime 没有 Kind,也就不会有 Local 和 Utc 的兼容性问题。



哈,你说的也对哦。。。。。
要不,发封信去问问微软?
 回复 引用 查看   

#22楼[楼主] 2010-03-15 18:36 洪星      
引用Ivony...:
引用洪星:
引用Ivony...:
引用洪星:
你是高手!
在 ToString("u") 方法中,问题就是 DateTime 的 Kind 属性为 Local 时,却不返回末尾跟“Z”表示为 UTC 时间的字符串,而是返回末尾跟“Z”表示为本地时间的字符串。



这个,其实可以解释为向下兼容。。。。。
因为那啥,.NET Framework 1.x没有Kind属性,所以DateTime不知道自己是什么东东,所以ToString就只能这样,但.NET Framework 2.0增加了Kind属性,但如...


怎么问?
 回复 引用 查看