Extract local hour from timestamp with time zone - oracle

I'm trying to extract the local hour from a timestamp with time zone, but as I am new to working with this data type, I get unexpected results.
I was expecting 22 as output for every row below.
with my_data as(
select to_timestamp_tz(ts, 'yyyy-mm-dd hh24:mi:ss tzh:tzm') as tsz
from (select '2017-12-07 22:23:24 +' || lpad(level, 2, '0') || ':00' as ts
from dual connect by level <= 10
)
)
select dbtimezone
,sessiontimezone
,tsz
,extract(hour from tsz)
from my_data;
Why does this happen, and what do I need to do to extract the local hour from a timestamp with time zone?

Check documentation of EXTRACT(datetime):
When extracting from a datetime with a time zone value, the value returned is in UTC.
Use TO_CHAR(tsz, 'HH24') or EXTRACT(hour from cast(tsz as timestamp)) if you like to get local hours.

Related

Oracle SQL convert number (that stores a timestamp) to human readable date time on select

I have a timestamp stored on a column called ts of type NUMBER(15, 0), and I want to print their corresponding human readable datetime in any human readable format, like '2022-03-15 23:08:24'.
None of what I have tried works, but the most closed thing is:
select
to_date('19700101', 'YYYYMMDD') + ( 1 / 24 / 60 / 60 / 1000) * ts
from my_table;
But this translates ts to a human readable date, not a datetime. I'm not able to show the hours, minutes and seconds. I think Oracle SQL has functions to translate timestamps to datetimes in a straightforward way, but it requires the timestamp is stored on a TIMESTAMP column, but in my case it's a NUMBER.
You are generating a date, which retains the time to second precision, but loses the milliseconds. You're also ignoring the time zone your ts is nominally in, which is presumably UTC - as an epoch/Unix time.
Anyway, you can change how the date is displayed by changing your session settings, or with to_char():
select
to_char(
date '1970-01-01' + (1 / 24 / 60 / 60 / 1000) * ts,
'YYYY-MM-DD HH24:MI:SS'
)
from my_table;
If you want to keep milliseconds, and preserve time zone, use a timestamp and intervals instead:
select
to_char(
timestamp '1970-01-01 00:00:00 UTC' + ((ts / 1000) * interval '1' second),
'YYYY-MM-DD HH24:MI:SS.FF3 TZR'
) as string_result
from my_table;
With an example ts value of 1655977424456, that gives result 2022-06-23 09:43:44.456 UTC
The result is still UTC. You can also convert the time to a different time zone if that's useful; for example:
select
to_char(
(timestamp '1970-01-01 00:00:00 UTC' + ((ts / 1000) * interval '1' second))
at time zone 'Europe/Madrid',
'YYYY-MM-DD HH24:MI:SS.FF3'
)
from my_table;
The same example ts value of 1655977424456 now gives 2022-06-23 11:43:44.456 EUROPE/MADRID, or just 2022-06-23 11:43:44.456 if you leave the TZR off the format model or convert to a plain timestamp.
And you should only convert to a string to display - not to store or manipulate or pass around the actual timestamp value.
db<>fiddle with some variations.

Oracle how to convert Timestamp with any Timezone, to Date with database server Timezone

