【问题标题】:Convert historical data in (date )type oracle to unix timestamp considering daylight saving考虑夏令时将 (date) 类型 oracle 中的历史数据转换为 unix 时间戳
【发布时间】:2016-06-21 14:42:26
【问题描述】:

我可以在 unix 时间戳中转换日期。但是我在将数据库中的历史数据转换为正确的 unix 时间戳时遇到了问题。由于数据以日期格式保存,因此这些数据没有可用的时区。有什么方法可以确定任何历史日期是 cdt 还是 cst。

目前我正在使用其中一种方法来转换时间戳中的日期。

create or replace function unix_time_from_date
      (
        in_date   in date
      --  in_src_tz in varchar2 default 'America/Chicago'
      )
    return integer
  as
    ut      integer       := 0;
    tz      varchar2(8)   := '';
    tz_date timestamp with time zone;
    tz_stmt varchar2(255);
    in_src_tz varchar2(255):='America/Chicago';
  begin
 tz_stmt := 'select systimestamp at time zone ''' || in_src_tz || ''' from dual';



    execute immediate tz_stmt into tz_date;
    select
      extract(timezone_abbr from tz_date)
    into tz
    from dual;



    -- Get the Unix timestamp
    select
      (new_time(in_date, tz, 'GMT') - to_date('01-JAN-1970', 'DD-MM-YYYY')) * (86400)
    into ut
    from dual;

    return ut;
end unix_time_from_date;

取自:http://jrfom.com/2015archive/2012/02/10/oracle-and-unix-timestamps-revisited/

由于存储的对象没有时区,它将以 sys 时区作为 cdt,并为 cst 时区中的数据提供 1 小时的差异。

【问题讨论】:

    标签: database datetime unix oracle10g oracle-sqldeveloper


    【解决方案1】:

    您的函数正在提取 today 的时区缩写并将其应用于提供的日期,有效地假设所有日期都在该区域中。它将在一半的时间内给出正确的结果 - 但只对传入的值的一半。(在冬天,它会弄错夏令时;在夏天,它会弄错冬天的时间)。如果您使用区域名称而不是缩写,那么它不会那样做。但是你不能使用new_time(),反正它只识别几个区域,所以你必须使用at time zone

    使用过去六个月的样本日期(跨越 DST 边界;这是在伦敦运行,但也将在芝加哥运行,如果假设是芝加哥,您的函数也可以),您可以看到当前函数为您提供了什么:

    with t (dt) as (
      select add_months(trunc(sysdate), -level)
      from dual
      connect by level <= 6
    )
    select dt dt,
      unix_time_from_date(dt) unix_time_from_date
    from t
    order by dt;
    
    DT                  UNIX_TIME_FROM_DATE
    ------------------- -------------------
    2015-12-21 00:00:00          1450674000
    2016-01-21 00:00:00          1453352400
    2016-02-21 00:00:00          1456030800
    2016-03-21 00:00:00          1458536400
    2016-04-21 00:00:00          1461214800
    2016-05-21 00:00:00          1463806800
    

    您可以告诉 Oracle 日期应该代表哪个时区。如果您将日期转换为时间戳,则它基本上保持不变。如果您将其转换为带有 tome zone 的时间戳,则它将假定服务器的时区。然后,您可以使用at time zone 将其转换为 UTC,并从中减去 1970-01-01 以获得纪元数:

    with t (dt) as (
      select add_months(trunc(sysdate), -level)
      from dual
      connect by level <= 6
    )
    select dt dt,
      cast(dt as timestamp) ts,
      cast(dt as timestamp with time zone) tstz,
      cast(dt as timestamp with time zone) at time zone 'UTC' as utc,
      86400 * (cast(cast(dt as timestamp with time zone) at time zone 'UTC' as date)
        - date '1970-01-01') as epoch
    from t
    order by dt;
    
    DT                  TS                  TSTZ                              UTC                           EPOCH
    ------------------- ------------------- --------------------------------- ----------------------- -----------
    2015-12-21 00:00:00 2015-12-21 00:00:00 2015-12-21 00:00:00 Europe/London 2015-12-21 00:00:00 UTC  1450656000
    2016-01-21 00:00:00 2016-01-21 00:00:00 2016-01-21 00:00:00 Europe/London 2016-01-21 00:00:00 UTC  1453334400
    2016-02-21 00:00:00 2016-02-21 00:00:00 2016-02-21 00:00:00 Europe/London 2016-02-21 00:00:00 UTC  1456012800
    2016-03-21 00:00:00 2016-03-21 00:00:00 2016-03-21 00:00:00 Europe/London 2016-03-21 00:00:00 UTC  1458518400
    2016-04-21 00:00:00 2016-04-21 00:00:00 2016-04-21 00:00:00 Europe/London 2016-04-20 23:00:00 UTC  1461193200
    2016-05-21 00:00:00 2016-05-21 00:00:00 2016-05-21 00:00:00 Europe/London 2016-05-20 23:00:00 UTC  1463785200
    

    作为获得等效 UTC 的替代方法,仍然基于服务器时区,您可以使用sys_extract_utc()

    with t (dt) as (
      select add_months(trunc(sysdate), -level)
      from dual
      connect by level <= 6
    )
    select dt dt,
      cast(dt as timestamp) ts,
      cast(dt as timestamp with time zone) tstz,
      sys_extract_utc(cast(dt as timestamp)) as utc,
      86400 * (cast(sys_extract_utc(cast(dt as timestamp with time zone)) as date)
        - date '1970-01-01') as epoch
    from t
    order by dt;
    

    或者,如果您不想使用服务器时区,而是指定一个,这对这个演示还是有帮助的:

    with t (dt) as (
      select add_months(trunc(sysdate), -level)
      from dual
      connect by level <= 6
    )
    select dt dt,
      cast(dt as timestamp) ts,
      from_tz(cast(dt as timestamp), 'America/Chicago') tstz,
      from_tz(cast(dt as timestamp), 'America/Chicago') at time zone 'UTC' as utc,
      86400 * (cast(from_tz(cast(dt as timestamp), 'America/Chicago') at time zone 'UTC' as date)
        - date '1970-01-01') as epoch
    from t
    order by dt;
    
    DT                  TS                  TSTZ                                UTC                           EPOCH
    ------------------- ------------------- ----------------------------------- ----------------------- -----------
    2015-12-21 00:00:00 2015-12-21 00:00:00 2015-12-21 00:00:00 America/Chicago 2015-12-21 06:00:00 UTC  1450677600
    2016-01-21 00:00:00 2016-01-21 00:00:00 2016-01-21 00:00:00 America/Chicago 2016-01-21 06:00:00 UTC  1453356000
    2016-02-21 00:00:00 2016-02-21 00:00:00 2016-02-21 00:00:00 America/Chicago 2016-02-21 06:00:00 UTC  1456034400
    2016-03-21 00:00:00 2016-03-21 00:00:00 2016-03-21 00:00:00 America/Chicago 2016-03-21 05:00:00 UTC  1458536400
    2016-04-21 00:00:00 2016-04-21 00:00:00 2016-04-21 00:00:00 America/Chicago 2016-04-21 05:00:00 UTC  1461214800
    2016-05-21 00:00:00 2016-05-21 00:00:00 2016-05-21 00:00:00 America/Chicago 2016-05-21 05:00:00 UTC  1463806800
    

    并将计算出的时期与您的函数进行比较:

    with t (dt) as (
      select add_months(trunc(sysdate), -level)
      from dual
      connect by level <= 6
    )
    select dt dt,
      unix_time_from_date(dt) unix_time_from_date,
      86400 * (cast(from_tz(cast(dt as timestamp), 'America/Chicago') at time zone 'UTC' as date)
        - date '1970-01-01') as epoch,
      unix_time_from_date(dt) -
        (  86400 * (cast(from_tz(cast(dt as timestamp), 'America/Chicago') at time zone 'UTC' as date)
        - date '1970-01-01')) as diff
    from t
    order by dt;
    
    DT                  UNIX_TIME_FROM_DATE       EPOCH   DIFF
    ------------------- ------------------- ----------- ------
    2015-12-21 00:00:00          1450674000  1450677600  -3600
    2016-01-21 00:00:00          1453352400  1453356000  -3600
    2016-02-21 00:00:00          1456030800  1456034400  -3600
    2016-03-21 00:00:00          1458536400  1458536400      0
    2016-04-21 00:00:00          1461214800  1461214800      0
    2016-05-21 00:00:00          1463806800  1463806800      0
    

    但您仍然必须说明 - 因此可能假设 - 最初代表的日期是哪个时区。你已经在你的函数中这样做了,所以我认为这不是问题。

    【讨论】:

    • 非常感谢。在发布问题之前,我无法在 UTC 进行投射。你解决了问题。
    【解决方案2】:

    对于它的价值,以下是我为此制作的两个函数:

    function toUnixEpoch(d in date) return number
    is
    begin
        return ROUND((cast(from_tz(cast(d as timestamp), 'Europe/Stockholm') at time zone 'UTC' as date) - date '1970-01-01') * 86400);
    end;
    
    function toDate(unixEpoch in number) return date
    is
    begin
        return cast(from_tz(timestamp '1970-01-01 00:00:00 UTC'+unixEpoch/86400, 'UTC') at time zone 'Europe/Stockholm' as date);
    end;
    

    将它们放在一个包中(下面的“包名”),然后, 将时区区域设置为您自己的区域(上面的“欧洲/斯德哥尔摩”)。

    用 Alex 完美的 SQL 测试:

    with t (d) as (
      select TRUNC(add_months(trunc(sysdate), -level))+8/24 from dual connect by level <= 6
    )
    select d as "Date",
      cast(d as timestamp) as "Timestamp",
      from_tz(cast(d as timestamp), 'Europe/Stockholm') as "Timestamp with time zone",
      from_tz(cast(d as timestamp), 'Europe/Stockholm') at time zone 'UTC' as "UTC",
      packagename.toUnixEpoch(d) as "Unix Epoch",
      packagename.toDate(wfg.toUnixEpoch(d)) as "Back to Date"
    from t
    order by 1
    

    如您所见,无论夏令时如何,“日期”和“回溯到日期”应该是相同的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-04
      • 2012-08-19
      • 2018-01-27
      • 2014-12-10
      • 2016-08-10
      • 2014-06-01
      • 2020-09-12
      • 2021-11-08
      相关资源
      最近更新 更多