带时区的Java和Oracle时间戳
我正试图用hibernate在Oracle中保存两个日期。这两个日期在莫斯科时区具有相同的时间戳:2005-10-30T02:00+03:00[欧洲/莫斯科]和2005-10-30T02:00+04:00[欧洲/莫斯科](“2005年10月30日星期日02:00:00 MSK”和“2005年10月30日星期日02:00:00 MSD”)。日期的时间间隔为一小时,并与冬季/夏季时间的过渡相关 我在Oracle中创建了表:带时区的Java和Oracle时间戳,oracle,hibernate,timestamp,ojdbc,offsetdatetime,Oracle,Hibernate,Timestamp,Ojdbc,Offsetdatetime,我正试图用hibernate在Oracle中保存两个日期。这两个日期在莫斯科时区具有相同的时间戳:2005-10-30T02:00+03:00[欧洲/莫斯科]和2005-10-30T02:00+04:00[欧洲/莫斯科](“2005年10月30日星期日02:00:00 MSK”和“2005年10月30日星期日02:00:00 MSD”)。日期的时间间隔为一小时,并与冬季/夏季时间的过渡相关 我在Oracle中创建了表: create table TMP ( ID LONG,
create table TMP
(
ID LONG,
TS TIMESTAMP,
TSLTZ TIMESTAMP WITH LOCAL TIME ZONE,
TSTZ TIMESTAMP WITH TIME ZONE
);
我的模块中的实体:
@Entity
@Table(name = "tmp")
public class DateTimeOracle {
private Long id;
private ZonedDateTime ts;
private ZonedDateTime tsltz;
private ZonedDateTime tstz;
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public ZonedDateTime getTs() {
return ts;
}
public ZonedDateTime setTs(ZonedDateTime ts) {
this.ts = ts;
}
public ZonedDateTime getTsltz() {
return tsltz;
}
public ZonedDateTime setTsltz(ZonedDateTime tsltz) {
this.tsltz = tsltz;
}
public ZonedDateTime getTstz() {
return tstz;
}
public ZonedDateTime setTstz1(ZonedDateTime tstz) {
this.tstz = tstz;
}
}
在实体中,所有字段都由一个日期初始化。
保存后,Oracle中的两个日期具有相同的值,如下所示:
ts=2005-10-3002:00:00.000000
TSLTZ=2005-10-29 23:00:00.000000
TSTZ=2005-10-30 02:00:00.000000+04:00
为什么oracle在不同的日期(包括偏移量+04:00)保持相同的值?有办法解决这个问题吗
p.S.Postgres正确存储日期。一个偏移量为+03:00,另一个偏移量为+04:00(分别为2005-10-29 23:00:00.000000和2005-10-29 22:00:00.000000)
更新
以下是我创建日期的方式:
Date dt2 = new Date(1130623200000L); //2005-10-29 23:00:00 +04:00
Date dt3 = new Date(1130626800000L); //2005-10-29 23:00:00 +03:00
ZonedDateTime zdt2 = ZonedDateTime.ofInstant(dt2.toInstant(), ZoneId.systemDefault()); // My zone is MSK
ZonedDateTime zdt3 = ZonedDateTime.ofInstant(dt3.toInstant(), ZoneId.systemDefault()); // My zone is MSK
OffsetDateTime odt2 = zdt2.toOffsetDateTime();
OffsetDateTime odt3 = zdt3.toOffsetDateTime();
如果我不使用Hibernate并直接使用jdbc,情况不会改变
Connection conn = DriverManager.getConnection("<oracle_url>",
"<username>", "<password>");
PreparedStatement pstmt = conn.prepareStatement("insert into tmp (id, TSTZ1, TSTZ2) values (200, ?, ?)", Statement.RETURN_GENERATED_KEYS);
pstmt.setDate(1, new java.sql.Date(dt2.getTime()));
pstmt.setDate(2, new java.sql.Date(dt3.getTime()));
int z1 = pstmt.executeUpdate();
pstmt.close();
conn.close();
我在DB中看到:
2005-10-29 22:00:00.000000 2005-10-29 23:00:00.000000 2005-10-30 02:00:00.000000 +04:00 2005-10-30 02:00:00.000000 +03:00
但是,如果我保存ZonedDateTime,则带有本地时区的时间戳中的值是正确的,而带有时区的时间戳中的值是不正确的
PreparedStatement pstmt = conn.prepareStatement("insert into tmp (TSLTZ1, TSLTZ2, TSTZ1, TSTZ2) values (?, ?, ?, ?)");
pstmt.setObject(1, zdt2);
pstmt.setObject(2, zdt3);
pstmt.setObject(3, zdt2);
pstmt.setObject(4, zdt3);
在DB中,我看到:
2005-10-29 22:00:00.000000 2005-10-29 23:00:00.000000 2005-10-30 02:00:00.000000 +04:00 2005-10-30 02:00:00.000000 +04:00
最后两个值不正确。有关Oracle时间戳数据类型的一些解释:
PreparedStatement pstmt = conn.prepareStatement("insert into tmp (TSLTZ1, TSLTZ2, TSTZ1, TSTZ2) values (?, ?, ?, ?)");
pstmt.setObject(1, zdt2);
pstmt.setObject(2, zdt3);
pstmt.setObject(3, zdt2);
pstmt.setObject(4, zdt3);
:不存储任何时区信息。如果输入带有时区的时间戳,则时区信息将被截断并丢失时间戳
:将时间戳插入数据库时,存储带时区信息的时间戳(即作为命名区域或UTC偏移量)带时区的时间戳
:时间戳存储为带有本地时区的时间戳
(推荐,通常为DBTIMEZONE
)。时间戳始终且仅在当前用户会话中显示。因此,它不显示任何时区信息,因为根据定义,这始终是您的本地时区UTC
时间戳
您不必关心客户端的任何设置,时间始终显示为本地时间。时间存储在DBTIMEZONE
中,因此您将丢失原始插入的时区
请注意,当您在带有时区的时间戳上创建索引时。无法直接在此类列上创建索引。相反,Oracle为SYS\u EXTRACT\u UTC(TSTZ)
创建一个虚拟列,并在此虚拟列上创建索引。在开发查询时,应该注意这一点
更新
你的处境很特殊。当您插入时间戳“2005-10-30 02:00:00 Europe/Moscow”时
则此时间不明确,它可能表示2005-10-30 02:00:00+03:00
或2005-10-30 02:00+04:00
举个例子:
SELECT TO_CHAR(TIMESTAMP '2005-10-30 00:00:00 Europe/Moscow' + LEVEL * INTERVAL '1' HOUR,
'YYYY-MM-DD hh24:mi:ss TZH:TZM TZD tzr') AS ts
FROM dual
CONNECT BY LEVEL <= 4;
+--------------------------------------------+
|TS |
+--------------------------------------------+
|2005-10-30 01:00:00 +04:00 MSD Europe/Moscow|
|2005-10-30 02:00:00 +04:00 MSD Europe/Moscow|
|2005-10-30 02:00:00 +03:00 MSK Europe/Moscow|
|2005-10-30 03:00:00 +03:00 MSK Europe/Moscow|
+--------------------------------------------+
如果未添加TZD format元素,并且datetime值不明确,则如果将error\u ON\u OVERLAP\u TIME
会话参数设置为TRUE
,则Oracle数据库将返回一个错误。如果将时间重叠上的ERROR\u设置为FALSE
(默认值),则Oracle数据库将不明确的日期时间解释为标准时间
注意,时区+04:00
或+03:00
不等于欧洲/莫斯科
。时区欧洲/莫斯科
考虑夏令时(大约10年前在俄罗斯仍然使用),但+04:00
/+03:00
不考虑
对不起,我从未使用过hibernate,所以我不知道这个框架如何处理这些数据。我也不熟悉Java。可能不支持夏令时信息
我可以猜测,classjava.sql.Date
和methodsetDate
指的是Oracle中的Date
数据类型。如前所述,最好使用java.sql.Timestamp
和setTimestamp
DATE
数据类型不支持任何时区信息。如果您尝试将日期
值插入到带有[本地]时区的时间戳
列中,那么Oracle实际上会这样做
FROM_TZ(CAST(<your DATE value> AS TIMESTAMP), SESSIONTIMEZONE)
FROM_TZ(CAST(作为时间戳),SESSIONTIMEZONE)
您的条件非常特殊,让我们试着减轻一点
Unix时间1130623200是UTC标准时间2005-10-29 22:00:00
- 莫斯科时间是2005-10-30 02:00:00 Europe/Moscow
,但这是不明确的。
可能是
2005-10-30 02:00:00+04:00
2005-10-30 02:00:00+03:00欧洲/莫斯科MSK
2005-10-29 23:00:00 UTC
- 莫斯科时间是2005-10-30 02:00:00 Europe/Moscow,但这是不明确的。 可能是
- 欧洲/莫斯科MSD
2005-10-30 02:00:00+04:00
- 或
2005-10-30 02:00:00+03:00欧洲/莫斯科MSK
时间戳“2005-10-30 02:00:00欧洲/莫斯科”
改为标准时间,即2005-10-30 02:00:00欧洲/莫斯科MSK+03:00
(与2005年一样!)
请注意,2005年莫斯科的标准时间是MSK=>+03:00
。2011年,俄罗斯政府宣布,未来全年都将使用夏时制,从而有效地取代了标准时间。也就是说,今天莫斯科的标准时间是MSK=>+04:00
,在2011年之前被称为MSD
核实
SELECT
TO_CHAR(TIMESTAMP '2005-10-29 22:00:00 UTC' AT TIME ZONE 'Europe/Moscow', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_1_UTC,
TO_CHAR(TIMESTAMP '2005-10-29 23:00:00 UTC' AT TIME ZONE 'Europe/Moscow', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_2_UTC,
TO_CHAR(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS,
TO_CHAR(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow MSK', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_MSK,
TO_CHAR(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow MSD', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_MSD
FROM dual
世界协调时1时
世界协调时2时
TS
苏木斯克
TS_MSD
2005-10-30 02:00:
SELECT
TO_CHAR(TIMESTAMP '2005-10-29 22:00:00 UTC' AT TIME ZONE 'Europe/Moscow', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_1_UTC,
TO_CHAR(TIMESTAMP '2005-10-29 23:00:00 UTC' AT TIME ZONE 'Europe/Moscow', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_2_UTC,
TO_CHAR(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS,
TO_CHAR(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow MSK', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_MSK,
TO_CHAR(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow MSD', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_MSD
FROM dual