介绍
因为系统迁移, 有一些函数要转成 Java 版本, Oracle 的
官方介绍 - MONTHS_BETWEEN
MONTHS_BETWEEN returns number of months between dates date1 and date2. The month and the last day of the month are defined by the parameter NLS_CALENDAR. If date1 is later than date2, then the result is positive. If date1 is earlier than date2, then the result is negative. If date1 and date2 are either the same days of the month or both last days of months, then the result is always an integer. Otherwise Oracle Database calculates the fractional portion of the result based on a 31-day month and considers the difference in time components date1 and date2.
翻译
MONTHS_BETWEEN返回日期date1和日期date2之间的月数。月份和月份的最后一天由参数NLS_CALENDAR定义。如果date1晚于date2,则结果为正。如果date1早于date2,则结果为负。如果date1和date2是同一个月的同一天,或者都是同一个月的最后一天,那么结果总是一个整数。否则,Oracle数据库根据31天的月份计算结果的小数部分,并考虑时间组件date1和date2的差异。
Java 代码
java
/**
* 计算两个日期之间的月份差值,精确到小数点.
* 逻辑参考 Oracle 的 MONTHS_BETWEEN 函数.由于函数的特殊性,两个日期的格式尽可能规整为 YYYY-MM-DD 格式,会忽略时分秒
*
* <pre>
* select MONTHS_BETWEEN('2019-01-31', '2019-02-28') from dual;
* </pre>
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 月份差值
*/
public static double monthsBetween(Date startDate, Date endDate) {
// 将旧版Date转换为Instant,然后调整到系统默认时区并转换为LocalDateTime
LocalDateTime dateTime1 = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime dateTime2 = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
// 确保dateTime1 >= dateTime2,若否则交换并取负数
if (dateTime1.isBefore(dateTime2)) {
return -monthsBetween(endDate, startDate);
}
LocalDate date1 = dateTime1.toLocalDate();
LocalDate date2 = dateTime2.toLocalDate();
// 判断是否为月末
boolean isDate1EndOfMonth = date1.equals(date1.withDayOfMonth(date1.lengthOfMonth()));
boolean isDate2EndOfMonth = date2.equals(date2.withDayOfMonth(date2.lengthOfMonth()));
// 计算年月日差值
int yearDiff = date1.getYear() - date2.getYear();
int monthDiff = date1.getMonthValue() - date2.getMonthValue();
int dayDiff = date1.getDayOfMonth() - date2.getDayOfMonth();
// 核心计算逻辑 - 月份部分
double totalMonths = yearDiff * 12 + monthDiff;
// 处理天数部分
if (isDate1EndOfMonth && isDate2EndOfMonth) {
// 若均为月末,天数差清零
dayDiff = 0;
}
// 核心计算逻辑 - 天数部分 Oracle按31天计算日占比
totalMonths += dayDiff / 31.0;
// 处理时间部分
Duration duration = Duration.between(dateTime2.toLocalTime(), dateTime1.toLocalTime());
long hours = duration.toHours();
long minutes = duration.toMinutes() % 60;
long seconds = duration.getSeconds() % 60;
// 核心计算逻辑 - 时分秒部分 将时分钟秒转换为月的比例 Oracle按31天744小时计算
double timeAsMonth = hours / 744.0 + minutes / (744.0 * 60) + seconds / (744.0 * 60 * 60);
// 处理位数为小数点后14位. Oracle 默认小数点后14位
return Math.round((totalMonths + timeAsMonth) * 100000000000000.0) / 100000000000000.0;
}
单元测试
java
@Test
void monthsBetween() {
/*
select
months_between(to_date('2025-01-07 01:12:41', 'yyyy-mm-dd hh24:mi:ss'), to_date('2025-03-09 11:22:48', 'yyyy-mm-dd hh24:mi:ss')) as "-2.07818361708483",
months_between(to_date('2025-02-27 01:12:41', 'yyyy-mm-dd hh24:mi:ss'), to_date('2025-06-01 21:22:48', 'yyyy-mm-dd hh24:mi:ss')) as "-3.18839867084827",
months_between(to_date('2025-03-17 01:12:41', 'yyyy-mm-dd hh24:mi:ss'), to_date('2025-05-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) as "-1.48224275686977"
from dual
*/
log.info("计算月份差: {}", DateUtils.monthsBetween(DateUtils.parse("2025-01-07 01:12:41", "yyyy-MM-dd HH:mm:ss"), DateUtils.parse("2025-03-09 11:22:48", "yyyy-MM-dd HH:mm:ss")));
log.info("计算月份差: {}", DateUtils.monthsBetween(DateUtils.parse("2025-02-27 01:12:41", "yyyy-MM-dd HH:mm:ss"), DateUtils.parse("2025-06-01 21:22:48", "yyyy-MM-dd HH:mm:ss")));
log.info("计算月份差: {}", DateUtils.monthsBetween(DateUtils.parse("2025-03-17 01:12:41", "yyyy-MM-dd HH:mm:ss"), DateUtils.parse("2025-05-01 00:00:00", "yyyy-MM-dd HH:mm:ss")));
}