【问题标题】:Good way for Java Date comparison without time没有时间的Java日期比较的好方法
【发布时间】:2021-04-22 02:59:50
【问题描述】:

需要查看某个日期(例如:到期日)是否大于或等于今天。目前已经使用 JODA 时间库来实现这种简单的比较。甚至有些帖子也推荐this

但最近发现时区有些问题。日期存在于 PST 中,当转换为 LocalDate 时,在太平洋标准时间下午 5:00 时转换为假,此时它应该为真 -

LocalDate now = LocalDate.fromDateFields(new Date()); // Current date in PST
LocalDate expiryDate = LocalDate.fromDateFields(expiresOn); // expiresOn is java.util.Date
boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);

仔细观察,LocalDate expiryDate 使用的是 UTC 年表。所以在太平洋标准时间下午 5:00,当变量 expiryDate 包含包含 "2021-01-16" 时,变量 now 变为 "2021- 01-17"

请推荐,解决这个问题的更好方法是什么。 我试图了解,使用 joda time 可能会获得哪些特殊优势,因为使用 SimpleDateFormatter 可以实现同样的同情。

【问题讨论】:

  • 始终以 UTC 时间处理并调整到用户的时区。
  • 您必须确定“今天”在您的上下文中的含义。如果“今天”应该在您的代码运行的时区中解释,那么您希望将两个时间都转换为本地时区,然后比较该时间的日期部分。但是,如果您想要一个无论您在哪个时区都始终给出相同答案的比较,那么您需要将两个时间都转换为您想用来定义“今天”的任何时区。在任何情况下,您都需要将两个时间值转换为一个共同的时区,然后再比较它们的日期才有意义......
  • 一旦您了解了必要的逻辑并决定如何定义“今天”,那么您应该能够使用几乎任何时间/日期 API 编写正确的解决方案。如果您可以假设 Java 是相当新的(Java 8+)版本,那么我建议您使用 Java 内置的较新的日期/时间 API,而不是使用 JodaTime。无论如何,我都会远离旧的 Java API。
  • 所以美洲的到期时间应该更晚,因为例如,1 月 16 日在美洲的截止日期要晚于亚洲和欧洲?
  • PST 是什么意思?您指的是太平洋时间(PST 还是 PDT,具体取决于一年中的时间)?

标签: java jodatime java.util.date


【解决方案1】:

java.util 的日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,转用modern date-time API

Trail: Date Time 了解现代日期时间 API。

LocalDate 默认使用JVM的时区

无论何时涉及时区,请确保在创建LocalDate 的实例时指定相同的时区。 LocalDate 默认使用 JVM 的时区,如果不将它们都转换为同一时区(推荐的时区为 UTC),则永远不要将一个时区的 LocalDate 与另一个时区的时区进行比较。 LocalDateTime 也是如此。不要使用LocalDate,您应该使用同时具有日期和时间的对象(例如LocalDateTime)进行所有处理,如果需要,您可以从中派生LocalDate

此外,java.util.Date 对象仅表示自称为“纪元”的标准基准时间以来的毫秒数,即January 1, 1970, 00:00:00 GMT(或 UTC)。当你打印一个java.util.Date 的对象时,它的toString 方法会返回JVM 时区中的日期时间,根据这个毫秒值计算得出。

因此,如果您从java.util.Date 对象派生expiryDate,则它本质上是UTC 中的日期时间。

您可以将 now-in-PST 和 expiryDate 转换为 java.time.Instant 并进行比较。 java.time.Instant 是 UTC 时间线上的一个瞬时点。

使用现代日期时间 API 的演示:

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        LocalDateTime nowInPST = LocalDateTime.now(ZoneId.of("America/Los_Angeles"));
        System.out.println(nowInPST);

        // Convert it to date in UTC
        Instant nowInPSTConvertedToInstant = nowInPST.atZone(ZoneId.of("America/Los_Angeles"))
                                                .withZoneSameInstant(ZoneId.of("Etc/UTC"))
                                                .toInstant();

        // Some java.util.Date
        Calendar calendar = Calendar.getInstance();
        calendar.set(2020, 0, 10, 10, 10, 10);
        Date date = calendar.getTime();
        Instant expiry = date.toInstant();
        
        System.out.println(nowInPSTConvertedToInstant.isBefore(expiry));
    }
}

输出:

2021-01-17T10:58:38.490041
false

注意:Home Page of Joda-Time查看以下通知

Joda-Time 是 Java 的事实上的标准日期和时间库 在 Java SE 8 之前。现在要求用户迁移到 java.time (JSR-310)。

简化你的表达方式

以下陈述

boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);

可以简化为

boolean notExpired = !expiryDate.isBefore(now);

【讨论】:

    【解决方案2】:

    您应该考虑两个 API:

    1. 到目前为止,您一直在使用的 Joda-Time 是一个不错的库,但处于维护模式。
    2. Joda-Time 的首席开发人员 Stephen Colebourne 借鉴了 Joda-Time 的优秀经验和不那么优秀的经验,继续开发了现代 Java 日期和时间 API java.time。

    从您的问题中还不是很清楚。我假设到期已以 UTC 记录,并且似乎提前了一天,因为它是在太平洋时间查看的。因此,我将向您展示如何将所有内容保持在 UTC 中,以便比较有意义且准确。

    乔达时间

        System.setProperty("user.timezone", "America/Vancouver");
        
        Date expiresOn = new Date(1_610_841_600_000L); // Jan 17 UTC
        System.out.println(expiresOn);
        
        LocalDate now = LocalDate.now(DateTimeZone.UTC);
        System.out.println(now);
        LocalDate expiryDate = new DateTime(expiresOn, DateTimeZone.UTC).toLocalDate();
        System.out.println(expiryDate);
        
        boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);
        System.out.println("Expired? " + (notExpired ? "No" : "Yes"));
    

    现在运行时的输出:

    Sat Jan 16 16:00:00 PST 2021
    2021-01-17
    2021-01-17
    Expired? No
    

    Joda-Time 主页说:

    请注意,Joda-Time 被认为是一个基本上“已完成”的项目。 没有计划进行重大改进。如果使用 Java SE 8,请迁移 到java.time (JSR-310)。

    java.time

        LocalDate now = LocalDate.now(ZoneOffset.UTC);
        System.out.println(now);
        LocalDate expiryDate = expiresOn.toInstant()
                .atOffset(ZoneOffset.UTC)
                .toLocalDate();
        System.out.println(expiryDate);
        
        boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);
        System.out.println("Expired? " + (notExpired ? "No" : "Yes"));
    
    2021-01-17
    2021-01-17
    Expired? No
    

    品味说明

    我的口味是避免在变量名(和其他地方)中出现不必要的否定。我会发现这样做更简单:

        boolean expired = expiryDate.isBefore(now);
        System.out.println("Expired? " + expired);
    

    过期了?假的

    链接

    【讨论】:

    • 我应用了您对 java.time 的建议,但仍然想知道,为什么 joda time 可能会给出错误的结果。想和你核实一下,如果你知道的话。 "LocalDate now = LocalDate.fromDateFields(new Date()); "如果我在太平洋标准时间下午 4:00 尝试,现在的值是 UTC 的第二天,而不是太平洋标准时间。
    猜你喜欢
    • 1970-01-01
    • 2017-10-12
    • 2021-11-19
    • 2012-07-22
    • 2011-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    相关资源
    最近更新 更多