三里屯摇滚 发表于 2012-03-12 22:07

Javascript日期的Format与Parse

Javascript日期的Format与Parse








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




我一直想仿照Java的SimpleDateFormat做一个javascript版的Formatter和Parser以完善 J$VM 项目。最近学习了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里,将会有以下的使用形式。







Js代码1.var sft = new js.text.SimpleDateFormat();
2.
3.sft.format(new Date()); // Format date to "Sun Mar 11 19:54:02 2012"
4.
5.var date = sft.parse("Sun Mar 11 19:54:02 2012"); // Parse date string
6.
7.sft.setPattern("yyyy年MM月dd日"); // Apply new pattern
8.
9.sft.format(date) // 2012年03月11日
10.
11.date = sft.parse("2012年03月11日");
   *** 3)提供多语言支持的接口DateFormatSymbols



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







Js代码1.var sft = new js.text.SimpleDateFormat("EEE MMM dd, yyyy", DateFormatSymbols);
2.
3.sft.format(new Date()); // Format date to "周日 三月 11, 2012"** 二、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的正则表达式,将会如下面这样:







Js代码1.var regx_date = /(\d{4})年(\d{1,2})月(\d{1,2})日/;
2.
3.var m = "2012年3月11日".match(regx_date);
   当然上面这个正则式是有很多问题的,比如月份部分\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}/

* 时分秒,是一位或两位数字,正则式:/()\1?/

* 毫秒S,也有一位或三位的区别,正则式:/S(?:SS)?/

* 上下午a,时区z/Z,纪元G,正则式: //




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







Js代码1.var TOKEN = /yy(?:yy)?|M{1,4}|d{1,2}|E{1,4}|()\1?|S(?:SS)?|/g;
2.
3.// 对于下面pattern
4.var pattern = "EEE MMM dd, yyyy hh:mm:ss.SSS a Z";
5.
6.// 按TOKEN正则式来replace
7.
8.pattern.replace(TOKEN, function($0){
9.
10.    //System.out.println($0); // 看看$0是什么?   
11.
12.    return $0;
13.});
   *** 2) 构建用于parse的正则表达式




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




* 位置信息,比如对于日期串“01/01/2012”来说,里面的两个“01”哪个是月份,哪

    个是日子。

* 数值信息,比如对于日期串”Mar 11, 2012“,里面的“Mar”是三月份,对于ISO

    格式的日期串"2012-03-11",三月份就是里面的“03”,如何提取到这些数值信息。




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







Js代码1.var tIndex = [];
2.
3.// 对于下面pattern
4.var pattern = "EEE MMM dd, yyyy hh:mm:ss.SSS a Z";
5.
6.// 按TOKEN正则式来replace
7.pattern.replace(TOKEN, function($0){
8.      
9.    tIndex.push($0); // 按顺序记录模式符号的位置
10.      
11.    return $0;
12.});
对于数值信息,我们得先建立一张表,里面有yy, yyyy,M, MM, MMM, MMMM等各种模式的可能对应的数值的正则表达式,比如:







Js代码1.var regx = {
2.    yy : "(\\d{2})", // 2位数字
3.    yyyy : "(\\d{4})", // 4位数字
4.    M : "(|1)", // 1到9和10,11,12
5.    MM : "(0|1)",// 01到12
6.    MMM : "(\\S+)", // 简单处理,非空白字符多个
7.    MMMM : "(\\S+)", // 简单处理,非空白字符多个
8.    d : "(||3)", // 1到31
9.    dd : "(0||3)", // 01到31
10.    //....
11.};
然后,用查表发替换模式符号,比如“yyyy年MM月dd日”这个pattern可以替换成



yyyy年MM月dd日




      |

      |

      v




(\\d{4})年(0|1)月(0||3)日



代码很好写,改造一下上面的方法:







Js代码1.var pattern = "yyyy年MM月dd日";
2.
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.// 生成正则表达式
17.var pRegx = new RegExp(str);*** 3) 实现上的一些设计




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




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







Js代码1.var Getter = new function(){
2.
3.    this.yyyy = function(date, symbols){
4.      return date.getFullYear();
5.    };
6.
7.    this.MMM = function(date, symbols){
8.      return symbols.getShortMonths();
9.    };
10.
11.    //...
12.};
而对于parse,也可以再造一个工具类叫Setter,里面同样有一堆以模式符号做为方法名的方法,比如:







Js代码1.var Setter = new function(){
2.
3.    this.yyyy = function(date, value, symbols){
4.      date.setFullYear(value);
5.      return date;
6.    };
7.
8.    this.MMM = function(date, value, symbols){
9.      var i = symbols.getShortMonths().indexOf(value);
10.      date.setMonth(i);
11.      return i;
12.    };
13.
14.    //...
15.};那么SimpleDateFormat的format和parse方法的实现就会显得很优雅了。







Js代码1.var SimpleDateFormat = function(pattern, symbols){
2.
3.    this.format = function(date){
4.
5.      var datestr = pattern.replace(TOKEN, function($0){
6.            return Getter[$0](date, symbols);
7.      });
8.
9.      return datestr;
10.    };
11.
12.    this.parse = function(datestr){
13.         
14.      var m = datestr.match(pRegx), $0,
15.      date = new Date();
16.
17.      for(var i=1, len=m.length; i<len; i++){
18.            $0 = tIndex; // 从符号顺序表中获得模式符号
19.            
20.            date = Setter[$0](date, m ,symbols);
21.      }
22.         
23.      return date;
24.    };
25.
26.};** 三、后记




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




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

清风鸟儿 发表于 2012-03-12 22:07

谢谢分享
页: [1]
查看完整版本: Javascript日期的Format与Parse