如何检查日期对象是否等于昨天?

47

目前我正在使用这段代码

Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DATE) - 1, 12, 0, 0); //Sets Calendar to "yeserday, 12am"
if(sdf.format(getDateFromLine(line)).equals(sdf.format(cal.getTime())))                         //getDateFromLine() returns a Date Object that is always at 12pm
{...CODE

我相信有更简单的方法来检查getdateFromLine()返回的日期是否是昨天,只需要判断日期,不需要判断时间。这也是我使用SimpleDateFormat的原因。 提前感谢您的帮助!

9个回答

91
Calendar c1 = Calendar.getInstance(); // today
c1.add(Calendar.DAY_OF_YEAR, -1); // yesterday

Calendar c2 = Calendar.getInstance();
c2.setTime(getDateFromLine(line)); // your date

if (c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
  && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR)) {

这也适用于像1月1日这样的日期。


4
为什么这适用于1月1日?12月31日年份不会不同吗? - ebi
6
这个方法的作用是通过计算输入数量和字段类型得出的偏移来改变日期的时间戳。因此,无论原始日期如何,它只使用时间戳(自1970年1月1日以来的毫秒数),并基于该时间戳加减偏移量来计算新日期。这实际上也会改变年份,即使字段是“DAY_OF_YEAR”。 - Andrei Fierbinteanu
2
任何看到这个线程的人都应该参考@Przemek的答案,他使用了java.time,应该优先使用它而不是joda-time、java.util.Datejava.util.Calendar:https://dev59.com/GnA75IYBdhLWcg3w9-Sx#33893147。 - liltitus27

25

java.time

使用内置于Java 8的java.time框架

LocalDate now = LocalDate.now(); //2015-11-24
LocalDate yesterday = LocalDate.now().minusDays(1); //2015-11-23

yesterday.equals(now); //false
yesterday.equals(yesterday); //true

官方Oracle LocalDate 教程 中提到:

应该使用equals方法进行比较。

如果您正在使用 LocalDateTimeZonedDateTimeOffsetDateTime 等对象,可以将其转换为 LocalDate 进行比较。

LocalDateTime.now().toLocalDate(); # 2015-11-24

1
我建议始终将可选的 ZoneId 传递给 LocalDate.now,而不是隐式地依赖于 JVM 的当前默认时区。该默认值甚至可以在运行时的任何时刻更改。像这样:LocalDate.now( ZoneId.of( "America/Montreal" ) ) - Basil Bourque

14

我同意 Ash Kim 的观点,如果你想保持理智,那么 Joda-Time 库是首选。

import org.joda.time.DateTime;

public static boolean dayIsYesterday(DateTime day) {
    DateTime yesterday = new DateTime().withTimeAtStartOfDay().minusDays(1);
    DateTime inputDay = day.withTimeAtStartOfDay();

    return inputDay.isEqual(yesterday);
}

在这个例子中,如果 DateTime 类型的 day 是昨天,则 dayIsYesterday(day) 函数将返回 true


Joda-Time中与午夜相关的类和方法基于一个有缺陷的想法,现在已经在Joda-Time 2.3中过时。请改用 withTimeAtStartOfDay 方法。 - Basil Bourque
另外,通常应该指定时区,而不是依赖于JVM的默认设置,因为这对于确定"今天"和"昨天"非常重要。 - Basil Bourque
现在 = "1/07/2016"。之前 = "12/06/1980"。当主题发起者询问如何检查日期是否为“昨天”时,您的previousFromYesteray将被设置为true。 - Dogcat
@Dogcat 说得好。已编辑。2011年的我需要做些解释。 - plowman
@plowman,还有更大的改进空间 :) 你使用了DateTime,这很合理,但对于这个特定的任务,JodaTime中的LocalDate将完美地完成。 - Dogcat

9

避免使用java.util.Date和.Calendar

接受的答案在技术上是正确的,但不够优化。java.util.Date和.Calendar类非常麻烦。避免使用它们,可以使用Joda-Time或新的java.time包(在Java 8中)。

时区

时区在日期时间处理中非常重要。如果忽略这个问题,JVM的默认时区将被应用。更好的做法是始终指定时区而不是依赖默认值。即使您想要默认值,也要明确调用getDefault

一天的开始由时区定义。在柏林比蒙特利尔早,所以“今天”和“昨天”的定义需要一个时区。

Joda-Time

Joda-Time 2.3中的示例代码。

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Berlin" );
DateTime today = DateTime.now( timeZone ); 

