【问题标题】:Adding 1 hour to 13 digit Timestamp将 1 小时添加到 13 位时间戳
【发布时间】:2017-11-09 10:46:32
【问题描述】:

我尝试了下面的代码。

Timestamp timestampDate = scheduled.getInterviewDateAndTime(); //from DB
Map<String, Object> map = new HashMap();    
map.put("eventTitle", "interview with");
map.put("startDateTime", timestampDate);
System.out.println("startDateTime : " + timestampDate);

long addTime = 1*60*60*1000;
timestampDate.setTime(timestampDate.getTime() + TimeUnit.HOURS.toMillis(addTime));
map.put("endDateTime", timestampDate);
System.out.println("endDateTime : " + timestampDate);

这是正确的方法还是有什么好的替代方法?

输出是:

startDateTime : 2017-11-07 09:08:00.0
endDateTime : 2428-07-15 09:08:00.0

如何得到正确的输出?

【问题讨论】:

  • 请用ctr + k格式化代码。
  • 正确吗 - 你检查了吗?
  • map 的定义是什么?为什么先输入 &lt;String, String&gt; 然后输入 &lt;String, long&gt; ? (第一行很奇怪)
  • map.put("startDateTime", timestampDate);和 map.put("endDateTime", timestampDate);引用同一个对象。您需要为 endDateTime 创建一个新对象。
  • 想知道您是否有可能摆脱早已过时的Timestamp 类?现代 Java 日期和时间 API 使用起来要好得多。 Instant 类是Timestamp 的自然替代品。即使您绑定到Timestamp,您仍然可以从使用Instant 计算“时间戳加1 小时”中受益。

标签: java collections timestamp


【解决方案1】:

这里有几个问题:

  • java.sql.Timestamp(我假设它就是这样)是一个可变类,因此在其上设置时间会更改时间戳状态。即使您的 println 语句不明显,之后调试地图也会立即显示出来。
  • 你计算小时的逻辑是错误的(你在那里乘以两次)
    1. 第一次制作addTime 变量时。
    2. 第二次使用TimeUnit.toMillis()

(当然)有几种方法可以解决这个问题:

我更喜欢的方式(不过,它需要 Java 8 或 ThreeTen 库):

Timestamp start = obtain();
Timestamp end = Timestamp.from(start.toInstant().plus(1, ChronoUnit.HOURS));

它利用将 Timestamp 转换为 java.time.Instant 对象(或 ThreeTen 的等效版本)的能力,然后是工厂构造函数,该构造函数将采用 Instant 并从中生成 Timestamp(这是 Java 8版本,ThreeTen 将在其他类中具有类似的工厂,而不是时间戳)。与 JDK 中的旧日期时间/日历类相比,它还利用了在 java.time 中添加的更简洁的时间计算逻辑。

第二个变体,大致相同,没有使用所有花哨的东西,但结果也不太可读(对我来说):

Timestamp start = obtain();
Timestamp end = new Timestamp(start.getTime() + TimeUnit.HOURS.toMillis(1));

如您所见,第二个变体几乎就是您所拥有的,但没有不必要的计算。

请不要手动计算这些值;我们都知道小时应该是 60 分钟,一分钟是 60 秒,等等,但是到处读这个会很快模糊眼睛。您自己已经通过首先手动计算它然后仍然使用 TimeUnit 来看到它。当您需要添加一天时,情况会更糟,因为考虑到夏令时和历史时区的变化,millis 不允许您在不提取有关特定时间点的大量日长信息的情况下精确添加一天。不是每分钟也是 60 秒,你知道,还有一些补偿措施。

