Oracle sql timezone issue - sql

My requirement here is to get time in GMT/UTC from a date type column. But when I use cast to cast date to timestamp, it is using US/Pacific timezone as reference though session timezone is set to GMT. So unless I use from_tz, I am not seeing desired result. Is there any other timezone setting in oracle sql that I need to modify to take GMT as reference always?
alter session set time_zone='+00:00';
select sessiontimezone from dual;
select current_timestamp from dual;
select sys_extract_utc(cast (sysdate as timestamp)) from dual;
select sys_extract_utc(from_tz(cast (sysdate as timestamp), '-07:00')) from dual;
select sys_extract_utc(current_timestamp) from dual;
Session altered.
SESSIONTIMEZONE
---------------------------------------------------------------------------
+00:00
CURRENT_TIMESTAMP
---------------------------------------------------------------------------
11-APR-16 08.46.42.292173 AM +00:00
SYS_EXTRACT_UTC(CAST(SYSDATEASTIMESTAMP))
---------------------------------------------------------------------------
11-APR-16 01.46.42.000000 AM
SYS_EXTRACT_UTC(FROM_TZ(CAST(SYSDATEASTIMESTAMP),'-07:00'))
---------------------------------------------------------------------------
11-APR-16 08.46.42.000000 AM
SYS_EXTRACT_UTC(CURRENT_TIMESTAMP)
---------------------------------------------------------------------------
11-APR-16 08.46.42.295310 AM
Tasks table has a date type column called task_started. I am looking to get UTC time from this date field. As part of that I was trying to alter session timezone to GMT while inserting the data so that I can simply cast it back to timestamp which is not working.
select task_started from tasks where rownum <2;
TASK_STAR
---------
10-APR-16
desc tasks;
Name Null? Type
----------------------------------------- -------- ----------------------------
...
TASK_STARTED DATE
...

