【Java】根据日历计算2个时间相差多少#自然#年、月、日、小时、分钟、秒
iOS 自带了控件,可以自动根据日历来计算 2 个时间相差的自然年、月、日、小时、分钟、秒。Java 没有自带此方法,只能自己来算了~
一、竖式减法实现
我自己写了一个方法,测试了一些时间和 iOS 作对比,暂时没有发现什么问题。如有错误,欢迎指正,也欢迎提意见~
主要方法是 int[] getTimeIntervalArray(Calendar nextDate, Calendar previousDate) 。
主要思路是:
根据数学的竖式减法,从低位由 "秒→年" 依次进行减法计算,不够则向前借一位。
这里先分别将 [年, 月, 日, 小时, 分钟, 秒] 每一位的差值计算出来,然后从低位开始由 "秒→年" 依次判断:如果小于 0 ,则向前借一位,当前位补上对应的值,前一位则需要减1。
补值这里应该也好理解:"秒"、"分"补 60,"时"补 24,"月"补 12。
需要注意的是 "日",也就是"天数"的处理。因为是按照日历上的日期来计算的日期差值,所以需要考虑到月份的问题,每个月的天数是不一样的,那么补值也是不一样的。每个月的天数可能有28、29、30、31天,这里我是按照截止日期 nextDate 的上一个月的天数来作补值的。比如 nextDate 是 20160229,那么上个月是 1 月份,补值是 31 天。
/**
* 获取 2 个时间的自然年历的时间间隔
*
* @param nextDate 后面的时间,需要大于 previousDate
* @param previousDate 前面的时间
* @return [年, 月, 日, 小时, 分钟, 秒]的数组
*/
public static int[] getTimeIntervalArray(Calendar nextDate, Calendar previousDate) {
int year = nextDate.get(Calendar.YEAR) - previousDate.get(Calendar.YEAR);
int month = nextDate.get(Calendar.MONTH) - previousDate.get(Calendar.MONTH);
int day = nextDate.get(Calendar.DAY_OF_MONTH) - previousDate.get(Calendar.DAY_OF_MONTH);
int hour = nextDate.get(Calendar.HOUR_OF_DAY) - previousDate.get(Calendar.HOUR_OF_DAY);// 24小时制
int min = nextDate.get(Calendar.MINUTE) - previousDate.get(Calendar.MINUTE);
int second = nextDate.get(Calendar.SECOND) - previousDate.get(Calendar.SECOND);
boolean hasBorrowDay = false;// "时"是否向"天"借过一位
if (second < 0) {
second += 60;
min--;
}
if (min < 0) {
min += 60;
hour--;
}
if (hour < 0) {
hour += 24;
day--;
hasBorrowDay = true;
}
if (day < 0) {
// 计算截止日期的上一个月有多少天,补上去
Calendar tempDate = (Calendar) nextDate.clone();
tempDate.add(Calendar.MONTH, -1);// 获取截止日期的上一个月
day += tempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
// nextDate是月底最后一天,且day=这个月的天数,即是刚好一整个月,比如20160131~20160229,day=29,实则为1个月
if (!hasBorrowDay
&& nextDate.get(Calendar.DAY_OF_MONTH) == nextDate.getActualMaximum(Calendar.DAY_OF_MONTH)// 日期为月底最后一天
&& day >= nextDate.getActualMaximum(Calendar.DAY_OF_MONTH)) {// day刚好是nextDate一个月的天数,或大于nextDate月的天数(比如2月可能只有28天)
day = 0;// 因为这样判断是相当于刚好是整月了,那么不用向 month 借位,只需将 day 置 0
} else {// 向month借一位
month--;
}
}
if (month < 0) {
month += 12;
year--;
}
return new int[]{year, month, day, hour, min, second};
}
重点讲一下 day < 0 的情况。
if (day < 0) {
// 计算截止日期的上一个月有多少天,补上去
Calendar tempDate = (Calendar) nextDate.clone();
tempDate.add(Calendar.MONTH, -1);// 获取截止日期的上一个月
day += tempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
// nextDate是月底最后一天,且day=这个月的天数,即是刚好一整个月,比如20160131~20160229,day=29,实则为1个月
if (!hasBorrowDay
&& nextDate.get(Calendar.DAY_OF_MONTH) == nextDate.getActualMaximum(Calendar.DAY_OF_MONTH)// 日期为月底最后一天
&& day >= nextDate.getActualMaximum(Calendar.DAY_OF_MONTH)) {// day刚好是nextDate一个月的天数,或大于nextDate月的天数(比如2月可能只有28天)
day = 0;
} else {// 向month借一位
month--;
}
}
这里需要注意一下: 什么情况下刚好算作日历上的一整个月呢?
(1)这里拿 2 月份来举个栗子。比如 2016 年 2 月有 29 天,① "20160129~20160229"、② "20160130~20160229"、③ "20160131~20160229",这 3 个时间段的差值结果分别是 31 天、30天、29天,但按自然月算其实都是 "1 个月" 。① 按照我们平常的普通算法直接"年-月-日"相减,刚好是 1 个月,③ 按照日历上的日期,也是刚好 1 个月。这种情况,是 nextDate 刚好是月底最后一天 2 月29 号,如果 previousDate 是在 29 号~31 号之间,并且 hasBorrowDay = false 没有借位,那么都是算作整月的。
nextDate 非月底的情况,如 "20160127~20160225" 是 29 天,"20160129~20160228" 是 30 天,计算结果则只显示天数,不会显示为 1 个月了。
(2)如果你还需要计算 时分秒 的差值,那么这里还要注意一点,如果 "时" 向 "天" 借了一位,会影响 "天" 的计算的(是否是刚好 1 个月,或者是否需要向 "月" 借位)。所以在 hour < 0 时添加了一个标识 hasBorrowDay = true 表示 "天" 被借了一位。
举个栗子:"2016-01-30 01:59:59 ~ 2016-02-29 00:59:59"。
我们来进行竖式减法运算, "[]" 内的顺序为 [year, month, day, hour, minute, second],以英文标识。
2016-02-29 00:59:59
- 2016-01-30 01:59:59
------------------------------
① [0, 1, -1, -1, 0, 0]
② [0, 1, -2, 23, 0, 0]
③ [0, 0, 29, 23, 0, 0]
① 分别计算 [year, month, day, hour, minute, second] 的差值,得到: [0, 1, -1, -1, 0, 0];
② 因为 hour = -1 < 0,需要向 day 借一位来补值,补值为 24,hour = -1 + 24 = 23;
day 被借位了,需要自减 1,day = -1 -1 = -2, 并标识 hasBorrowDay = true ;得到:[0, 1, -2, 23, 0, 0];
③ 因为 day = -2 < 0,需要向 month 借一位来补值。2016-02-29 的上一个月是 1 月份 有 31 天,所以补值为 31,day = -2 + 31 = 29;
这里,hasBorrowDay = true,所以 month 被借位了需要自减 1 ,month = 1 - 1 = 0;得到:[0, 0, 29, 23, 0, 0];
④ 最后结果为 [0, 0, 29, 23, 0, 0],即 29 天 23 小时。
和 iOS 的结果一致(见下图)。
那如果我们没有标识 hasBorrowDay = true 会有什么影响呢?
回到第 ③ 步,我们让 hasBorrowDay 一直为 false:
因为 day = -2 < 0,需要向 month 借一位来补值。2016-02-29 的上一个月是 1 月份 有 31 天,所以补值为 31,day = -2 + 31 = 29;
2016-02-29是月底最后一天,所以
nextDate.get(Calendar.DAY_OF_MONTH) == nextDate.getActualMaximum(Calendar.DAY_OF_MONTH) //为真
day = 29,而 2 月份正好只有 29 天,所以
day >= nextDate.getActualMaximum(Calendar.DAY_OF_MONTH) //为真
那么按照这个逻辑:
if (!hasBorrowDay && nextDate.get(Calendar.DAY_OF_MONTH) == nextDate.getActualMaximum(Calendar.DAY_OF_MONTH)// 日期为月底最后一天 && day >= nextDate.getActualMaximum(Calendar.DAY_OF_MONTH)) {// day刚好是nextDate一个月的天数,或大于nextDate月的天数(比如2月可能只有28天) day = 0; // 会执行此步,因为这样判断是相当于刚好是整月了,那么不用向 month 借位,只需将 day 置 0 } else {// 向month借一位 month--; }
day 被置 0 了,最后得到的结果是:[0, 1, 0, 23, 0, 0],即 1 个月 23 小时。这样和日历上的自然时间就不是很符合了。
-------------------------------------------------------------分析完毕-----------------------------------------------------
gist 可能打不开(墙),这里附上整个类的源码:
package com.test.Utils;
import android.text.TextUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
/**
* desc : 获取时间间隔
* version:
* date : 2018/8/7
* author : DawnYu
* GitHub : DawnYu9
*/
public class TimeIntervalUtils {
/**
* 获取当前时间
*
* @param template 时间格式,默认为 "yyyy-MM-dd HH:mm:ss"
* @return
*/
public static String getCurrentDateString(String template) {
if (TextUtils.isEmpty(template)) {
template = "yyyy-MM-dd HH:mm:ss";// 大写"HH":24小时制,小写"hh":12小时制
}
SimpleDateFormat formatter = new SimpleDateFormat(template, Locale.getDefault());
System.out.println("getCurrentDateString = " + formatter.format(new Date()));
return formatter.format(new Date());
}
/**
* 获取 2 个时间的自然年历的时间间隔
*
* @param nextTime 后面的时间,需要大于 previousTime,空则默认为当前时间
* @param previousTime 前面的时间,空则默认为当前时间
* @param format 时间格式,eg:"yyyy-MM-dd", "yyyy-MM-dd hh:mm:ss"
* @return [年, 月, 日, 小时, 分钟, 秒]的数组
*/
public static int[] getTimeIntervalArray(String nextTime, String previousTime, String format) {
SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.getDefault());
Date nextDate;
Date previousDate;
Calendar nextCalendar = Calendar.getInstance();
Calendar previousCalendar = Calendar.getInstance();
// 空则取当前时间
try {
nextDate = dateFormat.parse(TextUtils.isEmpty(nextTime) ? getCurrentDateString(format) : nextTime);
nextCalendar.setTime(nextDate);
} catch (ParseException e) {
e.printStackTrace();
}
// 空则取当前时间
try {
previousDate = dateFormat.parse(TextUtils.isEmpty(previousTime) ? getCurrentDateString(format) : previousTime);
previousCalendar.setTime(previousDate);
} catch (ParseException e) {
e.printStackTrace();
}
return getTimeIntervalArray(nextCalendar, previousCalendar);
}
/**
* 获取 2 个时间的自然年历的时间间隔
*
* @param nextDate 后面的时间,需要大于 previousDate
* @param previousDate 前面的时间
* @return [年, 月, 日, 小时, 分钟, 秒]的数组
*/
public static int[] getTimeIntervalArray(Calendar nextDate, Calendar previousDate) {
int year = nextDate.get(Calendar.YEAR) - previousDate.get(Calendar.YEAR);
int month = nextDate.get(Calendar.MONTH) - previousDate.get(Calendar.MONTH);
int day = nextDate.get(Calendar.DAY_OF_MONTH) - previousDate.get(Calendar.DAY_OF_MONTH);
int hour = nextDate.get(Calendar.HOUR_OF_DAY) - previousDate.get(Calendar.HOUR_OF_DAY);// 24小时制
int min = nextDate.get(Calendar.MINUTE) - previousDate.get(Calendar.MINUTE);
int second = nextDate.get(Calendar.SECOND) - previousDate.get(Calendar.SECOND);
boolean hasBorrowDay = false;// "时"是否向"天"借过一位
if (second < 0) {
second += 60;
min--;
}
if (min < 0) {
min += 60;
hour--;
}
if (hour < 0) {
hour += 24;
day--;
hasBorrowDay = true;
}
if (day < 0) {
// 计算截止日期的上一个月有多少天,补上去
Calendar tempDate = (Calendar) nextDate.clone();
tempDate.add(Calendar.MONTH, -1);// 获取截止日期的上一个月
day += tempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
// nextDate是月底最后一天,且day=这个月的天数,即是刚好一整个月,比如20160131~20160229,day=29,实则为1个月
if (!hasBorrowDay
&& nextDate.get(Calendar.DAY_OF_MONTH) == nextDate.getActualMaximum(Calendar.DAY_OF_MONTH)// 日期为月底最后一天
&& day >= nextDate.getActualMaximum(Calendar.DAY_OF_MONTH)) {// day刚好是nextDate一个月的天数,或大于nextDate月的天数(比如2月可能只有28天)
day = 0;// 因为这样判断是相当于刚好是整月了,那么不用向 month 借位,只需将 day 置 0
} else {// 向month借一位
month--;
}
}
if (month < 0) {
month += 12;
year--;
}
return new int[]{year, month, day, hour, min, second};
}
}
测试代码:
/**
* 测试时间间隔
*/
private void testTimeInterval() {
String nextTime = "2016-02-29 00:59:59";
String preTime = "2016-01-30 01:59:59";
String format = "yyyy-MM-dd hh:mm:ss";
System.out.println("----------------------\n"
+ "nextTime = " + nextTime + "\n"
+ "preTime = " + preTime + "\n"
+ Arrays.toString(TimeIntervalUtils.getTimeIntervalArray(nextTime, preTime, format)) + "\n"
+ "----------------------");
}
部分测试结果和 iOS 对比:
二、 Period 类(Java 8,Android 8.0 以上)
参考博客:Java计算时间差、日期差总结
现在 Java 8 中有一个周期类 Period ,Android 需要 API level 26 以上,即 8.0 以上系统才可以使用 Period 。但是 Period 只支持计算 "年、月、日" 的差值,而且和 iOS 的算法也不太一样。
在 Period 的源码注释里可以看到其只能计算 "年、月、日" 的差值。
测试代码:
public void testTimeIntervalByPeriod() {
LocalDate nextDate = LocalDate.of(2016, 2, 29);
LocalDate preDate = LocalDate.of(2016, 1, 31);
Period p = Period.between(preDate, nextDate);
Log.i("testTimeIntervalByPeriod",
"-------\n"
+ "nextDate:" + nextDate + "\n"
+ "preDate: " + preDate + "\n"
+ "Period 时间差:" + p.getYears() + " 年 " + p.getMonths() + " 月 " + p.getDays() + " 天 ");
}
测试结果:
可见,Period 并没有按照日历来计算,只是单纯地做了减法。。
下一篇: 令你发狂的无厘头语录