Oracle SQL: Handle with Daylight Saving Time - sql

I have the following system information:
I use Oracle Database 10g
The SysTimeStamp is UTC
The SessionTimeZone is Europe/Athens
The dbTimeZone is +03:00
So, I have the column date_1 from tbl_1 table, with the following datetime:
date_1
-----------------
08.02.2017 10:00
08.02.2017 11:00
08.02.2017 12:00
-----------------
The results I want is like this:
date_2
-----------------
08.02.2017 13:00
08.02.2017 14:00
08.02.2017 15:00
For that I use:
SELECT TO_CHAR(date_1 + INTERVAL '3' HOUR, 'DD.MM.YYYY HH24:MI') as date_2
FROM tbl_1
WHERE date_1 >= TO_DATE('08.02.2017 10:00','DD.MM.YYYY HH24:MI')
AND date_1 <= TO_DATE('08.02.2017 12:00','DD.MM.YYYY HH24:MI')
My problem appear when the hour from March and October is changing because in the last Sunday from March we have 23 hours in a day and in the last Sunday from October we have 25 hours in a day.
Because of this I have to change my query 4 times/year (On summer time, on winter time, when we have 23 hour in March and when we have 25 hour in October)
Can you recommend a query in this select that solve this problem?

If you have a plain date or timestamp with no embedded time zone information, you can tell Oracle to treat it as being in a specific time zone with the from_tz() function. You can then convert that value - which now has data type 'timestamp with zone zone' rather than a plain 'timestamp' - to another zone with the at time zone datetime expression syntax, either using the session time zone as 'local' or with a specific named time zone:
alter session set nls_date_format='YYYY-MM-DD HH24:MI:SS';
alter session set nls_timestamp_format='YYYY-MM-DD HH24:MI:SS';
alter session set nls_timestamp_tz_format='YYYY-MM-DD HH24:MI:SS TZR';
alter session set time_zone = 'America/New_York';
with cte (ts) as (
select timestamp '2017-02-08 12:00:00' from dual
)
select ts,
from_tz(ts, 'UTC') as ts_utc,
from_tz(ts, 'UTC') at local as ts_local,
from_tz(ts, 'UTC') at time zone 'Europe/Athens' as ts_athens
from cte;
TS TS_UTC TS_LOCAL TS_ATHENS
------------------- ----------------------- ------------------------------------ ---------------------------------
2017-02-08 12:00:00 2017-02-08 12:00:00 UTC 2017-02-08 07:00:00 AMERICA/NEW_YORK 2017-02-08 14:00:00 EUROPE/ATHENS
If you're starting from a date then you have to convert it to a timestamp before calling from_tz():
with cte (dt) as (
select cast( timestamp '2017-02-08 12:00:00' as date) from dual
)
select dt,
from_tz(cast(dt as timestamp), 'UTC') as ts_utc,
from_tz(cast(dt as timestamp), 'UTC') at local as ts_local,
from_tz(cast(dt as timestamp), 'UTC') at time zone 'Europe/Athens' as ts_athens
from cte;
DT TS_UTC TS_LOCAL TS_ATHENS
------------------- ----------------------- ------------------------------------ ---------------------------------
2017-02-08 12:00:00 2017-02-08 12:00:00 UTC 2017-02-08 07:00:00 AMERICA/NEW_YORK 2017-02-08 14:00:00 EUROPE/ATHENS
So the data type of your original date_1 values matters, as does the nominal time zone it is supposed to represent. If it's a;ready a 'timestamp with time zone' or 'timestamp with local time zone' then it already has embedded time zone information, so you don't need the from_tz() part at all. If it's a date you need to convert it to a timestamp.
Assuming that date_1 is stored as a plain timestamp (maybe implied by your interval addition, but not by the column name and filters you used) and that it's nominally UTC, you could do:
from_tz(date_1, 'UTC') at time zone 'Europe/Athens'
... which will give you a 'timestamp with time zone' result; or you could use local to rely on your session time zone. If `date_1 is stored as a date you'd add the conversion to timestamp:
from_tz(cast(date_1 as timestamp), 'UTC') at time zone 'Europe/Athens'
As a demo, generating timestamps (not dates) in a CTE including some around the DST change for this year:
with tbl_1(date_1) as (
select timestamp '2017-02-08 10:00:00' from dual
union all select timestamp '2017-02-08 11:00:00' from dual
union all select timestamp '2017-02-08 12:00:00' from dual
union all select timestamp '2017-03-23 12:00:00' + numtodsinterval(level, 'day')
from dual connect by level <= 4
)
select date_1,
-- cast(from_tz(date_1, 'UTC') at time zone 'Europe/Athens' as timestamp) as date_2
to_char(from_tz(date_1, 'UTC') at time zone 'Europe/Athens',
'DD.MM.YYYY HH24:MI') as date_2
from tbl_1
order by date_1;
DATE_1 DATE_2
------------------- ----------------
2017-02-08 10:00:00 08.02.2017 12:00
2017-02-08 11:00:00 08.02.2017 13:00
2017-02-08 12:00:00 08.02.2017 14:00
2017-03-24 12:00:00 24.03.2017 14:00
2017-03-25 12:00:00 25.03.2017 14:00
2017-03-26 12:00:00 26.03.2017 15:00
2017-03-27 12:00:00 27.03.2017 15:00
You can see that an extra hour is added automatically after the clocks change on March 26th. But the results are out by an hour for your sample February data - so either your data isn't actually stored as UTC (but is -01:00, and you can change the from_tz() call to reflect that), or your expected results are wrong.

You can apply a case to the select:
select date_1 + case
when to_char(date_1 ,'MM') <= 3 then 2/24 -- Jan/Feb/Mar
when to_char(date_1,'MM') <= 10 then 3/24 -- Apr to Oct
else 2/24 -- Nov/Dec
end as date_2
from tbl_1

For USA timezone
SELECT SYSDATE,
NEXT_DAY ( TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') + 7 dst_start,
NEXT_DAY ( TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/11/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') dst_end,
CASE WHEN SYSDATE >= NEXT_DAY ( TO_DATE ( TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') + 7 AND SYSDATE < NEXT_DAY ( TO_DATE ( TO_CHAR (SYSDATE, 'YYYY') || '/11/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') THEN 'Y' ELSE 'N' END AS dst_check_usa,
NEW_TIME ( SYSDATE, CASE WHEN SYSDATE >= NEXT_DAY ( TO_DATE ( TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') + 7 AND SYSDATE < NEXT_DAY ( TO_DATE ( TO_CHAR (SYSDATE, 'YYYY') || '/11/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') THEN 'CDT' ELSE 'CST' END, 'GMT') AS current_time_gmt
FROM DUAL;
For Europe Timezone
SELECT SYSDATE,
NEXT_DAY(LAST_DAY(TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM'))-7, 'SUN') dst_start_uk,
NEXT_DAY(LAST_DAY(TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/10/01 02:00 AM', 'YYYY/MM/DD HH:MI AM'))-7, 'SUN') dst_end_uk,
CASE WHEN SYSDATE >= NEXT_DAY(LAST_DAY(TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM'))-7, 'SUN') AND SYSDATE < NEXT_DAY(LAST_DAY(TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/10/01 02:00 AM', 'YYYY/MM/DD HH:MI AM'))-7, 'SUN') THEN 'Y' ELSE 'N' END AS dst_check_uk
FROM DUAL;

Related

Oracle Sql adding hours and minute to a to_date while insert statment

Hello I am trying to make an insert statement which includes adding 2 hours and 10 minutes to a to_date .
But i do not know which function i can use.
this is my statement so far
insert into xyz values (TO_DATE('22-Oct-2020 11:00 AM', 'DD-MM-YYYY HH:MI AM'))
but i want to add 2 hours and 10 minutes to the above while inserting.
Use date arithmetics. In Oracle, you can add decimal values to a date (1 stands for "1 day"):
insert into xyz
values (
to_date('22-Oct-2020 11:00 AM', 'DD-MM-YYYY HH:MI AM')
+ 2/24 + 10 / 60 / 24
)
Or:
insert into xyz
values (
to_date('22-Oct-2020 11:00 AM', 'DD-MM-YYYY HH:MI AM')
+ interval '2' hour + interval '10' minute
)
Or (with credits to Wernfried Domscheit):
insert into xyz
values (
to_date('22-Oct-2020 11:00 AM', 'DD-MM-YYYY HH:MI AM')
+ interval '2:10' hour to minute
)
You can use interval as well,
insert into xyz values (TO_DATE('22-Oct-2020 11:00 AM', 'DD-MM-YYYY HH:MI AM') + interval '130' minute);
select TO_DATE('22-Oct-2020 11:00 AM', 'DD-MM-YYYY HH:MI AM') + interval '130' minute from dual;
TO_DATE('22-OCT-202
-------------------
22-10-2020 01:10 PM
select TO_DATE('22-Oct-2020 11:00 AM', 'DD-MM-YYYY HH:MI AM') + interval '1' day from dual;
TO_DATE('22-OCT-202
-------------------
23-10-2020 11:00 AM

How to extract time from a date column in the where clause in ORACLE?

I want to select rows with a specific time interval but the date doesn't matter. So I need a function to return just the time part. I tried using:
to_char(mydate, 'HH12:MI:SS') between '00:00:00' and '08:00:00'
but this doesn't seem to work. Any ideas?
With some sample data you can see that using HH12 doens't necessarly produce the strings you are expecting:
alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';
-- CTE just for dummy data
with mytable (mydate) as (
select cast(timestamp '2018-08-01 00:00:00' as date) from dual
union all select cast(timestamp '2018-08-02 07:59:59' as date) from dual
union all select cast(timestamp '2018-08-03 08:00:00' as date) from dual
union all select cast(timestamp '2018-08-04 08:00:01' as date) from dual
union all select cast(timestamp '2018-08-05 19:59:59' as date) from dual
union all select cast(timestamp '2018-08-06 20:00:00' as date) from dual
union all select cast(timestamp '2018-08-07 20:00:01' as date) from dual
)
-- actual query
select mydate,
to_char(mydate, 'HH24:MI:SS') as time_24,
to_char(mydate, 'HH12:MI:SS') as time_12
from mytable;
MYDATE TIME_24 TIME_12
------------------- -------- --------
2018-08-01 00:00:00 00:00:00 12:00:00
2018-08-02 07:59:59 07:59:59 07:59:59
2018-08-03 08:00:00 08:00:00 08:00:00
2018-08-04 08:00:01 08:00:01 08:00:01
2018-08-05 19:59:59 19:59:59 07:59:59
2018-08-06 20:00:00 20:00:00 08:00:00
2018-08-07 20:00:01 20:00:01 08:00:01
So when you try to filter using that HH12-based string it includes records you don't expect to see, between 8am and 8pm; and also excludes midnight (as that is '12:00:00' not '00:00:00'):
select mydate
from mytable
where to_char(mydate, 'HH12:MI:SS') between '00:00:00' and '08:00:00';
MYDATE
-------------------
2018-08-02 07:59:59
2018-08-03 08:00:00
2018-08-05 19:59:59
2018-08-06 20:00:00
If you use HH24 instead then you get
select mydate
from mytable
where to_char(mydate, 'HH24:MI:SS') between '00:00:00' and '08:00:00';
MYDATE
-------------------
2018-08-01 00:00:00
2018-08-02 07:59:59
2018-08-03 08:00:00
Also, notice that between is inclusive, so it picks up records at exactly 08:00:00. That may not be what you want - if you're splitting the day into three 8-hour periods, you don't data for that second to be included multiple times; so you can use a more explicit range instead:
select mydate
from mytable
where to_char(mydate, 'HH24:MI:SS') >= '00:00:00'
and to_char(mydate, 'HH24:MI:SS') < '08:00:00';
MYDATE
-------------------
2018-08-01 00:00:00
2018-08-02 07:59:59
then your second shift is:
where to_char(mydate, 'HH24:MI:SS') >= '08:00:00'
and to_char(mydate, 'HH24:MI:SS') < '16:00:00';
and your third shift is:
where to_char(mydate, 'HH24:MI:SS') >= '16:00:00';
or if you prefer, for consistency:
where to_char(mydate, 'HH24:MI:SS') >= '16:00:00'
and to_char(mydate, 'HH24:MI:SS') < '24:00:00';
You can't ever get the hour reported as 24 but as it's a string comparison that doesn't matter here, though it is slightly jarring.
TO_CHAR(mydate, 'HH24:MI:SS')
WHERE mydate BETWEEN '00:00:00' AND '08:00:00';

Previous Weekdays

I have a requirement in which i have to find start and end date.
Start date is First sat of the previous month of created date and end date is previous friday of created date.
Eg Below .. I am passing created date and need to derive start and end date like this below.
CREATED_DT Start_date end_date
04/08/2015 15:36 04/07/2015 00:00 31/07/2015 23:59
07/07/2015 15:32 06/06/2015 00:00 03/07/2015 23:59
You should not depend on the locale-specific NLS settings.
You could use following functions:
NEXT_DAY
ADD_MONTHS
TRUNC
For example,
SQL> alter session set nls_date_format='DD/MM/YYYY HH24:MI:SS';
SQL> WITH t(created_dt) AS(
2 SELECT to_date('04/08/2015 15:36','DD/MM/YYYY HH24:MI') FROM DUAL UNION ALL
3 SELECT to_date('07/07/2015 15:32','DD/MM/YYYY HH24:MI') FROM DUAL
4 )
5 SELECT CREATED_DT,
6 NEXT_DAY(TRUNC(add_months(created_dt, -1),'MM') -1,TO_CHAR(to_date('6','J'),'Day')) -1 start_date,
7 NEXT_DAY(TRUNC(created_dt, 'MM') -1, TO_CHAR(to_date('5','J'),'Day')) -1 + 0.99999 AS end_date
8 FROM t;
CREATED_DT START_DATE END_DATE
------------------- ------------------- -------------------
04/08/2015 15:36:00 04/07/2015 00:00:00 31/07/2015 23:59:59
07/07/2015 15:32:00 06/06/2015 00:00:00 03/07/2015 23:59:59
SQL>
To get the time portion as 23:59:59, you could either add 0.99999 or subtract INTERVAL '1' SECOND. For example,
SQL> alter session set nls_date_format='DD/MM/YYYY HH24:MI:SS';
Session altered.
SQL> WITH t(created_dt) AS(
2 SELECT to_date('04/08/2015 15:36','DD/MM/YYYY HH24:MI') FROM DUAL UNION ALL
3 SELECT to_date('07/07/2015 15:32','DD/MM/YYYY HH24:MI') FROM DUAL
4 )
5 SELECT CREATED_DT,
6 NEXT_DAY(TRUNC(add_months(created_dt, -1),'MM') -1,TO_CHAR(to_date('6','J'),'Day')) -1 start_date,
7 NEXT_DAY(TRUNC(created_dt, 'MM') -1, TO_CHAR(to_date('5','J'),'Day')) - (INTERVAL '1' SECOND) AS end_date
8 FROM t;
CREATED_DT START_DATE END_DATE
------------------- ------------------- -------------------
04/08/2015 15:36:00 04/07/2015 00:00:00 31/07/2015 23:59:59
07/07/2015 15:32:00 06/06/2015 00:00:00 03/07/2015 23:59:59
SQL>
You can use some of the Date functions. I'm giving for sysdate. Use according to your requirement.
select NEXT_DAY(trunc((trunc (add_months (sysdate, -1), 'mm')), 'MONTH')-1, 'Saturday') as Start_date,
NEXT_DAY(SYSDATE-8, 'FRIDAY') as End_date
from dual;
Output
START_DATE END_DATE
04-JUL-15 21-AUG-15
Use Next_day function. The Oracle/PLSQL NEXT_DAY function returns the first weekday that is greater than a date.
select TO_DATE('04/08/2015 15:36' ,'DD/MM/YYYY hh24:mi') as created_date,
next_day(ADD_MONTHS(TRUNC(TO_DATE('04/08/2015 15:36','DD/MM/YYYY hh24:mi')+1,'MM'),-1),'SATURDAY')
as start_date,
next_day(trunc(TO_DATE('04/08/2015 15:36','DD/MM/YYYY hh24:mi')-8)+0.99999 ,'FRIDAY')as end_date
FROM DUAL
Instead of adding 0.99999 we can also achieve same thing with 1-(1/(24*60*60)) we are adding one day after that subtracting 1 part from 24*60*60 seconds.
I have achieved by this way
end date: Where created _dt is date value what i am passing..!!
next_day(TRUNC(to_date(created_dt,'DD-MM-YYYY HH24:MI:SS'))-7,'FRIDAY') +
INTERVAL '23:59:59' HOUR TO SECOND AS range_end_dt

Converting Oracle Date and Time

How do I convert the following timestamp in Oracle :
12-MAY-2013 12:00:00 AM
to this
11-MAY-2013 24:00:00
Thanks.
UPDATED A possible solution
SELECT CASE WHEN dt - TRUNC(dt) = 0
THEN TO_CHAR(TRUNC(dt) - 1, 'DD-Mon-YYYY') || ' 24:00:00'
ELSE TO_CHAR(dt, 'DD-Mon-YYYY HH24:MI:SS')
END dt
FROM
(
SELECT TO_DATE('12-MAY-2013 12:00:00 AM', 'DD-Mon-YYYY HH:MI:SS AM') dt
FROM dual
)
Output:
| DT |
------------------------
| 11-May-2013 24:00:00 |
Here is SQLFiddle demo
Original answer Try
SELECT TO_CHAR(TO_DATE('12-MAY-2013 12:00:00 AM', 'DD-Mon-YYYY HH:MI:SS AM'), 'DD-Mon-YYYY HH24:MI:SS') dt
FROM dual
Here is SQLFiddle demo
you cannot achieve this using standard Oracle functions. Oracle uses values 0 - 23, if you need to display midnight like 24:00, you have to write your own function.

Displaying values between times

I am trying to display the times that are between 03:00PM and 12:00AM within the same day. What can I do to get the desired results? I don't think my code should display anything with AM, but it does.
select TO_CHAR(orig_time, 'HH:MI AM') from orig_table
WHERE TO_CHAR(orig_time, 'HH:MI AM') > '03:00 PM'
AND TO_CHAR(orig_time, 'HH:MI AM') < '12:00 AM'
ORDER BY TO_CHAR(orig_time, 'AM'), TO_CHAR(orig_time, 'HH:MI');
TO_CHAR(
--------
05:00 AM
05:15 AM
06:46 AM
07:00 AM
08:00 AM
08:30 AM
08:33 AM
09:00 AM
09:05 AM
10:00 AM
10:10 AM
TO_CHAR(
--------
11:25 AM
11:30 AM
11:45 AM
05:00 PM
05:45 PM
05:58 PM
08:30 PM
09:10 PM
10:25 PM
11:20 PM
Your problem is that you are converting the values to a character type, which means comparisons are done alphabetically. That way, '05:00 AM' is greater than '03:00 PM'.
Several ways to fix this. One is converting to character, but using 24h based time:
select TO_CHAR(orig_time, 'HH:MI AM') from orig_table
WHERE TO_CHAR(orig_time, 'HH24:MI') > '15:00'
ORDER BY TO_CHAR(orig_time, 'HH24:MI');
Notice that you do not need to check for before midnight doing this way.
Additionally, you want to extend it to, say, 1AM in the next day, you'd have to start using whole dates portion. Also, use the BETWEEN keyword in that case.
If you mean midnight
select numtodsinterval(orig_time-trunc(orig_time)) as time
from orig_table
WHERE numtodsinterval(orig_time-trunc(orig_time)) > interval '15' hour
/
where there's no need to check for hour < 24
If you mean midday
select numtodsinterval(orig_time-trunc(orig_time),'day') as time
from orig_table
WHERE numtodsinterval(orig_time-trunc(orig_time),'day') between interval '12' hour and interval '15' hour
/
select TO_CHAR(orig_time, 'HH:MI AM') from orig_table
WHERE TO_CHAR(orig_time, 'HH24:MI') > '15:00'
AND TO_CHAR(orig_time, 'HH24:MI:SS') <= '23:59:59'
ORDER BY TO_CHAR(orig_time, 'AM'), TO_CHAR(orig_time, 'HH:MI');