I have a timestamp column, which contains data with different timezones. I need to select records which belongs to a given 'day' of database server timezone.
For example, if the data in MY_TIMESTAMP column is 19-MAR-19 00.37.56.030000000 EUROPE/PARIS.
And on given date 19-MAR-19 (also represented as 2019078) where the database server is on EUROPE/LONDON.
Is there any way so that if my database server is in EUROPE/LONDON timezone, then this record is ignored but if it is in EUROPE/PARIS, then the record is selected.
Please note that the given timestamp is first hour of the day and EUROPE/PARIS is ahead of EUROPE/LONDON by one hour
The query I tried, unfortunately ignores the timezone of timestamp.
select *
from MY_TABLE
where
to_number(to_char(CAST(MY_TIMESTAMP AS DATE), 'RRRRDDD')) between 2019078 AND 2019079
There is below mentioned way to convert TIMESTAMP from know timezone to a date in required timezone but I cannot use this logic as the source timezone is not know.
CAST((FROM_TZ(CAST(MY_TIMESTAMP AS TIMESTAMP),'EUROPE/PARIS') AT TIME ZONE 'EUROPE/LONDON') AS DATE)'
You don't need to convert the table data; as well as being more work, doing so would stop any index on that column being used.
Oracle will honour time zones when comparing values, so compare the original table data with the specific day - and convert that to a timestamp with time zone:
select *
from MY_TABLE
where MY_TIMESTAMP >= timestamp '2019-03-19 00:00:00 Europe/London'
and MY_TIMESTAMP < timestamp '2019-03-20 00:00:00 Europe/London'
or if you want to base it on today rather than a fixed date:
where MY_TIMESTAMP >= from_tz(cast(trunc(sysdate) as timestamp), 'Europe/London')
and MY_TIMESTAMP < from_tz(cast(trunc(sysdate) + 1 as timestamp), 'Europe/London')
or if you're being passed the dates as YYYYDDD values (replace fixed value with numeric argument name):
where MY_TIMESTAMP >= from_tz(to_timestamp(to_char(2019078), 'RRRRDDD'), 'Europe/London')
and MY_TIMESTAMP < from_tz(to_timestamp(to_char(2019079), 'RRRRDDD'), 'Europe/London')
Quick demo with some sample data in a CTE, in two zones for simplicity:
with my_table (id, my_timestamp) as (
select 1, timestamp '2019-03-19 00:37:56.030000000 Europe/Paris' from dual
union all
select 2, timestamp '2019-03-19 00:37:56.030000000 Europe/London' from dual
union all
select 3, timestamp '2019-03-19 01:00:00.000000000 Europe/Paris' from dual
union all
select 4, timestamp '2019-03-20 00:37:56.030000000 Europe/Paris' from dual
union all
select 5, timestamp '2019-03-20 00:37:56.030000000 Europe/London' from dual
)
select *
from MY_TABLE
where MY_TIMESTAMP >= timestamp '2019-03-19 00:00:00 Europe/London'
and MY_TIMESTAMP < timestamp '2019-03-20 00:00:00 Europe/London'
/
ID MY_TIMESTAMP
---------- --------------------------------------------------
2 2019-03-19 00:37:56.030000000 EUROPE/LONDON
3 2019-03-19 01:00:00.000000000 EUROPE/PARIS
4 2019-03-20 00:37:56.030000000 EUROPE/PARIS
The first sample row is excluded because 00:37 in Paris is still the previous day in London. The second and third are included because they are both in the early hours of that day - the third row just scrapes in. The fourth row is included for the same reason the first was excluded - 00:37 tomorrow is still today from London. And the fifth is excluded because it's after midnight in London.

oracle convert unix epoch time to date

