带有本地时区值的 Oracle 时间戳透明转换

标签 oracle session timezone timestamp-with-timezone

据我所知,TIMESTAMP WITH LOCAL TIME ZONE值(value)观是透明的
与用户的 session 时区相互转换。但我的值(value)观是从
数据库与之前插入的数据库不同。是否有数据库或 session
我可以调整参数来解决这个问题吗?

这是我的测试用例:

select systimestamp(0) from dual;

-- SYSTIMESTAMP   15/03/2017 19:01:13 +03:00

select dbtimezone from dual;

-- DBTIMEZONE     -07:00

create table test_timestamps
(
    id number generated by default on null as identity,
    systimestamp_col timestamp(0) with local time zone default on null systimestamp,
    sysdate_col timestamp(0) with local time zone default on null sysdate,
    current_timestamp_col timestamp(0) with local time zone default on null current_timestamp(0),
    date_col timestamp(0) with local time zone
);

alter session set time_zone='0:00';

insert into test_timestamps(date_col)
values (timestamp '2017-03-15 19:02:00');

select * from test_timestamps;

-- ID                                 1
-- SYSTIMESTAMP_COL                   15/03/2017 9:02:19
-- SYSDATE_COL                        15/03/2017 12:02:18
-- CURRENT_TIMESTAMP_COL              15/03/2017 9:02:19
-- DATE_COL                           15/03/2017 12:02:00

delete from test_timestamps;

alter session set time_zone='+3:00';

insert into test_timestamps(date_col)
values (timestamp '2017-03-15 19:05:00');

select * from test_timestamps;

-- ID                                 2
-- SYSTIMESTAMP_COL                   15/03/2017 12:05:43
-- SYSDATE_COL                        15/03/2017 12:05:43
-- CURRENT_TIMESTAMP_COL              15/03/2017 12:05:43
-- DATE_COL                           15/03/2017 12:05:00

我对 DATE_COL 特别困惑值(value)。据我所知,我从 DATE_COL 读取的值不管当前 session 是什么TIME_ZONE都应该和我插入的一样是(只要它在我的插入和选择之间没有改变)。

我也很困惑SYSTIMESTAMP默认值。
SELECT SYSTIMESTAMP FROM DUAL无论当前用户 session 的时区如何,始终以“+3:00”时区返回我的服务器的时间戳。但是如果我使用 SYSTIMESTAMP作为列的默认值,它被翻译。

我想看到的是:
-- for a user in my time zone
alter session set time_zone='+3:00';

insert into test_timestamps(date_col)
values (timestamp '2017-03-15 19:15:00');

select id, systimestamp_col, date_col from test_timestamps;

-- ID                                 3
-- SYSTIMESTAMP_COL                   15/03/2017 19:15:00
-- DATE_COL                           15/03/2017 19:15:00

-- same data from a GMT user's point of view
alter session set time_zone='+0:00';

select id, systimestamp_col, date_col from test_timestamps;

-- ID                                 3
-- SYSTIMESTAMP_COL                   15/03/2017 16:15:00
-- DATE_COL                           15/03/2017 16:15:00

这是可能的还是我错过了什么?

更新。这是我的 LiveSQL script .看起来它应该像我描述的那样工作,所以我猜我的数据库设置可能有问题。

最佳答案

TIMESTAMP WITH LOCAL TIME ZONE工作方式如下:当您必须在应用程序中使用时区时,常用的方法是

Store all times internally as UTC and convert them to current user local time zone on application level.



正是如此TIMESTAMP WITH LOCAL TIME ZONE有效 - 唯一的区别是

Store all times internally as DBTIMEZONE and convert them to current user local time zone on application level.



因此,您不能更改 DBTIMEZONE (使用 ALTER DATABASE SET TIME_ZONE='...'; )如果数据库包含一个带有 TIMESTAMP WITH LOCAL TIME ZONE 的表column 并且该列包含数据。
SYSTIMESTAMP以数据库服务器操作系统的时区返回。 DBTIMEZONE不是 SYSTIMESTAMP的时区或 SYSDATE .
DBTIMEZONE定义了TIMESTAMP WITH LOCAL TIME ZONE的内部存储格式数据类型列。忘记这一点,我无法想象您需要它的任何用例。