This is a demo using data inserted using sysdate on a system on London time, so currently on BST (+01:00). The difference is smaller than you'd see on the west coast but the same things apply.
Mimicking your table, you can see that the session time zone has no effect on the inserted value since sysdate uses the database server time, not the session (client) time (as explained here):
alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';
alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS.FF3';
alter session set nls_timestamp_tz_format = 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM';
create table tasks (id number, task_started date);
alter session set time_zone='America/Los_Angeles';
insert into tasks (id, task_started) values (1, sysdate);
select task_started, cast(task_started as timestamp) as ts
from tasks where rownum < 2;
TASK_STARTED TS
------------------- -----------------------
2016-04-11 11:55:59 2016-04-11 11:55:59.000
alter session set time_zone = 'UTC';
select task_started, cast(task_started as timestamp) as ts
from tasks where rownum < 2;
TASK_STARTED TS
------------------- -----------------------
2016-04-11 11:55:59 2016-04-11 11:55:59.000
So that's the BST time in both cases. There isn't any session or database setting that will show you a date (or timestamp, without a time zone) converted to a specific time zone automatically. The database doesn't know what that stored date/time represents unless you tell it. It doesn't know or case if you use sysdate, current_date, or a date literal; at the point the data is inserted into the table it has no time zone information at all.
To get the UTC equivalent you need to use from_tz to declare that the stored value represents a specific time zone; then you can use at time zone (which keeps the time zone info) or sys_extract_utc (which doesn't) on that to convert it; and optionally cast back to a date:
select from_tz(cast(task_started as timestamp), 'Europe/London') as db_tstz,
from_tz(cast(task_started as timestamp), 'Europe/London') at time zone 'UTC' as utc_tstz,
sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London')) as utc_ts,
cast(sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London')) as date) as utc_date
from tasks where rownum < 2;
DB_TSTZ UTC_TSTZ UTC_TS UTC_DATE
------------------------------ ------------------------------ ----------------------- -------------------
2016-04-11 11:55:59.000 +01:00 2016-04-11 10:55:59.000 +00:00 2016-04-11 10:55:59.000 2016-04-11 10:55:59
I've used the time zone region name so it takes care of summer time for me; you could use dbtimezone but that would always use -08:00, and as you showed in the question you need to use -07:00 at the moment. ANd you could use sessiontimezone but then you have to remember to set that properly. Obviously in your case you'd use your local region, e.g. America/Los_Angeles, instead of Europe/London.
And from that you can get the epoch, via an interval by comparing timestamps or more simply from comparing dates:
select sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London'))
- timestamp '1970-01-01 00:00:00' as diff_interval,
cast(sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London')) as date)
- date '1970-01-01' as diff_days,
86400 *
(cast(sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London')) as date)
- date '1970-01-01') as epoch
from tasks where rownum < 2;
DIFF_INTERVAL DIFF_DAYS EPOCH
---------------- --------- -----------
16902 10:55:59.0 16902.46 1460372159
If you put 1460372159 into an online converter (there are many) it will show the UTC time.

Related

Casting Local Time to UTC Formating Incorrect

HIRE_DATE is in a 'DATE' column. The timestamp is local (Los Angeles); I would like to convert it to UTC.
I can't for the life of me fathom why the UTC output is mangled (Last 2 digits of YY is the DD; and vice-versa) -- and the time does not convert to UTC.
HIRE_DATE: 30/04/2019 12:00:00 AM
select from_tz(to_timestamp(HIRE_DATE,'DD-MM-YY HH24:MI:SS'), 'America/Los_Angeles') at time zone 'UTC' from TABLE
OUTPUT: 19/04/2030 12:00:00 AM
If HIRE_DATE is a DATE data type then you don't need TO_TIMESTAMP.
TO_TIMESTAMP is used to convert a string (i.e. VARCHAR2) into a TIMESTAMP value but you have a DATE value.
Just do
select from_tz(CAST(HIRE_DATE AS TIMESTAMP), 'America/Los_Angeles') at time zone 'UTC'
from TABLE
Actually I don't understand why FROM_TZ does not accept DATE values whereas almost any other date/timestamp related function accept either DATE or TIMESTAMP value as input.
Note, the default output display format of this query is defined by current user session NLS_TIMESTAMP_TZ_FORMAT setting. If you are not satisfied with the output format, either change NLS_TIMESTAMP_TZ_FORMAT setting by executing ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = '...' or use TO_CHAR function to set output format explicitly.
Instead of
... AT TIME ZONE 'UTC'
you can also use
SYS_EXTRACT_UTC(...)
The upper returns a TIMESTAMP WITH TIME ZONE value, the second one returns a TIMESTAMP value.
Would this do any good?
SQL> select from_tz(cast (sysdate as timestamp), 'UTC') result from dual;
RESULT
---------------------------------------------------------------------------
27.09.20 10:59:28,000000 UTC
Or, in your case
select from_tz(cast (hire_date as timestamp), 'UTC' from dual
No need to apply any format mask to hire_date as it is a DATE datatype (at least, that's what you said).
You use the word "convert" which can mean one of two things:
change the data type, which is what FROM_TZ does
change the value from one time zone to another, which FROM_TZ does not do.
You didn't give your expected output, so we may misunderstand.
To change the data type:
with data(dte) as (
select date '2019-04-30' + interval '12' hour from dual
)
select from_tz(cast(dte as timestamp), 'America/Los_Angeles') from data
FROM_TZ(CAST(DTEASTIMESTAMP),'AMERICA/LOS_ANGELES')
30-APR-19 12.00.00.000000 PM AMERICA/LOS_ANGELES
To get the simultaneous datetime value in UTC:
with data(dte) as (
select date '2019-04-30' + interval '12' hour from dual
)
select cast(sys_extract_utc(from_tz(cast(dte as timestamp), 'America/Los_Angeles')) as date) from data
CAST(SYS_EXTRACT_UTC(FROM_TZ(CAST(DTEASTIMESTAMP),'AMERICA/LOS_ANGELES'))ASDATE)
2019-04-30 19:00:00

Convert datetime column to utc time

I would like to achieve the following.
I have a datetime column that i calculated with the following functions.
to_char(to_date(f_sta_date, 'YYYYMMDD') + (f_sta_time)/86400), 'YYYY-MM-DD HH24:MI:SS')
to_char(to_date(f_sta_date, 'YYYYMMDD') + (f_sta_time)/86400), 'YYYY-MM-DD HH24:MI:SS')
T1
f_sta_date f__sta_time f_sto_date f_sto_time DT_sta DT_sto
20191001 6689 20191001 7185 2019-10-01 01:54:49 2019-10-01 01:59:45
Desired table
T1
f_sta_date f_sta_time_id f_sto_date f_sto_time_id DT_sta DT_sto DT_sta_UTC DT_sto_UTC
20191001 6689 20191001 7185 2019-10-01 01:54:49 2019-10-01 01:59:45
What function could i add to the functions above to achieve the desired result?
To start with: to_char() returns a string from a date. So if you want a date, don't use it, ie replace this:
to_char(to_date(f_sta_date, 'YYYYMMDD') + (f_sta_time)/86400), 'YYYY-MM-DD HH24:MI:SS')
To:
to_date(f_sta_date, 'YYYYMMDD') + f_sta_time/86400
Then: when it comes to managing timezones, you need to use the timestamp with time zone datatype instead of date. To convert your date in localtime (ie the time zone of your session, that is defined by SESSIONTIMEZONE) to a timestamp and get the corresponding date/time at UTC, you can do:
cast(to_date(f_sta_date, 'YYYYMMDD') + f_sta_time/86400 as timestamp with time zone)
at time zone 'UTC'
Your query:
select
to_date(f_sta_date, 'YYYYMMDD') + f_sta_time/86400 dt_sta,
to_date(f_sto_date, 'YYYYMMDD') + f_sto_time/86400 dt_sto,
cast(to_date(f_sta_date, 'YYYYMMDD') + f_sta_time/86400 as timestamp)
at time zone 'UTC' dt_sta_utc,
cast(to_date(f_sto_date, 'YYYYMMDD') + f_sto_time/86400 as timestamp)
at time zone 'UTC' dt_sto_utc
from t
From the discussion in comments under your post, it appears that your date-time (which you are already able to calculate) should instead be a timestamp with time zone, specifically "at time zone CET". Perhaps for your purposes date with time zone would suffice (in Oracle, "date" always means date-time); alas, Oracle doesn't have such a data type, we must use timestamp with time zone.
Below I show how to calculate that, in the WITH clause. (Note that I use a different approach for computing even the date-time - I convert your "time" component to seconds directly, using the Oracle function specifically designed for that, instead of arithmetic operations. Also, while Oracle usually allows you to use a number - your date component - as if it were a string, in some cases it doesn't. For example, you can do that in TO_DATE, but you can't in TO_TIMESTAMP. In any case, you should never rely on such implicit conversions; always make them explicit in your code.)
I show how I created the table for testing; then, in the query, in the WITH clause I do a preliminary computation (computing your DT_STA and DT_STO, but as timestamps with time zone); then in the main query I show how to convert to a different time zone.
create table t1 (
f_sta_date number,
f_sta_time number,
f_sto_date number,
f_sto_time number
);
insert into t1 values (20191001, 6689, 20191001, 7185);
commit;
with
prep (f_sta_date, f_sta_time, f_sto_date, f_sto_time, dt_sta, dt_sto) as (
select f_sta_date, f_sta_time, f_sto_date, f_sto_time,
from_tz(to_timestamp(to_char(f_sta_date), 'yyyymmdd')
+ numtodsinterval(f_sta_time, 'second'), 'CET'),
from_tz(to_timestamp(to_char(f_sto_date), 'yyyymmdd')
+ numtodsinterval(f_sto_time, 'second'), 'CET')
from t1
)
select f_sta_date, f_sta_time, f_sto_date, f_sto_time, dt_sta, dt_sto,
dt_sta at time zone 'UTC' as dt_sta_utc,
dt_sto at time zone 'UTC' as dt_sto_utc
from prep
;
F_STA_DATE F_STA_TIME F_STO_DATE F_STO_TIME DT_STA DT_STO DT_STA_UTC DT_STO_UTC
---------- ---------- ---------- ---------- ----------------------- ----------------------- ----------------------- -----------------------
20191001 6689 20191001 7185 2019-10-01 01:51:29 CET 2019-10-01 01:59:45 CET 2019-09-30 23:51:29 UTC 2019-09-30 23:59:45 UTC

sysdate and dbtimezone different in Oracle Database

With this query: select sysdate from dual;
the result is: 27-09-2018 07:50:50 -- this UK time
with : Select dbtimezone from dual;
output: +10:00
I want the sysdate to be of the same timezone of dbtimezone
I was trying to do it with alter database set time_zone='AEST', but i'm bit confused if this is the right solution.
Any help most appreciated.
Thanks.
It is a common misunderstanding that DBTIMEZONE is the time zone for SYSDATE and SYSTIMESTAMP
SYSDATE and SYSTIMESTAMP are returned in time zone of the operating system on which the database server resides.
DBTIMEZONE is the (internal) time zone of TIMESTAMP WITH LOCAL TIME values. I don't know any practical use of it. Note, you cannot change DBTIMEZONE on your database if the database contains a table with a TIMESTAMP WITH LOCAL TIME ZONE column and the column contains data.
If you want current time at DBTIMEZONE run
select SYSTIMESTAMP AT TIME ZONE DBTIMEZONE
from dual;
CURRENT_TIMESTAMP AT TIME ZONE DBTIMEZONE is also working.
See also How to handle Day Light Saving in Oracle database
SYSDATE returns the current date and time set for the operating system on which the database server resides.
CURRENT_DATE returns the current date in the session time zone.
show parameter nls_date_format
NAME TYPE VALUE
------------------------------ ----------- ------------------------
nls_date_format string dd-Mon-yyyy hh24:mi:ss
select sysdate, current_date, dbtimezone from dual;
SYSDATE CURRENT_DATE DBTIME
-------------------- -------------------- ------
27-Sep-2018 08:16:00 27-Sep-2018 08:16:00 +00:00
alter session set time_zone = '+08:00';
Session altered.
select sysdate, current_date, dbtimezone from dual;
SYSDATE CURRENT_DATE DBTIME
-------------------- -------------------- ------
27-Sep-2018 08:13:06 27-Sep-2018 15:13:06 +00:00

to_Char(DATE) with time

I have a DB (Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production). In there is a table (Course) in which I have a Date column (start_dte). I want to format the output to a char so I used:
SELECT start_dte,
to_Char(start_dte) AS start_dte_2,
to_Char(start_dte,'DD.MM.YYYY') AS start_dte_3,
to_Char(start_dte,'DD.MM.YYYY HH24:MI:SS') AS start_dte_4,
to_Char(start_dte,'DD.MM.YYYY HH12:MI:SS') AS start_dte_5,
to_Char(start_dte,'DD.MM.YYYY HH.MI.SSXFF AM') AS start_dte_6,
to_Char(start_dte,'DD.MM.YYYY HH24:MI:SSxFF') AS start_dte_7,
to_Char(start_dte,'DD-MON-YYYY HH24:MI:SSxFF TZH:TZM') AS start_dte_8
FROM Course
The Results:
Number 6,7,8 give me
java.sql.SQLException: ORA-01821: date format not recognized
6 has the same format as:
SELECT value
FROM V$NLS_PARAMETERS
WHERE parameter = 'NLS_TIME_FORMAT'
7 and 8 are taken from the Oracle documentation but this is only for timestamps. So what i am missing? I know the actual datatype should be datetime but I only have read access to that DB. What i would actually like:
Or even better:
A DATE doesn't have any time zone information, so if you want to adjust the displayed time then you'll need to specify which time zone that date nominally represents, and which time zone you want to convert it to.
An an example, if your stored date represents UTC and you want to see the equivalent local time in Paris, you can state that the stored time is UTC by casting it as a plain timestamp and passing that into the from_tz() function; and then specify the target timezone with at:
-- CTE for your sample data
with course (start_dte) as (
select cast(timestamp '2018-10-17 14:00:00' as date) from dual
)
-- actual query
select from_tz(cast(start_dte as timestamp), 'UTC')
at time zone 'Europe/Paris' as start_timestamp_tz
from course;
START_TIMESTAMP_TZ
------------------------------------------
2018-10-17 16:00:00.000000000 EUROPE/PARIS
Since you're querying this via JDBC you probably want to retrieve that as its native data type, and then choose how to display it locally. (But you could presumably query it as a date and adjust it in Java too...)
If you want to convert it to a string on the DB side, though, just use the format model you already used:
select to_char(from_tz(cast(start_dte as timestamp), 'UTC')
at time zone 'Europe/Paris', 'DD.MM.YYYY HH24:MI') as start_date
from course;
START_DATE
----------------
17.10.2018 16:00
Getting it into two fields is also simple, and if you don't want to repeat the conversion you can use a CTE or an inline view:
select to_char(start_timestamp_tz, 'DD.MM.YYYY') as start_date,
to_char(start_timestamp_tz, 'HH24:MI') as start_time
from (
select from_tz(cast(start_dte as timestamp), 'UTC')
at time zone 'Europe/Paris' as start_timestamp_tz
from course
);
START_DATE START_TIME
---------- ----------
17.10.2018 16:00
But it sounds like Java should be doing that conversion to strings for display.
What i would actually like:
| START_DATE |
|------------------|
| 17.10.2018 16:00 |
Or even better:
| START_DATE | START_TIME |
|------------|------------|
| 17.10.2018 | 16:00 |
Use:
SELECT to_Char(start_dte,'DD.MM.YYYY') AS start_date,
to_Char(start_dte,'HH24:MI') AS start_time
FROM Course
If your data is stored in the table in one time zone (i.e. UTC) and you want it in another time zone then use:
CAST( date_value AS TIMESTAMP ) to convert it from a DATE data type to a TIMESTAMP data type.
FROM_TZ( timestamp_value, timezone_string ) to convert it from a TIMESTAMP data type to a TIMESTAMP WITH TIME ZONE data type at the given time zone.
timestamp_with_timezone_value AT TIME ZONE timezone_string to convert it from one time zone to another time zone.
Like this:
SELECT TO_CHAR(
FROM_TZ( CAST( start_dte AS TIMESTAMP ), 'UTC' ) AT TIME ZONE 'Europe/Berlin',
'DD.MM.YYYY'
) AS start_date,
TO_CHAR(
FROM_TZ( CAST( start_dte AS TIMESTAMP ), 'UTC' ) AT TIME ZONE 'Europe/Berlin',
'HH24:MI'
) AS start_time
FROM Course
start_dte is a DATE value.
DATE does neither have fractional seconds (i.e. XFF) nor time zone information (i.e. TZH:TZM)
Use TIMESTAMP WITH TIME ZONE is you like to get such output.

SQL UTC date instead of SYSDATE

Working on the following query.
SELECT MIN(departure_date), ch_invoice.invoice_id
FROM ch_invoice
INNER JOIN ch_trip
ON ch_invoice.invoice_id = ch_trip.invoice_id
WHERE departure_date < SYSDATE
AND service_rendered = 0
AND paid = 1
Group By ch_invoice.invoice_id
Since the database that it is hitting may be in a location where the date hasn't changed yet, and we are using UTC as a standard for our dates, is it possible to replace SYSDATE with something like UTCDATE? I just need to know what day UTC is currently at.
As AntDC suggested, you can use the SYS_EXTRACT_UTC() function. That converts a 'datetime with time zone' from whatever time zone it is in to UTC.
SYSDATE doesn't have a time zone. If you passed that in then it would be implicitly converted to the system time zone, so it would work, but you can use SYSTIMESTAMP instead as that is already zone-aware.
You said in a comment that you want the date, but all Oracle dates have a time component; you can get the result as a date type with the time set to midnight (which makes sense for the comparison you're doing) with TRUNC():
select systimestamp,
sys_extract_utc(systimestamp) as utctime,
trunc(sys_extract_utc(systimestamp)) as utcdate
from dual;
SYSTIMESTAMP UTCTIME UTCDATE
-------------------------------- ------------------------- -------------------
2016-04-08 17:18:27.352 +01:00 2016-04-08 16:18:27.352 2016-04-08 00:00:00
That's running on a server in the UK, so the local time is BST.
Since that doesn't involve a day change, the effect can be demonstrated using the session time instead of the server time:
alter session set time_zone = '+12:00';
select current_timestamp,
sys_extract_utc(current_timestamp) as utctime,
trunc(sys_extract_utc(current_timestamp)) as utcdate
from dual;
CURRENT_TIMESTAMP UTCTIME UTCDATE
-------------------------------- ------------------------- -------------------
2016-04-09 04:23:17.910 +12:00 2016-04-08 16:23:17.910 2016-04-08 00:00:00
My session time is 2016-04-09, but it still finds the UTC date as 2016-04-08.
(To be clear, I am not suggesting you switch to using current_timestamp or current_date; I'm using those instead of systimestamp and sysdate purely as a demo).
SELECT SYS_EXTRACT_UTC(departure_date)UTC_SYS, SYSTIMESTAMP FROM DUAL;