浏览器默认格式字符串
根据Spec的定义,每个ECMAScript的脚本引擎(在这里就是浏览器)都需要给定以下四个方法来给出表示当前Date对象时间信息的字符串:
- Date.prototype.toString():返回表示本地日期和时间的字符串。
- Date.prototype.toDateString():返回表示本地日期的字符串。
- Date.prototype.toTimeString():返回表示本地时间的字符串。
- Date.prototype.toUTCString():返回表示UTC日期和时间的字符串。
在Spec中,只要求了这些方法返回一个“人类可读”的字符串,用于表示Date对象所保存的时间信息,但是没有规定其格式。事实上,就拿IE和FireFox来说,这些方法的调用结果的确并不相同。首先是IE浏览器中的执行结果:
var d = new Date(0); alert(d.toString()); // Thu Jan 1 08:00:00 UTC+0800 1970 alert(d.toDateString()); // Thu Jan 1 1970 alert(d.toTimeString()); // 08:00:00 UTC+0800 alert(d.toUTCString()); // Thu, 1 Jan 1970 00:00:00 UTC
在FireFox中:
var d = new Date(0); alert(d.toString()); // Thu Jan 01 1970 08:00:00 GMT+0800 alert(d.toDateString()); // Thu Jan 01 1970 alert(d.toTimeString()); // 08:00:00 GMT+0800 alert(d.toUTCString()); // Thu, 01 Jan 1970 00:00:00 GMT
由于Spec没有规定这些方法返回字符串的格式,因此这两种结果都是符合标准的。不过请注意,toString、toDateString和toTimeString三者的结果有着确定的关系,如下:
d.toString() == d.toDateString() + " " + d.toTimeString()
解析时间日期字符串
既然有了得到字符串的方法,自然也少不了将字符串解析为一个Date类型对象的方法,这个方法的定义如下:
- Date.parse(str):解析str字符串,返回表示时间的time value。
在上一节中介绍的那些方法是用于返回浏览器用于表示日期和时间的默认格式字符串,而根据Spec中对于parse方法的定义,该方法能够解析的字符串格式即为浏览器决定的“默认格式”。因此,对于任意合法的time value的值t,下面三个表达式的值都是相等的:
- t
- Date.parse(new Date(t).toString());
- Date.parse(new Date(t).toUTCString());
由于不同的浏览器对于默认日期时间格式的设定并不统一,所以Date.parse方法能够识别的字符串在不同浏览器环境下也很可能各不相同。在加上默认表示的日期时间的字符串对于用户来说并不友好,因此在实际开发过程中,使用Date.parse方法的情形并不多见。但是在扩展时依旧必须保持这个方法功能的不变,在ASP.NET AJAX Beta 1中,正是因为覆盖了Date.parse方法,导致了在页面中所有的Google AdSense运行出错。在ASP.NET AJAX正式版中,Date.parse方法就被改为Date.parseLocale和Date.parseInvariant两个方法。后话不提。
系统设定时间日期字符串
之前介绍的方法都是用于得到浏览器默认格式的日期时间字符串,在实际开发角度来说用处不大。如果能够使用客户端设定相关的格式来显示日期和时间就好多了,JavaScript中自然想到了这一点,因此在Spec中定义了以下几个方法:
- Date.prototype.toLocaleString():根据系统设定,返回表示本地日期和时间的字符串
- Date.prototype.toLocaleDateString():根据系统设定,返回表示本地日期的字符串。
- Date.prototype.toLocaleTimeString():根据系统设定,返回表示本地时间的字符串。
请注意,这里并没有返回UTC时间的说法。“UTC时间”本身就是一个“表现”上的概念,而在“系统设定”中,已经包括了“系统时区”信息,因此结果字符串表示的一定是“本地时间”。
那么,上面三个方法返回的结果是怎么样的呢?因为结果字符串的格式只和客户端操作系统设定有关,因此无论是什么浏览器,它们返回的结果是一样的。在我的机器上,请注意是“在我的机器上”,三个方法分别返回如下的结果:
var d = new Date(0); alert(d.toLocaleString()); // 1970年1月1日 8:00:00 alert(d.toLocaleDateString()); // 1970年1月1日 alert(d.toLocaleTimeString()); // 8:00:00
这个结果是由我的系统设定决定的。在我的Vista操作系统中可以使用如下的方式来查看和设定(Windows操作系统其实大都差不多):开始——控制面板——区域和语言选项:
在上面的对话框中,“长日期”中定义的格式即为toLocaleDateString方法返回结果所用的格式,而“时间”中定义的格式即为toLocaleTimeString方法返回结果所用的格式。“数字”、“货币”和“短日期”定义的格式都会在.NET Framework中使用到,不过这已经超出了这片文章讨论的范围。如果需要修改日期时间的格式,只需要在上面对话框中的下拉框中选择不同的格式即可。
不过请注意,这里只是设置了一些“格式”,如果要设定时区信息,则需要选择上面对话框中的“位置”标签,在那里您可以选择系统所使用的时区信息。系统的“格式”和“时区”信息相互独立,也就是说,我们完全可以在使用中国时区的情况下,使用美国的格式来显示信息。
不过无论格式如何,toLocaleString、toLocaleDateString和toLocaleTimeString三个方法结果之间的关系是不会改变的。
如何在实际开发中使用客户端系统的格式
大约8个月前,我正在参与一个项目的中国本地化(Localization,指开发一些市场特有的功能)以及全球化(Globalization,指开发一些让当前应用支持多市场运作的工作,例如编写市场切换逻辑,将写在页面中的文字迁移至资源文件中等等)工作。那时,对于如何在页面上显示日期和时间有过一个讨论,讨论集中在两种选择上:
- 使用当前用户即将正在访问的市场来决定日期时间的显示格式。这样做可能得到的结果就是:一个系统设定格式为“中国”的用户在访问“美国”市场时,看到的时间和日期格式都是“美国”格式,而且用户看到的时间和系统时区设置无关。
- 使用客户端系统设定的格式来显示日期和时间。这样作可能得到的结果就是:一个系统设定格式为“中国”的用户访问“美国”市场时,看到的时间和日期格式为“中国”格式,而用户看到的时间与系统时区设置有关。
后一种做法有优点也有缺点。优点在于在客户端看到的时间能够根据用户操作系统设定(这往往反映了用户的喜好)的格式和时区来显示时间日期。缺点在于可能用户在浏览一个美国市场(全是英文)的情况下,日期时间显示为中文,造成了“不和谐”。不过鉴于后面一种情况非常少见,因此项目最终选择了后一种做法。
不过,在服务器端是无法得到操作系统的时区和格式设置的。曾经有人认为,操作系统设置的格式可以从Request.UserLanguages数组中获得的Culture信息来得到。其实这个观点是错误的,Request.UserLanguages得到的Culture列表其实是在如下的对话框中设置,如下(打开IE浏览器——工具——Internet选项——语言):
理论上这个设置可以独立于系统的格式和时区设定,我们完全能够使用北京的时区、美国的格式,同时服务器端得到阿拉伯文的信息。另外,在服务器端也是无法直接得到操作系统的时区信息的(事实上,我们都可以使用一个小技巧来获得客户端的格式和时区设定,不过这将会在下一篇文章中讲述)。
如果要将时间根据客户端的设置来显示,则需要将时间的“值”输出至客户端,并且在客户端显示出来。因此,我们可以使用以下的方法。这个方法的精髓在于使用客户端的document.write方法将信息写在页面上。首先,我们定义一个方法用户生成这段脚本:
protected string GetClientDisplayDate(DateTime dt) { DateTime utc = dt.ToUniversalTime(); return String.Format( "<script language='javascript' type='text/javascript'>\n" + " document.write(new Date({0}, {1}, {2}).toLocaleDateString());\n" + "</script>", dt.Year, dt.Month - 1, dt.Day); }
上面的代码有三个地方需要注意:
- 传入的DateTime对象需要调用其ToUniversalTime方法来得到它相应的UTC时间,这点在今后的文章中会谈到。
- 我们必须使用枚举年、月、日等信息的方法来构造Date类型对象。有人认为可以使用DateTime的Ticks属性来直接使用time value在客户端构造对象,其实这是不对的。.NET Framework中DateTime类型的设计和ECMAScript中Date类型设计不同,它的Ticks属性并不等同于time value。
- 在传入月份的时候,需要减去1,因为.NET Framework中DateTime类型的Month属性使用1到12表示月份,而ECAMScript中Date类型使用0到11来表示。
于是我们可以使用上面的方法在页面合适的地方显示时间,例如:
<%= GetClientDisplayDate(DateTime.Now) %>
这样,在客户端就会出现如下的脚本代码,这段代码将会使用客户端的系统设置格式来显示日期:
<script language='javascript' type='text/javascript'> document.write(new Date(2007, 5, 6).toLocaleDateString()); </script>