从TIMESTAMP WITH LOCAL TIMEZONE列中检索数据时,将时区调整为用户的时区吗?

时间:2019-08-13 07:13:20

标签: oracle jdbc time timezone

根据Oracle docs,带有本地时区的TIMESTAMP的行为如下。

  1. 存储在数据库中的数据被归一化为数据库时区,并且时区信息未存储为列数据的一部分。
  2. 用户检索数据时,Oracle会在用户的本地会话时区中将其返回。

我对第二点即检索有疑问。

也就是说,到用户时区的这种转换是数据库本身发生的还是由数据库客户端/驱动程序负责的?

例如:说我正在使用Oracle JDBC驱动程序。驱动程序是否将时间值转换为用户的时区,或者数据库应该在用户的时区中返回时间值?如果应该进行数据库转换,则客户端应该向数据库提供其时区的指示,不是吗?

1 个答案:

答案 0 :(得分:3)

数据库执行该操作。如果您在时间戳中存储了相同的标称时刻,则带时区的时间戳和带本地时区的时间戳列:

create table t42 (id number,
  ts timestamp,
  tstz timestamp with time zone,
  tsltz timestamp with local time zone
);
insert into t42 (id, ts, tstz, tsltz)
values (1,
  timestamp '2019-08-13 07:13:20 UTC',
  timestamp '2019-08-13 07:13:20 UTC',
  timestamp '2019-08-13 07:13:20 UTC'
);
insert into t42 (id, ts, tstz, tsltz)
values (2,
  timestamp '2019-08-13 12:34:56.789 Australia/Sydney',
  timestamp '2019-08-13 12:34:56.789 Australia/Sydney',
  timestamp '2019-08-13 12:34:56.789 Australia/Sydney'
);

alter session set time_zone = 'Europe/London';

select id, ts, tstz, tsltz from t42 order by id;

ID TS                      TSTZ                                     TSLTZ                  
-- ----------------------- ---------------------------------------- -----------------------
 1 2019-08-13 07:13:20.000 2019-08-13 07:13:20.000 UTC              2019-08-13 03:13:20.000
 2 2019-08-13 12:34:56.789 2019-08-13 12:34:56.789 AUSTRALIA/SYDNEY 2019-08-12 22:34:56.789

...您可以使用dump()来查看它们在内部的实际存储方式:

select id, 'TS' as col, to_char(ts, 'YYYY-MM-DD HH24:MI:SS.FF3') as value, dump(ts) as dumped from t42
union all
select id, 'TSTZ', to_char(tstz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tstz) from t42
union all
select id, 'TSLTZ', to_char(tsltz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tsltz) from t42
order by 1, 4;

ID COL   VALUE                                    DUMPED                                                 
-- ----- ---------------------------------------- -------------------------------------------------------
 1 TS    2019-08-13 07:13:20.000                  Typ=180 Len=7: 120,119,8,13,8,14,21                    
 1 TSTZ  2019-08-13 07:13:20.000 UTC              Typ=181 Len=13: 120,119,8,13,8,14,21,0,0,0,0,208,4     
 1 TSLTZ 2019-08-13 08:13:20.000 EUROPE/LONDON    Typ=231 Len=7: 120,119,8,13,8,14,21                    
 2 TS    2019-08-13 12:34:56.789                  Typ=180 Len=11: 120,119,8,13,13,35,57,47,7,47,64       
 2 TSTZ  2019-08-13 12:34:56.789 AUSTRALIA/SYDNEY Typ=181 Len=13: 120,119,8,13,3,35,57,47,7,47,64,133,128
 2 TSLTZ 2019-08-13 03:34:56.789 EUROPE/LONDON    Typ=231 Len=11: 120,119,8,13,3,35,57,47,7,47,64        

对于原始UTC值,您可以看到TS和TSLTZ值存储的字节数完全相同,但类型代码不同; TSTZ是第三种类型,前7个字节相同(存储的日期/时间-参见MoS Doc ID 69028.1here-这些字节与12类日期相同)加上其他字节用于时区信息(此处零表示小数秒)。日期/时间字节都是相同的,因为它们都是UTC。无论如何,在我的数据库中,因为那是我的DBTIMEZONE:

select dbtimezone, sessiontimezone from dual;

DBTIME SESSIONTIMEZONE                                                            
------ ---------------------------------------------------------------------------
+00:00 Europe/London                                                              

对于最初的悉尼值,TS值为当地时间,因此小时字节为13(12 + 1);对于TSTZ和TSLTZ,日期/时间字节仍然是UTC,因此它们具有3(2 + 1); TSTZ还具有时区信息。 TSLTZ没有时区信息,因为类型231始终为UTC。将其转换为字符串时,将应用会话时区,因此存储的小时字节3(2 + 1)变为本地小时3。

在不同的会话时区中,您几乎会看到相同的内容:

alter session set time_zone = 'America/New_York';

select id, 'TS' as col, to_char(ts, 'YYYY-MM-DD HH24:MI:SS.FF3') as value, dump(ts) as dumped from t42
union all
select id, 'TSTZ', to_char(tstz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tstz) from t42
union all
select id, 'TSLTZ', to_char(tsltz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tsltz) from t42
order by 1, 4;

ID COL   VALUE                                    DUMPED                                                 
-- ----- ---------------------------------------- -------------------------------------------------------
 1 TS    2019-08-13 07:13:20.000                  Typ=180 Len=7: 120,119,8,13,8,14,21                    
 1 TSTZ  2019-08-13 07:13:20.000 UTC              Typ=181 Len=13: 120,119,8,13,8,14,21,0,0,0,0,208,4     
 1 TSLTZ 2019-08-13 03:13:20.000 AMERICA/NEW_YORK Typ=231 Len=7: 120,119,8,13,8,14,21                    
 2 TS    2019-08-13 12:34:56.789                  Typ=180 Len=11: 120,119,8,13,13,35,57,47,7,47,64       
 2 TSTZ  2019-08-13 12:34:56.789 AUSTRALIA/SYDNEY Typ=181 Len=13: 120,119,8,13,3,35,57,47,7,47,64,133,128
 2 TSLTZ 2019-08-12 22:34:56.789 AMERICA/NEW_YORK Typ=231 Len=11: 120,119,8,13,3,35,57,47,7,47,64        

当然,存储的字节完全相同;但是现在TSLTZ值已调整为纽约时间,因此对于悉尼,小时字节3(2 + 1)变为本地时间22,并且在另一个日期。

(由于我格式化的方式,此处显示的是时区TSLTZ;正如您在第一个查询中看到的那样,它实际上没有时区,因此还进行了进一步的隐式转换以便显示该会话值。

如果让客户端格式化底层内部字节结构,您将看到相同的值,就像我在上面的第一个查询中所做的那样。但是使用to_char()可以回答问题的主要部分;数据库必须在之前发生这种情况,将值从其内部格式转换为字符串。如果客户端(或JDBC)将调整时间调整为本地时间,那么to_char()将只看到存储的基于DBTIMEZONE的字节,并始终为您提供与该时间相同的DBTIMEZONE。

您的客户端-或JDBC-告诉数据库使用哪个本地时区。我已经用alter session覆盖了它,您可以通过JDBC进行相同的操作。默认情况下,它基于Java语言环境和其他应用程序设置。但you can override it