PL/SQL 带日期参数的函数 returns 错误输出

PL/SQL function with date parameter returns wrong output

为了使我的主要代码更具可读性,我在函数中包装了一个将日期转换为纪元格式的操作。但是,函数 returns 的值不正确。我做错了什么?

我得到的
函数

create or replace function date_to_epoch(
        date_in in date)
        return number
        is
            epoch_out number;
            begin
            epoch_out := round((to_date(date_in, 'yyyy-mm-dd hh24:mi:ss') - to_date('1970-01-01', 'yyyy-mm-dd'))*24*60*60);
            return epoch_out;
            end;

我用函数调用:

declare
    date_out number;
begin
select date_to_epoch(to_date('2020-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) into date_out from dual;
DBMS_OUTPUT.PUT_LINE('Output:' || date_out);
end;

returns:-62134128000

我期待的
当我在一个简单的 SQL 语句中 运行 相同的操作时,我得到了预期的输出:

select round((to_date('2020-01-01', 'yyyy-mm-dd hh24:mi:ss') - to_date('1970-01-01', 'yyyy-mm-dd'))*24*60*60) from dual;

returns: 1577836800

您的输入参数已经是日期,因此您不应在其上使用 to_date()。当你这样做时,坏事就会发生。

我会把你的代码写成:

create or replace function date_to_epoch(date_in in date)
return number
is
    epoch_out number;
begin
    epoch_out := round((date_in - date '1970-01-01')*24*60*60);
    return epoch_out;
end;

请注意,您实际上不需要中间变量来存储函数的 return 值。你可以直接做:

create or replace function date_to_epoch(date_in in date)
return number
is
begin
   return round((date_in - date '1970-01-01')*24*60*60);
end;

在此demo on DB Fiddle中,代码产生:

Output:1577836800

What am I doing wrong?

TO_DATE function 有签名:

TO_DATE( date_string, format_model, nls_param )

其中所有参数都应具有字符串数据类型。

您没有将字符串作为第一个参数传递;您传递的是 DATE 数据类型。 Oracle 将尝试提供帮助,并通过使用当前会话的 NLS 日期格式会话参数隐式调用 TO_CHAR 将您传递的 DATE 转换为字符串。

所以,你的函数是有效的:

create or replace function date_to_epoch(
  date_in in date
) return number
is
  epoch_out number;
begin
  epoch_out := round(
                 ( to_date(
                     TO_CHAR(
                       date_in,
                       ( SELECT value
                         FROM   NLS_SESSION_PARAMETERS
                         WHERE  parameter = 'NLS_DATE_FORMAT' )
                     ),
                     'yyyy-mm-dd hh24:mi:ss'
                   )
                   -
                   to_date(
                     '1970-01-01',
                     'yyyy-mm-dd'
                   )
                 )*24*60*60
               );
  return epoch_out;
end;

要修复它,您只需将日期输入用作日期,而不是尝试将已经是日期的内容转换为日期:

CREATE FUNCTION date_to_epoch(
  date_in IN DATE
) RETURN NUMBER DETERMINISTIC
IS
BEGIN
  RETURN ROUND( ( date_in - DATE '1970-01-01' )*24*60*60 );
END date_to_epoch;

但是,您还需要确保您的日期在 UTC 时区内,如果它们不在计算纪元之前将它们转换为 UTC:

CREATE FUNCTION date_to_epoch(
  date_in  IN DATE,
  timezone IN VARCHAR2 DEFAULT 'UTC'
) RETURN NUMBER DETERMINISTIC
IS
BEGIN
  RETURN ROUND(
    ( CAST(
        FROM_TZ( date_in, timezone ) -- Convert to a timestamp in the correct TZ
        AT TIME ZONE 'UTC'           -- Change to the UTC TZ
        AS DATE                      -- Cast back to a date
      )
      - DATE '1970-01-01'
    )*24*60*60
  );
END date_to_epoch;
/

所以:

SELECT date_to_epoch( DATE '1970-01-01' )        AS utc_epoch,
       date_to_epoch( DATE '1970-01-01', 'PST' ) AS pst_epoch
FROM   DUAL;

输出:

UTC_EPOCH | PST_EPOCH
--------: | --------:
        0 |     28800

db<>fiddle here