cannot get year-month interval - oracle

I am learning PL-SQL and the exercise is to find the year-month interval difference between two dates. I wrote the following code:
DECLARE
t1 TIMESTAMP (2) WITH TIME ZONE := to_timestamp_tz('2019-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
t3 TIMESTAMP WITH TIME ZONE := to_timestamp_tz('2020-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
ym INTERVAL YEAR(2) to MONTH;
BEGIN
-- ym := '10-2';
ym := t3-t1;
DBMS_OUTPUT.PUT_LINE(ym);
END;
I would expect the ym variable to give '01-0' (1 year difference), but I get an error:
Error report -
ORA-06550: line 9, column 9:
PLS-00382: expression is of wrong type
ORA-06550: line 9, column 3:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I am really confused why this is happening, I tried changing the precision of the YEAR(_), but that doesn't help.
If ym is of datatype INTERVAL DAY(2) to SECOND(2), I get correct result. If I replace ym to ym := '10-2'; it also works. But with ym INTERVAL YEAR(2) to MONTH it is not working :(

I found in the psoug.org examples that you could use this syntax:
ym := (t3-t1) year to month;
dbfiddle demo

t3-t1 does not work as the resulting value is of the INTERVAL DAY TO SECOND data type and there is no implicit cast to INTERVAL YEAR TO MONTH.
Instead, you can use NUMTOYMINTERVAL( MONTHS_BETWEEN( t3, t1 ), 'MONTH' ); to dynamically create the correct data type:
DECLARE
t1 TIMESTAMP (2) WITH TIME ZONE := to_timestamp_tz('2019-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
t3 TIMESTAMP WITH TIME ZONE := to_timestamp_tz('2020-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
ym INTERVAL YEAR(2) to MONTH;
BEGIN
ym := NUMTOYMINTERVAL( MONTHS_BETWEEN( t3, t1 ), 'MONTH' );
DBMS_OUTPUT.PUT_LINE(ym);
END;
/
outputs:
+01-00
db<>fiddle here

Related

ORA-01873: the leading precision of the interval is too small with EXTRACT function

I faced strange issue with EXTRACT function when trying to get the seconds interval of 8-12 hours.
This is tested with Oracle 12.2
Example:
https://rextester.com/ZMR79428
declare
start_time_ TIMESTAMP;
exect_time_ NUMBER;
begin
start_time_ := SYSDATE- 1/3; -- 8 hours, error is for SYSDATE- 1/2
exect_time_ := extract(day from ((sysdate- start_time_)*86400));
dbms_output.put_line(exect_time_);
end;
I can't see what wrong I've done to get the leading precision of the interval is too small error in this code. It worked for all other scenarios.
Eg: SYSDATE -1/4 works fine.
If you subtract two DATE data type values you get a NUMBER representing the number of (fractional) days between the two values. If you subtract two TIMESTAMP data type values you get an INTERVAL data type.
So your answer could simply be:
declare
start_time_ DATE := SYSDATE- 1/3;
exect_time_ NUMBER;
begin
exect_time_ := ( sysdate - start_time_ ) *86400;
dbms_output.put_line( exect_time_ );
end;
/
Which outputs: 28800
Your problem is that you are multiplying by 86400 inside the EXTRACT function rather than outside; so SYSDATE - start_time_ gives INTERVAL '8' HOUR and then you are multiplying by 86400 and INTERVAL '8' HOUR * 86400 would give a value of INTERVAL '28800' DAY which does not fit into the default precision of an INTERVAL DAY TO SECOND data type (and would give you the wrong answer anyway).
What you would want (if you really want to use TIMESTAMPs) is:
declare
start_time_ TIMESTAMP := SYSTIMESTAMP - 1/3;
difference INTERVAL DAY TO SECOND := SYSTIMESTAMP - start_time_;
exect_time_ NUMBER;
begin
exect_time_ := EXTRACT( DAY FROM difference ) * 24 * 60 * 60
+ EXTRACT( HOUR FROM difference ) * 60 * 60
+ EXTRACT( MINUTE FROM difference ) * 60
+ EXTRACT( SECOND FROM difference );
dbms_output.put_line( exect_time_ );
end;
/
Which outputs something like 28800.246382 (as there is a fraction of a second between the two SYSTIMESTAMP calls).
or, if you do not care about fractional seconds then:
declare
start_time_ TIMESTAMP := SYSTIMESTAMP- 1/3;
exect_time_ NUMBER;
begin
exect_time_ := ( SYSDATE - CAST( start_time_ AS DATE ) ) * 86400;
dbms_output.put_line( exect_time_ );
end;
/
Which outputs 28800.
db<>fiddle
what I don't get is why it gives error
It is a strange error; the code below tests various cases:
DECLARE
TYPE test_case IS RECORD(
units VARCHAR2(20),
difference INTERVAL DAY TO SECOND,
multiplier NUMBER(8,0)
);
TYPE test_case_list IS TABLE OF test_case;
FUNCTION createTestCase(
units VARCHAR2,
difference INTERVAL DAY TO SECOND,
multiplier NUMBER
) RETURN test_case;
test_cases test_case_list := test_case_list(
createTestCase( 'SECOND', INTERVAL '1' SECOND, 24 * 60 * 60 ),
createTestCase( 'SECOND PLUS A LITTLE', INTERVAL '1' SECOND + INTERVAL '0.001' SECOND, 24 * 60 * 60 ),
createTestCase( 'MINUTE', INTERVAL '1' MINUTE, 24 * 60 ),
createTestCase( 'MINUTE PLUS A LITTLE', INTERVAL '1' MINUTE + INTERVAL '0.001' SECOND, 24 * 60 ),
createTestCase( 'HOUR', INTERVAL '1' HOUR, 24 ),
createTestCase( 'HOUR PLUS A LITTLE', INTERVAL '1' HOUR + INTERVAL '0.001' SECOND, 24 ),
createTestCase( 'DAY', INTERVAL '1' DAY, 1 ),
createTestCase( 'DAY PLUS A LITTLE', INTERVAL '1' DAY + INTERVAL '0.001' SECOND, 1 )
);
FUNCTION createTestCase(
units VARCHAR2,
difference INTERVAL DAY TO SECOND,
multiplier NUMBER
) RETURN test_case
IS
tc test_case;
BEGIN
tc.units := units;
tc.difference := difference;
tc.multiplier := multiplier;
RETURN tc;
END;
BEGIN
FOR i IN 1 .. test_cases.COUNT LOOP
BEGIN
DBMS_OUTPUT.PUT_LINE( test_cases(i).units );
DBMS_OUTPUT.PUT_LINE( test_cases(i).difference * test_cases(i).multiplier * 100000 );
DBMS_OUTPUT.PUT_LINE( (SYSTIMESTAMP + test_cases(i).difference - SYSTIMESTAMP) * test_cases(i).multiplier * 10000 );
DBMS_OUTPUT.PUT_LINE( (SYSTIMESTAMP + test_cases(i).difference - SYSTIMESTAMP) * test_cases(i).multiplier * 100000 );
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( SQLERRM );
END;
END LOOP;
END;
/
and outputs:
SECOND
+000100000 00:00:00.000000000
+000009999 21:21:36.000000000
ORA-01873: the leading precision of the interval is too small
SECOND PLUS A LITTLE
+000100100 00:00:00.000000000
+000010009 22:33:36.000000000
+000100099 19:12:00.000000000
MINUTE
+000100000 00:00:00.000000000
+000009999 23:59:31.200000000
ORA-01873: the leading precision of the interval is too small
MINUTE PLUS A LITTLE
+000100001 16:00:00.000000000
+000010000 03:59:02.400000000
+000100001 15:55:12.000000000
HOUR
+000100000 00:00:00.000000000
+000009999 23:59:59.280000000
ORA-01873: the leading precision of the interval is too small
HOUR PLUS A LITTLE
+000100000 00:40:00.000000000
+000010000 00:03:59.040000000
+000100000 00:39:55.200000000
DAY
+000100000 00:00:00.000000000
+000009999 23:59:59.970000000
ORA-01873: the leading precision of the interval is too small
DAY PLUS A LITTLE
+000100000 00:01:40.000000000
+000010000 00:00:09.960000000
+000100000 00:01:39.800000000
db<>fiddle here
Using INTERVAL directly in each test case works.
When you force the PL/SQL engine to work out SYSTIMESTAMP + an_interval - SYSTIMESTAMP then it must call the SYSTIMESTAMP function twice (which means that there is a few fractions of a second difference between the values) and the test cases fail.
When you add a tiny amount of time to the interval then the test cases all pass again suggesting that an Interval with a different precision has resulted from the calculation. (This is not intended to be a hacky solution, just an interesting side-note).
db<>fiddle shows that it only occurs in the PL/SQL scope; if you run the same statements in SQL statements then there are no exceptions.
There is probably a bug but it would require diving into the precision of the data-types returned when dynamically generating intervals from SYSTIMESTAMP to work out how exactly it is occurring; and, beyond being able to make a bug report to Oracle (which they might fix in a later version), it isn't going make your solution any more viable.
However, that's tangential to the solution; don't multiply your INTERVAL by 86400; you should be using EXTRACT multiple times with DAY, HOUR, MINUTE and SECOND respective arguments and converting the returned values of each to seconds and adding them or, alternatively, using CAST to convert back to use DATE arithmetic.
I think the issue is because of the limitation of the too-large numbers in intervals.
Actually, the Issue is with the multiplication.
If you execute it without or with a small multiplier than you will get an answer.
SQL> select extract(day from ((systimestamp - (systimestamp - 1/3)))) from dual ;
EXTRACT(DAYFROM((SYSTIMESTAMP-(SYSTIMESTAMP-1/3))))
---------------------------------------------------
0
SQL> select extract(day from ((systimestamp - (systimestamp - 1/3))*8640)) from dual ;
EXTRACT(DAYFROM((SYSTIMESTAMP-(SYSTIMESTAMP-1/3))*8640))
--------------------------------------------------------
2880
SQL> select extract(day from ((systimestamp - (systimestamp - 1/3))*86400)) from dual ;
select extract(day from ((systimestamp - (systimestamp - 1/3))*86400)) from dual
*
ERROR at line 1:
ORA-01873: the leading precision of the interval is too small
SQL>
Whatever you are doing with this number, you need to think of different logic as multiplication on the timestamp column is not recommended.
Cheers!!

Oracle Datetime difference issue with AM/ PM

I have the following stored procedure which calculates the time taken for a merge statement
create or replace procedure ModAuditData(
O_UpdatedCount out int
,O_EndTime out timestamp
,O_Duration out int)
as
P_StartTime timestamp(3) WITH LOCAL TIME ZONE;
-- EndTime timestamp;
begin
P_StartTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS');
-- merge Statment that does UPSERT
O_UpdatedCount :=SQL%ROWCOUNT;
commit;
O_EndTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS');
begin
select extract( second from (O_EndTime-P_StartTime) )
into O_Duration
from dual;
Exception When others then
O_Duration:=0;
end;
end ModAuditData;
The Issue is
O_EndTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS')
gives exact opposite of
P_StartTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS');
in terms of AM/PM
What is the correct way to calculate the start and end time
CURRENT_TIMESTAMP is already a TIMESTAMP WITH TIME ZONE, there is no reason to convert it first to VARCHAR2 and then back again into a TIMESTAMP.
Also timestamp(3) (which provides precision up to millisecond) does not make much sense when you return duration as INTEGER, i.e. full seconds.
Try it like this:
P_StartTime timestamp(3) WITH TIME ZONE;
begin
P_StartTime := current_timestamp;
-- merge Statment that does UPSERT
O_UpdatedCount :=SQL%ROWCOUNT;
commit;
O_Duration := EXTRACT(SECOND FROM (current_timestamp - P_StartTime));
end;
In case of SQL*Plus consider to use TIMING command.

PL/SQL Stored Procedure in Oracle Application Express not running

I created the stored procedure which compiled with no errors. But when I run exec sp_DATE_D(2005, 2006); I receive the following error:
ERROR at line 1:
ORA-01858: a non-numeric character was found where a numeric was expected
ORA-06512: at "SP_DATE_D", line 14
ORA-06512: at line 1
I expect my sample data to look like this as an example of one row:
DATE_KEY: 01/01/2005
FULL_DATE_DESCRIPTION: Monday, January 1, 2005
The procedure should populate from this point up until the last day of the 2nd parameter.
Stored Procedure:
CREATE OR REPLACE PROCEDURE sp_DATE_DD(v_START_YEAR IN INT, v_END_YEAR IN INT) AS
v_CURRENT_DATE DATE;
v_END_DATE DATE;
BEGIN
v_CURRENT_DATE := TO_DATE('0101' || v_START_YEAR, 'MMDDYYYY');
v_END_DATE := TO_DATE('1231' || v_END_YEAR, 'MMDDYYYY');
DELETE FROM DATE_D;
WHILE v_CURRENT_DATE <= v_END_DATE
LOOP
INSERT INTO DATE_D
(
DATE_KEY,
FULL_DATE_DESCRIPTION
)
VALUES
(
TO_DATE(v_CURRENT_DATE, 'MMDDYYYY'),
TO_CHAR(v_CURRENT_DATE, 'Day, Month DD, YYYY')
);
v_CURRENT_DATE := v_CURRENT_DATE + 1;
END LOOP;
END;
/
Table Definition:
CREATE TABLE DATE_D
(
DATE_KEY DATE NOT NULL,
FULL_DATE_DESCRIPTION VARCHAR2(64) NOT NULL,
CONSTRAINT DATE_DIMENSION_PK PRIMARY KEY (DATE_KEY)
);
To add:
Can't figure how out if its a format issue or I should convert.
Solution:
Modified Line 22 -
v_CURRENT_DATE,
The problem appears to be this line:
TO_DATE(v_CURRENT_DATE, 'MMDDYYYY'),
v_CURRENT_DATE is already a DATE variable. To pass it to the TO_DATE function Oracle has to convert it to a string, and the default string format for dates is DD-MON-RR. So if v_CURRENT_DATE already contains 1-JAN-2005, this is converted to the string '01-JAN-05', which is then passed to TO_DATE. But the format argument being passed to TO_DATE is 'MMDDYYYY', which doesn't match the string it's being handed ('01-JAN-05'), and so the TO_DATE function fails.
The solution is fairly simple: since v_CURRENT_DATE is already a DATE variable there's no need to call TO_DATE at all. Change the INSERT statement to:
INSERT INTO DATE_D
(
DATE_KEY,
FULL_DATE_DESCRIPTION
)
VALUES
(
v_CURRENT_DATE,
TO_CHAR(v_CURRENT_DATE, 'Day, Month DD, YYYY')
);

Casting Varchar2 to interval

I have a configuration table such that I want to delete a row if the record_tm is over a certain configurable time (config_recordtrim_hrs). In other words, the variable recordtrim_config comes from another table where somebody writes a value (e.g. 24 for 24 hours) after which a record will be deleted.
DECLARE
recordtrim_flag configs_table.value%type;
recordtrim_config configs_table.value%type;
recordtrim_interval interval;
BEGIN
SELECT configs_table.value INTO recordtrim_flag FROM configs_table WHERE configs_table.name = 'enable_recordtrim';
SELECT configs_table.value INTO recordtrim_config FROM configs_table WHERE configs_table.name = 'config_recordtrim_hrs';
SELECT CAST (recordtrim_config AS interval) INTO recordtrim_interval;
IF (recordtrim_flag = 'T') THEN
DELETE FROM main_records WHERE record_tm + recordtrim_interval < current_timestamp;
END IF;
END;
This code throws an error at the line where I try to cast recordtrim_config as an interval. How do I fix this?
EDIT: My error:
Error report: ORA-06550: line 8, column 40: PL/SQL: ORA-30089: missing
or invalid ORA-06550: line 8, column 1: PL/SQL: SQL
Statement ignored
EDIT 2: New Code:
DECLARE
recordtrim_flag configs_table.value%type;
recordtrim_config configs_table.value%type;
recordtrim_interval interval;
BEGIN
SELECT configs_table.value INTO recordtrim_flag FROM configs_table WHERE configs_table.name = 'enable_recordtrim';
SELECT configs_table.value INTO recordtrim_config FROM configs_table WHERE configs_table.name = 'config_recordtrim_hrs';
recordtrim_interval := NUMTODSINTERVAL (recordtrim_config, 'HOUR');
IF (recordtrim_flag = 'T') THEN
DELETE FROM main_records WHERE record_tm + recordtrim_interval < current_timestamp;
END IF;
END;
New Error at line 11, recordtrim_interval is an invalid identifier
The immediate cause of the error is that you've got just interval instead of the type, i.e. interval day to second. You also have to select from something - which can be from dual here, though a straight assignment is cleaner.
You can't cast it directly if it's a single number. Instead you can use the numtodsinterval function:
recordtrim_interval := NUMTODSINTERVAL (recordtrim_config, 'HOUR');
e.g.
set serveroutput on
declare
recordtrim_config varchar2(4) := '24';
recordtrim_interval interval day to second;
begin
recordtrim_interval := NUMTODSINTERVAL (recordtrim_config, 'HOUR');
dbms_output.put_line(recordtrim_interval);
end;
/
anonymous block completed
+01 00:00:00.000000
Or select that straight into the variable from the table.
If you need more complicated values, not a simple value that represents a number of hours - and if that was the case you'd store it as a number I assume - then if you store it in a suitable format, you can cast it, or use the to_dsinterval function instead; any of these give the same result:
declare
recordtrim_config varchar2(10) := '0 2:30:00';
recordtrim_interval interval day to second;
begin
recordtrim_interval := TO_DSINTERVAL (recordtrim_config);
dbms_output.put_line(recordtrim_interval);
SELECT CAST (recordtrim_config AS interval day to second)
INTO recordtrim_interval from dual;
dbms_output.put_line(recordtrim_interval);
recordtrim_interval := CAST (recordtrim_config AS interval day to second);
dbms_output.put_line(recordtrim_interval);
end;
/
anonymous block completed
+00 02:30:00.000000
+00 02:30:00.000000
+00 02:30:00.000000
In your edited question, the ORA-00904: "RECORDTRIM_INTERVAL": invalid identifier is only the last error in the list. Look back a bit and you'll see:
ORA-06550: line 4, column 21:
PLS-00201: identifier 'INTERVAL' must be declared
... followed by some other errors where you try to use the variable. Your declaration of the variable has to be:
recordtrim_interval interval day to second;
There is no 'interval' data type; they are interval year to month and interval day to second.

In Oracle, how can I detect the date on which daylight savings time begins / ends?

Is there a way in Oracle to select the date on which daylight savings will switch over for my locale?
Something vaguely equivalent to this would be nice:
SELECT CHANGEOVER_DATE
FROM SOME_SYSTEM_TABLE
WHERE DATE_TYPE = 'DAYLIGHT_SAVINGS_CHANGEOVER'
AND TO_CHAR(CHANGEOVER_DATE,'YYYY') = TO_CHAR(SYSDATE,'YYYY'); -- in the current year
Edit: I was hoping for a solution that would not require changes when Congress adjusts DST laws, as they did in 2007. The posted solutions will work, though.
To improve on Leigh Riffel's answer, this is much simpler with the same logic:
Function DaylightSavingTimeStart (p_Date IN Date)
Return Date Is
Begin
Return NEXT_DAY(TO_DATE(to_char(p_Date,'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') + 7;
End;
Function DaylightSavingTimeEnd (p_Date IN Date)
Return Date Is
Begin
Return NEXT_DAY(TO_DATE(to_char(p_Date,'YYYY') || '/11/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN');
End;
We use the following two functions to calculate the start and end dates for any given year (post 2007, US).
Function DaylightSavingTimeStart (p_Date IN Date)
Return Date Is
v_Date Date;
v_LoopIndex Integer;
Begin
--Set the date to the 8th day of March which will effectively skip the first Sunday.
v_Date := to_date('03/08/' || to_char(p_Date,'YYYY') || '02:00:00 AM','MM/DD/YYYY HH:MI:SS PM');
--Advance to the second Sunday.
FOR v_LoopIndex IN 0..6 LOOP
If (RTRIM(to_char(v_Date + v_LoopIndex,'DAY')) = 'SUNDAY') Then
Return v_Date + v_LoopIndex;
End If;
END LOOP;
End;
Function DaylightSavingTimeEnd (p_Date IN Date)
Return Date Is
v_Date Date;
v_LoopIndex Integer;
Begin
--Set Date to the first of November this year
v_Date := to_date('11/01/' || to_char(p_Date,'YYYY') || '02:00:00 AM','MM/DD/YYYY HH:MI:SS PM');
--Advance to the first Sunday
FOR v_LoopIndex IN 0..6 LOOP
If (RTRIM(to_char(v_Date + v_LoopIndex,'DAY')) = 'SUNDAY') Then
Return v_Date + v_LoopIndex;
End If;
END LOOP;
End;
There is probably a simpler way to do it, but these have worked for us. Of course this query doesn't know whether daylight saving time is observed for where you are. For that you will need location data.
Instead of looping to get the next sunday you can also use the next_day(date, 'SUN') function of oracle.
In the United States, Daylight Savings Time is defined as beginning on the second Sunday in March, and ending on the first Sunday in November, for the areas that observe DST, for years after 2007.
I don't think there's an easy way to get this information from Oracle, but based on the standard definition, you should be able to write a stored procedure that calculates the beginning and ending date using the Doomsday Algorithm.
Here is a way to use Oracles internal knowledge of whether a timezone observes daylight saving time or not to determine the start and end of it. Aside from the complexity and general strangeness of it, it requires two timezones to be know have identical times when daylight saving time is not in effect and different times when it is. As such it is resilient to congressional changes in when daylight saving time occurs (assuming your database is up to date with the patches), but is not resilient to regional changes effecting the timezones keyed off of. With those warnings, here is what I have.
ALTER SESSION SET time_zone='America/Phoenix';
DROP TABLE TimeDifferences;
CREATE TABLE TimeDifferences(LocalTimeZone TIMESTAMP(0) WITH LOCAL TIME ZONE);
INSERT INTO TimeDifferences
(
SELECT to_date('01/01/' || to_char(sysdate-365,'YYYY') || '12:00:00','MM/DD/YYYYHH24:MI:SS')+rownum-1
FROM dual CONNECT BY rownum<=365
);
COMMIT;
ALTER SESSION SET time_zone='America/Edmonton';
SELECT LocalTimeZone-1 DaylightSavingTimeStartAndEnd
FROM
(
SELECT LocalTimeZone,
to_char(LocalTimeZone,'HH24') Hour1,
LEAD(to_char(LocalTimeZone,'HH24')) OVER (ORDER BY LocalTimeZone) Hour2
FROM TimeDifferences
)
WHERE Hour1 <> Hour2;
I told you it was strange. The code only figures out the day of the change, but could be enhanced to show the hour. Currently it returns 09-MAR-08 and 02-NOV-08. It is also sensitive to the time of year it is run, which is why I had to do the -365...+365. All in all I don't recommend this solution, but it was fun to investigate. Maybe someone else has something better.
Here's my version of the above. It's advantage is that it does not need a second 'alter session set time zone', and can be used more easily from an application.
You create the stored function, and then you simply use:
ALTER SESSION SET time_zone='Asia/Jerusalem';
select GetDSTDates(2012,1) DSTStart,GetDSTDates(2012,2) DSTEnd,SessionTimeZone TZ from dual;
which will return the dst start date,dst end date, timezone for the specified year.
create or replace function GetDSTDates
(
year integer,
GetFrom integer
)
return Date
as
cursor c is
select 12-to_number(to_char(LocalTimeZone at time zone '+00:00','HH24')) offset,
min(to_char(LocalTimeZone at time zone '+00:00','DD/MM/YYYY')) fromdate,
max(to_char(LocalTimeZone at time zone '+00:00','DD/MM/YYYY')) todate
from (
SELECT cast((to_date('01/01/'||to_char(year)||'12:00:00','MM/DD/YYYYHH24:MI:SS')+rownum-1) as timestamp with local time zone) LocalTimeZone
FROM dual CONNECT BY rownum<=365
)
group by 12-to_number(to_char(LocalTimeZone at time zone '+00:00','HH24'));
dstoffset integer;
offset integer;
dstfrom date;
dstto date;
begin
offset := 999;
dstoffset := -999;
for rec in c
loop
if rec.offset<offset
then
offset := rec.offset;
end if;
if rec.offset>dstoffset
then
dstoffset := rec.offset;
dstfrom := to_date(rec.fromdate,'DD/MM/YYYY');
dstto :=to_date(rec.todate,'DD/MM/YYYY');
end if;
end loop;
if (offset<999 and dstoffset>-999 and offset<>dstoffset)
then
if GetFrom=1
then
return dstfrom;
else
return dstto;
end if;
else
return null;
end if;
end;
/
ALTER SESSION SET time_zone='Asia/Jerusalem';
select GetDSTDates(2012,1) DSTStart,
GetDSTDates(2012,2) DSTEnd,
SessionTimeZone TZ from dual;
Old question but here's a new answer. Use 08-MAR for the first date since that skips the first week
--Start of DST
select next_day(to_date('08-MAR-' || to_char(sysdate, 'YYYY')), 'SUN') from dual
--End of DST
select next_day(to_date('01-NOV-' || to_char(sysdate, 'YYYY')), 'SUN') from dual

Resources