Hello World
Spiga

深入JavaScript与.NET Framework中的日期时间(3):JavaScript中的Date类型(中)

2007-06-06 17:29 by 老赵, 7668 visits

浏览器默认格式字符串

根据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,指开发一些让当前应用支持多市场运作的工作,例如编写市场切换逻辑,将写在页面中的文字迁移至资源文件中等等)工作。那时,对于如何在页面上显示日期和时间有过一个讨论,讨论集中在两种选择上:

  1. 使用当前用户即将正在访问的市场来决定日期时间的显示格式。这样做可能得到的结果就是:一个系统设定格式为“中国”的用户在访问“美国”市场时,看到的时间和日期格式都是“美国”格式,而且用户看到的时间和系统时区设置无关。
  2. 使用客户端系统设定的格式来显示日期和时间。这样作可能得到的结果就是:一个系统设定格式为“中国”的用户访问“美国”市场时,看到的时间和日期格式为“中国”格式,而用户看到的时间与系统时区设置有关。

后一种做法有优点也有缺点。优点在于在客户端看到的时间能够根据用户操作系统设定(这往往反映了用户的喜好)的格式和时区来显示时间日期。缺点在于可能用户在浏览一个美国市场(全是英文)的情况下,日期时间显示为中文,造成了“不和谐”。不过鉴于后面一种情况非常少见,因此项目最终选择了后一种做法。

不过,在服务器端是无法得到操作系统的时区和格式设置的。曾经有人认为,操作系统设置的格式可以从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>
Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

