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

很多应用程序通常需要大量的日期操作,但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);
        this.index=this.month; //index用于实现月份的遍历
    }
}

至于年,月,日,时,分,秒涉及到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的变种,跟多少天或多少个月之前的日期都是如出一辙。

补充经常用到日期遍历方法和 getter访问器

next_month(){
    this.index+=1;
    return new DateTime(this.year,this.index,1);
}

prev_month(){
    this.index-=1;
    return new DateTime(this.year,this.index,1);
}

    get year() {
        return this.getFullYear();
    }

    get month() {
        return this.getMonth();
    }

    get date() {
        return this.getDate();
    }

    get hours() {
        return this.getHours();
    }

    get minutes() {
        return this.getMinutes();
    }

    get seconds() {
        return this.getSeconds();
    }

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268