Как в Oracle определить дату начала/окончания летнего времени?
Вопрос
Есть ли в Oracle способ выбрать дату перехода на летнее время для моего региона?
Было бы неплохо что-то смутно эквивалентное этому:
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
Редактировать:Я надеялся на решение, которое не потребовало бы изменений, когда Конгресс скорректирует законы о летнем времени, как это было в 2007 году.Однако опубликованные решения будут работать.
Решение
Мы используем следующие две функции для вычисления даты начала и окончания для любого данного года (после 2007 года, США).
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;
Вероятно, есть более простой способ сделать это, но они сработали для нас. Конечно, этот запрос не знает, наблюдается ли переход на летнее время для того места, где вы находитесь. Для этого вам понадобятся данные о местоположении .
Другие советы
Чтобы улучшить ответ Ли Риффеля, это намного проще с той же логикой:
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;
Вместо того, чтобы зацикливаться, чтобы получить следующее воскресенье, вы также можете использовать функцию оракула next_day (date, 'SUN').
В Соединенных Штатах летнее время определяется как начинающееся во второе воскресенье марта и заканчивающееся в первое воскресенье ноября для районов, где наблюдается летнее время, в течение нескольких лет после 2007 года.
Я не думаю, что есть простой способ получить эту информацию из Oracle, но на основе стандартного определения вы сможете написать хранимую процедуру, которая вычисляет дату начала и окончания, используя Алгоритм Судного дня .
Вот способ использовать внутренние знания Oracle о том, соблюдается ли часовой пояс летнего времени или нет, чтобы определить его начало и конец.Помимо сложности и общей странности, необходимо знать, что два часовых пояса имеют одинаковое время, когда летнее время не действует, и разное время, когда оно действует.По существу, он устойчив к изменениям Конгресса в переходе на летнее время (при условии, что ваша база данных обновлена с помощью исправлений), но не устойчив к региональным изменениям, влияющим на отключенные часовые пояса.С этими предупреждениями вот что у меня есть.
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;
Я говорил тебе, что это странно.Код определяет только день изменения, но его можно улучшить, чтобы показывать час.В настоящее время он возвращается 9 марта 2008 года и 2 ноября 2008 года.Он также чувствителен к времени года, в которое он запускается, поэтому мне пришлось использовать -365...+365.В общем, я не рекомендую это решение, но было интересно исследовать.Возможно, у кого-то есть что-то лучше.
Вот моя версия выше. Его преимущество заключается в том, что ему не требуется второй «часовой пояс для изменения набора сеансов», и его проще использовать в приложении. Вы создаете сохраненную функцию, а затем просто используете: ALTER SESSION SET time_zone = 'Азия / Иерусалим'; выберите GetDSTDates (2012,1) DSTStart, GetDSTDates (2012,2) DSTEnd, SessionTimeZone TZ из двойного;
, которая возвращает дату начала dst, дату окончания dst, часовой пояс для указанного года.
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;