. , Oracle tz_offset . , ,
SELECT TZ_OFFSET('US/Eastern') FROM DUAL;
'-04: 00.' "-05: 00". , , ( ), .
,
SELECT TZ_OFFSET('EST') FROM DUAL;
'-05: 00.' , (, ) . , . 16 , DST .
SELECT TZ_OFFSET('US/Eastern'), TZ_OFFSET('EST'), TZ_OFFSET('EST5EDT') FROM DUAL;
SELECT TZ_OFFSET('US/Central'), TZ_OFFSET('CST'), TZ_OFFSET('CST6CDT') FROM DUAL;
SELECT TZ_OFFSET('US/Mountain'), TZ_OFFSET('MST'), TZ_OFFSET('MST7MDT') FROM DUAL;
SELECT TZ_OFFSET('US/Pacific'), TZ_OFFSET('PST'), TZ_OFFSET('PST8PDT') FROM DUAL;
-04:00 -05:00 -04:00
-05:00 -05:00 -05:00
-06:00 -07:00 -06:00
-07:00 -07:00 -07:00
. tz_offset, , . , , , .
Now I can write code to determine if I am in standard or prevailing time. However, I do not see the ability to consistently pull tz_offsetfor standard or daytime, just prevailing.
This leaves the developer with the need to create his own table, as Bob Jarvis did, in order to crack the problem, as it was in the code below.
ALTER SESSION SET NLS_DATE_FORMAT = 'MM/DD/YYYY HH24:MI:SS';
Declare
-- ******** User declarations begin here ******** --
-- ******** User declarations end here ******** --
-- TimeZone Conversion Procedure
-- User Input (Parameters)
input_date date := TO_TIMESTAMP_TZ('7/1/2009 18:00','mm/dd/yyyy hh24:mi'); -- Try: LocalTimestamp; or TO_TIMESTAMP('2/1/2009 13:00','mm/dd/yyyy hh24:mi')
--input_date date := LocalTimestamp;
input_TZ varchar(3) := 'GMT'; -- Exmaples: EST, EDT or EPT for Eastern Standard, Daylight or Prevailing, respectively.
output_TZ varchar(3) := 'EPT'; --
-- Variables
type date_array is table of date;
return_date date := localtimestamp;
temp_date date := to_date('10/27/1974 02:00','mm/dd/yyyy hh24:mi');
type str_array is table of varchar2(10);
dow_list str_array;
Function dst_start_stop (input_date DATE)
RETURN date_array
AS
year_part number(4);
start_week number(1) := 0; -- week of month: -1=last, 1=1st, 2=nd, 0=fixed date (like 1974 and 1975)
stop_week number(1) := 0; -- week of month: -1=last, 1=1st, 2=nd, 0=fixed date (like 1974 and 1975)
dst_start date := to_date('01/06/1974 23:59','mm/dd/yyyy hh24:mi');
dst_stop date := to_date('10/27/1974 23:59','mm/dd/yyyy hh24:mi');
dst_date date := dst_start;
dst_msg varchar2(500) := ' ';
inc_dec number := 0;
Cnt number(1) := 0;
dst_dow number(1) := 1; -- 1=Sunday, 2=Monday, etc.
i number;
dst_range date_array;
BEGIN
dst_range := date_array();
dst_range.extend(2);
dst_range(1) := temp_date;
dst_range(2) := temp_date;
DBMS_OUTPUT.PUT_LINE(' ** Start: dst_start_stop Func **');
--insert into dst_range values(dst_start,dst_stop);
--dst_range(1) := dst_start;
--dst_range(2) := dst_stop;
year_part := to_number(to_char(input_date,'YYYY'));
DBMS_OUTPUT.PUT_LINE(' Year: '||year_part);
-- Determine DST formula based on year of input_date
If year_part > 9999 Then -- Invalid TempYear > 9999
dst_msg := 'N/A. I can''t guess if DST will be applied after 9999. Standard Time returned. ';
Goto found_start_stop;
ElsIf year_part >= 2007 Then -- 2007 forward. Latest DST Rules used after 2007.
dst_msg := '2007 forward: Third National DST Standard. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM second Sunday in March (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). '
--dst_msg := dst_msg || 'Fall Back 2:00 AM first Sunday in November (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). '
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
dst_start := to_date('03/01/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
start_week := 2; -- 2nd Sunday in March
dst_stop := to_date('11/01/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
stop_week := 1; -- 1st Sunday in November
ElsIf year_part >= 1987 Then -- 1987 thru 2006.
dst_msg := '1987 thru 2006: Second National DST Standard. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM first Sunday in April (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM last Sunday in October (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
start_week := 1;
dst_start := to_date('04/01/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
stop_week := -1;
dst_stop := to_date('10/31/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
ElsIf year_part >= 1976 Then -- 1976 thru 1986 OLD DST Rules used 1961 thru 1973.
dst_msg := '1976 thru 1986: First National DST Standard (resumed after 1974-1975 extended DST trials). ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM last Sunday in April (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM last Sunday in October (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
start_week := -1;
dst_start := to_date('04/30/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
stop_week := -1;
dst_stop := to_date('10/31/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
ElsIf year_part = 1975 Then -- 1975 Trial.
dst_msg := '1975 Trial of Extended DST. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM Feb 23 (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM Oct 26, the last Sun in Oct (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
dst_start := to_date('02/23/1975 02:00','mm/dd/yyyy hh24:mi');
dst_stop := to_date('10/26/1974 02:00','mm/dd/yyyy hh24:mi');
Goto found_start_stop;
ElsIf year_part = 1974 Then -- 1974 Trial.
dst_msg := '1974 Trial of Extended DST. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM Jan 6 (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00)). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM Oct 27 (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
dst_start := to_date('01/06/1974 02:00','mm/dd/yyyy hh24:mi');
dst_stop := to_date('10/27/1974 02:00','mm/dd/yyyy hh24:mi');
Goto found_start_stop;
ElsIf year_part >= 1961 Then -- 1961 thru 1973 First National DST Standard.
dst_msg := '1961 thru 1973: First National DST Standard. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM last Sunday in April (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM last Sunday in October (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
start_week := -1;
dst_start := to_date('04/30/'||input_date||' 02:00','mm/dd/yyyy hh24:mi');
stop_week := -1;
dst_stop := to_date('10/31/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
ElsIf year_part >=1900 Then -- DST was applied inconsistently or not at all
dst_msg := 'N/A. Before 1961, DST was applied inconsistently across states or not at all. Standard Time returned. ';
Goto found_start_stop;
ElsIf year_part < 1900 Then -- Invalid year_part
dst_msg := 'N/A. DST never active before 1900';
Goto found_start_stop;
Else -- Invalid year_part
dst_msg := 'N/A. Error. Invalid datetime value.';
Goto found_start_stop;
End If;
DBMS_OUTPUT.PUT_LINE(' The code specified the following DST rules for the input date ('||input_date||'). '||dst_msg);
if start_week > 0 then
DBMS_OUTPUT.PUT_LINE(' Start on '||dow_list(dst_dow)||' #'||start_week||' of '||trunc(dst_start,'W')||'. ');
else
DBMS_OUTPUT.PUT_LINE(' Starts '||start_week||' from the last '||dow_list(dst_dow)||' of '||trunc(dst_start,'W')||'. ');
end if;
if stop_week > 0 then
DBMS_OUTPUT.PUT_LINE(' End on '||dow_list(dst_dow)||' #'||stop_week||' of '||trunc(dst_stop,'W')||'. ');
else
DBMS_OUTPUT.PUT_LINE(' Ends '||stop_week||' from the last '||dow_list(dst_dow)||' of '||trunc(dst_stop,'W')||'. ');
end if;
DBMS_OUTPUT.PUT_LINE(' ');
-- DstStartDay
inc_dec := start_week/abs(start_week); -- results in +1 or -1
Cnt := 0; i:=0;
while (Cnt < abs(start_week) and i<20) loop
i:=i+1;
if (to_char(dst_start,'D') = dst_dow) then
Cnt := Cnt + 1;
--DBMS_OUTPUT.PUT_LINE(' Found '||dow_list(dst_dow))||' '||Cnt||': '||dst_start)
end if;
if (Cnt < abs(start_week)) then
dst_start := dst_start + inc_dec;
end if;
end loop;
case inc_dec
when 1 then
DBMS_OUTPUT.PUT_LINE(' Spring forward on '||dow_list(dst_dow)||' #'||Cnt||' of the month: '||dst_start);
else
DBMS_OUTPUT.PUT_LINE(' Spring forward on the last'||dow_list(dst_dow)||'of the month: '||dst_start);
end case;
-- DstStopDay
inc_dec := stop_week/abs(stop_week); -- results in +1 or -1
Cnt := 0; i :=0;
while (Cnt < abs(stop_week) and i <20) loop -- to_char(dst_stop,'D') > 1 loop
i:=i+1;
if (to_char(dst_stop,'D') = dst_dow) then
dst_stop := dst_stop + inc_dec;
Cnt := Cnt + 1;
end if;
if (Cnt < abs(stop_week)) then
dst_stop := dst_stop + inc_dec;
end if;
end loop;
case inc_dec
when 1 then
DBMS_OUTPUT.PUT_LINE(' Fall back on '||dow_list(dst_dow)||' #'||Cnt||' of the month: '||dst_stop);
else
DBMS_OUTPUT.PUT_LINE(' Fall back on the last'||dow_list(dst_dow)||'of the month: '||dst_stop);
end case;
<<found_start_stop>>
dst_range(1) := dst_start;
DBMS_OUTPUT.PUT_LINE(' dst_range(1): '||to_char(dst_range(1),'mm/dd/yyyy hh24:mi')||' = '||dst_start);
dst_range(2) := dst_stop;
DBMS_OUTPUT.PUT_LINE(' dst_range(2): '||to_char(dst_range(2),'mm/dd/yyyy hh24:mi')||' = '||dst_stop);
DBMS_OUTPUT.PUT_LINE(' ** Finish: dst_start_stop Func **');
Return dst_range;
END dst_start_stop;
Function is_dst_now
Return boolean
AS
--type date_array is table of date;
dst_range date_array;
curr_time date := LocalTimestamp;
Begin
dst_range := date_array();
dst_range.extend(2);
dst_range := dst_start_stop(curr_time);
If (dst_range(1) <= curr_time and curr_time < dst_range(2)) then
DBMS_OUTPUT.PUT_LINE('DST is active.');
Return True;
Else
DBMS_OUTPUT.PUT_LINE('DST is NOT active.');
Return False;
End If;
End;
FUNCTION dst_offset (prevailing_date DATE, dst_start DATE, dst_stop DATE)
RETURN number
AS
offset_days number :=0;
BEGIN
DBMS_OUTPUT.PUT_LINE(' Starting dst_offset sub-function:');
DBMS_OUTPUT.PUT_LINE(' where (input date, DST start, DST stop) = '||to_char(prevailing_date,'mm/dd/yyyy hh24:mi')
||', '||to_char(dst_start,'mm/dd/yyyy hh24:mi')||', '||to_char(dst_stop,'mm/dd/yyyy hh24:mi'));
If (dst_start <= prevailing_date and prevailing_date < dst_stop) then
offset_days :=1/24;
DBMS_OUTPUT.PUT_LINE(' input date is between dst start and stop');
Else
offset_days :=0;
DBMS_OUTPUT.PUT_LINE(' input date is not between dst start and stop');
End If;
DBMS_OUTPUT.PUT_LINE(' Result: DST Offset days = '||offset_days);
DBMS_OUTPUT.PUT_LINE(' hours = '||(offset_days*24));
Return offset_days;
END dst_offset;
-- Begin --move this down under the function -- ******************
FUNCTION Convert_TZ (input_date DATE, input_tz varchar2, output_tz varchar2)
RETURN date
AS
-- Variables
input_sz varchar(3) := substr(input_TZ,1,1)||'S'||substr(input_TZ,3,1);
input_sz varchar(3) := substr(output_TZ,1,1)||'S'||substr(output_TZ,3,1);
temp_str varchar2(1000);
dst_range date_array;
input_dst_offset number := 0;
input_tz_offset number := 0;
input_date_st date; --standard time
gmt_date date;
output_dst_offset number := 0;
output_tz_offset number := 0;
output_date date;
output_date_pt date; -- prevailing time
tz_offset_str varchar2(30);
--orig_nls_date_format varchar2(30) := NLS_DATE_FORMAT;
--TempYear number(4,0) := to_char(TempDate,'YYYY'); -- or := trunc(PrevailingTime, YYYY);
BEGIN
DBMS_OUTPUT.PUT_LINE('Starting Pl/sql procedure. ');
DBMS_OUTPUT.PUT_LINE('Input Date: '||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '||input_tz||'. ');
-- Find DST start/stop dates
dst_range := date_array();
dst_range.extend(2);
dst_range := dst_start_stop(input_date);
DBMS_OUTPUT.PUT_LINE('DST date range determined. ');
DBMS_OUTPUT.PUT_LINE(' dst_range(1): '||to_char(dst_range(1),'mm/dd/yyyy hh24:mi')||' = '||dst_range(1));
DBMS_OUTPUT.PUT_LINE(' dst_range(2): '||to_char(dst_range(2),'mm/dd/yyyy hh24:mi')||' = '||dst_range(2));
-- Convert Input Date from input time zone to GMT
If upper(input_TZ) in ('GMT','UCT') then
-- If input TZ is GMT, we can skip this conversion!
DBMS_OUTPUT.PUT_LINE(' Input Time is ('||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '
||input_tz||' GMT). No conversion required. ');
input_tz_offset := 0;
input_dst_offset := 0;
gmt_date := input_date;
Else
-- Convert from local prevailing to local standard time
-- Get input_dst_offset
Case upper(substr(input_TZ,2,1))
When 'S' then
-- already in standard time, not conversion needed.
input_dst_offset := 0; -- duplicative
DBMS_OUTPUT.PUT_LINE('Standard time ('||input_tz||') entered; no dst offset.' );
--input_tz_offset := input_tz_offset;
Else
-- run dst_offset function to convert from prevailing or daylight time to standard time.
input_dst_offset := dst_offset(input_date, dst_range(1), dst_range(2));
input_date_st := input_date - input_dst_offset;
DBMS_OUTPUT.PUT_LINE('Daylight Saving Time Effective ('||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '
||input_tz||'); 1 hour offset; input DST offset = '||input_dst_offset);
DBMS_OUTPUT.PUT_LINE(' where (input_date, dst_start, dst_stop) = '
||to_char(input_date,'mm/dd/yyyy hh24:mi')
||' '||input_tz||', '||dst_range(1)||', '||dst_range(2)||', ');
DBMS_OUTPUT.PUT_LINE(' which adjusts '||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '||input_tz
||' daylight to '||input_date_st||' standard time. ');
End Case;
-- Convert from local standard time to GMT
SELECT TZ_OFFSET((SELECT max(tzname) FROM V$TIMEZONE_NAMES where tzabbrev = input_TZ))
INTO tz_offset_str
FROM DUAL;
input_tz_offset := ( substr(tz_offset_str,1,3)/24 + substr(tz_offset_str,5,2)/1440 );
If is_dst_now then
input_tz_offset := input_tz_offset - 1/24;
End if;
DBMS_OUTPUT.PUT_LINE(' input_tz_offset (fractional days): '||input_tz_offset||'. ');
DBMS_OUTPUT.PUT_LINE(' input_tz_offset (hours): '||input_tz_offset*24||'. ');
gmt_date := input_date_st - input_tz_offset;
End If;
-- Convert input date from GMT to requested output time zone
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('Starting output_date analysis. ');
If upper(output_TZ) in ('GMT','UCT') then
-- If desired output TZ is GMT, we can skip this conversion!
output_tz_offset := 0;
output_dst_offset := 0;
output_date := gmt_date;
DBMS_OUTPUT.PUT_LINE(' Requested output format is GMT: ('||to_char(output_date,'mm/dd/yyyy hh24:mi')||' '||output_tz||'). No conversion required. ');
Else
-- Get output_dst_offset
Case upper(substr(output_TZ,2,1))
When 'S' then
output_dst_offset := 0; -- duplicative
DBMS_OUTPUT.PUT_LINE('Standard time ('||output_TZ||') entered; no dst offset.' );
Else
output_dst_offset := dst_offset(gmt_date + output_tz_offset, dst_range(1), dst_range(2));
End Case;
-- Convert from GMT to local standard time
SELECT TZ_OFFSET((SELECT max(tzname) FROM V$TIMEZONE_NAMES where tzabbrev = output_TZ)) INTO tz_offset_str FROM DUAL;
output_tz_offset := ( substr(tz_offset_str,1,3)/24 + substr(tz_offset_str,5,2)/1440 );
DBMS_OUTPUT.PUT_LINE(' output_tz_offset (fractional days): '||output_tz_offset||'. ');
DBMS_OUTPUT.PUT_LINE(' output_tz_offset (hours): '||output_tz_offset*24||'. ');
If is_dst_now then
output_tz_offset := output_tz_offset - 1/24;
DBMS_OUTPUT.PUT_LINE(' tz_offset correction... ');
DBMS_OUTPUT.PUT_LINE(' output_tz_offset (fractional days): '||output_tz_offset||'. ');
DBMS_OUTPUT.PUT_LINE(' output_tz_offset (hours): '||output_tz_offset*24||'. ');
End if;
output_date := gmt_date + output_tz_offset + output_dst_offset;
DBMS_OUTPUT.PUT_LINE(' gmt_date: '||gmt_date);
DBMS_OUTPUT.PUT_LINE(' output_tz_offset: '||output_tz_offset);
DBMS_OUTPUT.PUT_LINE(' output_dst_offset: '||output_dst_offset);
DBMS_OUTPUT.PUT_LINE('Daylight Saving Time ('||output_TZ||') offset = '||output_dst_offset);
DBMS_OUTPUT.PUT_LINE(' where (output_date, dst_start, DST_stop) = '||output_date||', '||dst_range(1)||', '||dst_range(2));
DBMS_OUTPUT.PUT_LINE(' which adjusts '||output_date||' standard to '||output_date_pt||' daylight time. ');
End If;
DBMS_OUTPUT.PUT_LINE('Output Date = '||to_char(output_date,'mm/dd/yyyy hh24:mi')||' '||output_tz||'. ');
Goto AllDone;
<<FoundError>>
<<AllDone>>
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('*** Results ***');
DBMS_OUTPUT.PUT_LINE(' ');
if input_dst_offset <> 0 then
temp_str := 'daylight saving';
else
temp_str := 'standard';
end if;
DBMS_OUTPUT.PUT_LINE(' Input Date: '||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '||input_tz||', which falls in '||temp_str||' time. ');
DBMS_OUTPUT.PUT_LINE(' GMT Date: '||to_char(gmt_date,'mm/dd/yyyy hh24:mi')||' UTC/GMT. ');
DBMS_OUTPUT.PUT_LINE(' Output Date: '||to_char(output_date,'mm/dd/yyyy hh24:mi')||' UTC/GMT. ');
if output_dst_offset <> 0 then
temp_str := 'daylight saving';
else
temp_str := 'standard';
end if;
DBMS_OUTPUT.PUT_LINE(' All Done. Return Value: '||to_char(output_date,'mm/dd/yyyy hh24:mi')||' '||output_tz||'. ');
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('*** End of Results ***');
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('LocalTimestamp EPT: '||LocalTimestamp);
DBMS_OUTPUT.PUT_LINE('LocalTimestamp GMT:'||LOCALTIMESTAMP at time zone '+00:00');
Return output_date;
END;
Begin
dow_list := str_array();
dow_list.extend(7);
dow_list(1) := 'Sun';
dow_list(2) := 'Mon';
dow_list(3) := 'Tue';
dow_list(4) := 'Wed';
dow_list(5) := 'Thu';
dow_list(6) := 'Fri';
dow_list(7) := 'Sat';
return_date := Convert_TZ (input_date, input_tz, output_tz);
DBMS_OUTPUT.PUT_LINE('ta-dah! '||return_date);
-- ******** User coded begins here ******** --
End;