Chinaunix首页 | 论坛 | 博客
  • 博客访问: 475834
  • 博文数量: 55
  • 博客积分: 1867
  • 博客等级: 上尉
  • 技术积分: 587
  • 用 户 组: 普通用户
  • 注册时间: 2006-12-29 01:33
文章分类

全部博文(55)

文章存档

2013年(1)

2012年(2)

2011年(16)

2010年(11)

2009年(5)

2008年(10)

2007年(8)

2006年(2)

分类: 系统运维

2012-03-12 01:58:34

  
  Javascript日期的Format与Parse

  网上已经有很多文章或代码介绍了如何用javascript格式化一个Date对象,但都和自己的应用与要求有一定的差距。尤其是如何Parse一个字符串的日期,大多依赖Date对象的parse方法,在有些应用中对如“2012年3月8日”这样的格式就需要进行特殊处理,甚至于据说IE浏览器对“2012-3-8”的格式似乎都不能直接parse(我没有太多这方面的经验)。

  我一直想仿照Java的SimpleDateFormat做一个javascript版的Formatter和Parser以完善 项目。最近学习了javascript的正则表达式,并在用javascript进行HTML转义与反转的实现中对正则表达式的应用有了进一步的熟悉。所以决定用正则表达式来比较彻底地实现一个javascript版的SimpleDateFormat。

  估计发明轮子的感觉是我这个年龄的程序员,如果还在(喜欢)编程的唯一的动力了。

** 一、SimpleDateFormat的功能

  参照Java的SimpleDateFormat,这个javascritp版的主要应有以下功能:

*** 1)符合Java的模式符号

  Java在格式化日期提供了很多模式符号,有些是我们几乎从未用过的,在javascript的版本里,主要支持以下的几个符号就基本够日常的应用了。

|--------+------------------------+--------------+-------------|
| Letter | Date or Time Component | Presentation | Examples    |
|--------+------------------------+--------------+-------------|
| G      | Era designator         | Text         | AD; BC      |
|--------+------------------------+--------------+-------------|
| y      | Year                   | Year         | 2012; 97    |
|--------+------------------------+--------------+-------------|
| M      | Month in year          | Month        | July;Jul;07 |
|--------+------------------------+--------------+-------------|
| d      | Day in month           | Number       | 9; 09       |
|--------+------------------------+--------------+-------------|
| E      | Day in week            | Text         | Tuesday;Tue |
|--------+------------------------+--------------+-------------|
| H      | Hour in day (0-23)     | Number       | 0           |
|--------+------------------------+--------------+-------------|
| h      | Hour in am/pm (1-12)   | Number       | 12          |
|--------+------------------------+--------------+-------------|
| m      | Minute in hour         | Number       | 30          |
|--------+------------------------+--------------+-------------|
| s      | Second in minute       | Number       | 59          |
|--------+------------------------+--------------+-------------|
| S      | Millisecond            | Number       | 999         |
|--------+------------------------+--------------+-------------|
| a      | AM/PM marker           | Text         | AM          |
|--------+------------------------+--------------+-------------|
| z      | Time Zone              | Genral       | CST         |
|--------+------------------------+--------------+-------------|
| Z      | Time Zone              | RFC-822      | +0800       |
|--------+------------------------+--------------+-------------|

*** 2)和Java的SimpleDateFormat类似的使用方式
  
  Java的SimpleDateFormat一般性的使用是非常简单的,常用的也就两个方法,就是format, parse。在javascript版本的SimpleDateFormat里,将会有以下的使用形式。
 

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var sft = new js.text.SimpleDateFormat();

  3. sft.format(new Date()); // Format date to "Sun Mar 11 19:54:02 2012"

  4. var date = sft.parse("Sun Mar 11 19:54:02 2012"); // Parse date string

  5. sft.setPattern("yyyy年MM月dd日"); // Apply new pattern

  6. sft.format(date) // 2012年03月11日

  7. date = sft.parse("2012年03月11日");

  8. #+end_src

