乐趣区

关于前端:处理new-Date在safari浏览器上的某些日期格式导致的Invalid-Date

1 起因

safari 浏览器的 new Date(dateStr: string) 格局要求如下:

/***  dateStr 里 必须要有 d(日);月、日和时分秒 都不必满足补 0 规定;但有 时钟 必须有 分钟 ***/
new Date('yyyy/MM/dd HH:mm:ss');
new Date('yyyy/MM/dd HH:mm');
new Date('yyyy/M/d');
new Date('yyyy/M/dd');
new Date('yyyy/MM/d');

new Date('yyyy/M');    // Invalid Date
new Date('yyyy/MM'); // Invalid Date
new Date('yyyy/MM HH'); // Invalid Date
new Date('yyyy/MM HH:mm:ss'); // Invalid Date
/***  dateStr 里 不用肯定要有 d(日);但 M(月)、d(日) 和时分秒 都必须满足 小于 10 后面补 0;而且如存在时分秒,必须用 T 连贯; 而且有 时钟 必须有 分钟 ***/
new Date('yyyy-MM-ddTHH:mm:ss');
new Date('yyyy-MM-ddTHH:mm');
new Date('yyyy-MM-dd');
new Date('yyyy-MM');

new Date('yyyy-M'); // Invalid Date
new Date('yyyy-M-d'); // Invalid Date
new Date('yyyy/MM HH'); // Invalid Date
new Date('yyyy-MM-dd H:MM'); // Invalid Date

2 解决

2.1 自定义一个构造函数

class MyDate extends Date {constructor()

  constructor(value: number | string)

  constructor(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number)

  constructor(...args: any) {super(...args);
    if (this.toString() === 'Invalid Date' && args.length === 1 && typeof args[0] === 'string') {const dateStr = args[0];
      const dateReg: any = {year: '0*(\\d+)',
        // 日期的分隔符
        splitChar: '(?:([ \\/\\-]))?',
        month: '(?:0*([1-9]|(?:1[0-2])))?',
        date: '(?:0*([1-9]|(?:[1-2]\\d)|(?:3[0-1])))?',
        hour: '(?:0*((?:1?\\d)|(?:2[0-3]))?)',
        minute: '(?::0*([1-5]?\\d)?:*)',
        second: '(?::0*([1-5]?\\d))?',
        millisecond: '(?::0*(\\d{0,3}))?',
        // 零点整
        zeroOClock: '(?:(24)(?::0*:*)(?::0*){0,2})',
      };
      // 后面一种状况匹配 24 点前,后一种是匹配 24 点整
      dateReg.time = `(?:(?:${dateReg.hour}${dateReg.minute}${dateReg.second}${dateReg.millisecond})|${dateReg.zeroOClock})`;

      const regStr = new RegExp(`^${dateReg.year}${dateReg.splitChar}${dateReg.month}(?:\\2${dateReg.date})?(?:[ T]+${dateReg.time}:?)?$`);
      const dateMathArr = dateStr.match(regStr);
      if (dateMathArr) {
        return new Date(...(dateMathArr
          .slice(1, 2) as Array<string | number>)
          .concat(// d( 日): 数字代表的月份比理论月份(字符串传入的)大 1,所以须要减去 1
            (Number(dateMathArr[3]) || 1) - 1,
            (dateMathArr[4] || 1),
            // H(时),因为匹配 time 时有两种状况,所以须要兼容第二种状况
            dateMathArr.pop() || dateMathArr[5] || 0,
            // MM:SS:MS
            dateMathArr
              .slice(6, 9)
              .map(item => item || 0),
          ));
      }
    }
  }
}

// 应用
const myDate = new MyDate('2022-1-1');

2.2 重写 Date 函数

