JavaScript自定義日期格式化函數(shù)詳細(xì)解析
我們對(duì) JavaScript 擴(kuò)展其中一個(gè)較常的做法便是對(duì) Date.prototype 的擴(kuò)展。因?yàn)槲覀冎?,Date 類只提供了若干獲取日期元素的方法,如 getDate(),getMinute()……卻沒有一個(gè)轉(zhuǎn)換為特定字符串的格式化方法。故所以,利用這些細(xì)微的方法,加以封裝,組合我們想要的日期字符串形式。一般來說,該格式化函數(shù)可以定義在 Date 對(duì)象的原型身上,也可以獨(dú)立一個(gè)方法寫出。定義原型方法的操作如 Date.prototype.format = function(date){……},使用時(shí)候直接 new Date().format(YYYY:MM:DD) 即可,仿佛就是 Date 對(duì)象的原生方法。但是定義原型方法卻略嫌有“入侵” JS 原型的不足。設(shè)計(jì) API 之時(shí)必須考慮這個(gè)問題。我的建議是,用戶按照自己的判斷去做決定,只是調(diào)用的方式不同,不影響過程的邏輯即可。
下面的一個(gè)例子就是以獨(dú)立函數(shù)寫出的 JavaScript 日期格式化函數(shù),獨(dú)立的 format 函數(shù)。回到格式化的這一知識(shí)點(diǎn)上,我們考查的是怎么實(shí)現(xiàn)的、運(yùn)用了哪些原理。傳統(tǒng)字符串拼接如 indexOf()+substr() 雖然能夠?qū)崿F(xiàn),但明顯不僅效率低下,而且代碼冗長,還是適宜引入正則表達(dá)式的方法,先寫出字符串正則然后再進(jìn)行結(jié)果的命中匹配。我們先看看來自 Steven Levithan 的例子:
/**
* Date Format 1.2.3
* @credit Steven Levithan <stevenlevithan.com> Includes enhancements by Scott Trenda <scott.trenda.net> and Kris Kowal <cixar.com/~kris.kowal/>
* Accepts a date, a mask, or a date and a mask.
* Returns a formatted version of the given date.
* The date defaults to the current date/time.
* The mask defaults to dateFormat.masks.default.
*/
dateFormat = (function(){
// 正則筆記, 1、token,(?:)表示非捕獲分組;/1 反向引用(思考:{1,2}可否和/1一樣意思?);根據(jù)這里的意義[LloSZ]表示括號(hào)內(nèi)的任意一個(gè)字符拿去匹配,很簡單,但暫時(shí)不明白/L|l|o|S|Z/在解析日期時(shí)的作用;最后的兩組“或”是匹配引號(hào)和引號(hào)內(nèi)的內(nèi)容(無所謂雙引號(hào)或單引號(hào))。
var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])/1?|[LloSZ]|"[^"]*"|'[^']*'/g,
// 2、timezone, [PMCEA][SDP]產(chǎn)生兩個(gè)字符的消耗;該reg的都是非捕獲分組,可加快正則速度。
timezone = //b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]/d{4})?)/b/g,
timezoneClip = /[^-+/dA-Z]/g,
// 不足兩位填充字符,或可指定位數(shù)
pad = function (val, len){
val = String(val);
len = len || 2;
while (val.length < len) val = "0" + val;
return val;
};
// 為什么返回一個(gè)function,因?yàn)榍懊嬲f明的變量都變作常量,下面返回的參數(shù)才是真正到時(shí)執(zhí)行的函數(shù)。這一點(diǎn)透過閉包的寫法來實(shí)現(xiàn)。如英文注釋說的,可以提速。
// Regexes and supporting functions are cached through closure
// 參數(shù)說明:date: Date 被解析的日期或新日期;mask:String 格式化日期的模板;utc:Stirng 可選的UTC。
return function (date, mask, utc) {
var i18n = dateFormat.i18n;
var masks = dateFormat.masks;
// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
// 如果只有一個(gè)參數(shù),其該參數(shù)是不包含數(shù)字的字符串,則視作這個(gè)參數(shù)為mask。date由下一個(gè)if中的new Date產(chǎn)生,那么date就是現(xiàn)在的日期。
if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !//d/.test(date)) {
mask = date;
date = undefined;
}
// Passing date through Date applies Date.parse, if necessary
date = date ? new Date(date) : new Date;
if (isNaN(date)) throw SyntaxError("invalid date");
// 通過判斷多種情況明確mask是什么,不論前面是如何指定的。留意 || 的技巧。
mask = String(masks[mask] || mask || masks["default"]);
// Allow setting the utc argument via the mask
if (mask.slice(0, 4) == "UTC:") {
mask = mask.slice(4);
utc = true;
}
// 分兩種情況,用UTC格式的情況和一般的。注意通過JS的字面索引也可以返回方法的成員。
var _ = utc ? "getUTC" : "get",
d = date[_ + "Date"](),
D = date[_ + "Day"](),
m = date[_ + "Month"](),
y = date[_ + "FullYear"](),
H = date[_ + "Hours"](),
M = date[_ + "Minutes"](),
s = date[_ + "Seconds"](),
L = date[_ + "Milliseconds"](),
o = utc ? 0 : date.getTimezoneOffset(),
flags = {
d: d,
dd: pad(d),
ddd: i18n.dayNames[D],
dddd: i18n.dayNames[D + 7],// 位寬:7, 見 dateFormat.dayNames。
m: m + 1, // 從0開始起月份
mm: pad(m + 1),
mmm: i18n.monthNames[m],
mmmm: i18n.monthNames[m + 12], // 位寬:12,見 dateFormat.monthNames
yy: String(y).slice(2),// 字符串slice()的用法
yyyy: y,
h: H % 12 || 12, // h表示12小時(shí)制,h除以12(因?yàn)槭M(jìn)制),取余的結(jié)果為12小時(shí)制的。
hh: pad(H % 12 || 12),
H: H,
HH: pad(H),
M: M,
MM: pad(M),
s: s,
ss: pad(s),
l: pad(L, 3), // Max,999ms
L: pad(L > 99 ? Math.round(L / 10) : L),
// 大小寫有影響
t: H < 12 ? "a" : "p",
tt: H < 12 ? "am" : "pm",
T: H < 12 ? "A" : "P",
TT: H < 12 ? "AM" : "PM",
// 這一步求 timezone,就是要處理一下。
// 前文有timezone,timezoneClip = /[^-+/dA-Z]/g,
// String返回日期的字符串形式,包括很長的……UTC……信息
// 假如沒有,則[""].pop() 返回空字符
Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
// 4位的TimezoneOffset
o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
// 求英文的["th", "st", "nd", "rd"],依據(jù)是d的個(gè)位數(shù)
S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
};
return mask.replace(token, function ($0 /*很好$0,須知$1、$2由系統(tǒng)占用了*/) {
// 怎么檢測(cè)某個(gè)對(duì)象身上有指定的屬性?用 in 檢測(cè)即可!
// $0.slice(1, $0.length - 1);?什么意思?
return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
});
};
})();
該段代碼對(duì)日期處理考慮得比較周全,我們就進(jìn)入原理看看它的奧秘,——是怎么處理日期的!
日期字符串模板中,我們約定用 yyyy/mm/dd 等的有意義的符號(hào)分別表示日期中某一個(gè)元素,像 y 即 year 某一年份,m 即 month 某一月份,d 即 day 某一天,如果是大寫的話還要注意區(qū)分開來,大寫 M 代表分鐘,小寫 m 是月份??傊?,這是一份我們?nèi)藶橐?guī)范好的約定,即上述代碼所謂的“mask”,遵照此約定輸入欲格式化模式的參數(shù),便可將日期類型的值輸出可供打印的字符串。至于解析的日期過程是,先按照 Mask 的全部要求,逐個(gè)獲取到日期的每一個(gè)元素(getDate(),getMinute()……可以很快獲取到),接著按照 Mask 真實(shí)的條件是什么,即Mask.replace(正則, 元素)方法進(jìn)行字符串模板與元素之間的替換,替換的過程還是以 flag 為標(biāo)志去逐一匹配的對(duì)照表。至于正則部分,關(guān)鍵在于理解 token 和 replace() 函數(shù)的過程。參加上述代碼注釋,即可了解內(nèi)部細(xì)節(jié)。
如果每一次都要輸入冗長的 Mask 字符串豈不是很累?我們可以通過定義常量的方法縮減我們的工作量:
dateFormat.masks = {
"default": "ddd mmm dd yyyy HH:MM:ss",
shortDate: "m/d/yy",
shortDate2: "yy/m/d/h:MM:ss",
mediumDate: "mmm d, yyyy",
longDate: "mmmm d, yyyy",
fullDate: "dddd, mmmm d, yyyy",
shortTime: "h:MM TT",
mediumTime: "h:MM:ss TT",
longTime: "h:MM:ss TT Z",
isoDate: "yyyy-mm-dd",
isoTime: "HH:MM:ss",
isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
// 加入中國類型的日期 @Edit 2010.8.11
,ChineseDate :'yyyy年mm月dd日 HH時(shí)MM分'
}
dateFormat.i18n = {
dayNames: [
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
],
monthNames: [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
]
};
Steve 的 dateFormat 足可以完成大多數(shù)日期轉(zhuǎn)化的任務(wù),不過在茫茫代碼中,我們找到了更優(yōu)的解法,不出20行代碼,把正則運(yùn)用得收放自如,就是來自月影前輩的JS !
Date.prototype.format = function(format) //author: meizz
{
var o = {
"M+" : this.getMonth()+1, //month
"d+" : this.getDate(), //day
"h+" : this.getHours(), //hour
"m+" : this.getMinutes(), //minute
"s+" : this.getSeconds(), //second
"q+" : Math.floor((this.getMonth()+3)/3), //quarter
"S" : this.getMilliseconds() //millisecond
}
if(/(y+)/.test(format)) format=format.replace(RegExp.$1,
(this.getFullYear()+"").substr(4 - RegExp.$1.length));
for(var k in o)if(new RegExp("("+ k +")").test(format))
format = format.replace(RegExp.$1,
RegExp.$1.length==1 ? o[k] :
("00"+ o[k]).substr((""+ o[k]).length));
return format;
}
alert(new Date().format("yyyy-MM-dd hh:mm:ss"));
原理上與 Steve 方法相似,但更濃縮的代碼,卻集技巧性和全面性于一身。從源碼第12行開始,test() 方法不但可以檢測(cè)是否匹配的這個(gè)起碼功能,而且實(shí)際上是有記憶匹配結(jié)果的,產(chǎn)生 RegExp.$1 結(jié)果組來處理年份(開始我認(rèn)為 test() 效率高并不會(huì)產(chǎn)生結(jié)果,實(shí)則不然)。然后,再使用 new RegExp 在字符串形式創(chuàng)建正則表達(dá)式的實(shí)例,又是一個(gè)高明的地方,——因?yàn)橹苯优c o 的 hash 表直接對(duì)接起來了!繼而依法瓢葫蘆,先測(cè)試是否命中匹配,有的話就進(jìn)行替換。
另外,代碼中的 ("00" + o[k]).substr(String(o[k]).length) 也是有趣的地方,前面加上兩個(gè)什么意思呢?原來目的是為了取數(shù)組的最后兩位。這是綜合利用 substr() 方法的一個(gè)技巧,substr 第一個(gè)參數(shù)是開始截取的 index,若不指定第二個(gè)參數(shù) index 則保留字符串到最后(str.length)。于是,我們事先加多了多少位,原本固定的字符串長度不變(String(o[k].length))的情況下,那么就留下多少個(gè)位。(p.s “00”相當(dāng)于占位符,亦可用其他字符串“XX”代替無區(qū)別)
仍然覺得這段代碼有不少的困難?我們嘗試把月影的函數(shù)重寫為可讀性較強(qiáng)的代碼,原理上趨于一致可是沒那么多的技巧,相信這樣可以節(jié)省大家的時(shí)間,回頭再去看月影的代碼也不遲。
date = {
format: function(date, format){
date = new Date(date); // force con.
date = {
year : date.getFullYear()
,month : date.getMonth() + 1 // 月份, 月份從零算起
,day : date.getDate()
,hour : date.getHours()
,minute : date.getMinutes()
,second : date.getSeconds()
,milute : date.getMilliseconds()
};
var
match
,reg = /(y+)|(Y+)|(M+)|d+|h+|m+|s+|u+/g;
while((match = reg.exec(format)) != null){
match = match[0];
if(/y/i.test(match)){
format = format.replace(match, date.year);
}
if(match.indexOf('M') != -1){
format = format.replace(match, date.month);
}
if(match.indexOf('d') != -1){
format = format.replace(match, date.day);
}
if(match.indexOf('h') != -1){
format = format.replace(match, date.hour);
}
if(match.indexOf('m') != -1){
format = format.replace(match, date.minute);
}
if(match.indexOf('s') != -1){
format = format.replace(match, date.second);
}
if(match.indexOf('u') != -1){
format = format.replace(match, date.milute);
}
}
return format;
}
};
2011--1-7:
從 ext 4.0 淘到的日期格式化的代碼,怎么講字符串轉(zhuǎn)為 js 標(biāo)準(zhǔn)日期?看看新 ext 是怎么做的?
/**
* 按照特定的格式模式格式化日期。
* Parse a value into a formatted date using the specified format pattern.
* @param {String/Date} value 要格式化的值(字符串必須符合JavaScript日期對(duì)象的格式要求,參閱<a mce_>parse()</a>)The value to format (Strings must conform to the format expected by the javascript
* Date object's <a mce_>parse()</a> method)
* @param {String} format (可選的)任意的日期格式化字符串。(默認(rèn)為'm/d/Y')(optional) Any valid date format string (defaults to 'm/d/Y')
* @return {String} 已格式化字符串。The formatted date string
*/
date: function(v, format) {
if (!v) {
return "";
}
if (!Ext.isDate(v)) {
v = new Date(Date.parse(v));
}
return v.dateFormat(format || Ext.util.Format.defaultDateFormat);
}
date 構(gòu)造器還可以通過算出距離1970起為多久的毫秒數(shù)來確定日期?——的確,這樣也行,——也就說,舉一反三,從這個(gè)問題說明,js 日期最小的單位是毫秒。
最終版本:
/**
* 日期格式化。詳見博客文章:http://blog.csdn.net/zhangxin09/archive/2011/01/01/6111294.aspx
* e.g: new Date().format("yyyy-MM-dd hh:mm:ss")
* @param {String} format
* @return {String}
*/
Date.prototype.format = function (format) {
var $1, o = {
"M+": this.getMonth() + 1, // 月份,從0開始算
"d+": this.getDate(), // 日期
"h+": this.getHours(), // 小時(shí)
"m+": this.getMinutes(), // 分鐘
"s+": this.getSeconds(), // 秒鐘
// 季度 quarter
"q+": Math.floor((this.getMonth() + 3) / 3),
"S": this.getMilliseconds() // 千秒
};
var key, value;
if (/(y+)/.test(format)) {
$1 = RegExp.$1,
format = format.replace($1, String(this.getFullYear()).substr(4 - $1));
}
for (key in o) { // 如果沒有指定該參數(shù),則子字符串將延續(xù)到 stringvar 的最后。
if (new RegExp("(" + key + ")").test(format)) {
$1 = RegExp.$1,
value = String(o[key]),
value = $1.length == 1 ? value : ("00" + value).substr(value.length),
format = format.replace($1, value);
}
}
return format;
}
- js 格式化時(shí)間日期函數(shù)小結(jié)
- js時(shí)間戳格式化成日期格式的多種方法
- javascript格式化日期時(shí)間函數(shù)
- AngularJS 日期格式化詳解
- Extjs中DisplayField的日期或者數(shù)字格式化擴(kuò)展
- js中格式化日期時(shí)間型數(shù)據(jù)函數(shù)代碼
- JavaScript格式化日期時(shí)間的方法和自定義格式化函數(shù)示例
- 用JavaScript將從數(shù)據(jù)庫中讀取出來的日期型格式化為想要的類型。
- JS 日期驗(yàn)證正則附asp日期格式化函數(shù)
- Js獲取當(dāng)前日期時(shí)間及格式化代碼
相關(guān)文章
js實(shí)現(xiàn)二代身份證號(hào)碼驗(yàn)證詳解
本文給大家分享一段超級(jí)全面的二代身份證號(hào)碼驗(yàn)證程序,由JS編寫而成,可以校驗(yàn)身份證的地址碼、出生日期碼、順序碼和數(shù)字校驗(yàn)碼。是身份證去偽存真的一大利器。2014-11-11JS+HTML實(shí)現(xiàn)的圓形可點(diǎn)擊區(qū)域示例【3種方法】
這篇文章主要介紹了JS+HTML實(shí)現(xiàn)的圓形可點(diǎn)擊區(qū)域,結(jié)合實(shí)例形式分析了javascript結(jié)合HTML元素屬性實(shí)現(xiàn)一個(gè)圓形的可點(diǎn)擊區(qū)域相關(guān)操作技巧,需要的朋友可以參考下2018-08-08js+css實(shí)現(xiàn)的簡單易用兼容好的分頁
使用html、js、css實(shí)現(xiàn)的簡單易用兼容好的分頁,具體的實(shí)現(xiàn)如下,感興趣的朋友可以參考下2013-12-12js實(shí)現(xiàn)時(shí)鐘定時(shí)器
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)時(shí)鐘定時(shí)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03javascript實(shí)現(xiàn)簡單的可隨機(jī)變色網(wǎng)頁計(jì)算器示例
這篇文章主要介紹了javascript實(shí)現(xiàn)簡單的可隨機(jī)變色網(wǎng)頁計(jì)算器,具有基本的四則運(yùn)算與背景色隨機(jī)變換功能,需要的朋友可以參考下2016-12-12js實(shí)現(xiàn)適用于素材網(wǎng)站的黑色多級(jí)菜單導(dǎo)航條效果
這篇文章主要介紹了js實(shí)現(xiàn)適用于素材網(wǎng)站的黑色多級(jí)菜單導(dǎo)航條效果,涉及javascript鼠標(biāo)事件及頁面元素樣式的動(dòng)態(tài)切換技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08javascript實(shí)現(xiàn)點(diǎn)擊按鈕變色
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)點(diǎn)擊按鈕變色,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07