在 oracle 中使用 to_char 将时间戳转换为 tz 时的奇怪行为

Strange behaviour when converting timestamp with tz using to_char in oracle

我要求 Oracle 告诉我当地时间下周一 12:00 中午圣地亚哥的日期和时间。

以下SQL:

SELECT to_timestamp(
            to_char(trunc(next_day(sysdate, 1)) + 0.5, 
                    'DD-MM-YY HH24:MI')) 
    AT TIME ZOME 'America/Santiago' 
FROM dual;

Returns: 16-08-21 06:00:00,000000000 AMERICA/SANTIAGO

我想提取日期、小时和分钟,所以我正在做:

SELECT to_char(
           to_timestamp(
               to_char(trunc(next_day(sysdate,1)) + 0.5, 
                       'DD-MM-YY HH24:MI')) 
               AT TIME ZONE 'America/Santiago', 
           'DD-MM-YY HH24:MI') 
FROM dual;

Returns: 16-08-21 07:00

谁能解释为什么 06:00 在转换 to_char 时变成 07:00?!

提前致谢

这不是一个完整的答案;使用“答案”格式,以便我可以 post 格式化代码。如果我能找到更多,我可能会编辑这个答案。

首先,为了隔离问题,最好编写最简单的示例来重现错误。这与 next_day 函数或初始转换无关 - 它与最后一次转换有关,从带时区的时间戳到字符串。

在下面的查询中,我 select 一个带有时区文字的硬编码时间戳(使用我会话的 nls_timestamp_tz_format 设置显示在我的屏幕上),并且同样的时间戳隐式转换为字符串(也使用相同的 nls_timestamp_tz_format 模型)。如您所见,错误重现 - 我们获得了更多信息:当 Oracle 将时间戳转换为字符串时,它更改了时区 DST 标志(它显示 CLST 而不是 CLT;CLST 是智利 Summer 时间,这不应该适用于八月 - 那是南半球的冬天)。

为什么会发生这种情况是一个很好的问题。首先要看的地方是时区文件——那里很可能有错误,而且可能有补丁。 (无论这个问题如何,都应定期维护时区文件的补丁和更新等。)我能够在我的系统上复制它,因为我是一个业余爱好者,使用免费版本的 Oracle,它不附带访问补丁和更新等;但这是我首先要看的地方。

如果这没有帮助,也许是时候询问 Oracle 本身了(提出服务请求,或者可能报告为错误)。

所以 - 这是我的 nls_timezone_tz_format 首先,以确保我们知道发生了什么:

select value
from   v$nls_parameters
where  parameter = 'NLS_TIMESTAMP_TZ_FORMAT'
;

VALUE                                                           
----------------------------------------------------------------
yyyy-mm-dd hh24:mi:ss.ff3 tzr tzd

下面是重现问题的简单查询。请注意,“时间戳....”是一个硬编码常量(文字),直到并包括时区规范。

select         timestamp '2021-08-16 12:00:00 America/Santiago'  as tz
     , to_char(timestamp '2021-08-16 12:00:00 America/Santiago') as str
from   dual
;

TZ                                           STR                                          
-------------------------------------------- ---------------------------------------------
2021-08-16 11:00:00.000 America/Santiago CLT 2021-08-16 12:00:00.000 America/Santiago CLST

明显的问题是 TIMESTAMP with TIMEZONE 转换为 DATE

无论如何,如果您使用显式 时区 转换为 DATE,一切正常 - 请参阅 dt2 结果下方的查询。

with ts as (
select 
   to_timestamp(TO_DATE ('2021-08-16 12:00','yyyy-mm-dd hh24:mi'))  AT TIME ZONE 'America/Santiago'  ts  
from dual)
select
   ts,
   CAST(ts as TIMESTAMP) ts1,
   CAST(ts as DATE) dt1,
   CAST(ts AT TIME ZONE 'America/Santiago'  AS DATE) dt2
from ts; 

TS                                             TS1                           DT1                 DT2                
---------------------------------------------- ----------------------------- ------------------- -------------------
16.08.2021 06:00:00,000000000 AMERICA/SANTIAGO 16.08.2021 07:00:00,000000000 16.08.2021 07:00:00 16.08.2021 07:00:00