Date = function(Date) {
  MyDate.prototype = Date.prototype;
  // 将 Date 结构器的属性办法,复制到 MyDate 结构器上
  var propertys = Object.getOwnPropertyNames(Date.prototype.constructor);
  if(propertys && propertys.length > 0){for(var i = 0; i < propertys.length; i++){var name = propertys[i];
      MyDate[name] = Date.prototype.constructor[name];
    }
  }
  return MyDate;

  function MyDate() {
      // 只对不反对的日期格局做解决
    const thisDate = new Date(...arguments);
    if (thisDate.toString() === 'Invalid Date' && arguments.length === 1 && typeof arguments[0] === 'string') {const dateStr = arguments[0];
      const dateReg = {year: '0*(\\d+)',
        // 日期的分隔符
        splitChar: '(?:([ \\/\\-]))?',
        month: '(?:0*([1-9]|(?:1[0-2])))?',
        date: '(?:0*([1-9]|(?:[1-2]\\d)|(?:3[0-1])))?',
        hour: '(?:0*((?:1?\\d)|(?:2[0-3]))?)',
        minute: '(?::0*([1-5]?\\d)?:*)',
        second: '(?::0*([1-5]?\\d))?',
        millisecond: '(?::0*(\\d{0,3}))?',
        // 零点整
        zeroOClock: '(?:(24)(?::0*:*)(?::0*){0,2})',
      };
      // 后面一种状况匹配 24 点前,后一种是匹配 24 点整
      dateReg.time = `(?:(?:${dateReg.hour}${dateReg.minute}${dateReg.second}${dateReg.millisecond})|${dateReg.zeroOClock})`;

      const regStr = new RegExp(`^${dateReg.year}${dateReg.splitChar}${dateReg.month}(?:\\2${dateReg.date})?(?:[ T]+${dateReg.time}:?)?$`);
      const dateMathArr = dateStr.match(regStr);
      if (dateMathArr) {
        const date = new Date(...dateMathArr
          .slice(1, 2)
          .concat(// d( 日): 数字代表的月份比理论月份(字符串传入的)大 1,所以须要减去 1
            (dateMathArr[3] || 1) - 1,
            (dateMathArr[4] || 1),
            // H(时),因为匹配 time 时有两种状况,所以须要兼容第二种状况
            dateMathArr.pop() || dateMathArr[5] || 0,
            // MM:SS:MS
            dateMathArr
              .slice(6, 9)
              .map(item => item || 0))
        );
        if (date && date.toString() !== 'Invalid Date') return date;
      }
    }
    return thisDate;
  }

}(Date);

// 应用
const myDate = new Date('2022-1-1');

2.2.1 简配版

没有对日期字符串里的数字做更深刻的测验,只反对失常的日期字符串;

Date = function(Date) {
  MyDate.prototype = Date.prototype;
  // 将 Date 结构器的属性办法,复制到 MyDate 结构器上
  var propertys = Object.getOwnPropertyNames(Date.prototype.constructor);
  if(propertys && propertys.length > 0){for(var i = 0; i < propertys.length; i++){var name = propertys[i];
      MyDate[name] = Date.prototype.constructor[name];
    }
  }
  return MyDate;

  function MyDate() {
      // 只对不反对的日期格局做解决
    const thisDate = new Date(...arguments);
    if (thisDate.toString() === 'Invalid Date' && arguments.length === 1 && typeof arguments[0] === 'string') {const dateStr = arguments[0];
      const dateMathArr = dateStr.match(/^(\d+)(?:([ \/\-])(\d+))?(?:\2(\d+))?(?:[ T]+(?:(\d+))(?::(\d+))(?::(\d+))?(?::(\d+))?)?$/);
      if (dateMathArr) {
        const date = new Date(...dateMathArr
          .slice(1, 2)
          .concat((dateMathArr[3] || 1) - 1, dateMathArr.slice(4).map((item, index) => item || (index > 0 ? 0 : 1))));

        if (date && date.toString() !== 'Invalid Date') return date;
      }
    }
    return thisDate;
  }

}(Date);

3 没有解决的问题

  1. 比方日期字符串的第一个数字不是年份,在 chrome 和 safari 上解析出的日期不同:

    new Date('10')
    // chrome: Mon Oct 01 2001 00:00:00 GMT+0800 (中国规范工夫)
    // safari: Fri Jan 01 0010 08:05:43 GMT+0805 (CST)

参考:

  1. JavaScript new Date() 在 Safari 上的坑
  2. 重构 Date 结构器,解决 safari 浏览器在 new Date() 报 Invalid value
退出移动版