说明
用来对日期进行处理。
源码注解
/*eslint-disable*/
// 把 YYYY-MM-DD 改成了 yyyy-MM-dd
(function (main) {
'use strict';
/**
* 解析或格式化日期
* @class fecha
*/
// 最后暴露出去的对象
var fecha = {};
// 匹配以下情况中的一种
// 1. 1-4个 d
// 2. 1-4个 M
// 3. yy 或者 yyyy
// 4. 1-3个 S
// 5. Do
// 6. ZZ
// 7. HH 或者 hh 或者 MM 或者 ss 或者 DD 或者 mm
// 8. a 或者 A
// 9. 双引号包含着的内容 ""
// 10. 单引号包含着的内容 ''
var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g;
// 匹配一位或者两位数字
var twoDigits = /\d\d?/;
// 匹配三位数字
var threeDigits = /\d{3}/;
// 匹配四位数字
var fourDigits = /\d{4}/;
// 匹配一个或两个字符的词,包括以阿拉伯计数描述的两个数字的月份
// 注:这里在 moment.js 也出现过,暂时还没有分析出来为什么是这个区间,因为查看unicode,还不知道中间为什么去除那些字符集
var word = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
// 空函数
var noop = function () {
};
/**
* 将数组里面的每一项缩短到不长于某个值
* @param arr 要改变的数组
* @param sLen 要设置的最长的长度
* @returns {Array} 处理后的数组
*/
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;
}
/**
* 根据名称转换回对应的数字(月份中的第几天)
* @param arrName 要转成的名称列表
* @returns {Function} 转换函数
*/
function monthUpdate(arrName) {
return function (d, v, i18n) {
var index = i18n[arrName].indexOf(v.charAt(0).toUpperCase() + v.substr(1).toLowerCase()); // 使用前两位来匹配
if (~index) { // 负数返回false,非负数返回true,即判断是否找到对应的索引
d.month = index; // 对应的月份
}
};
}
/**
* 使用0左填充
* @param val 要填充的原始值
* @param len 要填充的长度
* @returns {string|*}
*/
function pad(val, len) {
val = String(val);
len = len || 2; // 默认位数为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) { // 获取对应的后缀,这里需要注意的是取模操作的优先级高于加减乘除,还有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小时制的小时数(0-11)
return dateObj.getHours() % 12 || 12;
},
hh: function(dateObj) { // 12小时制的小时数(00-11)
return pad(dateObj.getHours() % 12 || 12);
},
H: function(dateObj) { // 24小时制的小时数(0-23)
return dateObj.getHours();
},
HH: function(dateObj) { // 24小时制的小时数(00-23)
return pad(dateObj.getHours());
},
m: function(dateObj) { // 分钟数(0-59)
return dateObj.getMinutes();
},
mm: function(dateObj) { // 分钟数(00-59)
return pad(dateObj.getMinutes());
},
s: function(dateObj) { // 秒数(0-59)
return dateObj.getSeconds();
},
ss: function(dateObj) { // 秒数(00-59)
return pad(dateObj.getSeconds());
},
S: function(dateObj) { // 一位毫秒数(0-9)
return Math.round(dateObj.getMilliseconds() / 100);
},
SS: function(dateObj) { // 两位毫秒数(00-99)
return pad(Math.round(dateObj.getMilliseconds() / 10), 2);
},
SSS: function(dateObj) { // 三位毫秒数(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) { // 时区偏移(±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; // 小时
}],
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位数的毫秒
}],
SS: [/\d{2}/, function (d, v) {
d.millisecond = v * 10; // 2位数的毫秒
}],
SSS: [threeDigits, function (d, v) {
d.millisecond = v; // 1位数的毫秒
}],
D: [twoDigits, noop], // 日期中的第几天
ddd: [word, noop], // 三位字母的星期数
MMM: [word, monthUpdate('monthNamesShort')], // 月份缩写转换
MMMM: [word, monthUpdate('monthNames')], // 月份转换
a: [word, function (d, v, i18n) { // 12小时制的判断
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) { // 解析时区偏移,因为是分钟数,其实只需要匹配前两位
var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes;
if (parts) { // 如果匹配到
minutes = +(parts[1] * 60) + parseInt(parts[2], 10); // 第一部分是符号,第二部分是前两位,第三部分是后两位
d.timezoneOffset = parts[0] === '+' ? minutes : -minutes; // 正负时区判断
}
}]
};
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 日期对象或时间戳
* @param mask 日期掩码,例如'mm-dd-yy'或者'shortDate'
* @param i18nSettings 本地化设置
* @returns {String} 格式化后的日期
*/
fecha.format = function (dateObj, mask, i18nSettings) {
// 没有传入新的本地化设置,则用默认的
var i18n = i18nSettings || fecha.i18n;
// 如果日期对象是时间戳,就将它转换成对象
if (typeof dateObj === 'number') {
dateObj = new Date(dateObj);
}
// 不是日期就报错
if (Object.prototype.toString.call(dateObj) !== '[object Date]' || isNaN(dateObj.getTime())) {
throw new Error('Invalid Date in fecha.format');
}
// 获取掩码对应的格式
mask = fecha.masks[mask] || mask || fecha.masks['default'];
// 使用对应的格式化方法,如果不存在则截取第二位以后的(暂时未发现为什么)
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}
*/
/**
* 将日期字符串解析成对象
* @param dateStr 日期字符串
* @param format 格式
* @param i18nSettings 本地化设置
* @returns {Date|Boolean} 日期对象或者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
// 正则表达式对于太长的字符串无法处理,简单的说非确定性有穷自动机(NFA)在字符串长度增加时需要决策的长度会呈指数级增长
if (dateStr.length > 1000) {
return false;
}
var isValid = true;
var dateInfo = {};
format.replace(token, function ($0) {
if (parseFlags[$0]) { // 如果存在对应的解析方式
var info = parseFlags[$0]; // 获取对应的解析方式
var index = dateStr.search(info[0]); // 查找对应的匹配
if (!~index) { // 如果没有匹配到
isValid = false;
} else { // 如果匹配到
dateStr.replace(info[0], function (result) {
info[1](dateInfo, result, i18n); // 使用对应的函数进行处理
dateStr = dateStr.substr(index + result.length); // 截断匹配部分
return result;
});
}
}
return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1);
});
if (!isValid) {
return false;
}
// 处理12小时制
var today = new Date();
if (dateInfo.isPm === true && dateInfo.hour != null && +dateInfo.hour !== 12) {
dateInfo.hour = +dateInfo.hour + 12; // 下午的时间+12
} else if (dateInfo.isPm === false && +dateInfo.hour === 12) {
dateInfo.hour = 0; // 上午12点即下午0点
}
// 处理时区偏移
var date;
if (dateInfo.timezoneOffset != null) { // 如果解析出来时区偏移
dateInfo.minute = +(dateInfo.minute || 0) - +dateInfo.timezoneOffset; // 时区偏移以分钟记
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);