Analog of ORACLE function MONTHS_BETWEEN in Java

后端 未结 8 470
时光说笑
时光说笑 2021-01-12 09:56

Does Java have some analog of Oracle\'s function MONTHS_BETWEEN?

相关标签:
8条回答
  • 2021-01-12 10:28

    I've run into the same need and started from @alain.janinm answer which is good but doesn't give the exact same result in some cases.
    ex :

    Consider months between 17/02/2013 and 11/03/2016 ("dd/MM/yyyy")
    Oracle result : 36,8064516129032
    Java method from @Alain.janinm answer : 36.74193548387097

    Here's the changes i made, to get a closer result to Oracle's months_between() function :

    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;
    } 
    

    With this function the result of the call for the dates 17/02/2013 and 11/03/2016 is : 36.806451612903224

    Note : From my understanding Oracle's months_between() function considers that all months are 31 days long

    0 讨论(0)
  • 2021-01-12 10:28

    java.time

    The other Answers use the troublesome old Calendar class that is now legacy, supplanted by the java.time classes.

    MONTHS_BETWEEN

    The doc says:

    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.

    LocalDate

    The LocalDate class represents a date-only value without time-of-day and without time zone.

    Retrieve a LocalDate from the database using JDBC 4.2 and later. The java.sql.Date class is now legacy, and can be avoided.

    LocalDate start = myResultSet.getObject( … , LocalDate.class ) ;  // Retrieve a `LocalDate` from database using JDBC 4.2 and later.
    

    For our demo here, let’s simulate those retrieved dates.

    LocalDate start = LocalDate.of( 2018 , Month.JANUARY , 23 );
    LocalDate stop = start.plusDays( 101 );
    

    Period

    Calculate the elapsed time as a span of time unattached to the timeline, a Period.

    Period p = Period.between( start , stop );
    

    Extract the total number of months.

    long months = p.toTotalMonths() ; 
    

    Extract the number of days part, the days remaining after calculating the months.

    int days = p.getDays() ;
    

    BigDecimal

    For accuracy, use BigDecimal. The double and Double types use floating-point technology, trading away accuracy for fast execution performance.

    Convert our values from primitives to BigDecimal.

    BigDecimal bdDays = new BigDecimal( days );
    BigDecimal bdMaximumDaysInMonth = new BigDecimal( 31 );
    

    Divide to get our fractional month. The MathContext provides a limit to resolving the fractional number, plus a rounding mode to get there. Here we use the constant MathContext.DECIMAL32, because I am guessing the Oracle function is using 32-bit math. The rounding mode is RoundingMode.HALF_EVEN, the default specified by IEEE 754, and also known as “Banker’s rounding” which is more mathematically fair than “schoolhouse rounding” commonly taught to children.

    BigDecimal fractionalMonth = bdDays.divide( bdMaximumDaysInMonth , MathContext.DECIMAL32 ); 
    

    Add this fraction to our number of whole months, for a complete result.

    BigDecimal bd = new BigDecimal( months ).add( fractionalMonth );
    

    To more closely emulate the behavior of the Oracle function, you may want to convert to a double.

    double d = bd.round( MathContext.DECIMAL32 ).doubleValue();
    

    Oracle did not document the gory details of their calculation. So you may need to do some trial-and-error experimentation to see if this code has results in line with your Oracle function.

    Dump to console.

    System.out.println( "From: " + start + " to: " + stop + " = " + bd + " months, using BigDecimal. As a double: " + d );
    

    See this code run live at IdeOne.com.

    From: 2018-01-23 to: 2018-05-04 = 3.3548387 months, using BigDecimal. As a double: 3.354839

    Caveat: While I answered the Question as asked, I must remark: Tracking elapsed time as a fraction as seen here is unwise. Instead use the java.time classes Period and Duration. For textual representation, use the standard ISO 8601 format: PnYnMnDTnHnMnS. For example, the Period seen in our example above: P3M11D for three months and eleven days.


    About java.time

    The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

    The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

    To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

    Where to obtain the java.time classes?

    • Java SE 8, Java SE 9, and later
      • Built-in.
      • Part of the standard Java API with a bundled implementation.
      • Java 9 adds some minor features and fixes.
    • Java SE 6 and Java SE 7
      • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
    • Android
      • Later versions of Android bundle implementations of the java.time classes.
      • For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

    The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

    0 讨论(0)
提交回复
热议问题