将OffsetDateTime转换为UTC时间戳

25

我有一个java.time.OffsetDateTime对象,想将其转换为java.sql.Timestamp对象。由于Timestamp对象不存储任何偏移信息,因此我打算将所有日期/时间存储为UTC格式。

如何将OffsetDateTime转换为UTC格式的Timestamp对象?

编辑:

我认为以下是答案,但似乎是一种相当复杂的将其转换为UTC的方式:

OffsetDateTime dateTime = OffsetDateTime.now();
Timestamp timestamp = Timestamp.valueOf(dateTime.atZoneSameInstant(ZoneId.of("Z")).toLocalDateTime());
4个回答

24

这将是一种进行转换并确保使用UTC的方法。我认为这比使用纪元秒提出的解决方案要更加简洁。

Timestamp test = Timestamp.valueOf(entityValue.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());

这是错误的。如果 dateTime 包含 2015-10-23T12:44:43Z,并且您在时区 UTC+2 中,则 timestamp 将保存 2015-10-23 14:44:43.0,与答案中提供的结果(2015-10-23 12:44:43.0)不同。 - rve
是的,Timestamp.from(instant)似乎会将其转换为您的本地时区。另一种方法是确保它保持在UTC的方式是'Timestamp test = Timestamp.valueOf(entityValue.atZoneSameInstant(ZoneOffset.UTC)。toLocalDateTime());'我已经编辑了上面的答案以反映这一点。 - Notso
5
为了便于以后的参考,我想澄清上面的评论。之前我的回答建议使用“Timestamp.from(entityValue.toInstant)”即可,但是正如@rve所指出的那样,虽然instant基本上是从时代开始计算的时间轴上的一个瞬间,但是“Timestamp.from(instant)”会将其转换为您所在的本地时区。因此,一种保证将其保留在UTC的方法是:“Timestamp test = Timestamp.valueOf(entityValue.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());”。 - Notso
这正是@Cheetah在他对问题的更新中所写的内容 :-) 所以我想这只有一种复杂的方法来进行此转换。 - rve
1
在处理 java.util.DateTimeTimestamp 时,始终使用系统时区而非 UTC。这使得它无法表示 LocalDateTime,但至少可以获得精确的 Instant。 - Eugen Pechanec
我同意使用系统时间更可靠。例如,确保系统时间为UTC。否则,在代码中将时间转换为UTC时,会在匹配日志时间戳与应用程序操作的时间戳等任务时造成很多混乱。 - Notso

11

另一个解决方案是:

Timestamp.valueOf(LocalDateTime.ofInstant(dateTime.toInstant(), ZoneOffset.UTC));

它将dateTime转换为UTC时间,去除时区信息,然后将结果转换为Timestamp。尽管仍然有些复杂,但在我看来更加简洁。

只使用toInstance()toEpochSeconds()会根据提供的偏移量调整结果。

以下显示了此答案和其他答案的测试结果:

OffsetDateTime dateTime = 
    OffsetDateTime.of(2015, 10, 23, 12, 44, 43, 0, ZoneOffset.UTC);
    // OffsetDateTime.of(2015, 10, 23, 12, 44, 43, 0, ZoneOffset.ofHours(-5));

err.println("dateTime            = " 
    + dateTime
);

err.println("as LocalDateTime    = " 
    + dateTime.toLocalDateTime()
);

err.println("as timestamp (mine) = " 
    + Timestamp.valueOf(LocalDateTime.ofInstant(dateTime.toInstant(), ZoneOffset.UTC))
);

err.println("@Cheetah (correct)  = " 
    + Timestamp.valueOf(dateTime.atZoneSameInstant(ZoneId.of("Z"))
        .toLocalDateTime())
);

err.println("@Notso (wrong)      = " 
    + Timestamp.from(dateTime.toInstant())
);

err.println("@Glorfindel (wrong) = " 
    + new Timestamp(1000 * dateTime.toEpochSecond())
);

以下是它所给出的结果(我的时区是CET):

