需求分析
一、描述
当我们获取到两个时间,想要计算两个时间之间的距离,即两个时间之间相隔多少年、月、日、时、分、秒、毫秒,我们无法对两个时间直接运算,在JavaScript当中,两个Date对象进行运算时,会将两个Date对象转换成时间戳,然后让两个时间戳进行运算,这样得到的结果是个毫秒数,它表示两个时间之间的距离的毫秒数。
因此我们要实现两个Date对象的运算,就必须通过自己封装函数来实现。
二、难点
在这个需求当中有一个难以琢磨的问题,那就是“一个月有多少天”,正是这个问题的存在,使得我们的时间转换变得复杂。一个月的天数是由它的月份来决定的,可以分为以下四种情况:
- 大月份:即1、3、5、7、8、10、12月,天数为31天;
- 小月份:即4、6、9、11月,天数为30天;
- 闰年2月:天数为29天;
- 平年2月:天数为28天。
其中闰年的条件成立需要满足以下三个条件:
- 年份能被4整除;
- 年份不能被100整除;
- 年份能被400整除。
实现方法
一、单位计算法(推荐)
1.说明
这是一种比较简单的实现方法,它是通过将两个时间进行计算来得到,使用这个方法的好处就是不用进行单位的转换,这样就可以避免天数转换月份的复杂问题。
2.思路
使用结束时间的年、月、日、时、分、秒、毫秒,按毫秒到年份的顺序依次与起始时间相同的单位进行减法运算,当结束时间的同单位数值小于起始时间时,需要向下一个单位借一个数,然后在数值上加上当前单位进制的最大值。
3.代码
/**
* 计算日期时间函数
* 参数
* beforeTime:起始时间
* afterTime:结束时间
*/
function countTime(beforeTime, afterTime) {
//起始时间对象
var beforeDate = new Date(beforeTime);
//结束时间对象
var afterDate = new Date(afterTime);
var year = 0;
var month = 0;
var day = 0;
var hour = 0;
var minute = 0;
var second = 0;
var millisecond = (afterDate.getTime() - beforeDate.getTime()) % 1000;
//秒钟计算
if (afterDate.getSeconds() < beforeDate.getSeconds()) {
second = afterDate.getSeconds() + 60 - beforeDate.getSeconds();
afterDate.setMinutes(afterDate.getMinutes() - 1);
} else {
second = afterDate.getSeconds() - beforeDate.getSeconds();
}
//分钟计算
if (afterDate.getMinutes() < beforeDate.getMinutes()) {
minute = afterDate.getMinutes() + 60 - beforeDate.getMinutes();
afterDate.setHours(afterDate.getHours() - 1);
} else {
minute = afterDate.getMinutes() - beforeDate.getMinutes();
}
//小时计算
if (afterDate.getHours() < beforeDate.getHours()) {
hour = afterDate.getHours() + 24 - beforeDate.getHours();
afterDate.setDate(afterDate.getDate() - 1);
} else {
hour = afterDate.getHours() - beforeDate.getHours();
}
//天数计算
if (afterDate.getDate() < beforeDate.getDate()) {
//判断月份
switch (beforeDate.getMonth() + 1) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
day = afterDate.getDate() + 31 - beforeDate.getDate();
break;
case 4:
case 6:
case 9:
case 11:
day = afterDate.getDate() + 30 - beforeDate.getDate();
break;
case 2:
if (i % 4 != 0 || i % 100 == 0 && i % 400 != 0) {
day = afterDate.getDate() + 28 - beforeDate.getDate();
} else {
day = afterDate.getDate() + 29 - beforeDate.getDate();
}
break;
}
afterDate.setMonth(afterDate.getMonth() - 1);
} else {
day = afterDate.getDate() - beforeDate.getDate();
}
//月份计算
if (afterDate.getMonth() + 1 < beforeDate.getMonth() + 1) {
month = (afterDate.getMonth() + 1) + 12 - (beforeDate.getMonth() + 1);
afterDate.setFullYear(afterDate.getFullYear() - 1);
} else {
month = (afterDate.getMonth() + 1) - (beforeDate.getMonth() + 1);
}
//年份计算
year = afterDate.getFullYear() - beforeDate.getFullYear();
/**
* 二位数字补位函数
*/
function twoNumFormat(num){
return num >= 0 && num < 10 ? "0" + num : num;
}
/**
* 三位数字补位函数
*/
function threeNumFormat(num){
return num >= 0 && num < 10 ? "00" + num : num >= 10 && num < 100 ? "0" + num : num;
}
return {
year: year,
month: twoNumFormat(month),
day: twoNumFormat(day),
hour: twoNumFormat(hour),
minute: twoNumFormat(minute),
second: twoNumFormat(second),
millisecond: threeNumFormat(millisecond)
}
}
var result = countTime("2015-10-10 00:00:30", "2020-9-9 03:00:20");
document.write("2020-9-9 03:00:20距离2015-10-10 00:00:30:" + result.year + "年" + result.month + "月" + result.day + "天" + result.hour + "时" + result.minute + "分" + result.second + "秒" + result.millisecond + "毫秒");
二、时间戳转换法
1.说明
这种方法是将两个时间转换成时间戳,然后进行减法运算,得到两个时间距离的毫秒数,再将毫秒数进行单位转换,得到年、月、日、时、分、秒、毫秒的格式。这种方法会比较复杂一些,它无法避免天数转换月份的复杂问题,因此需要用很多的分支判断。
2.思路
- 先将两个时间转换成时间戳,进行减法运算;
- 将得出的毫秒数转换成日(天数)、时、分、秒、毫秒;
- 通过循环,将天数分配给年、月,循环从起始时间的年份开始到结束时间的年份结束。在循环时要先将起始年份和结束年份单独挪出来操作,因为这两个年份是有指定月份的,因此不能当做一年来计算。其它的年份可以当做闰年366天,平年365天来分配给年份;
- 然后将起始年份和结束年份两年中间的天数循环分配给月份,在循环时也要先将起始年份的其实月份和结束年份的结束月份单独挪出来操作,因为这两个月份是有指定天数的,因此不能当做一个月来计算;
- 检查剩余的天数有没有多出一个月,有的话将多出的部分分配给月份;
- 最后更新年份和月份的值。
3.代码
本案例是将时间戳转换成距离,即计算当前时间距离“1970-01-01 08:00:00”的具体时间,并且用定时器不断刷新数据
/**
* 计算日期时间函数
* 参数
* time:指定时间
*/
function countTime(time) {
//指定时间对象
var targetDate = new Date(time);
//当前时间对象
var currentDate = new Date();
//两个时间的时间戳相减,得出两个时间相隔的毫秒数
var difference = currentDate - targetDate;
var year = 0, month = 0;
var day = parseInt(difference / 1000 / 60 / 60 / 24);
var hour = parseInt(difference / 1000 / 60 / 60 % 24);
var minute = parseInt(difference / 1000 / 60 % 60);
var second = parseInt(difference / 1000 % 60);
var millisecond = difference % 1000;
//遍历两个时间之间的所有年份,将天数分配给年和月
for (var i = targetDate.getFullYear(); i <= currentDate.getFullYear(); i++) {
//先排除指定的年份和当前的年份
if (i == targetDate.getFullYear() || i == currentDate.getFullYear()) {
//当前年的起始月份
var j = 1;
if (i == targetDate.getFullYear()) {
//指定年的起始月份
j = targetDate.getMonth() + 1;
}
//遍历指定年、当前年需要经过的月份
for (; j <= 12; j++) {
//如果为指定年的指定月份
if (i == targetDate.getFullYear() && j == targetDate.getMonth() + 1) {
//指定年和当前年为同年,且当前遍历月份等于当前年的当前月份
if (targetDate.getFullYear() == currentDate.getFullYear() && j == currentDate.getMonth() + 1) {
break;
}
continue;
}
//如果为当前年的当前月份
if (i == currentDate.getFullYear() && j == currentDate.getMonth() + 1) {
break;
}
switch (j) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
//减去31天
day -= 31;
month++;
break;
case 4:
case 6:
case 9:
case 11:
//减去30天
day -= 30;
month++;
break;
case 2:
//闰年,减去29天
if (i % 4 == 0 || i % 100 != 0 && i % 400 == 0) {
day -= 29;
month++;
//平年,减去28天
} else {
day -= 28;
month++;
}
break;
}
}
//其余年份
} else {
//闰年
if (i % 4 == 0 || i % 100 != 0 && i % 400 == 0) {
if (day < 366) {
break;
}
//减去366天
day -= 366;
//平年
} else {
if (day < 365) {
break;
}
//减去365天
day -= 365;
}
year++;
}
}
//判断指定年的指定月份和当前年的当前月份之间的天数是否超过1个月,如果是则将超过的天数分配给月份
switch (targetDate.getMonth() + 1) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
if (day >= 31) {
month++;
day -= 31;
}
break;
case 4:
case 6:
case 9:
case 11:
if (day >= 30) {
month++;
day -= 30;
}
break;
case 2:
if (i % 4 == 0 || i % 100 != 0 && i % 400 == 0) {
if (day >= 29) {
month++;
day -= 29;
}
} else {
if (day >= 28) {
month++;
day -= 28;
}
}
break;
}
//更新年份
year += parseInt(month / 12);
//更新月份
month -= parseInt(month / 12) * 12;
/**
* 二位数字补位函数
*/
function twoNumFormat(num) {
return num >= 0 && num < 10 ? "0" + num : num;
}
/**
* 三位数字补位函数
*/
function threeNumFormat(num) {
return num >= 0 && num < 10 ? "00" + num : num >= 10 && num < 100 ? "0" + num : num;
}
document.body.innerHTML = "";
document.write("当前时间距离1970-01-01 08:00:00:" + year + "年" + twoNumFormat(month) + "月" + twoNumFormat(day) + "天" + twoNumFormat(hour) + "时" + twoNumFormat(minute) + "分" + twoNumFormat(second) + "秒" + threeNumFormat(millisecond) + "毫秒");
}
setInterval(countTime, 1, 0);
总结
总体来说,还是第一种方法比较好用,能够很大程度避开“一个月多少天”的问题,省去了复杂的逻辑判断,本来还打算想第三种方法,但是依然绕不开天数转换月份的复杂问题,如果小伙伴们有其它实现方法,可以互相交流一下。