【问题标题】:In Oracle, how can I detect the date on which daylight savings time begins / ends?在 Oracle 中,如何检测夏令时开始/结束的日期?
【发布时间】:2010-09-22 04:49:39
【问题描述】:

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

编辑:当国会调整 DST 法律时,我希望有一个不需要更改的解决方案,就像他们在 2007 年所做的那样。不过,发布的解决方案将起作用。

【问题讨论】:

    标签: oracle dst


    【解决方案1】:

    为了改进 Leigh Riffel 的答案,使用相同的逻辑要简单得多:

    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;
    

    【讨论】:

      【解决方案2】:

      我们使用以下两个函数来计算任何给定年份(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;
      

      可能有一种更简单的方法可以做到这一点,但这些方法对我们有用。当然,此查询不知道您所在的位置是否遵守夏令时。为此,您需要location data

      【讨论】:

        【解决方案3】:

        您也可以使用 oracle 的 next_day(date, 'SUN') 函数,而不是循环获取下一个星期日。

        【讨论】:

          【解决方案4】:

          在美国,夏令时被定义为从 3 月的第二个星期日开始,到 11 月的第一个星期日结束,适用于 2007 年之后的 DST 地区。

          我认为从 Oracle 获取这些信息并不容易,但根据标准定义,您应该能够编写一个使用 Doomsday Algorithm 计算开始和结束日期的存储过程。

          【讨论】:

            【解决方案5】:

            这是一种使用 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;  
            

            我告诉过你这很奇怪。该代码仅计算出更改的日期,但可以增强以显示小时。目前它返回 09-MAR-08 和 02-NOV-08。它对一年中运行的时间也很敏感,这就是为什么我必须使用 -365...+365。总而言之,我不推荐这种解决方案,但调查起来很有趣。也许别人有更好的东西。

            【讨论】:

              【解决方案6】:

              这是我上面的版本。它的优点是它不需要第二个“更改会话设置时区”,并且可以更轻松地从应用程序中使用。 您创建存储的函数,然后您只需使用: ALTER SESSION SET time_zone='亚洲/耶路撒冷'; select GetDSTDates(2012,1) DSTStart,GetDSTDates(2012,2) DSTEnd,SessionTimeZone TZ from dual;

              这将返回指定年份的 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;
              

              【讨论】:

                【解决方案7】:

                老问题,但这里有一个新答案。使用 08-MAR 作为第一个日期,因为它会跳过第一周

                --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
                

                【讨论】:

                  猜你喜欢
                  • 2011-03-10
                  • 2015-11-23
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-10-25
                  • 2014-08-13
                  • 2022-11-16
                  • 2021-09-06
                  • 2015-05-03
                  相关资源
                  最近更新 更多