【问题标题】:Strange behaviour when converting timestamp with tz using to_char in oracle在 oracle 中使用 to_char 用 tz 转换时间戳时的奇怪行为
【发布时间】:2021-08-12 12:51:33
【问题描述】:

我要求 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;

返回: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;

返回:16-08-21 07:00

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

提前致谢

【问题讨论】:

  • 一个错误很明显;它可能会也可能不会解释您观察到的情况。在您的公式中,您有 to_timestamp(to_char(.......)),但您只有一种格式模型 - 与 to_char 一起使用。您没有to_timestamp 的格式模型。字符串格式为dd-mm-yy hh24:mi;但如果您的nls_timestamp_format 不同,则可能会导致问题。现在,问你一个问题:你想知道如何正确地做到这一点,或者你想知道具体是什么导致了你观察到的差异?
  • 另请注意,使用数字作为第二个参数调用next_day 是未记录的。你不应该依赖它来进行严肃的工作。
  • @mathguy 感谢您的回复。不幸的是,为 to_timestamp 添加格式模型并没有产生任何影响。我很想知道是什么导致了这种差异,因为这是我看到这种行为的大约 15 个时区列表中的唯一示例。但是,如果有更好的方法来解决我的问题,那就太好了。我这样做的原因是因为我们的应用程序使用了一个数据库表,该表表示不同地区的 GMT 偏移量。随着 DST 的变化,我们想用这个 SQL 来监控。
  • 作为一个侧面观察,为什么您在所有格式模型中都使用 2 位数年份 ('YY')?你没听说过一个叫做“千年虫问题”的小东西吗?
  • 如果你去掉内部(不必要的)to_char,你还会得到这个差异吗?

标签: oracle timestamp-with-timezone


【解决方案1】:

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

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

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

为什么会发生这种情况是一个很好的问题。首先要看的地方是时区文件——那里很可能有一个错误,并且可能有补丁。 (无论这个问题如何,都应该定期维护时区文件和更新等的补丁。)我能够在我的系统上复制它,因为我是使用免费版本的 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

然后这里是重现问题的简单(st)查询。请注意,“timestamp....”是一个硬编码常量(字面量),直至并包括时区规范。

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

【讨论】:

  • 非常感谢您深入研究这个问题。我现在将离开并与我们的 DBA 讨论时区更新的情况。
【解决方案2】:

明显的问题在于TIMESTAMP with TIMEZONEDATE转换

无论如何,如果您使用明确的 timezone 转换为 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

【讨论】:

  • 我不同意最初的前提。为什么你认为带有时区的时间戳在to_char() 中被转换为date 数据类型(隐式)?你知道这个,还是认为是这样的?我相信我在答案中发布的查询证明这是错误的;如果我在带有时区的时间戳上调用 to_char,则结果字符串表示带有时区的时间戳,而不是日期。
  • 删除了无效的前提 - 我将它与trunc 混淆了,感谢您发现@mathguy 未解决的问题原因仍然存在...
猜你喜欢
  • 2015-10-20
  • 2016-05-24
  • 2016-06-07
  • 2018-11-28
  • 2019-12-19
  • 2011-11-20
  • 2018-07-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多