(with ZoneOffset.UTC)
dateTime            = 2015-10-23T12:44:43Z
as LocalDateTime    = 2015-10-23T12:44:43
as timestamp (mine) = 2015-10-23 12:44:43.0
@Cheetah (correct)  = 2015-10-23 12:44:43.0
@Notso (wrong)      = 2015-10-23 14:44:43.0
@Glorfindel (wrong) = 2015-10-23 14:44:43.0

(with ZoneOffset.ofHours(-5))
dateTime            = 2015-10-23T12:44:43-05:00
as LocalDateTime    = 2015-10-23T12:44:43
as timestamp (mine) = 2015-10-23 17:44:43.0
@Cheetah (correct)  = 2015-10-23 17:44:43.0
@Notso (wrong)      = 2015-10-23 19:44:43.0
@Glorfindel (wrong) = 2015-10-23 19:44:43.0

(NotsoеЬ®2016еєі2жЬИ17жЧ•дєЛеЙНеПСеЄГзЪДзЙИжЬђ)


7
使用.toEpochSecond()方法获取距离参考日期(UTC时间)的秒数,将其乘以1000并传递给Timestamp构造函数(因为它需要毫秒)。
new Timestamp(1000 * offsetDateTime.toEpochSecond());

2
这样做会丢失关于纳秒的信息,对吗? - Cheetah
是的,也许你自己的答案更好。 - Glorfindel
4
如果你想要毫秒级别的时间戳,只需使用 .toInstant().toEpochMilli()。这应该可以正常工作,直到2038年某个时间...... - Lambart

6
我提供现代化的答案。
Java.time和JDBC 4.2
您应该避免使用Timestamp类。它设计不良且非常令人困惑,是对已经设计不良的java.util.Date类的真正破解。其他答案导致与rve答案中记录的比较结果不同,我认为这说明了混淆非常明显。您已经在使用java.time,这是现代Java日期和时间API,并且只要您拥有符合JDBC 4.2标准的JDBC驱动程序,就可以并且应该坚持使用java.time中的类。
最好将其存储为带有时区的时间戳
像您所说,将日期和时间以UTC格式存储在数据库中是一个好的建议性做法。如果可以的话,请更改数据库中的数据类型为"timestamp with time zone"。虽然这不会存储时区(尽管名称如此),但它确保数据库也“知道”时间戳是UTC,这已经防止了许多错误。下一个优点是(如果我理解正确),您可以直接存储您的OffsetDateTime,并自动进行到UTC的转换。
    OffsetDateTime odt = OffsetDateTime.of(
            2015, 6, 4, 19, 15, 43, 210987000, ZoneOffset.ofHours(1));
    PreparedStatement stmt = yourDbConnection.prepareStatement(
            "insert into your_table (your_timestamp_with_time_zone) values (?);");
    stmt.setObject(1, odt);
    stmt.executeUpdate();

如果你想在Java代码中更清晰地表明时间是以UTC存储的,请先进行显式转换:

    odt = odt.withOffsetSameInstant(ZoneOffset.UTC);

如果您的数据库存储的时间戳(timestamp)没有时区

如果您的数据库中的数据类型只是简单的 timestamp (没有时区)(不推荐使用),在Java端应该使用 LocalDateTime 类型。我会按以下方式进行转换为UTC:

    LocalDateTime ldt = odt.withOffsetSameInstant(ZoneOffset.UTC).toLocalDateTime();
    System.out.println("UTC datetime        = " + ldt);

输出结果为:

UTC日期时间 = 2015-06-04T18:15:43.210987

将其存储到数据库中与之前相似:

    PreparedStatement stmt = yourDbConnection.prepareStatement(
            "insert into your_table (your_timestamp) values (?);");
    stmt.setObject(1, ldt);

2
当你说:“如果你的数据库中的数据类型仅是时间戳(没有时区)(不建议使用),在Java端使用的类型是LocalDateTime。”时,这并不完全正确。如果你设置属性:hibernate.jdbc.time_zone=UTC,Hibernate将把所有的Timestamp列视为UTC,你可以在代码中使用OffsetDateTime,Hibernate会为你进行转换。 - Felipe Desiderati
1
嗯,@FelipeDesiderati,我的回答中没有使用任何Hibernate。无论如何,感谢您提供有趣的信息,这可能对许多人有用。 - Ole V.V.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接