【问题标题】:Convert OffsetDateTime to UTC Timestamp将 OffsetDateTime 转换为 UTC 时间戳
【发布时间】:2015-08-19 11:59:39
【问题描述】:

我有一个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());

【问题讨论】:

    标签: java


    【解决方案1】:

    这是一种进行转换并确保使用 UTC 的方法。我认为这比使用 epoch seconds 提出的解决方案要干净一些。

    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)。
    • 是的,Timestamp.from(instant) 似乎确实将其转换为您当地的时区。执行此操作并确保它保持在 UTC 中的另一种方法是“Timestamp test = Timestamp.valueOf(entityValue.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());”我已经编辑了上面的答案,反映了这一点。
    • 澄清上述 cmets 以供将来参考:以前我的回答建议 Timestamp.from(entityValue.toInstant) 会这样做。然而,虽然instant 本质上是从纪元开始的时间线上的一个瞬间,但 Timestamp.from(instant) 正如@rve 所指出的那样,在您的本地时区中返回它。因此,执行此操作并确保它保持在 UTC 中的一种方法是 'Timestamp test = Timestamp.valueOf(entityValue.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());'
    • 这正是@Cheetah 在他对问题的更新中所写的内容 :-) 所以我想只有一种复杂的方式来进行这种转换。
    • 使用java.util.DateTimeTimestamp 时始终使用系统时区NOT UTC。这使得表示 LocalDateTime 无法使用,但至少您可以获得精确的 Instant。
    【解决方案2】:

    另一种解决方案是:

    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 日编辑之前的版本)

    【讨论】:

      【解决方案3】:

      使用.toEpochSecond() 获取从参考日期开始的秒数(以 UTC 为单位),乘以 1000 并将其传递给 Timestamp 构造函数(因为它期望毫秒)。

      new Timestamp(1000 * offsetDateTime.toEpochSecond());
      

      【讨论】:

      • 这会丢失有关纳秒的信息 - 对吧?
      • 是的,也许你自己的答案更好。
      • 如果你想要毫秒,只需使用.toInstant().toEpochMilli()。应该可以正常工作到 2038 年的某个时候...
      【解决方案4】:

      我正在提供现代答案。

      java.time 和 JDBC 4.2

      您应该避免使用 Timestamp 类。它的设计很糟糕而且非常令人困惑,这是对已经设计很糟糕的java.util.Date 类的真正破解。在我看来,其他答案导致不同结果的事实很好地说明了这种混淆。您已经在使用现代 Java 日期和时间 API java.time 中的OffsetDateTime,并且只要您拥有符合 JDBC 4.2 的 JDBC 驱动程序,您就可以并且应该坚持使用 java.time 中的类。

      最好存储为timestamp with time zone

      按照您的意愿在数据库中以 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);
      

      【讨论】:

      • 当你说:“如果你的数据库中的数据类型仅仅是时间戳(没有时区)(不推荐),Java 端使用的类型是 LocalDateTime。”,这并不完全正确.如果您设置属性:hibernate.jdbc.time_zone=UTC,Hibernate 会将所有 Timestamp 列视为 UTC,您可以在代码中使用 OffsetDateTime,Hibernate 将为您进行转换。
      • 好吧,@FelipeDesiderati,我没有在答案中使用任何 Hibernate。无论如何,感谢您提供有趣的信息,它可能对很多人都很有用。
      猜你喜欢
      • 2019-08-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-27
      • 1970-01-01
      • 2013-06-30
      • 1970-01-01
      相关资源
      最近更新 更多