Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, jimmy .

Asked: May 02, 2000 - 1:20 pm UTC

Last updated: July 08, 2004 - 12:37 am UTC

Version:

Viewed 10K+ times! This question is

You Asked

How could i count how many days in a
year by SQL query.
ie. this year 2000 is a leap year
it has 366 days?


and Tom said...



truncate the date to the YEAR boundary, add 12 months and subtract from that the date trunced to the year again:

ops$tkyte@8.0> select add_months(trunc(sysdate,'year'), 12) - trunc(sysdate,'year') from dual
2 /

ADD_MONTHS(TRUNC(SYSDATE,'YEAR'),12)-TRUNC(SYSDATE,'YEAR')
----------------------------------------------------------
366

ops$tkyte@8.0> select add_months(trunc(to_date('01-jan-1999'),'year'), 12) - trunc(to_date('01-jan-1999'),'year') from dual
2 /

ADD_MONTHS(TRUNC(TO_DATE('01-JAN-1999'),'YEAR'),12)-TRUNC(TO_DATE('01-JAN-1999'),'YEAR')
----------------------------------------------------------------------------------------
365



Rating

  (22 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Determination of Leap Year.

Steve Booth, October 02, 2002 - 3:29 pm UTC

I needed to determine whether a year was a leap year and was able to successfully use his code snippet.

Tom Kyte
October 02, 2002 - 7:34 pm UTC

even easier might be

to_char( last_day( to_date('02' || :year,'mmyyyy') ), 'dd' )

if that is 28, no leap year, 29 leap year.

lesser code..

SB, October 02, 2002 - 8:23 pm UTC

mod(:year, 4)
If the result is 0 then it's a leap year.


Leap year is not so easy

Rick, October 02, 2002 - 8:46 pm UTC

>
mod(:year, 4)
-- If the result is 0 then it's a leap year.
>
This is wrong!

Years divisible by 4 are leap years
BUT
Years divisible by 100 are NOT leap years
BUT
Years divisible by 400 ARE leap years

1900 was not a leap year, 2000 was a leap year


Tom Kyte
October 03, 2002 - 9:11 am UTC

Thanks, you saved me the effort of writing the query to show them wrong ;)

But look out for the 'Gotcha!'

Phil Singer, October 02, 2002 - 10:10 pm UTC

In terms of using Oracle functions to answer the question of
how many calendar days in a calendar year, this cannot be
questioned. However, this brings back memories of my prior
life of doing financial calculations. In the USA at least,
these questions are much more difficult. For example,
depending on State law, and when the payments of a load are
made, a year can turn out to have anywhere from 358 to 367 days. So, if the question is not really about leap years,
but really about days to collect interest, I urge you to
contact your compliance officer.

Year 2000 gives 366 days

Dawar, October 03, 2002 - 10:58 am UTC

Hello Tom,

It is interested when I used your defined query to the
year 2000. Output always is 366 days.
Rest of the years are fine. Even year 1900 gives 365 days.
why is that?
select add_months(trunc(to_date('01-jan-2000'),'year'), 12) -
trunc(to_date('01-jan-2000'),'year') from dual;

366


Tom Kyte
October 03, 2002 - 6:07 pm UTC

1* select last_day( to_date( '02-2000', 'mm-yyyy' )) from dual
scott@ORA920.LOCALHOST> /