【讨论】:

    【解决方案2】:

    我对你的建议是停止使用早已过时的Timestamp 类。如果可以的话,要么完全,要么至少尽量减少对它的使用。我将向您展示这两个选项的代码。 The modern Java date and time API known as java.time or JSR-310 使用起来更好。在时间算术方面更是如此,例如在日期时间上加一小时。

    java.time

    更改getInterviewDateAndTime() 以返回InstantInstant 是来自 java.time 的类,它自然地替换了旧的 Timestamp 类。还要更改Map 的接收者以接受其中包含Instant 对象的地图。现代版本的 JDBC、JPA 等可以愉快地从您的数据库中检索 Instant 对象并将 Instants 存储回其中。

        Instant instantStart = scheduled.getInterviewDateAndTime(); //from DB
        Map<String, Object> map = new HashMap<>();    
        map.put("eventTitle", "interview with");
        map.put("startDateTime", instantStart);
        System.out.println("startDateTime : " + instantStart);
    
        Instant instantEnd = instantStart.plus(1, ChronoUnit.HOURS);
        map.put("endDateTime", instantEnd);
        System.out.println("endDateTime : " + instantEnd);
    

    注意事项:代码更自然、更直接地表达了增加一小时的事实。不需要乘法,不需要读者检查你是否乘以正确的常数,或者你的乘法没有溢出;这一切都得到了照顾。 Instant 类是不可变的,因此不会有意外更改您已经添加到地图中的 Instant 对象的风险。

    在我的示例中打印了上面的代码:

    startDateTime : 2017-11-29T09:15:00Z
    endDateTime : 2017-11-29T10:15:00Z
    

    时间以 UTC 为单位。

    编辑:正如 Basil Bourque 在评论中有用地指出的那样,也可以通过这种方式增加一个小时:

        Instant instantEnd = instantStart.plus(Duration.ofHours(1));
    

    结果是一样的。

    与旧版 API 一起使用

    假设您无法更改 getInterviewDateAndTime() 的返回类型和/或地图的接收者绝对需要其中的 Timestamp 对象。解决这种限制的标准方法是您仍然在自己的代码中使用现代 API。收到Timestamp 后,立即将其转换为Instant。当您需要将Timestamp 传递给您的旧代码时,您只能在最后一刻转换您的Instant

        Timestamp timestampStart = scheduled.getInterviewDateAndTime(); //from DB
        Instant instantStart = timestampStart.toInstant();
        Map<String, Object> map = new HashMap<>();    
        map.put("eventTitle", "interview with");
        map.put("startDateTime", timestampStart);
        System.out.println("startDateTime : " + timestampStart);
    
        Instant instantEnd = instantStart.plus(1, ChronoUnit.HOURS);
        Timestamp timestampEnd = Timestamp.from(instantEnd);
        map.put("endDateTime", timestampEnd);
        System.out.println("endDateTime : " + timestampEnd);
    

    您仍然拥有上面提到的现代 API 的大部分优势,但需要多行转换。打印此代码

    startDateTime : 2017-11-29 10:15:00.0
    endDateTime : 2017-11-29 11:15:00.0
    

    时间与上面相同,但在我的本地时区,因为Timestamp.toString() 以这种方式呈现它们(这让很多人感到困惑)。

    问题:我可以在我的 Java 版本中使用现代 API 吗?

    如果至少使用 Java 6,则可以。

    • 在 Java 8 及更高版本中内置了新的 API。
    • 在 Java 6 和 7 中获得 the ThreeTen Backport,新类的反向端口(ThreeTen 代表 JSR 310)。 TimestampInstant 之间的转换有点不同,通过一个名为 DateTimeUtils 的类,但并不复杂。其余的都是一样的。
    • 在 Android 上,使用 ThreeTen Backport 的 Android 版本。叫ThreeTenABP,我觉得this question: How to use ThreeTenABP in Android Project有很好的解释。

    【讨论】:

    • 这个答案是正确的。从数据库中提取java.time.Instant 对象,而不是过时的java.sql.Timestamp 对象。增加一小时的另一种方法是使用Duration 类。 myResultSet.getObject( … , Instant.class ).plus( Duration.ofHours( 1 ) )
    • @BasilBourque,虽然我们通常应该避免在新代码中使用 Timestamp,但 OP 代码可能在已发布的遗留 API 的上下文中工作,迁移到 java.time 类可能是不可能的。
    猜你喜欢
    • 2010-11-10
    • 1970-01-01
    • 2020-05-04
    • 1970-01-01
    • 2017-10-22
    • 2019-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多