*** 3)提供多语言支持的接口DateFormatSymbols
  
  尽管在javascript层面上处理多语言支持,似乎还不多见,但对于web应用逐步向编程化方向转换,直接在前端提供多语言的处理能力将会是非常有意义的。Java的SimpleDateFormat里,就使用了一个DateFormatSymbols的接口,从这个接口上就可以get到月份、星期、上午、下午等的多语言支持的符号。那么在javascript版本里,引入这个接口我们就可以实现如下的应用了。
 

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var sft = new js.text.SimpleDateFormat("EEE MMM dd, yyyy", DateFormatSymbols);

  3. sft.format(new Date()); // Format date to "周日 三月 11, 2012"

  4. #+end_src

** 二、format和parse的实现设想

  format和parse的功能都和一个东西有关,就是日期格式的pattern,而各种日期格式都可以用上面提到的几个模式符号(y, M, d, H ...)的排列来表示,如pattern

  yyyy-MM-dd

就表示日期可以格式成“2012-03-08”。用正则表达式的思想来做,无非遍历这个模板,替换里面的模式符号,比如把yyyy,用日期的年来替换,等等。

而对于一个形如“2012年3月8日”的字符串,我们使用一个形如下面的pattern
  
  yyyy年M月d日

也应该是可以parse出一个日期来的。比如按照模式符号和其位置,我们可以写出一个正则式来提取和日期有关的数值。比如写一个简单的能提取年、月、日的javascript的正则表达式,将会如下面这样:
 

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var regx_date = /(\d{4})(\d{1,2})(\d{1,2})/;

  3. var m = "2012年3月11日".match(regx_date);

  4. #+end_src
  当然上面这个正则式是有很多问题的,比如月份部分\d{1,2}将匹配0到99的数字,99这个数字对于月份来说显然是错误的,所以这部分还需要更复杂的表达式。

*** 1) 构建提取模式符号的正则表达式

  我们需要从日期格式的pattern里提取模式符号,要提取的有:
  
  * 两位数的年yy(还有人在用吗?), 四位数年yyyy。 正则式:/yy(?:yy)?/
  * 月份M, M有一到四位的四钟可能。正则式:/M{1,4}/
  * 日子d (Day in month),正则式: /d{1,2}/
  * 星期E,一般有缩写和完整单词两种,正则式:/E{1,4}/
  * 时分秒,是一位或两位数字,正则式:/([Hhms])\1?/
  * 毫秒S,也有一位或三位的区别,正则式:/S(?:SS)?/
  * 上下午a,时区z/Z,纪元G,正则式: /[azZG]/

  到此,我们可以构建一个完整的正则表达式来提取模式符号了。
 

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var TOKEN = /yy(?:yy)?|M{1,4}|d{1,2}|E{1,4}|([Hhms])\1?|S(?:SS)?|[azZG]/g;

  3. // 对于下面pattern
  4. var pattern = "EEE MMM dd, yyyy hh:mm:ss.SSS a Z";

  5. // 按TOKEN正则式来replace

  6. pattern.replace(TOKEN, function($0){

  7.     //System.out.println($0); // 看看$0是什么?

  8.     return $0;
  9. });

  10. #+end_src

