Skip to Main Content
  • Questions
  • How to add years to a timestamp without losing fractions

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Johannes.

Asked: February 14, 2017 - 10:55 am UTC

Last updated: May 16, 2022 - 2:30 am UTC

Version: 11.2.0.4.0

Viewed 1000+ times

You Asked

Hi,

I have a column of type
TIMESTAMP
t in which I safe timestamps with 6 digit precision e.g. 03-FEB-17 04.45.01.909000 PM

Due to the way they have ben added to the table they are missing two things:
1. The time zone information
2. the correct century

Regarding 1: To add the timezone I created a new column t_tz of type
TIMESTAMP(6) WITH TIME ZONE
and want to copy the old to the new column, adding the timezone in the process with:
UPDATE myTable
  SET t_tz = t


Regarding 2: The display 03-FEB-17 is irritating because the database understands the value as 3rd February 17 BC but should be 3rd February 2017. I tried
add_month( t, 2000 * 12 )
to get the year right but in the process the fractional seconds are lost e.g.
03-FEB-17 04.45.01.909000 PM --> 03-FEB-17 04.45.01.000000 PM -05:00

What would be the right way to achieve both goals.

I wasn't able to find a matching answer despite extensive search.

and Chris said...

1. Yes, you can set the new timestamp with time zone column equal to the original:

alter session set nls_timestamp_format = 'DD-MON-YYYY HH24.MI.SSXFF';
alter session set nls_timestamp_tz_format = 'DD-MON-YYYY HH24.MI.SSXFF TZH:TZM';

create table t (
  ts timestamp
);

insert into t values (timestamp'0017-02-03 04:45:01.909');

select * from t;

alter table t add (tsz timestamp with time zone);

update t 
set    tsz = ts;

select * from t;

TS                              
03-FEB-0017 04.45.01.909000000 

update t 
set    tsz = to_timestamp_tz (
         to_char(ts, 'YYYY-MM-DD HH:MI:SSXFF') || ' -08:00', 
         'YYYY-MM-DD HH:MI:SSXFF TZH:TZM'
       );

TS                              TSZ                                    
03-FEB-0017 04.45.01.909000000  03-FEB-0017 04.46.01.909000000 +00:00


Though it's better practice to explicitly state your conversions. This also enables you to set a particular time zone if you want:

update t 
set    tsz = to_timestamp_tz (
         to_char(ts, 'YYYY-MM-DD HH:MI:SSXFF') || ' -08:00', 
         'YYYY-MM-DD HH:MI:SSXFF TZH:TZM'
       );

select * from t;

TS                              TSZ                                    
03-FEB-0017 04.45.01.909000000  03-FEB-0017 04.45.01.909000000 -08:00


If your table is "large" it may be better (faster) to use "create as select" to make a new table with the correct data type:

create table t_new as
  select to_timestamp_tz (
           to_char(ts, 'YYYY-MM-DD HH:MI:SSXFF') || ' -08:00', 
           'YYYY-MM-DD HH:MI:SSXFF TZH:TZM'
         ) ts
  from   t;


Then have a process to copy the constraints to the new table and switch them over.

2. Add_months implicitly converts the timestamps to a date. This is why you're losing the fractional seconds:

alter session set nls_timestamp_format = 'DD-MON-YYYY HH24.MI.SSXFF';

select add_months (
         to_timestamp (
           '03-FEB-0017 04.45.01.909000 PM', 
           'DD-MON-YYYY HH:MI:SS.FF AM'
         ), 
         2000*12
       ) t         
from   dual;

T                     
03-FEB-2017 16:45:01 


To overcome this, add an interval of 2000 years, avoiding the implicit conversion:

select to_timestamp (
           '03-FEB-0017 04.45.01.909000 PM', 
           'DD-MON-YYYY HH:MI:SS.FF AM'
       ) + interval '2000' year(4) t         
from   dual;

T                               
03-FEB-2017 16.45.01.909000000 

Rating

  (1 rating)

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

Comments

NUTHAN, May 14, 2022 - 9:04 am UTC

Hi Chris,


In the last query to preserve the timestamp you have added YEAR as an INTERVAL. This works if the date value is static.

Suppose, if we replace it with SYSTIMESTAMP then there is a chance of getting an error: "ORA 01839 date not valid for the month specified ". This is happening in case of Leap year.

Could you please help me to understand how can we preserve the timestamp

In the below case

SELECT systimestamp + INTERVAL '10' YEAR from dual;

A date value of Systimestamp : 29-FEB-2020

Connor McDonald
May 16, 2022 - 2:30 am UTC

The issue there is one of ambiguity.

What does it *mean* to add 10 years to Feb 29 2020? Even on a smaller scale, eg

SQL> select timestamp '2020-01-31 12:12:12.123' + INTERVAL '1' MONTH from dual;
select timestamp '2020-01-31 12:12:12.123' + INTERVAL '1' MONTH from dual
                                           *
ERROR at line 1:
ORA-01839: date not valid for month specified


there is the issue of what the result should be. Last day of Feb? 1st of March ? 2nd of March? etc etc

ADD_MONTHS works because define in the docs what the behaviour will be in such scenarios (and rest assured, for every person that says "Yes I agree with what it does" there is someone who says "No it should not do that")