如何强制日期构造函数在 IE 上接受格式
How to force Date constructor to accept format on IE
我有一个 API 提供以下格式的日期:
yyyy-MM-dd'T'HH:mm:ssZ,例如:
2017-04-18T11:18:05-0300。 Chrome 可以从这样的字符串构建日期,但 IE 不能。有什么方法可以强制 IE 也接受这种格式,解决这个问题最简单的方法是什么?
我尝试了类似下面代码片段的方法,但没有成功
(function() {
var originalDateFn = Date;
var month_names_short = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date = function(date) {
var splitted = splitIfOnApiFormat(date);
if(splitted !== null) {
return new Date( month_names_short[splitted[2]] + ' ' + splitted[3] + ' ' + splitted[1] + ' ' + splitted[4] +' GMT'+ splitted[5]);
}
return originalDateFn(date);
};
function splitIfOnApiFormat(date) {
return typeof date === 'string' && date.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}\:\d{2}\:\d{2})([-+]\d{4})$/);
}
})();
好的,知道了
对于可能需要的任何其他人,以下代码修复了它:
(function() {
var originalDateFn = Date.prototype.constructor;
var month_names_short = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date = function(date) {
var splitted = splitIfOnApiFormat(date);
if(splitted !== null) {
return new originalDateFn( month_names_short[(Number(splitted[2])) -1] + ' ' + splitted[3] + ' ' + splitted[1] + ' ' + splitted[4] +' GMT'+ splitted[5]);
}
return new originalDateFn(date);
};
function splitIfOnApiFormat(date) {
return typeof date === 'string' ? date.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}\:\d{2}\:\d{2})([-+]\d{4})$/) : null;
}
})();
这种情况下的问题是最后的偏移部分中的小时和分钟之间缺少 :
。在这方面,IE 有权将此报告为无效日期,因为它不符合 definition of date in the spec,即 YYYY-MM-DDTHH:mm:ss.sssZ
或 YYYY-MM-DDTHH:mm:ss.sss±hh:mm
( 一个冒号)。
您可以通过以下方式解决此问题:
const date = "2017-04-18T11:18:05-0300";
function fixDate(date) {
return date.replace(/(\d\d)(\d\d)$/,
function(_, hh, mm) { return hh + ':' + mm; });
}
console.log(new Date(fixDate(date)));
黄金法则是 从不 使用内置解析器解析字符串(即使用 Date 构造函数或 Date.parse)。您已经将字符串分成几部分,因此只需将它们传递给 Date 构造函数并针对时区进行调整。
下面将解析 ECMA-262 中指定的 ISO 8601 扩展格式字符串,希望评论足够了。
/* Parse string in ISO 8601 extended format: YYYY-MM-DDTHH:mm:ss.sssZ.
** Date only strings are treated as local (per ISO 8601)
**
** @param {string} s - string to parse. Can be just a date, date and time,
** or date, time and timezone.
** Separator can be T or " " (space)
** Date string must be at least year and month: YYYY-MM
** All time parts are optional, if time provided then
** date must have year, month and day.
** All time parts are optional.
** Timezone, if present, must be Z or z or +/-HH:mm or +/-HHmm
**
** @returns {Date} - if s is a invalid date, contains invalid values,
** or is an invalid format, returns an invalid Date
*/
function parseISO(s) {
// Create base Date object
var date = new Date();
date.setHours(12, 0, 0, 0);
var invalidDate = new Date(NaN);
// Set some defaults
var sign = -1,
tzMins = 0;
var tzHr, tzMin;
// Trim leading and trailing whitespace
s = s.replace(/^\s*|\s*$/g, '').toUpperCase();
// Get parts of string and split into numbers
var d = (s.match(/^\d+(-\d+){0,2}/) || [''])[0].split(/\D/);
var t = (s.match(/[\sT]\d+(:\d+){0,2}(\.\d+)?/) || [''])[0].split(/\D/);
var tz = (s.match(/Z|[+\-]\d\d:?\d\d$/) || [''])[0];
// Resolve timezone to minutes, may be Z, +hh:mm or +hhmm
// substr is old school but more compatible than slice
// Don't need to split into parts but makes validation easier
if (tz) {
sign = /^-/.test(tz) ? 1 : -1;
tzHr = tz == 'Z' ? 0 : tz.substr(1, 2);
tzMin = tz == 'Z' ? 0 : tz.substr(tz.length - 2, 2) * 1;
tzMins = sign * (tzHr * 60 + tzMin);
}
// Validation
function isLeap(year) {
return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}
// Check number of date parts and month is valid
if (d.length > 3 || d[1] < 1 || d[1] > 12) return invalidDate;
// Test day is valid
var monthDays = [, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
var monthMax = isLeap(d[0]) && d[1] == 2 ? 29 : monthDays[+d[1]];
if (d[2] < 1 || d[2] > monthMax) return invalidDate;
// Test time parts
if (t.length > 5 || t[1] > 23 || t[2] > 59 || t[3] > 59 || t[4] > 999) return invalidDate;
// Test tz within bounds
if (tzHr > 12 || tzMin > 59) return invalidDate;
// If there's a timezone, use UTC methods, otherwise local
var method = tz ? 'UTC' : '';
// Set date values
date['set' + method + 'FullYear'](d[0], (d[1] ? d[1] - 1 : 0), d[2] || 1);
// Set time values - first memeber is '' from separator \s or T
date['set' + method + 'Hours'](t[1] || 0, (+t[2] || 0) + tzMins, t[3] || 0, t[4] || 0);
return date;
}
// Some tests
['2017-04-18T11:18:05-0300', // No colon in timezone
'2017-04-18 11:18:05-0300', // Space separator instead of T
'2016-04-12T04:31+05:30', // Colon in timezone
'2016-02-29T04:31+05:30', // Colon, leap year
'2016-04-12T04:31:56.004Z', // GMT
'2000-02-29T04:31+05:30', // Colon, leap year
'1900-02-29T04:31+05:30', // Colon, not leap year (invalid date)
'2016-04-12T04:31:56.004', // Local
'2016-04-12', // Date only (local)
'2016-04' // Minimal date (local)
].forEach(function(d) {
console.log(d + ': ' + parseISO(d).toString());
});
或者,使用库,有很多好的库可供选择。
我有一个 API 提供以下格式的日期:
yyyy-MM-dd'T'HH:mm:ssZ,例如: 2017-04-18T11:18:05-0300。 Chrome 可以从这样的字符串构建日期,但 IE 不能。有什么方法可以强制 IE 也接受这种格式,解决这个问题最简单的方法是什么?
我尝试了类似下面代码片段的方法,但没有成功
(function() {
var originalDateFn = Date;
var month_names_short = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date = function(date) {
var splitted = splitIfOnApiFormat(date);
if(splitted !== null) {
return new Date( month_names_short[splitted[2]] + ' ' + splitted[3] + ' ' + splitted[1] + ' ' + splitted[4] +' GMT'+ splitted[5]);
}
return originalDateFn(date);
};
function splitIfOnApiFormat(date) {
return typeof date === 'string' && date.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}\:\d{2}\:\d{2})([-+]\d{4})$/);
}
})();
好的,知道了 对于可能需要的任何其他人,以下代码修复了它:
(function() {
var originalDateFn = Date.prototype.constructor;
var month_names_short = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date = function(date) {
var splitted = splitIfOnApiFormat(date);
if(splitted !== null) {
return new originalDateFn( month_names_short[(Number(splitted[2])) -1] + ' ' + splitted[3] + ' ' + splitted[1] + ' ' + splitted[4] +' GMT'+ splitted[5]);
}
return new originalDateFn(date);
};
function splitIfOnApiFormat(date) {
return typeof date === 'string' ? date.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}\:\d{2}\:\d{2})([-+]\d{4})$/) : null;
}
})();
这种情况下的问题是最后的偏移部分中的小时和分钟之间缺少 :
。在这方面,IE 有权将此报告为无效日期,因为它不符合 definition of date in the spec,即 YYYY-MM-DDTHH:mm:ss.sssZ
或 YYYY-MM-DDTHH:mm:ss.sss±hh:mm
( 一个冒号)。
您可以通过以下方式解决此问题:
const date = "2017-04-18T11:18:05-0300";
function fixDate(date) {
return date.replace(/(\d\d)(\d\d)$/,
function(_, hh, mm) { return hh + ':' + mm; });
}
console.log(new Date(fixDate(date)));
黄金法则是 从不 使用内置解析器解析字符串(即使用 Date 构造函数或 Date.parse)。您已经将字符串分成几部分,因此只需将它们传递给 Date 构造函数并针对时区进行调整。
下面将解析 ECMA-262 中指定的 ISO 8601 扩展格式字符串,希望评论足够了。
/* Parse string in ISO 8601 extended format: YYYY-MM-DDTHH:mm:ss.sssZ.
** Date only strings are treated as local (per ISO 8601)
**
** @param {string} s - string to parse. Can be just a date, date and time,
** or date, time and timezone.
** Separator can be T or " " (space)
** Date string must be at least year and month: YYYY-MM
** All time parts are optional, if time provided then
** date must have year, month and day.
** All time parts are optional.
** Timezone, if present, must be Z or z or +/-HH:mm or +/-HHmm
**
** @returns {Date} - if s is a invalid date, contains invalid values,
** or is an invalid format, returns an invalid Date
*/
function parseISO(s) {
// Create base Date object
var date = new Date();
date.setHours(12, 0, 0, 0);
var invalidDate = new Date(NaN);
// Set some defaults
var sign = -1,
tzMins = 0;
var tzHr, tzMin;
// Trim leading and trailing whitespace
s = s.replace(/^\s*|\s*$/g, '').toUpperCase();
// Get parts of string and split into numbers
var d = (s.match(/^\d+(-\d+){0,2}/) || [''])[0].split(/\D/);
var t = (s.match(/[\sT]\d+(:\d+){0,2}(\.\d+)?/) || [''])[0].split(/\D/);
var tz = (s.match(/Z|[+\-]\d\d:?\d\d$/) || [''])[0];
// Resolve timezone to minutes, may be Z, +hh:mm or +hhmm
// substr is old school but more compatible than slice
// Don't need to split into parts but makes validation easier
if (tz) {
sign = /^-/.test(tz) ? 1 : -1;
tzHr = tz == 'Z' ? 0 : tz.substr(1, 2);
tzMin = tz == 'Z' ? 0 : tz.substr(tz.length - 2, 2) * 1;
tzMins = sign * (tzHr * 60 + tzMin);
}
// Validation
function isLeap(year) {
return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}
// Check number of date parts and month is valid
if (d.length > 3 || d[1] < 1 || d[1] > 12) return invalidDate;
// Test day is valid
var monthDays = [, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
var monthMax = isLeap(d[0]) && d[1] == 2 ? 29 : monthDays[+d[1]];
if (d[2] < 1 || d[2] > monthMax) return invalidDate;
// Test time parts
if (t.length > 5 || t[1] > 23 || t[2] > 59 || t[3] > 59 || t[4] > 999) return invalidDate;
// Test tz within bounds
if (tzHr > 12 || tzMin > 59) return invalidDate;
// If there's a timezone, use UTC methods, otherwise local
var method = tz ? 'UTC' : '';
// Set date values
date['set' + method + 'FullYear'](d[0], (d[1] ? d[1] - 1 : 0), d[2] || 1);
// Set time values - first memeber is '' from separator \s or T
date['set' + method + 'Hours'](t[1] || 0, (+t[2] || 0) + tzMins, t[3] || 0, t[4] || 0);
return date;
}
// Some tests
['2017-04-18T11:18:05-0300', // No colon in timezone
'2017-04-18 11:18:05-0300', // Space separator instead of T
'2016-04-12T04:31+05:30', // Colon in timezone
'2016-02-29T04:31+05:30', // Colon, leap year
'2016-04-12T04:31:56.004Z', // GMT
'2000-02-29T04:31+05:30', // Colon, leap year
'1900-02-29T04:31+05:30', // Colon, not leap year (invalid date)
'2016-04-12T04:31:56.004', // Local
'2016-04-12', // Date only (local)
'2016-04' // Minimal date (local)
].forEach(function(d) {
console.log(d + ': ' + parseISO(d).toString());
});
或者,使用库,有很多好的库可供选择。