Web开发三剑客....让前端飞Web前端之路

封装一个适合实际项目需求的日期类

2019-07-20  本文已影响45人  铁甲万能狗

很多应用程序通常需要大量的日期操作,但JavaScript的核心Date对象并没有提供一些额外的方法,那么单凭内置的方法,你还得为实现每一个需求,使用这些内置的方法组织一番代码。如果日期操作非常频繁的话,将加重了代码的复杂性。尤其那丑陋的获取月份时,要month++,修改当前Date实例的日期事件month--,非常丑陋和繁琐。

在涉及酒店预定,贸易跟单,财务这些系统,对日期比较敏感的应用尤其格外慎重。在本文中,我将向您展示如何向Date对象添加自定义方法,这些方法由每个日期实例继承,目的在于简化调用层的代码,和提供代码的复用性。

当然,这里显示的所有方法都封装到一个叫DateTime的类里面,javascrit的Date类比较美中不中的是month参数,1月到12月分别是由0到11表示,所以我们在DateTime类将month参数规范由1到12来表示符合人的主观认知,所以DateTime类中可以这么重写

class DateTime extends Date{
        constructor() {
        if (arguments.length >= 3) {
            let month=arguments[1];
            arguments[1] = month - 1;
        }
        super(...arguments);
    }
}

至于年,月,日,时,分,秒涉及到setter和getter,没必要每个都重写,因为会用到month参数的Date类方法就只有下面两个,其他的原生api保持原样

重写setFullYear和setMonth方法

中间有两个check_month和check_date的扩展方法,用于对year, month,date的参数进行检测,稍后下文会提到

   /**
    * 重写setFullYear方法
    * @param year
    * @param month
    * @param date
    */
   setFullYear(year, month, date) {
       if (DateTime.check_month(month) && 
               DateTime.check_date(year, month, date)) {
           super.setFullYear(year, month - 1, date);
       }
   }

   /**
    * 重写setMonth方法
    * @param month
    * @param date
    */
   setMonth(month, date) {
       if (DateTime.check_month(month) &&
           DateTime.check_date(this.getFullYear(), month, date)) {
           super.setMonth(month - 1, date);
       }
   }

   /**
    * 重写getMonth方法
    * @returns {number}
    */
   getMonth() {
       return super.getMonth() + 1;
   }

month,date参数检测方法

这两个扩展方法,跟我们在进行实例化一个DateTime类没任何联系,你可能为何在DateTime实例化时不对month,date做参数进行区间检测.我想说那是浪费表情。

比如2019年的7月份只有30天,但如果故意传递35这个date参数,Date类内部会将自动加上超出的4天,也是说会表示为2019年8月4日.这也是我们应用在日期操作中经常碰到下面的一个基本问题。

Date类已经内置了这样自动进行日期加减的机制,解决了这些问题。

好,回到日期参数检测这两个问题上,它们主要用于比较频繁的日期判断问题.

这是问题的最终的归属到一个基本问题:月末问题或叫指定日期区间问题,这也是下面要引入last_day方法的原因.

    /**
     * 检查month参数
     * @param month
     * @returns {boolean}
     */
    static check_month(month) {
        if (month < 1 || month > 12) {
            throw new TypeError('月份错误!!');
        }
        return true;
    }

    /**
     * 检查date参数
     * @param year
     * @param month
     * @param date
     * @returns {boolean}
     */
    static check_date(year, month, date) {
        const lastDay = DateTime.last_day(year, month);
        if (date < 1 && date > lastDay) {
            throw TypeError('month参数指定的月份的天数和date参数不相符');
        }
        return true;
    }

其实一看上面的两个check方法最重要的方法就是last_day(year,month)这个静态方法,然后我想说日期的区间问题又引申到“闰年的判断问题

以下是last_day方法的实现

    static last_day(year, month) {
        if (month < 1 || month > 12) {
            throw new TypeError('月份错误!!');
        }
        return month === 2 ? year & 3 || !(year % 25) &&
        year & 15 ? 28 : 29 : 30 + (month + (month >> 3) & 1)
    }

