Skip to Main Content

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Jacob.

Asked: October 01, 2024 - 8:30 pm UTC

Last updated: October 04, 2024 - 5:02 am UTC

Version: 12c

Viewed 1000+ times

You Asked

I am trying to understand how oracle calculates months_between.
Here is the query:

SELECT MONTHS_BETWEEN(to_date('07/17/2027','mm/dd/yyyy'),to_date('06/22/2027','mm/dd/yyyy')) "Months" FROM dual;

This returns: 0.8387096774193548387096774193548387096774

The duration is 25 Days. The documentation says if the days both dates are same or both last days of the month the result will be an integer. Otherwise it uses 31 month to compute fractional part.

Not sure how the function gave me the result I got.


25/30 = 0.8333333333333333333333333333333333333333
25/31 = 0.8064516129032258064516129032258064516129

Where did the function come up with 0.8387096774193548387096774193548387096774 ?

I have also tried adding a time component of 00:00:00 to both dates but the result is the same.

and Connor said...

"The duration is 25 Days" .... because June has 30 days. If June had 31 days, then the duration would be 26.

Lets shift things forward a month

SQL> select to_date('08/17/2027','mm/dd/yyyy') - to_date('07/22/2027','mm/dd/yyyy') from dual;

TO_DATE('08/17/2027','MM/DD/YYYY')-TO_DATE('07/22/2027','MM/DD/YYYY')
---------------------------------------------------------------------
                                                                   26

SQL> SELECT MONTHS_BETWEEN(to_date('08/17/2027','mm/dd/yyyy'),to_date('07/22/2027','mm/dd/yyyy')) "Months" FROM dual;

    Months
----------
.838709677

SQL> select .838709677 * 31 from dual;

.838709677*31
-------------
           26

Rating

  (2 ratings)

Comments

months_between not suitable in all situations

mathguy, October 02, 2024 - 5:51 pm UTC

It should perhaps be pointed out that months_between (as defined by Oracle) may not be suitable for all situations.

The best, most natural use is when both arguments, of DATE data type, are truncated to the beginning of the month - values like DATE '2024-08-01' or equivalently TO_DATE('2024-08-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss').

In other situations (for more general dates), results in some special cases are definitely "wrong" in that they defy any kind of reasonable understanding. For a simple example: the value of "months between" 2023-03-30 and 2023-02-28 is 1.0645...; but if we change the first argument to 2023-03-31 (so we extend the interval by one day), the result is smaller, 1.000 even. A lot of nonsensical calculations may result from this kind of behavior.

There are other, more meaningful ways to measure "months between" two dates, different from Oracle's definition. All such definitions, even those that avoid such contradictions, will still be less than perfect, for the obvious reason that "months between" general dates (not truncated to the month) is not a well-defined concept. It can't be. But definitions better than Oracle's are, indeed, available - you just need to write your own function.

Another oddity about months_between - while it does not truncate to the month, it DOES truncate to the day (!!); time-of-day components of DATE arguments are discarded before the computation is performed. One may wonder why Oracle didn't go all the way to truncating both dates to the month - and stating very clearly that the result is only an approximation, unless the input dates are already truncated to the month.
Chris Saxon
October 03, 2024 - 6:23 pm UTC

time-of-day components of DATE arguments are discarded before the computation is performed.

That's not strictly true. If both dates have the same day of month, the result is an integer. Meaning any time differences are ignored.

If they are different days, then the time is taken into account:

select 
  months_between ( 
    to_date ( '2024-10-01 12:34:56', 'yyyy-mm-dd hh24:mi:ss'), 
    to_date ( '2024-09-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') 
  ) same_day_diff_time, 
  months_between ( 
    to_date ( '2024-10-02 00:00:00', 'yyyy-mm-dd hh24:mi:ss'), 
    to_date ( '2024-09-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') 
  ) diff_day_same_time, 
  months_between ( 
    to_date ( '2024-10-02 12:34:45', 'yyyy-mm-dd hh24:mi:ss'), 
    to_date ( '2024-09-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') 
  ) diff_day_diff_time;

SAME_DAY_DIFF_TIME DIFF_DAY_SAME_TIME DIFF_DAY_DIFF_TIME
------------------ ------------------ ------------------
                 1         1.03225806         1.04916555

Treatment of time-of-day in MONTHS_BETWEEN

mathguy, October 03, 2024 - 11:18 pm UTC

That's not strictly true. If both dates have the same day of month, the result is an integer. Meaning any time differences are ignored.
If they are different days, then the time is taken into account.


Wow! When I noticed what I reported, I must have worked with the same day of the month (or perhaps with month-ends, where the time-of-day is also truncated).

You are 100% correct, and I would argue, this makes it even worse. How does that make any sense? I would note also that this treatment of the time-of-day component is not addressed in the documentation in any way.
Connor McDonald
October 04, 2024 - 5:02 am UTC

Well.... once you head into days arithmetic with a function called MONTHS_BETWEEN, pretty much you are the whim of how someone decided the algorithm should work.

As per your earlier comment - "The best, most natural use is when both arguments, of DATE data type, are truncated to the beginning of the month"