很多应用程序通常需要大量的日期操作,但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日.这也是我们应用在日期操作中经常碰到下面的一个基本问题。
- 从某个日期算起多少天之后 (或多少天之前)的日期是什么?
好,回到日期参数检测这两个问题上,它们主要用于比较频繁的日期判断问题.
- 上个月的月末是几号?
- 这个月份的最后一天是几号?
- 这个月有多少天?
- 某段外部程序传递给的参数是否符合当前日期的常规约束?
这是问题的最终的归属到一个基本问题:月末问题或叫指定日期区间问题,这也是下面要引入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,总结到此,喜欢本文的,欢迎点赞
网友评论