SQL Date Math and the Leap Year

Ying pointed out on my article about finding the last Saturday of the year that adding a year to a DATE datatype works great with syntax like this:

SQL> select sysdate+numtoyminterval(1, 'YEAR') from dual;

SYSDATE+N
---------
27-NOV-07

It works great right up until you hit February, 29:

SQL> select to_date('02/29/2004', 'MM/DD/YYYY')+numtoyminterval(1, 'YEAR') from dual;
*
ERROR at line 1:
ORA-01839: date not valid for month specified

So what can we do about this? Well, as far as I can guess there are only two options. First, you could avoid ever using +numtoyminterval(1, 'YEAR') in your code and instead use +numtodsinterval(365, 'DAY') like this:

SQL> select to_date('02/29/2004', 'MM/DD/YYYY')+numtodsinterval(365, 'DAY') from dual

TO_DATE('
---------
28-FEB-05

This may cause some confusion as 365 days after January 15, 2004 would be January 14, 2005 and 365 days after January 1, 2004 is actually December 31, 2004, but it should never throw an ORA- error.

UPDATE: Ying commented with this solution which is probably better in most scenarios. By using the add_months function we can step forward a number of months and Oracle will automatically truncate to the last day of the month if the resulting month has fewer days than the starting month.

SQL> SELECT Add_Months(to_date('02/29/2004', 'MM/DD/YYYY'), 12) from dual;

ADD_MONTH
---------
28-FEB-05

The other alternative is to handle the exception programmatically. Either avoid inserting a February 29 or avoid using the +numtoyminterval(1, 'YEAR') only when handling a February 29.

I’d say option 1 is the better choice, but both have their drawbacks.

This is the same error you get if you attempt to add a month to, say January 30th since there is never a February 30th.

SQL> select to_date('01/30/2006', 'MM/DD/YYYY')+numtoyminterval(1, 'MONTH') from dual;
select to_date('01/30/2006', 'MM/DD/YYYY')+numtoyminterval(1, 'MONTH') from dual
*
ERROR at line 1:
ORA-01839: date not valid for month specified

I guess this is why most things are good for “30 days” not “1 month”.

Thanks Ying for pointing out this anomaly.

database, oracle, date, to_date, timestamp, time, database administration, database development

UNIX Time and UNIX Timestamps

A recent comment on my story about converting UNIX timestamps to Oracle dates prompted me to do a little extra digging on UNIX time.

UNIX time is a standard system used not only in UNIX but in many other modern computer systems. Instead of being divided into years, months, hours, minutes, etc. UNIX time is simply a number which represents the number of seconds which have passed since midnight Coordinated Universal time (UTC, the same time zone as Greenwich Mean Time, sometimes referred to as Zulu time), January 1, 1970. This date is often referred to as the UNIX epoch.

Sound like a lot of seconds? It is. At the time of this writing it has been 1,145,404,660 since the UNIX epoch, but since people like to think of dates the old fashioned way, in years, months, days, hours, minutes and seconds the computer is almost always nice enough to convert the UNIX time into the familiar date and time format, and to your local time zone.

One of the strengths of UNIX time is that when it is recorded (a point in UNIX time is typically referred to as a UNIX timestamp) it is always relative to Greenwich Mean Time. That means UNIX timestamps can be easily converted to different time zones with no ambiguity.

For all the gruesome details on UNIX time, Wikipedia has a typically thorough article on the topic.

While there are several sites on the web to convert a UNIX timestamp to human readable format and vice-verse be careful. Many sites will do the conversion based on their time zone. 4WebHelp.net provides a great page for converting both ways.

In contrast to UNIX time, Oracle Databases record time in a more traditional year, month, day, hour, minute, second manner. In order to convert Oracle dates to a different time zone you need to know what time zone the date was originally recorded in. Only recently has Oracle introduced a time datatype with a time zone attribute.

unix, time, timestamp, time zone, date, oracle, database, solaris, linux