闰年问题

 isLeapYear(year) {
      return !(year & 3 || year & 15 && !(year % 25));
}

这里有必要花点时间说一下,上面提到的那些问题集基本上都起源于“月末问题”,而月末问题可以,可用结合闰年的判断条件来解决上述的问题。

year & 3和year % 4是一样的代表4年为一个周期,year & 15和 year % 16也是一样的。因此,如果year不能被4整除或year不能被16整除但能被25整除,那么year不是闰年,这意味着每个是25的倍数不是闰年,也就是说year是4x25的倍数。除非year同时是16的倍数,因为16和25没有任何共同质因数,只有当year的条件同时满足16*25的倍数才满足是闰年的条件。

1900年不是闰年,因为它能被100整除,2000年是闰年因为能被400整除,但2100年不是闰年
那么用结合上述闰年的判断条件来判断某个月有多少天的问题

上面代码的意思就是:

如果是4月,6月,9月,10月就返回30天
如果当前month不是2月就返回31天
如果当前month是2月就需要根据上述的闰年判断条件
判断当前年份是否为闰年,如果是闰年就返回29天,否则就返回28天。

以上棘手的问题和相关实现算法都罗列出来了
那么我们这里可以封装一些解决问题的常用方法

问题1:获取某个年份或某个月的天数

    /**
     * 获取当前实例对应月份的天数
     */
    get days() {
        return DateTime.last_day(this.year, this.month);
    }

或者

get days(){
    return new DateTime(this.year,this.month+1,0)
}

两种算法的的耗时基本上差别不大,上面的第二个方法其实在前文已经提到了,不在多废话了。

问题2:两个指定的日期相隔多少天

const SECOND_IN_DAY = 86400000; //一天的秒数

static interval_days(d1, d2) {
        if (!(d1 instanceof DateTime) || !(d2 instanceof DateTime)) {
            throw new TypeError('参数不是MyDate实例!!');
        }

        const a = d1.getTime();
        const b = d2.getTime();
        let days = Math.abs(a - b);

        if (days == 0) {
            return 1;
        } else {
            return Math.ceil(days / SECOND_IN_DAY);
        }
    }

这个方法的基本思路就是通过两个日期对应的时间戳的差的绝对值,不满1天以一天计算。0天视为1天返回。

问题3:某个特定日期是星期几?

首先定义几个模拟枚举类的静态数组,这里提供了三个中,英,日三个版本的WEEK名称数组

static get WEEK_SHORT_JP_NAME(){
        return ['日','月','火','水','木','金','土'];
    }

    static get WEEK_SHORT_EN_NAME(){
        return [' Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
    }

    static get WEEK_SHORT_CN_NAME(){
        return ['日','一','二','三','四','五','六'];
    }

下面是解决该问题的实现,主要是对原生getDay方法的扩展

static what_day(year,month,date,format='CN'){
        if(DateTime.check_date(year,month,date)){
            const obj=new DateTime(year,month,date);
            const i=obj.getDay();

            switch (format) {
                case "CN":
                    return DateTime.WEEK_SHORT_CN_NAME[i];
                case "JP":
                    return DateTime.WEEK_SHORT_JP_NAME[i];
                case "EN":
                    return DateTime.WEEK_SHORT_EN_NAME[i];
                default:
                    return DateTime.WEEK_SHORT_CN_NAME[i];
            }

        }
    }

问题4:某个日期时隔xx天后的日期是什么?

    static after_days(obj, num) {
        if (!obj instanceof DateTime) {
            throw new TypeError('obj参数不是MyDate实例!!');
        }

        let date = obj.date + num;

        let year = obj.year;
        let month = obj.month;
        let hours = obj.hours;
        let minutes = obj.minutes;
        let seconds = obj.seconds;

        return new DateTime(year, month, date, hours, minutes, seconds, 0);
    }

其实就是问题1的变种,跟多少天或多少个月之前的日期都是如出一辙。

ok,总结到此,喜欢本文的,欢迎点赞

上一篇下一篇

猜你喜欢

热点阅读