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里,将会有以下的使用形式。

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

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

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

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

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

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

 

*** 3)提供多语言支持的接口DateFormatSymbols

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

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

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的正则表达式,将会如下面这样:

var regx_date = /(\d{4})年(\d{1,2})月(\d{1,2})日/;

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}/
* 时分秒,是一位或两位数字,正则式:/([Hhms])\1?/
* 毫秒S,也有一位或三位的区别,正则式:/S(?:SS)?/
* 上下午a,时区z/Z,纪元G,正则式: /[azZG]/

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

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

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

// 按TOKEN正则式来replace

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

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

return $0;
});

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

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

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

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

var tIndex = [];

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

// 按TOKEN正则式来replace
pattern.replace(TOKEN, function($0){

tIndex.push($0); // 按顺序记录模式符号的位置

return $0;
});

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

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

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

yyyy年MM月dd日

          |
          |
          v

(\\d{4})年(0[1-9]|1[012])月(0[1-9]|[12][0-9]|3[01])日

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

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

// 按TOKEN正则式来replace
var str = pattern.replace(TOKEN, function($0){

tIndex.push($0); // 按顺序记录模式符号的位置

// 查表获得模式符号的数值正则表达式
if(typeof regx[$0] === "string"){
return regx[$0];
}

return $0;
});

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

 

*** 3) 实现上的一些设计

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

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

var Getter = new function(){

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

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

//...
};

 

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

var Setter = new function(){

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

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

//...
};

 

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

var SimpleDateFormat = function(pattern, symbols){

this.format = function(date){

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

return datestr;
};

this.parse = function(datestr){

var m = datestr.match(pRegx), $0,
date = new Date();

for(var i=1, len=m.length; i<len; i++){
$0 = tIndex[i-1]; // 从符号顺序表中获得模式符号

date = Setter[$0](date, m[i] ,symbols);
}

return date;
};

};

 

** 三、后记

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

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

转载于:https://www.cnblogs.com/hoodng/archive/2012/03/16/2399569.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值