52 条回复

  1. Tiny
    *.*.*.*
    链接

    Tiny 2007-06-06 17:39:00

    先不看文章,纯顶!

  2. winnerzone
    *.*.*.*
    链接

    winnerzone 2007-06-06 17:39:00

    好文章.看懂了.的确要注意这些地方.

  3. Tiny
    *.*.*.*
    链接

    Tiny 2007-06-06 17:40:00

    @winnerzone
    紧顶,慢顶,还被你抢先了。呵呵

  4. Anthan
    *.*.*.*
    链接

    Anthan 2007-06-06 20:35:00

    个人觉得格式还是有服务器同意控管比较好,这样一个系统就有整体性。
    如果由客户端控管的话需要考虑和兼容的情况太多了,而且成本比较高。
    不过具体还是得取决于用户需求而定了

  5. Join[未注册用户]
    *.*.*.*
    链接

    Join[未注册用户] 2007-06-06 20:42:00

    留个记号,感觉比较实在

  6. 老赵
    admin
    链接

    老赵 2007-06-06 20:51:00

    @Anthan
    嗯?客户端还需要考虑什么情况呢?
    其实有时候倒不是用户需求了,做服务很多时候也是自己定的需求,用户并不在意,呵呵。当然如果用户反馈比较XXOO那么就需要重新考虑了。

  7. Anthan
    *.*.*.*
    链接

    Anthan 2007-06-06 21:25:00

    @Jeffrey Zhao
    因为有时候不仅仅是显示格式,还有输入等等。
    需要把不同输入转化为同样的格式传到服务器端去做相应的处理
    用户需求,呵呵,我们公司的日期格式一年变三次,那个分公司比较赚钱就用哪种格式,现在是大陆的格式,呵呵

  8. 老赵
    admin
    链接

    老赵 2007-06-06 21:59:00

    @Anthan
    有一定道理,呵呵。
    不过其实让用户直接输入时间日期倒不是好方法,一般建议使用控件让用户选择。不过格式还是要用来Check的。
    对了,话说选择日期可以有Calender,那么选择时间该怎么办呢?

  9. Anthan
    *.*.*.*
    链接

    Anthan 2007-06-06 22:10:00

    @Jeffrey Zhao
    日期输入是用公司自己的组件Calender控件,支持输入和选择
    4月份的时候尝试写过一个时间输入的控件,用脚本来控制格式,当时写的太痛苦了,总是不能兼容所有格式,看了你这个系列以后需要翻出来再看看了。
    不知道MS为啥不出个时间选择或者输入控件,开源的好像也不多。
    难道时间输入不重要吗?

  10. 老赵
    admin
    链接

    老赵 2007-06-06 22:36:00

    @Anthan
    但是时间输入用什么方式呢?时钟?这个开发难度就高了。

  11. Anthan
    *.*.*.*
    链接

    Anthan 2007-06-06 22:50:00

    @Jeffrey Zhao
    最开始是只是三个输入框,后来改成可选可输入的
    只是格式必须固定,动态格式的就搞不定了
    不过现实需求中我遇到的确实很少要求输入精确时间的
    如果有时间以你的能力应该没问题噢,呵呵,加油啊。

  12. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2007-06-07 00:14:00

    关于根据客户端区域设置自动localization的,在日期上没有什么大问题,但我见过在货币上出问题的。有个网站不知道使用什么框架,反正能够自动对货币单位localize,开发人员以为网站自动根据数据库中的14.99输出为$14.99了,但在zh-cn的浏览器中却看到¥14.99,这就可能导致被投诉“价格欺诈”了。

  13. 老赵
    admin
    链接

    老赵 2007-06-07 00:58:00

    @Cat Chen
    如果真的是同样的货品在多个市场销售的话,汇率换算的确是个问题。

  14. 老赵
    admin
    链接

    老赵 2007-06-07 01:30:00

    @Anthan
    JavaScript/CSS无法做出旋转效果,所以一般只能加载一张很大的指针图片然后设置clip,唉唉……

  15. Anthan
    *.*.*.*
    链接

    Anthan 2007-06-07 08:35:00

    @Jeffrey Zhao
    做旋转估计很难而且选择时候也不方便,弄成电子表估计要方便很多,呵呵

  16. yinix
    *.*.*.*
    链接

    yinix 2007-06-07 09:01:00

    日期如果没有考虑到的,当做多语言的系统的时候,是要出现这样那样的问题。典型的就是中文下的日期到日文下格式的变化,比如:中文:2007/06/07到日文下就变成了2007-06-07。:)

  17. Jonny
    *.*.*.*
    链接

    Jonny 2007-06-07 10:04:00

    不做Web好多年的童子飘过 ……

  18. 老赵
    admin
    链接

    老赵 2007-06-07 15:13:00

    @Anthan
    的确,那就容易多了,不过要做的好看很难啊。AjaxControlTookit里的Calender极强的。

  19. Anthan
    *.*.*.*
    链接

    Anthan 2007-06-07 15:34:00

    @Jeffrey Zhao
    公司有自己的共用日期控件,实际项目中必须使用公司的,呵呵
    不过平时自己研究一下AjaxControlTookit里的Calender倒是很有帮助的

  20. 老赵
    admin
    链接

    老赵 2007-06-07 17:18:00

    @Anthan
    AjaxControlTookit里的Calender太复杂,想要提取出来不容易啊。

  21. Join miao
    *.*.*.*
    链接

    Join miao 2007-06-07 22:48:00

    一个时间也这么复杂?
    长学问~想了解一些数据库方面时间问题~继续再写吧~!加油!

  22. 乐趣电子书[未注册用户]
    *.*.*.*
    链接

    乐趣电子书[未注册用户] 2007-06-07 23:05:00

    如果真的是同样的货品在多个市场销售的话,汇率换算的确是个问题。

  23. 老赵
    admin
    链接

    老赵 2007-06-07 23:07:00

    @Join miao
    其实关键还是使用UTC来存储,呵呵。其他的倒也简单。
    时间问题如果要注意的话还是非常简单的。:)

  24. 老赵
    admin
    链接

    老赵 2007-06-07 23:08:00

    @乐趣电子书
    您这儿有IT类电子书吗?呵呵。

  25. Join miao
    *.*.*.*
    链接

    Join miao 2007-06-07 23:28:00

    @Jeffrey Zhao
    :)

  26. QQ大侠[未注册用户]
    *.*.*.*
    链接

    QQ大侠[未注册用户] 2007-06-08 03:14:00

    做个记号哈~~

  27. 怪怪[未注册用户]
    *.*.*.*
    链接

    怪怪[未注册用户] 2007-06-08 04:16:00

    Request.UserLanguages,这个,我的想法是,如果用户真设了阿拉伯文,就说明想用阿拉伯文看,倒不一定非要按他用的系统显示.

  28. 老赵
    admin
    链接

    老赵 2007-06-08 06:59:00

    @怪怪
    一种选择吧,呵呵。

  29. Michael[未注册用户]
    *.*.*.*
    链接

    Michael[未注册用户] 2007-06-08 16:55:00

    在服务器端也是无法直接得到操作系统的时区信息的(事实上,我们都可以使用一个小技巧来获得客户端的格式和时区设定,不过这将会在下一篇文章中讲述)。


    怎么实现呀??我没有找到下一篇文章,给个链接吧

  30. 老赵
    admin
    链接

    老赵 2007-06-08 17:05:00

    @Michael
    呵呵,还没有写呢。

  31. Michael[未注册用户]
    *.*.*.*
    链接

    Michael[未注册用户] 2007-06-08 17:48:00

    这个需求如何实现: APS.Net + Javascript + IE

    设我有一个网页Test.aspx从数据库里面根据用户选择的时间查出一批数据,用户来自全球,数据库里面保存的是UTC的时间,如果北京(GMT+8:00)的用户想查询 2007/06/07~2007/06/08两天的数据,实际上他是要在数据库里面查询 2007/06/06 16:0:0 - 2007/06/07 16:0:0 之间的数据,现在的问题是如何当用户第一次访问Test.aspx的时候就能根据用户的系统时区来查询。

    我试过在页面上放一个隐藏的textbox控件,用javascript来在客户端初始用户的系统时区,在服务器端读该值在做转换。这种方法要求页面先发送到客户端一次,javascript才可以操作。

    但是当用户第一次访问这个页面的时候,如何在服务器端取得用户的时区呢?

    多谢了!

  32. 老赵
    admin
    链接

    老赵 2007-06-09 00:12:00

    @Michael
    使用cookie,然后redirect一下。

  33. Lan[未注册用户]
    *.*.*.*
    链接

    Lan[未注册用户] 2007-06-11 09:37:00


    <script language='javascript' type='text/javascript'>
    document.write(new Date(2007, 5, 6).toLocaleDateString());
    </script>

    这个东西很cool,但是如果这段code放在Ajax.Net的UpdatePanel里面,就没法显示了,有解决方案么?

  34. 老赵
    admin
    链接

    老赵 2007-06-11 10:49:00

    @Lan
    只能靠执行JavaScript来做了。

  35. Lan[未注册用户]
    *.*.*.*
    链接

    Lan[未注册用户] 2007-06-11 11:19:00

    Jeffrey,
    给个例子吧。

  36. sky[未注册用户]
    *.*.*.*
    链接

    sky[未注册用户] 2007-06-11 13:36:00

    给个例子吧。

  37. 老赵
    admin
    链接

    老赵 2007-06-11 20:56:00

    @Lan
    就是使用ScriptManager.RegisterXXXX方法来注册脚本。:)

  38. Jackieqiang[未注册用户]
    *.*.*.*
    链接

    Jackieqiang[未注册用户] 2007-08-21 11:27:00

    受教。期待下篇。

  39. Boon
    *.*.*.*
    链接

    Boon 2008-01-23 09:37:00

    JavaScript new Date(_year,_month,_day)创建的是一个UCT对象.而这个对象直接传参给ASP.NET AJAX WebService的话会转化为Local Time.
    也就是说传进的可能不是你想要的结果.

    希望和大家多多沟通:-)

  40. 老赵
    admin
    链接

    老赵 2008-01-23 10:12:00

    @Boon
    客户端其实创建的是一个JavaScript的Date对象,不区分UTC与否的。

  41. Boon
    *.*.*.*
    链接

    Boon 2008-01-23 10:53:00

    @Jeffrey Zhao :
    请教,为什么DEBUGGER时.在客户端的时间和服务器端是有一个时间差.
    如JS里面时间是2008-1-22 01:01:01 而WebService AJAX DateTime 参数获取到的值是 2008-1-21 xx:01:01,在JS端改成直接字符传参就OK:

    如:
    MyNameSpace.WebServices.MyWebMethod('2008-1-22 01:01:01')
    但:
    MyNameSpace.WebServices.MyWebMethod(new Date(2008,0,22,1,1,1)) 这样WebMethod得到值就是 2008-1-21 xx:01:01

  42. 老赵
    admin
    链接

    老赵 2008-01-23 15:40:00

    @Boon
    JS里的Date对象是不带时区信息的,.NET里的时间是带时区信息的。
    客户端和服务器端的时间都是一个,如果转换成同一个时区的话。

  43. Boon
    *.*.*.*
    链接

    Boon 2008-01-24 08:11:00

    @Jeffrey Zhao
    我在是本地调试.所以来会存在不在同一时区的问题.

  44. SZW
    *.*.*.*
    链接

    SZW 2008-02-25 12:51:00

    请问老赵一个问题,我们都知道ASP.NET AJAX 将 .NET DateTime 值编码为 JSON 字符串之后,字符串内容是 \/Date(ticks)\/结构的(或者用JavaScriptSerializer对DateTime转换也是一样),其中 ticks 表示从 epoch (UTC) 开始的毫秒数。那么如果我要在浏览器中将这个 \/Date(ticks)\/转转为人类可读的形式,有没有直接的函数可以帮助转换呢?

  45. 老赵
    admin
    链接

    老赵 2008-02-25 14:38:00

    @SZW
    客户端吗?似乎可以使用new Date(ticks)来构建一个Date对象。

  46. SZW
    *.*.*.*
    链接

    SZW 2008-02-25 19:34:00

    @Jeffrey Zhao
    new Date(ticks)应该是有这个用法,我是说对 \/Date(ticks)\/ 这样的格式,js是否能有直接转换的方法。不然的话这个产生的所谓JSON格式,似乎也不能和js完全兼容,还不如直接输出Now.ToString()格式了。
    另外看到有地方说js和C#对ticks的起始时间是不统一的,如果这样的话AJAX.NET 中的JSON数据若要在客户端做进一步处理,岂不是很麻烦

  47. 老赵
    admin
    链接

    老赵 2008-02-25 20:02:00

    @SZW
    JS没有自动转法,Microsoft AJAX Library是通过正则表达式替换成new Date再eval的。
    其实时间不一样问题不大,服务器端有AddTicks等方法,取客户端基准时间一样就可以了。

  48. SZW
    *.*.*.*
    链接

    SZW 2008-02-25 20:10:00

    @Jeffrey Zhao
    嗯,实现起来是没问题,只是本来不是问题的地方现在要多留意一下了,呵呵。

  49. happyqq01[未注册用户]
    *.*.*.*
    链接

    happyqq01[未注册用户] 2008-07-11 09:57:00

    在JavaScript中如何将
    格式为2008-02-20
    转换为2008/02/20

  50. 浮云的等待
    *.*.*.*
    链接

    浮云的等待 2008-10-07 17:54:00

    赵老师,我可以请教一下吗?
    ClientScriptManager cs = this.ClientScript;

    //添加一個隱藏屬性?這個還不知道如何引用
    cs.RegisterHiddenField("HelloHidden", "Hiddenworld");

    在您讲课的范例中有提到这个隐藏属性,可是我一直都不知道要如何把它值抓出来,麻烦您抽空帮我看看,谢谢!我的MSN是gmrBrian@hotmail.com

  51. 过客是我[未注册用户]
    *.*.*.*
    链接

    过客是我[未注册用户] 2009-11-12 16:15:00

    超好,解决了我的问题。

  52. 过客是我[未注册用户]
    *.*.*.*
    链接

    过客是我[未注册用户] 2009-11-12 16:15:00

    这个超好,解决了在firefox时间显示问题。谢谢了。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我