前端時間處理小結

相信,幾乎每一個前端項目都不可避免地要接觸到時間處理,最最多見的就是時間格式化。JS中,內置對象Date封裝了時間處理方法。但說實話,這個對象方法太多,並且平時業務開發中也不多會直接用到這些方法,因此我老是對Date對象感受到陌生!最近對時間處理做了下小結,用此文來記錄一下。前端

Date對象複習

在本文正式開始以前,先一塊兒簡單複習下Date對象git

  • date.getFullYear() - 獲取4位數年份
  • date.getMonth() - 獲取月份,取值0~11,0對應1月份
  • date.getDay() - 獲取星期,取值0~6,0對應星期天,1對應星期一,6對應星期六
  • date.getDate() - 獲取一個月中的某天,取值1~31。1即1號,31即31號
  • date.getHours() - 獲取小時數,取值0~23
  • date.getMinutes() - 獲取分鐘數,取值0~59
  • date.getSeconds() - 獲取秒數,取值0~59
  • date.getMilliseconds() - 獲取毫秒數,取值0~999
  • date.getTime() - 返回1970年1月1日至當前時間的毫秒數

除上面date.getXXX()方法外,還有一系列與之對應的date.getUTCXXX()方法。date.getUTCXXX()方法與date.getXXX()方法惟一的區別是帶UTC的方法使用的是世界時時區,咱們處在東八區,比世界時快8小時。除date.getHours()外,其它方法有UTC與沒有UTC返回的返回的結果是同樣的。github

getXXX-getUTCXXX

除了date.getXXX(), date.getUTCXXX(),還有一系列date.setXXX(), date.setUTCXXX()。這些方法相似咱們常說的setter/getter。windows

另外要特別注意的是,new Date()建立時間對象時,參數最好是字符串格式,年月日之間用「/」,時分秒毫秒之間用「:」,如new Date(2017/7/25 12:12:12:100)函數

時間處理庫

github上有許多時間處理庫,比較高星的有工具

但這兩個庫過重了,說白了就是考慮得太多。大多數功能是用不到的。在H5項目裏面引這麼重的庫真的是不划算。但它們解決問題的思路與方法,咱們卻能夠借鑑。後面提到的不少方法都借鑑或直接使用了date-fns這個庫。性能

時間格式化

後臺通常返回的是時間的毫秒數,而在前端頁面中顯示的就多種多樣了,多是:測試

  • 2017-7-25
  • 2017/7/25
  • 2017年7月25日
  • 2017年07月25日
  • 2017年07月25日 12時05分
  • 等等...

相信你們都會有本身的時間格式化方法。U3在這裏單獨提,是想說太依賴正則的格式化方法,性能可能會很是差。ui

const formatTime = (mdate, correct = 'm') => {
  if (!mdate) {
    return '';
  }
  if (mdate === 'now') {
    mdate = Date.now();
  }

  let date = typeof mdate === 'number' ? new Date(mdate) : mdate;
  let year = date.getFullYear();
  let month = date.getMonth() + 1;
  let day = date.getDate();
  const formatNumber = (n) => {
    n = n.toString();
    return n[1] ? n : '0' + n;
  };

  let hour = date.getHours();
  let minute = date.getMinutes();
  let second = date.getSeconds();
  let YMD = [year, month, day, ].map(formatNumber).join('-') + ' ';
  if (correct === 'Y') return year;
  if (correct === 'M') return [year, month, ].map(formatNumber).join('-');
  if (correct === 'D') return [year, month, day, ].map(formatNumber).join('-');
  if (correct === 'h') return YMD + hour;
  if (correct === 'm') return YMD + [hour, minute, ].map(formatNumber).join(':');
  if (correct === 's') return YMD + [hour, minute, second, ].map(formatNumber).join(':');
  return [year, month, day, ].map(formatNumber).join('-') + ' ' + [hour, minute, second, ].map(formatNumber).join(':');
};

const formatTime2 = function (d, fmt) {
  var date,
      week,
      o,
      k,
      startTime = new Date('1970-01-01 0:0:0').getTime();

  if (d < startTime) {
    return null;
  }

  date = new Date(d);
  week = {
    0: '星期日',
    1: '星期一',
    2: '星期二',
    3: '星期三',
    4: '星期四',
    5: '星期五',
    6: '星期六'
  };
  o = {
    E: week[date.getDay() + ''],
    Y: date.getFullYear(), //年
    M: date.getMonth() + 1, //月份
    D: date.getDate(), //日
    h: date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, //12小時制
    H: date.getHours(), //24小時制
    m: date.getMinutes(), //分
    s: date.getSeconds(), //秒
    q: Math.floor((date.getMonth() + 3) / 3), //季度
    S: date.getMilliseconds() //毫秒
  };

  for (k in o) {
    fmt = fmt.replace(new RegExp(k + '+', 'g'), function (w) {
      var value = (k != 'E') ? '000' + o[k] : o[k];
      return value.substr(value.length - w.length >= 0 ? value.length - w.length : 0);
    });
  }

  return fmt;
};

/**
 * Parse the given pattern and return a formattedtimestamp.
 * https://github.com/jonschlinkert/time-stamp
 *
 * u3有修改。增長了星期,是否加前置0配置
 *
 * @param  {String} `pattern` Date pattern.
 * @param  {Date} `date` Date object.
 * @return {String}
 */

const formatTime3 = function(pattern, date, noPadLeft) {
  if (typeof pattern !== 'string') {
    date = pattern;
    pattern = 'YYYY-MM-DD';
  }

  if (!date) date = new Date();

  return timestamp(pattern);

  function timestamp(pattern) {
    // ?=exp 匹配exp前面的位置
    let regex = /(?=(YYYY|YY|MM|DD|HH|mm|ss|ms|EE))\1([:\/]*)/;
    let match = regex.exec(pattern);

    if (match) {
      let res;

      if (match[0] === 'EE') {
        res = dayTrans(date.getDay());
      } else {
        let increment = method(match[1]);
        let val = noPadLeft ?
        ''+(date[increment[0]]() + (increment[2] || 0)) :
        '00' + (date[increment[0]]() + (increment[2] || 0));
        res = val.slice(-increment[1]) + (match[2] || '');
      }

      pattern = pattern.replace(match[0], res);

      return timestamp(pattern);
    }

    return pattern;
  }

  function method(key) {
    return ({
      YYYY: ['getFullYear', 4],
      YY: ['getFullYear', 2],
      // getMonth is zero-based, thus the extra increment field
      MM: ['getMonth', 2, 1],
      DD: ['getDate', 2],
      HH: ['getHours', 2],
      mm: ['getMinutes', 2],
      ss: ['getSeconds', 2],
      ms: ['getMilliseconds', 3],
    })[key];
  }

  function dayTrans(day) {
    return ['星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'][day];
  }
};

上面三個格式化方法,都能知足經常使用的格式化需求,但它們的性能數據卻相差很大,特別是第二個方法,因爲實現太依賴正則,性能相比其它兩個要慢3~4倍。若是你尚未一個滿意的時間格式化方法,U3推薦最後一個,即formatTime3方法。下面是這三個方法的benchmark數據(基於NodeJS,windows平臺測試):spa

format-benchmark

時間處理實用的工具函數

/**
 * 判斷某年是不是潤年
 * https://github.com/sindresorhus/leap-year/blob/master/index.js
 *
 * @param year
 * @return {boolean}
 */
const isLeapYear = function (year) {
  year = year || new Date();

  if (!(year instanceof Date) && typeof year !== 'number') {
    throw new TypeError(`Expected \`year\` to be of type \`Date\` or \`number\`, got \`${typeof year}\``);
  }

  year = year instanceof Date ? year.getFullYear() : year;
  return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
};

/**
 * 獲取某年中有多少天
 * https://github.com/sindresorhus/year-days/blob/master/index.js
 *
 * @param year
 * @return {number}
 */
const getDaysInYear = function (year) {
  return isLeapYear(year) ? 366 : 365;
};

/**
 * 獲取某月有多少天
 * @param {number} month 從0開始
 * @return {number} 28/29/30/31
 */
const getDaysInMonth = function (month, year) {
  const now = new Date();

  month = month || now.getUTCMonth();
  year = year || now.getUTCFullYear();

  return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
};

如下方法都直接copy或借鑑於date-fns庫

/**
 * 獲取某天開始的時間戳
 * @param year
 * @param month
 * @param day
 * @returns {number}
 */
const startOfDay = function(year, month, day) {
  return new Date(`${year}/${month}/${day}`).setHours(0, 0, 0, 0);
};

/**
 * 獲取某天結束的時間戳
 * @param year
 * @param month
 * @param day
 * @returns {number}
 */
const endOfDay = function(year, month, day) {
  return new Date(`${year}/${month}/${day}`).setHours(23, 59, 59, 999);
};

/**
 * 獲取某小時開始時間戳
 * @param year
 * @param month
 * @param day
 * @param hour
 * @returns {number}
 */
const startOfHour = function (year, month, day, hour) {
  return new Date(`${year}/${month}/${day} ${hour}:0:0:0`).getTime();
};

