日期计算没有那么难——java简易实现日期跨度计算
- 小序
- 定理 & 依据
- 简单实现
- year-1
- +=、-=、+=
- i < month
- 检查 & 测试
小序
在java中,我们计算日期跨度通常可以使用各种工具包,比如
java.util.Calendar
java.time.LocalDate
有时候会看看它的实现,发现还蛮复杂的,
@Override
public long toEpochDay() {
long y = year;
long m = month;
long total = 0;
total += 365 * y;
if (y >= 0) {
total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400;
} else {
total -= y / -4 - y / -100 + y / -400;
}
total += ((367 * m - 362) / 12);
total += day - 1;
if (m > 2) {
total--;
if (isLeapYear() == false) {
total--;
}
}
return total - DAYS_0000_TO_1970;
}
老外写的,源码 LocalDate:line:1828,这计算看得有点头疼,于是就想,咱能不能自己写个简单的算法,来实现日期跨度的计算。
定理 & 依据
关于日期的计算,最关键以及最复杂的莫过于闰年:
年份为4的倍数,且不是100的倍数,是闰年。
年份为400的倍数,是闰年。
其他月份则亘古不变(实际历史上有过修正,此文省略不计),1至12月天数分别为 31, 28(29), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31。
简单实现
根据基本定理,直接上代码:
// 平年各月天数。只包含前11个月,最后一个月用不到
private static final int[] COMMON_MONTH_DAY = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30};
/**
* 获取标准日
* @param year 年份(正整数)
* @param month 月份(0~11)
* @param day 日(月的实际日期,比如28号)
* @return
*/
private int getEpoDays(int year, int month, int day) {
// 正常天数减去闰年天数:逢4、400加1天
int total = (year - 1) * 365;
total += ((year - 1) / 4);
total -= (year - 1) / 100;
total += (year - 1) / 400;
for (int i = 0; i < COMMON_MONTH_DAY.length; i++) {
if (i < month) {
total += COMMON_MONTH_DAY[i];
} else {
break;
}
}
total += day;
if (month > Calendar.FEBRUARY && isBigYear(year)) {
total++;
}
return total;
}
// 是否闰年
private boolean isBigYear(int year) {
if (year % 4 == 0 && year % 100 != 0) {
return true;
}
if (year % 400 == 0) {
return true;
}
return false;
}
year-1
因为最后一年手动计算[滑稽脸],这几个包含 year-1 的公式只计算最后一年之前的天数。
+=、-=、+=
第一个“+=”是假设所有4的倍数的都是闰年;
第二个"-="是减去100倍数的年份多的那一天,因为100必是4的倍数:100=4*25【因式分解】;
第三个“+=”是加上上一步误减的400倍数,因为400必是100的倍数。
至此,整年计算结束。
i < month
最后计算当前年份的天数,这就比较好办了,只需要先按平年把已经过去的各月天数相加,最后判断是否闰年相机加一天就可以了。
检查 & 测试
使用 LocalDate#until 方法作为对照,来检查我们自定义的计算是否有误:
@Test
public void calcDays() {
int fromYear = 2000;
int fromMonth = 0; // 注意,Calendar中month起始为0
int fromDay = 1;
int toYear = 2000;
int toMonth = 6;
int toDay = 7;
// LocalDate.until, 在中国的月份是从1开始的
LocalDate from = LocalDate.of(fromYear, fromMonth + 1, fromDay);
LocalDate to = LocalDate.of(toYear, toMonth + 1, toDay);
long sampleDays = from.until(to, ChronoUnit.DAYS);
System.out.println("sampleDays:" + sampleDays);
int calcDays = getEpoDays(toYear, toMonth, toDay) - getEpoDays(fromYear, fromMonth, fromDay);
System.out.println("calcDays:" + calcDays);
System.out.println("equal:" + (sampleDays == calcDays));
}
随机改变日期,多次测试后证明我们的计算没有问题。