LAST_DAY(
---------
29-FEB-00


2000 was a LEAP YEAR, 1900 was not, that is why.

Sorry and thankx for the clarification !!!

SB, October 03, 2002 - 12:35 pm UTC


RE: Year 2000 gives 366 days (dawar)

Mark A. Williams, October 03, 2002 - 1:39 pm UTC

Dawar:

Because 2000 was a leap year, 366 is the correct NOD...

HTH,

Mark

Leap Year Error.

SSN., October 22, 2002 - 6:13 am UTC

Hi Tom,

last_day(to_date('01-FEB-1500','dd-mon-yyyy')) returns 29-FEB-1500. Is it correct ? If not, then why it is like that?

I have tested it in 8.1.7 under HP-UX 11.0.

Thanks.

1500 was not a leap year

A P Clarke, October 22, 2002 - 8:27 am UTC

Tom

I was able to reproduce SBB's problem with 1500:

SQL> select last_day(to_date('01-FEB-1500','dd-mon-yyyy')) from dual;

LAST_DAY(
---------
29-FEB-00

This is wrong because 1500 was not a leap year - divisble by 100 but not 400.  This is also a problem with 1400, etc.  It isn't a problem with 1700, 1800 or 1900.  Nor does it apply to 2500, 2600 or 2900.

Yes, I have too much time on my hands - it's lunchtime and it's raining - but this does imply that Oracle's date generation lacks a consistent algorithm.  Another good reason for hoping that a wormhole in the spacetime continuum doesn't open up and send us back to the Middle Ages :P

Cheers, APC 

Tom Kyte
October 22, 2002 - 8:51 am UTC

$ cal 02 1500
February 1500
S M Tu W Th F S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29


and see below...

Leap year

Michel Cadot, October 22, 2002 - 8:31 am UTC

Hi,

1200 is a leap year
1300 is a leap year
1400 is a leap year
1500 is a leap year
1600 is a leap year
1700 is not a leap year
1800 is not a leap year
1900 is not a leap year
2000 is a leap year
...

Why ?

Because in Gregorian calendar xx00 is a leap year if and only if xx is divisible by 4.
But what about 1300, 1400 and 1500?
Gregorian calendar started during the night of 4 october 1582 to 15 october 1582. Before there was the Julian calendar in which all of the years divisible by 4 are leaps.

select to_char(to_date('04/10/1582','DD/MM/YYYY')+1,'DD/MM/YYYY') from dual;
TO_CHAR(TO
----------
15/10/1582

Regards



1500

Helen, October 22, 2002 - 8:35 am UTC

Up until 1582 any year divisibly by 4 qualified as a leap year. After this Pope Gregory XIII added that centennial years should also be divisible by 400 to qualify.

The metric system

Connor McDonald, October 22, 2002 - 11:13 am UTC

I look forward to the day when we go metric with this...

100 mins in an hour
10 hours in a day
10 days in a month
10 months in a year

Simple!

:-)
Connor

Leap Year

A Reader, March 22, 2004 - 3:18 am UTC

I understood that we can use Last_Day to find out leap year but my situation is i will get from and to date as input parameters eg:

15/07/2003 from date
14/07/2004 to date

So the month feb falls in 2004 so the select should fetch Last_Day of feb-2004

another example

02/02/2004 from date
01/02/2005 to date

here i need to take the later one and find out whether it falls in leap year!

How can i do this?


Tom Kyte
March 22, 2004 - 7:03 am UTC

I don't understand what you need here? "here i need to take the later one" later one what? find out if "what" falls in a leap year?

if you have a date and want to know "is it a leap year", then:



to_char( last_day( add_months( trunc(DATE,'y'), 1 ) ), 'dd' )


will do that -- it'll return 28 or 29

Life, the Universe & Oracle

DN, March 22, 2004 - 11:04 am UTC

This site is fantastic for learning useful work-related stuff, but I never thought the day would come when I'd be reading AskTom for info about Popes and history and gregorians - and wormholes!!! Bring on Who Wants To Be a Millionaire!

A reader, March 22, 2004 - 11:44 am UTC

<Quote>

$ cal 02 1500

</Quote>

Can we know the script cal.sql you are running ??? Its Just excellent.

Tom Kyte
March 22, 2004 - 11:53 am UTC

it was the unix prompt, cal is a unix command.

but...
</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:14741686777707#14796840665338 <code>


An interesting observation about the Gregorian Calendar

Robert Shepard, March 22, 2004 - 12:46 pm UTC

I've been fascinated with the whole calendar reform issue ever since I was a kid, so thought I'd share this little historical oddity with your readers.

select * from v$version;

Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Production
PL/SQL Release 8.1.7.4.0 - Production
CORE 8.1.7.0.0 Production
TNS for Solaris: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

select to_date('10-4-1582','MM-DD-YYYY') from dual;

10/4/1582

select to_date('10-5-1582','MM-DD-YYYY') from dual;

10/15/1582

select to_date('10-10-1582','MM-DD-YYYY') from dual;

10/15/1582

select to_date('10-15-1582','MM-DD-YYYY') from dual

10/15/1582

select add_months(trunc(to_date('01-jan-1582'),'year'), 12) -
trunc(to_date('01-jan-1582'),'year') from dual;

355

None of the above should be surprising. When Pope Gregory XIII adopted the
Gregorian Calendar in 1582, 10 days were dropped from October. Thus, the day
after October 4 became October 15. Oracle reflects this.

So far, so good. But now, try this from a Solaris system prompt:

csh> uname -a

SunOS kaos 5.8 Generic_117000-01 sun4u sparc SUNW,Ultra-4

csh> cal 10 1582

October 1582
S M Tu W Th F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31

Hmmmm. This looks like a normal 31-day month to me. What gives?

Well, my interest in calendars over the years prompted me to try this next:

csh> cal 09 1752

September 1752
S M Tu W Th F S
1 2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Aha!

Back in Oracle:

select add_months(trunc(to_date('01-jan-1752'),'year'), 12) -
trunc(to_date('01-jan-1752'),'year') from dual

366

So, to Oracle, 1752 is a perfectly ordinary (leap) year.

Why the difference? Is Oracle "wrong"? Or is the problem with Solaris?

Well, neither, as it turns out. The issue is actually one of politics. When the Pope adopted the Gregorian calendar, Catholic countries tended to make the switch pretty quickly. Protestant countries, however, didn't like the Pope telling them what to do, so they balked.

The United Kingdom (along with her American colonies), finally made the switch in 1752. Thus, Solaris reflects Anglo-American history, whereas Oracle goes along with the original Gregorian calendar.

As a side note, other countries took even longer to make the switch, in particular those with an Orthodox Christian background. For instance, the Russians didn't make the switch until around 1918, which is why the anniversary of the "October Revolution" (on the old Julian calendar) currently falls in November (on the Gregorian calendar). Because the changeover was so recent, dates in Russian history frequently are referred to as "Old Style" versus "New Style".

At any rate, this disagreement as to when the Gregorian calendar actually began could conceivably lead to some confusion for anyone using Oracle to store historical dates, if they're performing date arithmetic on them.


Just use Julian dates for a standard that works everywhere! Oh, wait....

Mark J. Bobak, March 22, 2004 - 2:07 pm UTC

Lest one thinks that Julian dates are the way around these
date issues (surely, there's no variance in them, right?),
yes, there's a well-known, outstanding Julian date bug w/
Oracle. (Bug #106242) See Steve Adams' write-up at:
</code> http://www.ixora.com.au/notes/julian_zero.htm <code>for the full story! ;-)






Thanks for the reply

A Reader, March 22, 2004 - 8:35 pm UTC

Dear Tom,

Lets assume start date is 15/07/2003 and the end is date 14/07/2004.Here first thing we have to find whether feb falls inbetween this period if yes then which year it belongs to. In this above example yes it is in 2004.So
we need to find 2004 is leap or not.
The second example the starts is 02/02/2004 and the end date is 01/03/2005.Here the start falls in feb 2004 and end date also crosses feb 2005. So we need to find both years 2004 and 2005 whether leap or not.


Thanks


Tom Kyte
March 22, 2004 - 9:27 pm UTC

ops$tkyte@ORA9IR2> variable x varchar2(20);
ops$tkyte@ORA9IR2> variable y varchar2(20)
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec :x := '15/07/2003';
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> exec :y := '14/07/2004';
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select last_day( add_months( trunc( to_date(:x,'dd/mm/yyyy'),'mm'), r ) )
  2    from
  3  (select rownum-1 r
  4     from all_objects
  5    where rownum <=
  6          ceil(months_between(last_day(to_date(:y,'dd/mm/yyyy')),
  7               trunc(to_date(:x,'dd/mm/yyyy'),'mm')))
  8  )
  9  where to_char( add_months( trunc( to_date(:x,'dd/mm/yyyy'),'mm'), r ), 'mm' ) = '02'
 10  /
 
LAST_DAY(
---------
29-FEB-04
 
ops$tkyte@ORA9IR2>
 

Thanks a lot

Muhammad Ibrahim, March 22, 2004 - 11:42 pm UTC

Dear Tom,

You are THE MAN. Thanks a lot.

Regards,
Ibrahim.

Function for DAYS360?

Regee Chacko, March 23, 2004 - 4:27 am UTC

I was looking for the logic to create a function which will give me the result of DAYS360 function in Microsoft Excel.
Can any of the Oracle Date functions be used for this purpose?



Tom Kyte
March 23, 2004 - 7:15 am UTC

you would sort of have to explain what days360 does/means. i don't do "spreadsheets"

DAYS360 is weird....

Mark J. Bobak, July 08, 2004 - 12:37 am UTC

unless perhaps you're a financial analyst....

"DAYS360 calculates the number of days between dates, based
on the 360 day year used in interest calculations."

Also, apparently, there is a "US method", and a "European
method"!

Personally, I like my years to be around 365.2425 days long.
;-)

days360 Function

Phil Singer, March 08, 2008 - 8:24 pm UTC

I was trying to look up something else, which I knew I had commented on, so I searched on myself

Then I found this thread, and just had to recall what I had said.

Then I uncovered this dare, and just had to supply the function (not enough room here to comment):

create or replace function days360(
       p_start_date           date,
       p_end_date             date,
       p_rule_type            char default 'F'
       )
    RETURN number
IS
  v_mm1    pls_integer;
  v_dd1    pls_integer;
  v_yyyy1  pls_integer;
  v_mm2    pls_integer;
  v_dd2    pls_integer;
  v_yyyy2  pls_integer;
BEGIN
  v_yyyy1 := to_number(to_char(p_start_date,'yyyy'));
  v_mm1   := to_number(to_char(p_start_date,'mm'));
  v_dd1   := to_number(to_char(p_start_date,'dd'));
  v_yyyy2 := to_number(to_char(p_end_date,'yyyy'));
  v_mm2   := to_number(to_char(p_end_date,'mm'));
  v_dd2   := to_number(to_char(p_end_date,'dd'));
  IF p_rule_type = 'F' THEN
     IF v_dd1 = 31 THEN v_dd1 := 30; END IF;
     IF v_mm1 = 2  AND v_dd1 = to_number(to_char(last_day(p_start_date),'dd'))
          THEN v_dd1 := 30; END IF;
     IF v_dd2 = 31
          THEN IF v_dd1 < 30
                    THEN v_dd2 := 1;
                         v_mm2 := v_mm2 + 1;
                         IF v_mm2 = 13 THEN v_mm2 := 1;
                                            v_yyyy2 := v_yyyy2 +1;
                         END IF;
                    ELSE v_dd2 := 30;
               END IF;
     END IF;
     IF v_mm2 = 2  AND v_dd2 = to_number(to_char(last_day(p_end_date),'dd'))
          THEN v_dd2 := 30;
               IF  (v_dd1 < 30)
                   THEN v_dd2 := 1;
                        v_mm2 := 3;
               END IF;
     END IF;
     IF v_mm2 IN (4, 6, 9, 11) AND v_dd2 = 30
          AND v_dd1 < 30
          THEN v_dd2 := 1;
               v_mm2 := v_mm2 + 1;
     END IF;
  ELSIF p_rule_type = 'T' THEN
     IF v_dd1 = 31 THEN v_dd1 := 30; END IF;
     IF v_dd1 = 31 THEN v_dd1 := 30; END IF;
     IF v_mm1 = 2  AND v_dd1 = to_number(to_char(last_day(p_start_date),'dd'))
          THEN v_dd1 := 30; END IF;
     IF v_dd2 = 31 THEN v_dd2 := 30; END IF;
     IF v_mm2 = 2  AND v_dd2 = to_number(to_char(last_day(p_end_date),'dd'))
          THEN v_dd2 := 30; END IF;
  ELSE RAISE_APPLICATION_ERROR('-20002','3VL Not Allowed Here');
  END IF;
  RETURN (v_yyyy2 - v_yyyy1) * 360
       + (v_mm2 - v_mm1) * 30
       + (v_dd2 - v_dd1);
END;
/


As an example, try the function with beginning and ending dates of 20060731 and 20070731. Then try these pairs:

     Start Date Pay Date
 ----------- -------------
 20060731 20060831
 20060831 20060930
 20060930 20061031
 20061031 20061130
 20061130 20061231
 20061231 20070131
 20070131 20070228
 20070228 20070331
 20070331 20070430
 20070430 20070531
 20070531 20070630
 20070630 20070731


Add the days together. Consider if you will ever trust a bank again.