一种确定昨天的方法是转换为LocalDate对象。另一种方法是将“昨天”表示为时间跨度,如此处所示。我们定义该跨度从昨天的第一个时刻到今天的第一个时刻之前,但不包括今天的第一个时刻。这种方法被称为“半开放”,即开始时间是包含在内的,结束时间是排除在外的。 减去一天以到达昨天(或前一天)。
DateTime yesterdayStartOfDay = today.minusDays( 1 ).withTimeAtStartOfDay();
Interval yesterdayInterval = new Interval( yesterdayStartOfDay, today.withTimeAtStartOfDay() );

将目标java.util.Date对象转换为Joda-Time DateTime对象。将时区应用于该新对象,而不是依赖于应用JVM的默认时区。从技术上讲,在这种情况下时区是无关紧要的,但包括时区是一个好习惯。
DateTime target = new DateTime( myJUDate, timeZone );

测试目标是否落在昨天区间内。
boolean isYesterday = yesterdayInterval.contains( target );

显然,这种半开放时间跨度的方法不仅适用于“昨天”,还适用于“本周”,“上个月”等等。
更新:Joda-Time项目现在处于维护模式。团队建议迁移到java.time类。请参阅Przemek的正确答案中的java.time解决方案

关于java.time

java.time框架内置于Java 8及更高版本中。这些类代替了麻烦的旧版遗留日期时间类,例如java.util.Date, CalendarSimpleDateFormat

想了解更多,请参阅Oracle教程。在Stack Overflow上搜索许多示例和解释。规范是JSR 310

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

你可以直接使用 java.time 对象与数据库进行交互。使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。不需要字符串,也不需要 java.sql.* 类。Hibernate 5 和 JPA 2.2 支持 java.time
在哪里获取 java.time 类?

3

不要设置日历,试试这个方法:

public static void main(String[] args) {
    int DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
    Date date = new Date();
    SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy");
    String prevDate = dateFormat.format(date.getTime() - DAY_IN_MILLIS);
    String currDate = dateFormat.format(date.getTime());
    String nextDate = dateFormat.format(date.getTime() + DAY_IN_MILLIS);
    System.out.println("Previous date: " + prevDate);
    System.out.println("Current date: " + currDate);
    System.out.println("Next date: " + nextDate);
  }

这样可以让您在日历上前后移动。

然后,您只需将getDateFromLine(line)与prevDate值或其他您喜欢的值进行比较即可。


1
我发现使用这种方式测试该方法有些困惑。
 Calendar now = new GregorianCalendar(2000, 1, 1);
 now.add(Calendar.DATE, -1);

 SimpleDateFormat format= new SimpleDateFormat("yyyy-MM-dd");
 System.out.println(format.format(now.getTime()));

我的期望是打印出1999-12-31,但实际上是2001-1-31。我猜测Calendar.add()只处理月份中的天数。
但实际错误在于我创建Calendar对象的方式。在Calendar中,月份从0开始计算,变量now的值为2001-2-1,我太自负了以至于没有打印它,当我发现有问题时才意识到。正确的创建Calendar的方法是:
 Calendar now = new GregorianCalendar(2000, Calendar.JANUARY, 1);

日历对于一个曾经的C#程序员来说太奇怪了 :(

1
最受欢迎的答案使用已经过时的java.util日期时间API,这是在2010年提出问题时正确的做法。 在2014年3月,java.time API取代了易错的遗留日期时间API。 自那时以来,强烈建议使用这种现代日期时间API。

获取昨天日期的三种方法:

有三种方法:

  1. LocalDate#minusDays(long daysToSubtract)
  2. LocalDate#minus(long amountToSubtract, TemporalUnit unit): 它具有一些附加功能,例如您可以使用它将本地日期按天、周、月、年等减少。
  3. LocalDate#minus(TemporalAmount amountToSubtract): 您可以指定要减去的Period(或任何其他实现TemporalAmount的类型)。

一旦您获得了昨天的日期,就可以使用LocalDate#isEqual检查它是否等于给定的本地日期。

Demo:

import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;

