說(shuō)明
用來(lái)對(duì)日期進(jìn)行處理。
源碼注解
/*eslint-disable*/
// 把 YYYY-MM-DD 改成了 yyyy-MM-dd
(function (main) {
'use strict';
/**
* 解析或格式化日期
* @class fecha
*/
// 最后暴露出去的對(duì)象
var fecha = {};
// 匹配以下情況中的一種
// 1. 1-4個(gè) d
// 2. 1-4個(gè) M
// 3. yy 或者 yyyy
// 4. 1-3個(gè) S
// 5. Do
// 6. ZZ
// 7. HH 或者 hh 或者 MM 或者 ss 或者 DD 或者 mm
// 8. a 或者 A
// 9. 雙引號(hào)包含著的內(nèi)容 ""
// 10. 單引號(hào)包含著的內(nèi)容 ''
var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g;
// 匹配一位或者兩位數(shù)字
var twoDigits = /\d\d?/;
// 匹配三位數(shù)字
var threeDigits = /\d{3}/;
// 匹配四位數(shù)字
var fourDigits = /\d{4}/;
// 匹配一個(gè)或兩個(gè)字符的詞逗嫡,包括以阿拉伯計(jì)數(shù)描述的兩個(gè)數(shù)字的月份
// 注:這里在 moment.js 也出現(xiàn)過育瓜,暫時(shí)還沒有分析出來(lái)為什么是這個(gè)區(qū)間葫隙,因?yàn)椴榭磚nicode,還不知道中間為什么去除那些字符集
var word = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
// 空函數(shù)
var noop = function () {
};
/**
* 將數(shù)組里面的每一項(xiàng)縮短到不長(zhǎng)于某個(gè)值
* @param arr 要改變的數(shù)組
* @param sLen 要設(shè)置的最長(zhǎng)的長(zhǎng)度
* @returns {Array} 處理后的數(shù)組
*/
function shorten(arr, sLen) {
var newArr = [];
for (var i = 0, len = arr.length; i < len; i++) {
newArr.push(arr[i].substr(0, sLen));
}
return newArr;
}
/**
* 根據(jù)名稱轉(zhuǎn)換回對(duì)應(yīng)的數(shù)字(月份中的第幾天)
* @param arrName 要轉(zhuǎn)成的名稱列表
* @returns {Function} 轉(zhuǎn)換函數(shù)
*/
function monthUpdate(arrName) {
return function (d, v, i18n) {
var index = i18n[arrName].indexOf(v.charAt(0).toUpperCase() + v.substr(1).toLowerCase()); // 使用前兩位來(lái)匹配
if (~index) { // 負(fù)數(shù)返回false躏仇,非負(fù)數(shù)返回true恋脚,即判斷是否找到對(duì)應(yīng)的索引
d.month = index; // 對(duì)應(yīng)的月份
}
};
}
/**
* 使用0左填充
* @param val 要填充的原始值
* @param len 要填充的長(zhǎng)度
* @returns {string|*}
*/
function pad(val, len) {
val = String(val);
len = len || 2; // 默認(rèn)位數(shù)為2
while (val.length < len) {
val = '0' + val;
}
return val;
}
var dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var monthNamesShort = shorten(monthNames, 3); // 月份前三位字母
var dayNamesShort = shorten(dayNames, 3); // 星期前三位字母
fecha.i18n = {
dayNamesShort: dayNamesShort,
dayNames: dayNames,
monthNamesShort: monthNamesShort,
monthNames: monthNames,
amPm: ['am', 'pm'],
DoFn: function DoFn(D) { // 獲取對(duì)應(yīng)的后綴,這里需要注意的是取模操作的優(yōu)先級(jí)高于加減乘除焰手,還有4-20都是th后綴
return D + ['th', 'st', 'nd', 'rd'][D % 10 > 3 ? 0 : (D - D % 10 !== 10) * D % 10];
}
};
// 匹配不同的格式
var formatFlags = {
D: function(dateObj) { // 星期中的第幾天(0-6)
return dateObj.getDay();
},
DD: function(dateObj) { // 星期中的第幾天(0-6)
return pad(dateObj.getDay());
},
Do: function(dateObj, i18n) { // 月份中的第幾天(1st-31st)慧起,加上后綴
return i18n.DoFn(dateObj.getDate());
},
d: function(dateObj) { // 月份中的第幾天(1-31)
return dateObj.getDate();
},
dd: function(dateObj) { // 月份中的第幾天(01-31)
return pad(dateObj.getDate());
},
ddd: function(dateObj, i18n) { // 星期中的第幾天(Sun-Sat)
return i18n.dayNamesShort[dateObj.getDay()];
},
dddd: function(dateObj, i18n) { // 星期中的第幾天(Sunday-Saturday)
return i18n.dayNames[dateObj.getDay()];
},
M: function(dateObj) { // 月份(1-12)
return dateObj.getMonth() + 1;
},
MM: function(dateObj) { // 月份(01-12)
return pad(dateObj.getMonth() + 1);
},
MMM: function(dateObj, i18n) { // 月份(Jan-Dec)
return i18n.monthNamesShort[dateObj.getMonth()];
},
MMMM: function(dateObj, i18n) { // 月份(January-December)
return i18n.monthNames[dateObj.getMonth()];
},
yy: function(dateObj) { // 年份最后兩位
return String(dateObj.getFullYear()).substr(2);
},
yyyy: function(dateObj) { // 年份完整四位
return dateObj.getFullYear();
},
h: function(dateObj) { // 12小時(shí)制的小時(shí)數(shù)(0-11)
return dateObj.getHours() % 12 || 12;
},
hh: function(dateObj) { // 12小時(shí)制的小時(shí)數(shù)(00-11)
return pad(dateObj.getHours() % 12 || 12);
},
H: function(dateObj) { // 24小時(shí)制的小時(shí)數(shù)(0-23)
return dateObj.getHours();
},
HH: function(dateObj) { // 24小時(shí)制的小時(shí)數(shù)(00-23)
return pad(dateObj.getHours());
},
m: function(dateObj) { // 分鐘數(shù)(0-59)
return dateObj.getMinutes();
},
mm: function(dateObj) { // 分鐘數(shù)(00-59)
return pad(dateObj.getMinutes());
},
s: function(dateObj) { // 秒數(shù)(0-59)
return dateObj.getSeconds();
},
ss: function(dateObj) { // 秒數(shù)(00-59)
return pad(dateObj.getSeconds());
},
S: function(dateObj) { // 一位毫秒數(shù)(0-9)
return Math.round(dateObj.getMilliseconds() / 100);
},
SS: function(dateObj) { // 兩位毫秒數(shù)(00-99)
return pad(Math.round(dateObj.getMilliseconds() / 10), 2);
},
SSS: function(dateObj) { // 三位毫秒數(shù)(000-999)
return pad(dateObj.getMilliseconds(), 3);
},
a: function(dateObj, i18n) { // 上下午(a或者p)
return dateObj.getHours() < 12 ? i18n.amPm[0] : i18n.amPm[1];
},
A: function(dateObj, i18n) { // 上下午(A或者P)
return dateObj.getHours() < 12 ? i18n.amPm[0].toUpperCase() : i18n.amPm[1].toUpperCase();
},
ZZ: function(dateObj) { // 時(shí)區(qū)偏移(±0000-1200)
var o = dateObj.getTimezoneOffset();
return (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4);
}
};
// 匹配不同的解析
var parseFlags = {
d: [twoDigits, function (d, v) {
d.day = v; // 月份中的第幾天
}],
M: [twoDigits, function (d, v) {
d.month = v - 1; // 月份
}],
yy: [twoDigits, function (d, v) {
var da = new Date(), cent = +('' + da.getFullYear()).substr(0, 2);
d.year = '' + (v > 68 ? cent - 1 : cent) + v; // 年
}],
h: [twoDigits, function (d, v) {
d.hour = v; // 小時(shí)
}],
m: [twoDigits, function (d, v) {
d.minute = v; // 分鐘
}],
s: [twoDigits, function (d, v) {
d.second = v; // 秒
}],
yyyy: [fourDigits, function (d, v) {
d.year = v; // 全年
}],
S: [/\d/, function (d, v) {
d.millisecond = v * 100; // 3位數(shù)的毫秒
}],
SS: [/\d{2}/, function (d, v) {
d.millisecond = v * 10; // 2位數(shù)的毫秒
}],
SSS: [threeDigits, function (d, v) {
d.millisecond = v; // 1位數(shù)的毫秒
}],
D: [twoDigits, noop], // 日期中的第幾天
ddd: [word, noop], // 三位字母的星期數(shù)
MMM: [word, monthUpdate('monthNamesShort')], // 月份縮寫轉(zhuǎn)換
MMMM: [word, monthUpdate('monthNames')], // 月份轉(zhuǎn)換
a: [word, function (d, v, i18n) { // 12小時(shí)制的判斷
var val = v.toLowerCase();
if (val === i18n.amPm[0]) { // am
d.isPm = false;
} else if (val === i18n.amPm[1]) { // pm
d.isPm = true;
}
}],
ZZ: [/[\+\-]\d\d:?\d\d/, function (d, v) { // 解析時(shí)區(qū)偏移,因?yàn)槭欠昼姅?shù)册倒,其實(shí)只需要匹配前兩位
var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes;
if (parts) { // 如果匹配到
minutes = +(parts[1] * 60) + parseInt(parts[2], 10); // 第一部分是符號(hào)蚓挤,第二部分是前兩位,第三部分是后兩位
d.timezoneOffset = parts[0] === '+' ? minutes : -minutes; // 正負(fù)時(shí)區(qū)判斷
}
}]
};
parseFlags.DD = parseFlags.D;
parseFlags.dddd = parseFlags.ddd;
parseFlags.Do = parseFlags.dd = parseFlags.d;
parseFlags.mm = parseFlags.m;
parseFlags.hh = parseFlags.H = parseFlags.HH = parseFlags.h;
parseFlags.MM = parseFlags.M;
parseFlags.ss = parseFlags.s;
parseFlags.A = parseFlags.a;
// 通用的格式字符串
fecha.masks = {
'default': 'ddd MMM dd yyyy HH:mm:ss',
shortDate: 'M/D/yy',
mediumDate: 'MMM d, yyyy',
longDate: 'MMMM d, yyyy',
fullDate: 'dddd, MMMM d, yyyy',
shortTime: 'HH:mm',
mediumTime: 'HH:mm:ss',
longTime: 'HH:mm:ss.SSS'
};
/**
* 格式化日期
* @param dateObj 日期對(duì)象或時(shí)間戳
* @param mask 日期掩碼驻子,例如'mm-dd-yy'或者'shortDate'
* @param i18nSettings 本地化設(shè)置
* @returns {String} 格式化后的日期
*/
fecha.format = function (dateObj, mask, i18nSettings) {
// 沒有傳入新的本地化設(shè)置灿意,則用默認(rèn)的
var i18n = i18nSettings || fecha.i18n;
// 如果日期對(duì)象是時(shí)間戳,就將它轉(zhuǎn)換成對(duì)象
if (typeof dateObj === 'number') {
dateObj = new Date(dateObj);
}
// 不是日期就報(bào)錯(cuò)
if (Object.prototype.toString.call(dateObj) !== '[object Date]' || isNaN(dateObj.getTime())) {
throw new Error('Invalid Date in fecha.format');
}
// 獲取掩碼對(duì)應(yīng)的格式
mask = fecha.masks[mask] || mask || fecha.masks['default'];
// 使用對(duì)應(yīng)的格式化方法崇呵,如果不存在則截取第二位以后的(暫時(shí)未發(fā)現(xiàn)為什么)
return mask.replace(token, function ($0) {
return $0 in formatFlags ? formatFlags[$0](dateObj, i18n) : $0.slice(1, $0.length - 1);
});
};
/**
* Parse a date string into an object, changes - into /
* @method parse
* @param {string} dateStr Date string
* @param {string} format Date parse format
* @returns {Date|boolean}
*/
/**
* 將日期字符串解析成對(duì)象
* @param dateStr 日期字符串
* @param format 格式
* @param i18nSettings 本地化設(shè)置
* @returns {Date|Boolean} 日期對(duì)象或者false
*/
fecha.parse = function (dateStr, format, i18nSettings) {
var i18n = i18nSettings || fecha.i18n;
if (typeof format !== 'string') {
throw new Error('Invalid format in fecha.parse');
}
format = fecha.masks[format] || format;
// Avoid regular expression denial of service, fail early for really long strings
// https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
// 正則表達(dá)式對(duì)于太長(zhǎng)的字符串無(wú)法處理缤剧,簡(jiǎn)單的說(shuō)非確定性有窮自動(dòng)機(jī)(NFA)在字符串長(zhǎng)度增加時(shí)需要決策的長(zhǎng)度會(huì)呈指數(shù)級(jí)增長(zhǎng)
if (dateStr.length > 1000) {
return false;
}
var isValid = true;
var dateInfo = {};
format.replace(token, function ($0) {
if (parseFlags[$0]) { // 如果存在對(duì)應(yīng)的解析方式
var info = parseFlags[$0]; // 獲取對(duì)應(yīng)的解析方式
var index = dateStr.search(info[0]); // 查找對(duì)應(yīng)的匹配
if (!~index) { // 如果沒有匹配到
isValid = false;
} else { // 如果匹配到
dateStr.replace(info[0], function (result) {
info[1](dateInfo, result, i18n); // 使用對(duì)應(yīng)的函數(shù)進(jìn)行處理
dateStr = dateStr.substr(index + result.length); // 截?cái)嗥ヅ洳糠? return result;
});
}
}
return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1);
});
if (!isValid) {
return false;
}
// 處理12小時(shí)制
var today = new Date();
if (dateInfo.isPm === true && dateInfo.hour != null && +dateInfo.hour !== 12) {
dateInfo.hour = +dateInfo.hour + 12; // 下午的時(shí)間+12
} else if (dateInfo.isPm === false && +dateInfo.hour === 12) {
dateInfo.hour = 0; // 上午12點(diǎn)即下午0點(diǎn)
}
// 處理時(shí)區(qū)偏移
var date;
if (dateInfo.timezoneOffset != null) { // 如果解析出來(lái)時(shí)區(qū)偏移
dateInfo.minute = +(dateInfo.minute || 0) - +dateInfo.timezoneOffset; // 時(shí)區(qū)偏移以分鐘記
date = new Date(Date.UTC(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0));
} else { // 否則
date = new Date(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0);
}
return date;
};
/* istanbul ignore next */
if (typeof module !== 'undefined' && module.exports) {
module.exports = fecha;
} else if (typeof define === 'function' && define.amd) {
define(function () {
return fecha;
});
} else {
main.fecha = fecha;
}
})(this);