实际上你的表相当于这个选择:
select 
   CAST(systimestamp AS timestamp(0) with local time zone) as SYSTIMESTAMP_COL,
   CAST(sysdate AS timestamp(0) with local time zone) as SYSDATE_COL,
   CAST(current_timestamp AS timestamp(0) with local time zone) as CURRENT_TIMESTAMP_COL,
   CAST(timestamp '2017-03-15 19:02:00' AS timestamp(0) with local time zone) as DATE_COL
from dual;

当您制作 CAST({time without time zone} with local time zone)然后您尝试将没有任何时区信息的日期/时间值转换为带时区的日期/时间值。原则上这是不可能的,因为 Oracle 缺少时区信息,所以 Oracle 假定一个时区。如果您进行此类转换,则 Oracle 始终将 {time without time zone} 视为 SESSIONTIMEZONE 中给出的(在转换的那一刻)。

所以CAST(sysdate AS timestamp(0) with local time zone)相当于
CAST(FROM_TZ(TO_TIMESTAMP(SYSDATE), SESSIONTIMEZONE) AS TIMESTAMP(0) WITH LOCAL TIME ZONE)` 

分别CAST(timestamp '2017-03-15 19:02:00' AS timestamp(0) with local time zone)方法
CAST(FROM_TZ(TIMESTAMP '2017-03-15 19:02:00', SESSIONTIMEZONE) AS TIMESTAMP(0) WITH LOCAL TIME ZONE)

对于 SYSDATE这实际上是错误的,因为 SYSDATE在数据库服务器操作系统的时区中给出,而不是在 SESSIONTIMEZONE 中。对于第二个,结果是否正确取决于您的意图。
SYSTIMESTAMP返回值 TIMESTAMP WITH TIME ZONE ,它始终独立于您当前的SESSIONTIMEZONE .但是如果你转换成 TIMESTAMP WITH LOCAL TIME ZONE当然,它会转换为您当前的本地时区。您也可以使用 CURRENT_TIMESTAMPSYSTIMESTAMP AT LOCAL这或多或少是相同的。

这段代码

select systimestamp(0) from dual;

-- SYSTIMESTAMP   15/03/2017 19:01:13 +03:00

alter session set time_zone='0:00';

insert into test_timestamps(date_col)
values (timestamp '2017-03-15 19:02:00');

select * from test_timestamps;

-- ID                                 1
-- SYSTIMESTAMP_COL                   15/03/2017 9:02:19
-- SYSDATE_COL                        15/03/2017 12:02:18
-- CURRENT_TIMESTAMP_COL              15/03/2017 9:02:19
-- DATE_COL                           15/03/2017 12:02:00


似乎是错误的。结果应该是
-- SYSTIMESTAMP_COL                   15/03/2017 16:01:14
-- SYSDATE_COL                        15/03/2017 19:01:14
-- CURRENT_TIMESTAMP_COL              15/03/2017 16:01:14
-- DATE_COL                           15/03/2017 19:02:00

差异看起来应该是,但绝对值似乎是“伪造的”(或者您的数据库存在真正的问题)。

关于带有本地时区值的 Oracle 时间戳透明转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42815633/

相关文章:

sql - 如何在 SQL 中检索 PL/SQL 记录类型?

sql - 递归WITH子句必须在UNION ALL分支之一中直接引用自身

javascript - express-session 不会删除 mongodb 中的 session

Java TimeZone 让人头皮发麻

java - 解析日期时间字符串时时区不一致

sql - 统计多种族学生的数量,二进制(1,0),每个种族都有自己的列

php - 通过重新加载第一页然后再次滚动回来使浏览器返回

python - Flask session 变量在请求之间不持久

Linux 日期时区

sql - dbms_metadata.get_ddl 不工作