Как в Oracle определить дату начала/окончания летнего времени?

StackOverflow https://stackoverflow.com/questions/287563

  •  08-07-2019
  •  | 
  •  

Вопрос

Есть ли в 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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top