美文网首页Web开发三剑客....让前端飞Web前端之路
封装一个适合实际项目需求的日期类

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

作者: 铁甲万能狗 | 来源:发表于2019-07-20 20:00 被阅读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(year, month,date)
    • setMonth(month,date)

    重写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,总结到此,喜欢本文的,欢迎点赞

    相关文章

      网友评论

        本文标题:封装一个适合实际项目需求的日期类

        本文链接:https://www.haomeiwen.com/subject/njaylctx.html