Java 没有时间的日期比较的好方法

Good way for Java Date comparison without time

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

但最近发现时区有些问题。日期存在于 PST 中,当转换为 LocalDate 后转换在 5:00 pm PST 时变为 false,当它应该为 true -

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:00pm,当变量 expiryDate 包含 "2021-01-16" 时,变量 now 变为 "2021-01-17"

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

java.util 的日期时间 API 及其格式 API、SimpleDateFormat 已过时且容易出错。建议完全停止使用它们并切换到 modern date-time API.

  • 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它向后移植了大部分 java.time Java 6 和 7 的功能。
  • 如果您正在为 Android 项目工作,并且您的 Android API 水平仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring and

Trail: Date Time.

了解现代日期时间 API

LocalDate 默认使用 JVM 的时区

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

此外,java.util.Date 对象仅表示自称为“纪元”的标准基准时间以来的毫秒数,即 January 1, 1970, 00:00:00 GMT(或 UTC)。当您打印 java.util.Date 的对象时,其 toString 方法 returns 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 is the de facto standard date and time library for Java prior to Java SE 8. Users are now asked to migrate to java.time (JSR-310).

简化你的表达方式

下面的语句

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

可以简化为

boolean notExpired = !expiryDate.isBefore(now);

您应该考虑两个 API:

  1. 你一直在使用的 Joda-Time 是一个很好的库,但是处于维护模式。
  2. Joda-Time 的首席开发人员 Stephen Colebourne 继续开发 java.time,现代 Java 日期和时间 API,吸取了好的和不好的教训来自 Joda-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 主页说:

Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to 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);

Expired? false

链接