*** 2) 构建用于parse的正则表达式

  上面的正则表达式对于format已经足够了,但对于parse一个日期字符串来说,还要进一步构建可以提取年月日等数值的正则表达式。提取年月日等数值信息,首先需要提取两个信息:

  * 位置信息,比如对于日期串“01/01/2012”来说,里面的两个“01”哪个是月份,哪
    个是日子。
  * 数值信息,比如对于日期串”Mar 11, 2012“,里面的“Mar”是三月份,对于ISO
    格式的日期串"2012-03-11",三月份就是里面的“03”,如何提取到这些数值信息。

  对于位置信息,根据前面提取的pattern中的模式符号,我们只要按找匹配到的顺序进行记录就可以了,比如:
 

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var tIndex = [];

  3. // 对于下面pattern
  4. var pattern = "EEE MMM dd, yyyy hh:mm:ss.SSS a Z";

  5. // 按TOKEN正则式来replace
  6. pattern.replace(TOKEN, function($0){
  7.     
  8.     tIndex.push($0); // 按顺序记录模式符号的位置
  9.     
  10.     return $0;
  11. });

  12. #+end_src

  对于数值信息,我们得先建立一张表,里面有yy, yyyy,M, MM, MMM, MMMM等各种模式的可能对应的数值的正则表达式,比如:
 

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var regx = {
  3.     yy : "(\\d{2})", // 2位数字
  4.     yyyy : "(\\d{4})", // 4位数字
  5.     M : "([1-9]|1[012])", // 1到9和10,11,12
  6.     MM : "(0[1-9]|1[012])",// 01到12
  7.     MMM : "(\\S+)", // 简单处理,非空白字符多个
  8.     MMMM : "(\\S+)", // 简单处理,非空白字符多个
  9.     d : "([1-9]|[12][0-9]|3[01])", // 1到31
  10.     dd : "(0[1-9]|[12][0-9]|3[01])", // 01到31
  11.     //....
  12. };

  13. #+end_src

  然后,用查表发替换模式符号,比如“yyyy年MM月dd日”这个pattern可以替换成
  
  yyyy年MM月dd日

        |
        |
        v

  (\\d{4})年(0[1-9]|1[012])月(0[1-9]|[12][0-9]|3[01])日
  
  代码很好写,改造一下上面的方法:
 

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var pattern = "yyyy年MM月dd日";

  3. // 按TOKEN正则式来replace
  4. var str = pattern.replace(TOKEN, function($0){
  5.     
  6.     tIndex.push($0); // 按顺序记录模式符号的位置
  7.     
  8.     // 查表获得模式符号的数值正则表达式
  9.     if(typeof regx[$0] === "string"){
  10.         return regx[$0];
  11.     }
  12.     
  13.     return $0;
  14. });

  15. // 生成正则表达式
  16. var pRegx = new RegExp(str);

  17. #+end_src
 
*** 3) 实现上的一些设计

   至此,实际上我们已经可以看到format和parse的初步样子了,无非是缺少一些如何get/set年月日等信息从(到)一个Date对象的体力活了。关于这部分如果按OO的思路来设计,程序虽然会略显臃肿,但会比较好维护。

  对于format,我们可以造一个工具类叫Getter,里面有一堆方法,而方法名正好是模式符号,比如:
 

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var Getter = new function(){

  3.     this.yyyy = function(date, symbols){
  4.         return date.getFullYear();
  5.     };

  6.     this.MMM = function(date, symbols){
  7.         return symbols.getShortMonths()[date.getMonth()];
  8.     };

  9.     //...
  10. };

  11. #+end_src

  而对于parse,也可以再造一个工具类叫Setter,里面同样有一堆以模式符号做为方法名的方法,比如:

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var Setter = new function(){

  3.     this.yyyy = function(date, value, symbols){
  4.         date.setFullYear(value);
  5.         return date;
  6.     };

  7.     this.MMM = function(date, value, symbols){
  8.         var i = symbols.getShortMonths().indexOf(value);
  9.         date.setMonth(i);
  10.         return i;
  11.     };

  12.     //...
  13. };

  14. #+end_src

  那么SimpleDateFormat的format和parse方法的实现就会显得很优雅了。

点击(此处)折叠或打开

  1. #+begin_src javascript

  2. var SimpleDateFormat = function(pattern, symbols){

  3.     this.format = function(date){

  4.         var datestr = pattern.replace(TOKEN, function($0){
  5.             return Getter[$0](date, symbols);
  6.         });

  7.         return datestr;
  8.     };

  9.     this.parse = function(datestr){
  10.         
  11.         var m = datestr.match(pRegx), $0,
  12.         date = new Date();

  13.         for(var i=1, len=m.length; i<len; i++){
  14.             $0 = tIndex[i-1]; // 从符号顺序表中获得模式符号
  15.             
  16.             date = Setter[$0](date, m[i] ,symbols);
  17.         }
  18.         
  19.         return date;
  20.     };

  21. };

  22. #+end_src

** 三、后记

  不要pattern可以parse日期时间吗? 是不是要收集足够多的pattern,然后一个一个测试能否parse出Date对象来?

  以上的代码在一般情况下是可以工作的,但用于生产环境的话,还需要做一些例外和出错时的处理,具体的就不在这篇技术文章中写了。有兴趣了解的可以到我的开源项目 去看正式的源代码,主要是js.text.SimpleDateFormat。

阅读(2820) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~