Java是否具有Oracle函数MONTHS_BETWEEN
的一些模拟?
答案 0 :(得分:2)
你可以这样做:
public static int monthsBetween(Date minuend, Date subtrahend){
Calendar cal = Calendar.getInstance();
cal.setTime(minuend);
int minuendMonth = cal.get(Calendar.MONTH);
int minuendYear = cal.get(Calendar.YEAR);
cal.setTime(subtrahend);
int subtrahendMonth = cal.get(Calendar.MONTH);
int subtrahendYear = cal.get(Calendar.YEAR);
return ((minuendYear - subtrahendYear) * (cal.getMaximum(Calendar.MONTH)+1)) +
(minuendMonth - subtrahendMonth);
}
根据这个documentation MONTHS_BETWEEN返回一个小数结果,我认为这个方法也是如此:
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Date d = sdf.parse("02/02/1995");
Date d2 = sdf.parse("01/01/1995");
System.out.println(monthsBetween(d, d2));
}
public static double monthsBetween(Date baseDate, Date dateToSubstract){
Calendar cal = Calendar.getInstance();
cal.setTime(baseDate);
int baseDayOfYear = cal.get(Calendar.DAY_OF_YEAR);
int baseMonth = cal.get(Calendar.MONTH);
int baseYear = cal.get(Calendar.YEAR);
cal.setTime(dateToSubstract);
int subDayOfYear = cal.get(Calendar.DAY_OF_YEAR);
int subMonth = cal.get(Calendar.MONTH);
int subYear = cal.get(Calendar.YEAR);
//int fullMonth = ((baseYear - subYear) * (cal.getMaximum(Calendar.MONTH)+1)) +
//(baseMonth - subMonth);
//System.out.println(fullMonth);
return ((baseYear - subYear) * (cal.getMaximum(Calendar.MONTH)+1)) +
(baseDayOfYear-subDayOfYear)/31.0;
}
答案 1 :(得分:2)
我遇到了同样的需求并从@ alain.janinm回答开始,这很好但在某些情况下并没有给出完全相同的结果。
例如:
考虑2013年2月17日至2016年3月11日("dd/MM/yyyy"
)之间的月份
Oracle结果:36,8064516129032
来自@ Alain.janinm的Java方法回答:36.74193548387097
以下是我所做的更改,以便更接近Oracle的months_between()
功能:
public static double monthsBetween(Date startDate, Date endDate){
Calendar cal = Calendar.getInstance();
cal.setTime(startDate);
int startDayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
int startMonth = cal.get(Calendar.MONTH);
int startYear = cal.get(Calendar.YEAR);
cal.setTime(endDate);
int endDayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
int endMonth = cal.get(Calendar.MONTH);
int endYear = cal.get(Calendar.YEAR);
int diffMonths = endMonth - startMonth;
int diffYears = endYear - startYear;
int diffDays = endDayOfMonth - startDayOfMonth;
return (diffYears * 12) + diffMonths + diffDays/31.0;
}
使用此功能,日期为17/02/2013和2016年3月11日的通话结果为:36.806451612903224
注意:根据我的理解,Oracle的months_between()
函数认为所有月份都是31天
答案 2 :(得分:2)
我不得不将一些Oracle代码迁移到java,并且没有找到oracle函数之间的months_ analog。虽然测试列出的例子发现了一些产生错误结果的案例。
所以,创建了我自己的功能。创建了1600多个测试,比较了db与我的函数的结果,包括带有时间组件的日期 - 一切正常。
希望,这可以帮助别人。
public static double oracle_months_between(Timestamp endDate,Timestamp startDate) {
//MONTHS_BETWEEN returns number of months between dates date1 and date2.
// 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.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String endDateString = sdf.format(endDate), startDateString = sdf.format(startDate);
int startDateYear = Integer.parseInt(startDateString.substring(0,4)), startDateMonth = Integer.parseInt(startDateString.substring(5,7)), startDateDay = Integer.parseInt(startDateString.substring(8,10));
int endDateYear = Integer.parseInt(endDateString.substring(0,4)), endDateMonth = Integer.parseInt(endDateString.substring(5,7)), endDateDay = Integer.parseInt(endDateString.substring(8,10));
boolean endDateLDM = is_last_day(endDate), startDateLDM = is_last_day(startDate);
int diffMonths = -startDateYear*12 - startDateMonth + endDateYear * 12 + endDateMonth;
if (endDateLDM && startDateLDM || extract_day(startDate) == extract_day(endDate)){
// 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.
return (double)(diffMonths);
}
double diffDays = (endDateDay - startDateDay)/31.;
Timestamp dStart = Timestamp.valueOf("1970-01-01 " + startDateString.substring(11)), dEnd = Timestamp.valueOf("1970-01-01 " + endDateString.substring(11));
return diffMonths + diffDays + (dEnd.getTime()-dStart.getTime())/1000./3600./24./31.;
}
public static boolean is_last_day(Timestamp ts){
Calendar calendar = Calendar.getInstance();
calendar.setTime(ts);
int max = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
return max == Integer.parseInt((new SimpleDateFormat("dd").format(ts)));
}
答案 3 :(得分:0)
在Joda Time中,org.joda.time.Months类中有一个月份。
答案 4 :(得分:0)
我遇到同样的问题并且遵循Oracle MONTHS_BETWEEN我对@ alain.janinm和@ Guerneen4的答案进行了一些更改以纠正某些情况:
考虑31/07/1998和30/09/2013之间的月份(“dd / MM / yyyy”)Oracle结果:来自@ Guerneen的182 Java方法答案:181.96774193548387
问题在于,根据规范,如果date1和date2都是月的最后几天,那么结果总是一个整数。
为了便于理解,您可以找到Oracle MONTHS_BETWEEN规范:https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions089.htm。我在这里复制总结一下:
“返回date1和date2之间的月数。如果date1晚于date2,则结果为正。如果date1早于date2,则结果为负。如果date1和date2是相同的日期在月份的最后几个月或两个月,结果总是一个整数。否则Oracle数据库会根据31天的月份计算结果的小数部分,并考虑时间组件date1和date2的差异。“
以下是我所做的更改,获得了与Oracle的months_between()函数最接近的结果:
public static double monthsBetween(Date startDate, Date endDate) {
Calendar calSD = Calendar.getInstance();
Calendar calED = Calendar.getInstance();
calSD.setTime(startDate);
int startDayOfMonth = calSD.get(Calendar.DAY_OF_MONTH);
int startMonth = calSD.get(Calendar.MONTH);
int startYear = calSD.get(Calendar.YEAR);
calED.setTime(endDate);
int endDayOfMonth = calED.get(Calendar.DAY_OF_MONTH);
int endMonth = calED.get(Calendar.MONTH);
int endYear = calED.get(Calendar.YEAR);
int diffMonths = endMonth - startMonth;
int diffYears = endYear - startYear;
int diffDays = calSD.getActualMaximum(Calendar.DAY_OF_MONTH) == startDayOfMonth
&& calED.getActualMaximum(Calendar.DAY_OF_MONTH) == endDayOfMonth ? 0 : endDayOfMonth - startDayOfMonth;
return (diffYears * 12) + diffMonths + diffDays / 31.0;
}
答案 5 :(得分:0)
实际上,我认为正确的实现就是这个:
class Point {
constructor(stattX, startY){
this.x = startX;
this.y = startY;
}
// class methods here...
}
答案 6 :(得分:0)
其他Answers使用现在遗留下来的麻烦的旧Calendar
类,取而代之的是 java.time 类。
MONTHS_BETWEEN
MONTHS_BETWEEN返回date1和date2之间的月份数。如果date1晚于date2,则结果为正。如果date1早于date2,则结果为负数。如果date1和date2是该月的同一天或两个月的最后几天,则结果始终为整数。否则,Oracle数据库会根据31天的月份计算结果的小数部分,并考虑时间组件date1和date2的差异。
LocalDate
LocalDate
类表示没有时间且没有时区的仅限日期的值。
使用JDBC 4.2及更高版本从数据库中检索LocalDate
。 java.sql.Date
类现在是遗留的,可以避免。
LocalDate start = myResultSet.getObject( … , LocalDate.class ) ; // Retrieve a `LocalDate` from database using JDBC 4.2 and later.
对于我们的演示,让我们模拟那些检索日期。
LocalDate start = LocalDate.of( 2018 , Month.JANUARY , 23 );
LocalDate stop = start.plusDays( 101 );
Period
将经过时间计算为未附加到时间轴的时间跨度Period
。
Period p = Period.between( start , stop );
long months = p.toTotalMonths() ;
提取number of days part,计算月份后的剩余天数。
int days = p.getDays() ;
BigDecimal
为了准确,请使用BigDecimal
。 double
和Double
类型使用floating-point technology,为了快速执行性能而牺牲准确性。
将我们的值从基元转换为BigDecimal
。
BigDecimal bdDays = new BigDecimal( days );
BigDecimal bdMaximumDaysInMonth = new BigDecimal( 31 );
除以获得我们的分数月份。 MathContext
提供了解决小数的限制,以及到达那里的舍入模式。这里我们使用常量MathContext.DECIMAL32
,因为我猜测Oracle函数使用的是32位数学。舍入模式为RoundingMode.HALF_EVEN
,默认由IEEE 754指定,也称为“Banker’s rounding”,这在数学上比通常教给孩子的“校舍舍入”更公平。
BigDecimal fractionalMonth = bdDays.divide( bdMaximumDaysInMonth , MathContext.DECIMAL32 );
将此分数添加到我们的整月数中,以获得完整的结果。
BigDecimal bd = new BigDecimal( months ).add( fractionalMonth );
为了更接近地模拟Oracle函数的行为,您可能希望转换为double
。
double d = bd.round( MathContext.DECIMAL32 ).doubleValue();
Oracle没有记录他们计算的血腥细节。因此,您可能需要进行一些试错法实验,以查看此代码的结果是否与Oracle函数一致。
转储到控制台。
System.out.println( "From: " + start + " to: " + stop + " = " + bd + " months, using BigDecimal. As a double: " + d );
请参阅此code run live at IdeOne.com。
从:2018-01-23到:2018-05-04 = 3.3548387个月,使用BigDecimal。作为双倍:3.354839
警告:当我按照要求回答问题时,我必须说:跟踪经过的时间,如此处所示的分数是不明智的。而是使用java.time类Period
和Duration
。对于文本表示,请使用标准ISO 8601 format:PnYnMnDTnHnMnS。例如,上面示例中显示的Period
:P3M11D
为期三个月和十一天。
java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和& SimpleDateFormat
现在位于Joda-Time的maintenance mode项目建议迁移到java.time类。
要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310。
从哪里获取java.time类?
ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如Interval
,YearWeek
,YearQuarter
和more。
答案 7 :(得分:-1)
之前的答案并不完美,因为它们不处理日期,例如2月31日。
这是我在Javascript中对MONTHS_BETWEEN的迭代解释......
// Replica of the Oracle function MONTHS_BETWEEN where it calculates based on 31-day months
var MONTHS_BETWEEN = function(d1, d2) {
// Don't even try to calculate if it's the same day
if (d1.getTicks() === d2.getTicks()) return 0;
var totalDays = 0;
var earlyDte = (d1 < d2 ? d1 : d2); // Put the earlier date in here
var laterDate = (d1 > d2 ? d1 : d2); // Put the later date in here
// We'll need to compare dates using string manipulation because dates such as
// February 31 will not parse correctly with the native date object
var earlyDteStr = [(earlyDte.getMonth() + 1), earlyDte.getDate(), earlyDte.getFullYear()];
// Go in day-by-day increments, treating every month as having 31 days
while (earlyDteStr[2] < laterDate.getFullYear() ||
earlyDteStr[2] == laterDate.getFullYear() && earlyDteStr[0] < (laterDate.getMonth() + 1) ||
earlyDteStr[2] == laterDate.getFullYear() && earlyDteStr[0] == (laterDate.getMonth() + 1) && earlyDteStr[1] < laterDate.getDate()) {
if (earlyDteStr[1] + 1 < 32) {
earlyDteStr[1] += 1; // Increment the day
} else {
// If we got to this clause, then we need to carry over a month
if (earlyDteStr[0] + 1 < 13) {
earlyDteStr[0] += 1; // Increment the month
} else {
// If we got to this clause, then we need to carry over a year
earlyDteStr[2] += 1; // Increment the year
earlyDteStr[0] = 1; // Reset the month
}
earlyDteStr[1] = 1; // Reset the day
}
totalDays += 1; // Add to our running sum of days for this iteration
}
return (totalDays / 31.0);
};