The context is that there is an existing application in our product which generates and sends the EPOCH number to an existing oracle procedure & vice versa. It works in that procedure using something like this
SELECT UTC_TO_DATE (1463533832) FROM DUAL
SELECT date_to_utc(creation_date) FROM mytable
When I tried these queries it does work for me as well with Oracle 10g server (and oracle sql developer 4.x if that matters).
In the existing procedure the requirement was to save the value as date itself (time component was irrelevant), however in the new requirement I have to convert unix EPOCH value to datetime (at the hours/mins/seconds level, or better in a specific format such as dd-MMM-yyyy hh:mm:ss) in an oracle query. Strangely I am unable to find any documentation around the UTC_TO_DATE and DATE_TO_UTC functions with Google. I have looked around at all different questions on stackoverflow, but most of them are specific to programming languages such as php, java etc.
Bottom line, how to convert EPOCH to that level of time using these functions (or any other functions) in Oracle query? Additionally are those functions I am referring could be custom or specific somewhere, as I don't see any documentation or reference to this.
To convert from milliseconds from epoch (assume epoch is Jan 1st 1970):
select to_date('19700101', 'YYYYMMDD') + ( 1 / 24 / 60 / 60 / 1000) * 1322629200000
from dual;
11/30/2011 5:00:00 AM
To convert that date back to milliseconds:
select (to_date('11/30/2011 05:00:00', 'MM/DD/YYYY HH24:MI:SS') - to_date('19700101', 'YYYYMMDD')) * 24 * 60 * 60 * 1000
from dual;
1322629200000
If its seconds instead of milliseconds, just omit the 1000 part of the equation:
select to_date('19700101', 'YYYYMMDD') + ( 1 / 24 / 60 / 60 ) * 1322629200
from dual;
select (to_date('11/30/2011 05:00:00', 'MM/DD/YYYY HH24:MI:SS') - to_date('19700101', 'YYYYMMDD')) * 24 * 60 * 60
from dual;
Hope that helps.
Another option is to use an interval type:
SELECT TO_TIMESTAMP('1970-01-01 00:00:00.0'
,'YYYY-MM-DD HH24:MI:SS.FF'
) + NUMTODSINTERVAL(1493963084212/1000, 'SECOND')
FROM dual;
It has this advantage that milliseconds won't be cut.
If your epoch time is stored as an integer.....
And you desire the conversion to Oracle date format.
Step 1-->
Add your epoch date (1462086000) to standard 01-jan-1970. 86400 is seconds in a 24 hour period.
*Select TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 from dual*
**output is 5/1/2016 7:00:00 AM**
Step 2--> Convert it to a CHAR . This is needed for formatting before additional functions can be applied.
*Select TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss') from dual*
output is 2016-05-01 07:00:00
Step 3--> Now onto Timestamp conversion
Select to_timestamp(TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss') from dual
output is 5/1/2016 7:00:00.000000000 AM
Step 4--> Now need the TimeZone, usage of UTC
Select from_tz(to_timestamp(TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss'),'UTC') from dual
output is 5/1/2016 7:00:00.000000000 AM +00:00
Step 5--> If your timezone need is PST
Select from_tz(to_timestamp(TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss'),'UTC') at time zone 'America/Los_Angeles' TZ from dual
output is 5/1/2016 12:00:00.000000000 AM -07:00
Step 6--> Format the PST Timezone timestamp.
Select to_Char(from_tz(to_timestamp(TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss'),'UTC') at time zone 'America/Los_Angeles' ,'DD-MON-YYYY HH24:MI:SS') TZ from dual
output is 01-MAY-2016 00:00:00
Step 7--> And finally, if your column is date datatype
Add to_DATE to the whole above Select.
Here it is for both UTC/GMT and EST;
GMT select (to_date('1970-01-01 00','yyyy-mm-dd hh24') +
(1519232926891)/1000/60/60/24) from dual;
EST select new_time(to_date('1970-01-01 00','yyyy-mm-dd hh24') +
(1519232926891)/1000/60/60/24, 'GMT', 'EST') from dual;
I thought somebody would be interested in seeing an Oracle function version of this:
CREATE OR REPLACE FUNCTION unix_to_date(unix_sec NUMBER)
RETURN date
IS
ret_date DATE;
BEGIN
ret_date:=TO_DATE('19700101','YYYYMMDD')+( 1/ 24/ 60/ 60)*unix_sec;
RETURN ret_date;
END;
/
I had a bunch of records I needed dates for so I updated my table with:
update bobfirst set entered=unix_to_date(1500000000+a);
where a is a number between 1 and 10,000,000.
A shorter method to convert timestamp to nanoseconds.
SELECT (EXTRACT(DAY FROM (
SYSTIMESTAMP --Replace line with desired timestamp --Maximum value: TIMESTAMP '3871-04-29 10:39:59.999999999 UTC'
- TIMESTAMP '1970-01-01 00:00:00 UTC') * 24 * 60) * 60 + EXTRACT(SECOND FROM
SYSTIMESTAMP --Replace line with desired timestamp
)) * 1000000000 AS NANOS FROM DUAL;
NANOS
1598434427263027000
A method to convert nanoseconds to timestamp.
SELECT TIMESTAMP '1970-01-01 00:00:00 UTC' + numtodsinterval(
1598434427263027000 --Replace line with desired nanoseconds
/ 1000000000, 'SECOND') AS TIMESTAMP FROM dual;
TIMESTAMP
26/08/20 09:33:47,263027000 UTC
As expected, above methods' results are not affected by time zones.
A shorter method to convert interval to nanoseconds.
SELECT (EXTRACT(DAY FROM (
INTERVAL '+18500 09:33:47.263027' DAY(5) TO SECOND --Replace line with desired interval --Maximum value: INTERVAL '+694444 10:39:59.999999999' DAY(6) TO SECOND(9) or up to 3871 year
) * 24 * 60) * 60 + EXTRACT(SECOND FROM (
INTERVAL '+18500 09:33:47.263027' DAY(5) TO SECOND --Replace line with desired interval
))) * 1000000000 AS NANOS FROM DUAL;
NANOS
1598434427263027000
A method to convert nanoseconds to interval.
SELECT numtodsinterval(
1598434427263027000 --Replace line with desired nanoseconds
/ 1000000000, 'SECOND') AS INTERVAL FROM dual;
INTERVAL
+18500 09:33:47.263027
As expected, millis, micros and nanos are converted and reverted, dispite of SYSTIMESTAMP doesn't have nanosecounds information.
Replace 1000000000 by 1000, for example, if you'd like to work with milliseconds instead of nanoseconds.
I've tried some of posted methods, but almost of them are affected by the time zone or result on data loss after revertion, so I've decided do post the methods that works for me.

Wrong difference in minutes in Oracle (DATE2-DATE1)*24*60

I'm trying to calculate difference in minutes between two dates in Oracle with this testing query:
SELECT
(DATE2-DATE1)*24*60 DIFFINMINUTES
FROM
(
SELECT
TO_DATE('2014-06-06 10:30:00', 'YYYY-MM-DD HH24:MI:SS') DATE1,
TO_DATE('2014-06-06 11:25', 'YYYY-MM-DD HH24:MI') DATE2
FROM DUAL
);
The expected result should be 55 minutes but I'm getting 54,99999999.
ROUNDing this value gets the job done but I really wants to understand why this calculation was this behavior.
DATE2 - DATE1 Oracle calculates the difference in days. That's why some inaccuracy may occur.
Just an example with timestamps
select inter,
extract (day from inter) days,
extract (hour from inter) hours,
extract (minute from inter) minutes,
extract (second from inter) seconds
from
(select
cast(date2 as timestamp) - cast(date1 as timestamp) inter
FROM
(
SELECT
TO_DATE('2014-06-06 10:30:00', 'YYYY-MM-DD HH24:MI:SS') DATE1,
to_date('2014-06-06 11:25', 'YYYY-MM-DD HH24:MI') date2
from dual
));
Substraction of timestamps gives you exact INTERVAL

Oracle Timestamp, Max and Minimal Values

I was searching, also in the Oracle Doc, for the following:
What is the range for Timestamp in Oracle?
I know for date it is -4712, Jan-01 to 9999 Dec-31, but what for Timestamp?
Anyone a clue or hint where I can search?
You can always just try it:
SQL> select to_timestamp( '9999-12-31 23:59:59', 'yyyy-mm-dd hh24:mi:ss' ) from dual;
TO_TIMESTAMP('9999-12-3123:59:59','YYYY-MM-DDHH24:MI:SS')
---------------------------------------------------------------------------
31-DEC-99 11.59.59.000000000 PM
and:
SQL> select to_timestamp( '9999-12-31 23:59:59', 'yyyy-mm-dd hh24:mi:ss' )+1 from dual;
select to_timestamp( '9999-12-31 23:59:59', 'yyyy-mm-dd hh24:mi:ss' )+1 from dual
*
ERROR at line 1:
ORA-01841: (full) year must be between -4713 and +9999, and not be 0
It would be surprising if the range for the DATE portion of a TIMESTAMP was smaller than the range for a DATE, so it should be:
-4712-01-01 00:00:00 to 9999-12-31 23:59:59.999999
That assumes no time zone; the UTC value is probably constrained to that range, but someone in an Eastern time zone might manage to see a data value on 1000-01-01 in their time zone.
It is hard to find definitive data off Oracle's site. The best I found in a casual survey was:
http://www.techonthenet.com/oracle/datatypes.php
There are probably others.
I found a quote which says:
TIMESTAMP Datatype
The TIMESTAMP datatype is an extension of the DATE datatype. It stores the year,
month, and day of the DATE datatype, plus hour, minute, and second values.

Resources