资讯专栏INFORMATION COLUMN

前端时间处理小结

Hydrogen / 1769人阅读

摘要:相信,几乎每个前端项目都不可避免地要接触到时间处理,最最常见的就是时间格式化。中,内置对象封装了时间处理方法。

相信,几乎每个前端项目都不可避免地要接触到时间处理,最最常见的就是时间格式化。JS中,内置对象Date封装了时间处理方法。但说实话,这个对象方法太多,而且平时业务开发中也很少会直接用到这些方法,所以我总是对Date对象感觉到陌生!最近对时间处理作了下小结,用此文来记录一下。

Date对象复习

在本文正式开始之前,先一起简单复习下Date对象

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返回的返回的结果是一样的。

除了date.getXXX(), date.getUTCXXX(),还有一系列date.setXXX(), date.setUTCXXX()。这些方法类似我们常说的setter/getter。

另外要特别注意的是,new Date()创建时间对象时,参数最好是字符串格式,年月日之间用“/”,时分秒毫秒之间用“:”,如new Date(2017/7/25 12:12:12:100)

时间处理库

github上有许多时间处理库,比较高星的有

momentjs

date-fns

但这两个库太重了,说白了就是考虑得太多。大多数功能是用不到的。在H5项目里面引这么重的库真的是不划算。但它们解决问题的思路与方法,我们却可以借鉴。后面提到的很多方法都借鉴或直接使用了date-fns这个库。

时间格式化

后台一般返回的是时间的毫秒数,而在前端页面中显示的就多种多样了,可能是:

2017-7-25

2017/7/25

2017年7月25日

2017年07月25日

2017年07月25日 12时05分

等等...

相信大家都会有自己的时间格式化方法。U3在这里多带带提,是想说太依赖正则的格式化方法,性能可能会非常差。

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平台测试):

时间处理实用的工具函数
/**
 * 判断某年是否是润年
 * 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

实现思路其实很简单,只要找到上面所有时间断点的起始时刻,然后就是一个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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90019.html

相关文章

  • 前端面试题小结

    摘要:如何解决不同终端的适配问题弹性盒子,非常不错的选择的运行流程生命周期生命周期优化解释中虚拟存在的好处为什么可以解决跨域问题地址栏输入流程总结初级阶段是会用。 前几天也是有人问我的一些问题,我觉得还是挺有了解价值的,也是一些平时开发可能比较会忽略的问题。别的不多说,直接开门见山: 1.post和get的区别? 我们都知道GET和POST是HTTP请求的两种基本方法。我相信如果有人问到你这...

    fuchenxuan 评论0 收藏0
  • 前端面试题小结

    摘要:如何解决不同终端的适配问题弹性盒子,非常不错的选择的运行流程生命周期生命周期优化解释中虚拟存在的好处为什么可以解决跨域问题地址栏输入流程总结初级阶段是会用。 前几天也是有人问我的一些问题,我觉得还是挺有了解价值的,也是一些平时开发可能比较会忽略的问题。别的不多说,直接开门见山: 1.post和get的区别? 我们都知道GET和POST是HTTP请求的两种基本方法。我相信如果有人问到你这...

    silenceboy 评论0 收藏0
  • 前端面试题小结

    摘要:如何解决不同终端的适配问题弹性盒子,非常不错的选择的运行流程生命周期生命周期优化解释中虚拟存在的好处为什么可以解决跨域问题地址栏输入流程总结初级阶段是会用。 前几天也是有人问我的一些问题,我觉得还是挺有了解价值的,也是一些平时开发可能比较会忽略的问题。别的不多说,直接开门见山: 1.post和get的区别? 我们都知道GET和POST是HTTP请求的两种基本方法。我相信如果有人问到你这...

    wangtdgoodluck 评论0 收藏0
  • 2018.11.19秋招末第二波前端实习/校招小结

    摘要:背景个人背景就读于东北某普通二本院校计算机软件工程专业,现大四,北京实习前端方向,自学,技术栈时间背景大概是在月日准备好简历开始投递秋招差不多已经结束招聘岗位不多,投递对象为大一些的互联网公司事件背景第一个入职的是好未来的前端实习岗,待遇工 背景 个人背景 就读于东北某普通二本院校计算机软件工程专业,现大四,北京实习 前端方向,自学,vue技术栈 时间背景 大概是在11月9日准备...

    suxier 评论0 收藏0
  • 2018.11.19秋招末第二波前端实习/校招小结

    摘要:背景个人背景就读于东北某普通二本院校计算机软件工程专业,现大四,北京实习前端方向,自学,技术栈时间背景大概是在月日准备好简历开始投递秋招差不多已经结束招聘岗位不多,投递对象为大一些的互联网公司事件背景第一个入职的是好未来的前端实习岗,待遇工 背景 个人背景 就读于东北某普通二本院校计算机软件工程专业,现大四,北京实习 前端方向,自学,vue技术栈 时间背景 大概是在11月9日准备...

    canger 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<