public class Main {
    public static void main(String[] args) {
        // Note: Replace the default ZoneId with the required ZoneId e.g. ZoneId.of("Europe/London")
        LocalDate today = LocalDate.now(ZoneId.systemDefault());
        System.out.println(today);

        // Decrementing by one day
        LocalDate yesterday = today.minusDays(1);
        System.out.println(yesterday);

        // Alternatively
        yesterday = today.minus(1, ChronoUnit.DAYS);
        System.out.println(yesterday);

        // or
        yesterday = today.minus(Period.ofDays(1));
        System.out.println(yesterday);

        // A sample LocalDate
        LocalDate givenDate = LocalDate.of(2022, 10, 20);

        // Check for equality
        System.out.println(givenDate.isEqual(yesterday));
        givenDate = yesterday;
        System.out.println(givenDate.isEqual(yesterday)); // true
    }
}

输出:

2022-11-13
2022-11-12
2022-11-12
2022-11-12
false
true

如何从传统的日期时间API切换到现代API?

您可以使用 Date#toInstant 将 java-util-date 实例从传统的日期时间API切换到现代API。一旦您有了一个 Instant,就可以轻松获得其他日期时间类型的 java.time API。 Instant 表示时间点,与时区无关,即它表示UTC中的日期时间(通常显示为Z,代表Zulu时间,具有+00:00ZoneOffset)。

演示

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        Date date = new Date();
        Instant instant = date.toInstant();
        System.out.println(instant);

        ZonedDateTime zdt = instant.atZone(ZoneId.of("Asia/Kolkata"));
        System.out.println(zdt);

        OffsetDateTime odt = instant.atOffset(ZoneOffset.of("+05:30"));
        System.out.println(odt);
        // Alternatively, using time-zone
        odt = instant.atZone(ZoneId.of("Asia/Kolkata")).toOffsetDateTime();
        System.out.println(odt);

        LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Kolkata"));
        System.out.println(ldt);
        // Alternatively,
        ldt = instant.atZone(ZoneId.of("Asia/Kolkata")).toLocalDateTime();
        System.out.println(ldt);
    }
}

输出:

2022-11-13T21:08:36.538Z
2022-11-14T02:38:36.538+05:30[Asia/Kolkata]
2022-11-14T02:38:36.538+05:30
2022-11-14T02:38:36.538+05:30
2022-11-14T02:38:36.538
2022-11-14T02:38:36.538

请从日期时间教程了解更多关于现代日期时间API的内容。


1

大致上是这样的:

         Calendar c1 = Calendar.getInstance();
         Date d1 = new Date(/* control time */);
         c1.setTime(d1);

        //current date
        Calendar c2 = Calendar.getInstance();

        int day1=c1.get(Calendar.DAY_OF_YEAR);
        int day2=c2.get(Calendar.DAY_OF_YEAR);

       //day2==day1+1 

还应该同时比较年份的值。 - Greg Case
哦,是的,我忘记了年份。 - Inv3r53

0

请随意重用这些扩展方法。它们受到此帖子答案的很大影响。顺便感谢大家!

fun Date.isSameDay(other: Date): Boolean {
    val thisCalendar = Calendar.getInstance()
    val otherCalendar = Calendar.getInstance()
    thisCalendar.time = this
    otherCalendar.time = other
    return thisCalendar.get(Calendar.YEAR) == otherCalendar.get(Calendar.YEAR)
            && thisCalendar.get(Calendar.DAY_OF_YEAR) == otherCalendar.get(Calendar.DAY_OF_YEAR)
}

fun Date.isYesterday(other: Date): Boolean {
    val yesterdayCalendar = Calendar.getInstance()
    yesterdayCalendar.time = other
    yesterdayCalendar.add(Calendar.DAY_OF_YEAR, -1)

    return yesterdayCalendar.time.isSameDay(this)
}

这些可怕的日期时间类早在多年前就被 JSR 310 中定义的现代 java.time 类所取代。 - Basil Bourque
不要讨厌它们。在Android上,除非你只支持Android 26及以上版本或依赖于第三方库,否则你必须处理这些可怕的日期时间类。让我们尊重那些可怜的旧的和弃用的类。它们仍然是有用的。 - Roc Boronat
不需要使用可怕的遗留类。Gradle插件4现在提供了“API解糖”功能,以便在旧版Android上使用java.time类的子集。如果这对您来说还不够,可以使用在ThreeTenABP库中适配Android的ThreeTen-Backport项目。请参见我的答案底部的链接。 - Basil Bourque
是的,我正在将ThreeTenABP库添加到项目中,但后来意识到我不想再添加另一个第三方库来进行比较...关于Gradle插件4 API desugaring,也许我会尝试!但是这段代码通过了我编写的大量测试以检查该功能,所以...也许我不需要。但或许在另一个项目中会用到。非常感谢您的评论! - Roc Boronat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接