/**
 * 獲取某小時結束時間戳
 * @param year
 * @param month
 * @param day
 * @param hour
 * @returns {number}
 */
const endOfHour = function (year, month, day, hour) {
  return new Date(`${year}/${month}/${day} ${hour}:59:59:999`).getTime();
};

/**
 * 獲取某分鐘開始時間戳
 * @param year
 * @param month
 * @param day
 * @param hour
 * @param minute
 * @returns {number}
 */
const startOfMinute = function (year, month, day, hour, minute) {
  return new Date(`${year}/${month}/${day} ${hour}:${minute}:0:0`).getTime();
};

/**
 * 獲取某分鐘結束時間戳
 * @param year
 * @param month
 * @param day
 * @param hour
 * @param minute
 * @returns {number}
 */
const endOfMinute = function (year, month, day, hour, minute) {
  return new Date(`${year}/${month}/${day} ${hour}:${minute}:59:999`).getTime();
};

實戰案例

相信看了上面的方法,之前以爲很麻煩的時間處理是否是感受變得簡單了呢?下面咱們一塊兒來看一個在項目中可能會遇到的一個真實案例,展現文章的發佈時間與當前時間相差多少,模板規則爲:

  • 1分鐘內 - 剛剛
  • 1小時內 - x分鐘前,
  • 今天內 - 今天 10:12
  • 1天內 - 昨天 12:05
  • 2天內 - 前天 00:05
  • 1月內 - x月x日 10:35
  • 1年內 - 2016年x月x日 10:10

case

實現思路其實很簡單,只要找到上面全部時間斷點的起始時刻,而後就是一個timeIn的問題了。仔細分析這裏給出的時間斷點,有以分,小時,天,月,年爲單位,換言之就是要找某每分/小時/天/月/年的起始時間點。根據前面的方法,要解決這個老是其實很簡單了。下面是代碼實現:

const timesToNow = function (date) {
  if (!(date instanceof Date) && typeof date !== 'number') {
    throw new TypeError(`Expected \`date\` to be of type \`Date\` or \`number\`, got \`${typeof date}\``);
  }

  let boundaryTimesList = buildBoundaryTimesBaseOnNow();

  let dateTimestamp = date instanceof Date ? date.getTime() : new Date(date).getTime();

  for(let i = 0; i < boundaryTimesList.length; i++) {
    let temp = boundaryTimesList[i];

    if (dateTimestamp >= temp.start && dateTimestamp < temp.end) {
      if (temp.desc === 'justNow') {
        return temp.format;
      }

      if (temp.desc === 'inOneHour') {
        return temp.format.replace('x', new Date().getMinutes() - new Date(dateTimestamp).getMinutes());
      }

      return formatTime(temp.format, new Date(dateTimestamp), true);
    }
  }

  function buildBoundaryTimesBaseOnNow() {
    const now = Date.now();
    const nowDate = new Date(now);
    const year = nowDate.getFullYear();
    const month = nowDate.getMonth() + 1;
    const day = nowDate.getDate();
    const hour = nowDate.getHours();
    const minute = nowDate.getMinutes();

    return [
      {
        desc: 'justNow',
        start: startOfMinute(year, month, day, hour, minute),
        end: endOfMinute(year, month, day, hour, minute),
        format: '剛剛'
      },
      {
        desc: 'inOneHour',
        start: startOfHour(year, month, day, hour),
        end: endOfHour(year, month, day, hour),
        format: 'x分鐘前'
      },
      {
        desc: 'today',
        start: startOfDay(year, month, day),
        end: endOfDay(year, month, day),
        format: '今天 HH:mm'
      },
      {
        desc: 'yestoday',
        start: startOfDay(year, month, day - 1),
        end: endOfDay(year, month, day - 1),
        format: '昨天 HH:mm'
      },
      {
        desc: 'beforeYestoday',
        start: startOfDay(year, month, day - 2),
        end: endOfDay(year, month, day - 2),
        format: '前天 HH:mm'
      },
      {
        desc: 'curYear',
        start: startOfDay(year, 1, 1),
        end: endOfDay(year, 12, 31),
        format: 'MM月DD日 HH:mm'
      },
      {
        desc: 'anotherYear',
        start: startOfDay(1990, 1, 1),
        end: endOfDay(year - 1, 12, 31),
        format: 'YYYY年MM月DD日 HH:mm'
      }
    ];
  }

};

小結

本文章主要介紹了前端中的時間處理,拋磚引玉,但願對你們有所幫助!

更多原創文章:u3xyz.com

相關文章
相關標籤/搜索