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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM