【问题标题】:Is this the correct way to obtain a java.sql.Timestamp at UTC from a Date?这是从日期获取 UTC 的 java.sql.Timestamp 的正确方法吗?
【发布时间】:2015-11-08 14:15:45
【问题描述】:

我开发了一个 SonarQube 插件,出于我的一个需求,我需要将项目的分析日期存储为 SQL TIMESTAMP(请注意: TIMESTAMP 和 不是TIMESTAMP WITH TIMEZONE)。

这是我目前的做法:

// In the SonarQube Sensor
// .getAnalysisDate() returns a java.util.Date
final Instant instant = module.getAnalysisDate().toInstant();

// Timestamp at UTC from the Instant
final LocalDateTime dt = LocalDateTime.frominstant(instant, ZoneOffset.UTC);
final Timestampt ts = Timestamp.valueOf(dt);

我在理解 Instant 的概念时有点困难,还有 ZonedDateTime 等......

无论如何,这似乎是我想要的,但它是正确的方式吗?

【问题讨论】:

  • 假设您的分析日期是 2015 年 11 月 8 日星期日晚上 7 点 UTC,您计算机的本地时区是 CET(冬季时间为 UTC+1),即晚上 8 点。您想在数据库中的TIMESTAMP 中存储什么?晚上七点还是八点?从您的代码的外观来看,您想要晚上 8 点 - 但是除非检索数据的机器位于同一时区 (CET),否则您将无法获得相同的即时返回...也许您应该提供更多背景信息... (ps:使用没有时区的时间戳通常是个坏主意,除非您始终为该列使用相同的 TZ,通常是 UTC)。
  • 这可能也很有趣:stackoverflow.com/a/14070771/829571
  • @assylias 从我的代码看来,相反,给定您的代码,结果将是晚上 7 点——这正是所要求的。是的,我知道这是个坏主意,但这是我被要求的……如果只是为了我,我会存储一个 TIMESTAMP WITH TIME ZONE 而不是纯粹的 TIMESTAMP。

标签: java java-8 java-time


【解决方案1】:

如果性能很重要,我会使用以下内容:

final long timeAtLocal = module.getAnalysisDate(); // or System.currentTimeMillis(); or new Date().getTime(); etc. 
final long offset = TimeZone.getDefault().getOffset(timeAtLocal);
final Timestamp timeAtUTC = new Timestamp(timeAtLocal - offset);

【讨论】:

  • 这是唯一对我有用的东西。我不知道谁决定存储日期/时间戳数据(特别是将 date.getTime() 转换为时间戳对象,然后以某种方式从纪元中失去它的价值,即使你实际上只是从纪元开始给它一个很长的时间,但我希望他们找到了新的职业。
【解决方案2】:

Timestamp.from(instant) 就是您所需要的。

java.sql.Timestamp 和 java.time.Instant 都没有时区,因此您无需转换为 UTC。

或者直接来自java.util.Date

long millisSinceEpoch = module.getAnalysisDate().getTime();
Timestamp timestamp = new Timestamp(time);

【讨论】:

  • 好吧,如果我尝试打印`new TimeStamp(new Date().getTime()),它会返回当前时区的时间戳,所以...
  • @fge 那是因为 TimeStamp#toString() 使用您的本地时区:stackoverflow.com/questions/14070572/… - 您的版本在转换到/从 LocalDateTime 时所做的是设置时间,以便在实际运行时打印 UTC 时间格式化为当地时间。 LocalDateTime dt 假定是在当地时间给出,而您实际上将其构造为 UTC - 在这里您“打破”它。
  • 嗯,是的,但是我的时区恰好不总是UTC,我需要UTC值!我知道当地时区,我确实需要 UTC 的表示...此外,存储在数据库中的类型是 TIMESTAMP,而不是 TIMESTAMP WITH TIME ZONE(我已经编辑了我的问题以澄清这一事实)跨度>
  • @fge 我对 SQL 服务器和配置不太熟悉,但就像在 stackoverflow.com/questions/15206194/… 中一样,我想说这不是您必须修复的 Timestamp,而是您的数据库/jdbc 驱动程序映射器存储值。
  • sigh 我迷路了。我的目标只是创建一个“伪造”UTC 时区的Timestamp。请注意,我不会在其他任何地方重用这个实例,它只是为了插入到数据库中。
【解决方案3】:

要在您的数据库中存储 UTC TIMESTAMP,您需要创建一个 Java Timestamp,它代表您的报告日期(例如 11 月 8 日晚上 7 点 UTC),但在本地时区 没有转换(比如欧洲中部时间 11 月 8 日晚上 7 点)。所以您的方法是正确的:获取分析日期的LocalDateTime UTC(11 月 8 日晚上 7 点),并在您当地时区的 LocalDateTime 创建一个时间戳。

我认为没有更短/更好的方法可以做到这一点。如果您使用 sql TIMESTAMP WITH TIME ZONE 字段,则无需进行任何操作,Date.from(Instant) 将产生正确的结果。


澄清所涉及的概念,以您发布问题的时间为例(2015 年 11 月 8 日星期日晚上 7 点 UTC)并假设您的本地时区是 CET(中欧时间 = UTC+1):

  • Java Timestamp 将是自纪元以来的毫秒数,即它代表您发布问题的时间线上的唯一时刻,并且没有任何时区信息
  • 当将Timestamp 存储到TIMESTAMP(即没有时区)字段时,jdbc 驱动程序将在默认时区计算与您的Timestamp 对应的日期/时间(除非Calendar 明确提供)- 所以您的数据库将显示 11 月 8 日星期日晚上 8 点
  • java.time.Instant 类似于 Java 的Timestamp:它代表一个唯一的时间点,没有时区信息
  • LocalDateTime 类似于 sql TIMESTAMP,例如,11 月 8 日星期日晚上 8 点,但如果没有额外的时区信息,您不知道是哪个时间点
  • ZonedDateTime 本质上是LocalDateTime + 时区。例如,11 月 8 日星期日晚上 8 点 [Europe/Paris] - 这通常标识一个唯一的时刻,但不一定(想想 DST 的时钟何时向后改变并且同一小时重复两次)。
  • OffsetDateTime 本质上是LocalDateTime + 与 UTC 的偏移量。例如 11 月 8 日星期日晚上 8 点 +01:00。这标识了一个独特的时刻。

标准方法通常是将即时存储为 sql TIMESTAMP WITH TIME ZONE,并在 Java 方面使用 Timestamp 或 OffsetDateTime。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2019-06-25
  • 1970-01-01
  • 1970-01-01
  • 2020-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多