Java:如何检查日期是否在一定范围内?


77

我有一系列包含开始日期和结束日期的范围。我想检查日期是否在该范围内。

Date.before()和Date.after()似乎有点尴尬。我真正需要的是这样的伪代码:

boolean isWithinRange(Date testDate) {
    return testDate >= startDate && testDate <= endDate;
}

不确定是否相关,但是我从数据库中提取的日期带有时间戳。



1
仅供参考,麻烦的旧日期时间类(例如java.util.Datejava.util.Calendar现在已被遗留)由java.time类取代。请参见Oracle教程
罗勒·布尔克

Answers:


141
boolean isWithinRange(Date testDate) {
   return !(testDate.before(startDate) || testDate.after(endDate));
}

对我来说似乎并不尴尬。请注意,我是这样写的,而不是

return testDate.after(startDate) && testDate.before(endDate);

因此,即使testDate等于最终情况之一,它也可以正常工作。


4
使用此代码,以下内容无效:date is 01-01-2014 and the range is from 01-01-2014 to 31-01-2014如何使其有效?
沙河2014年

4
@Shahe我要做的第一件事是检查日期中的时间部分。您可能date是在清晨,然后startDate是清晨。
Paul Tomblin,2014年

这个否定的把戏还是七年后还是不错的。:)
Tosz

当testDate是今天的当前日期时,它不起作用。如何在上述建议的解决方案中处理这种情况。
斯威夫特(Swift)2013年

@SwiftDate包含时间(小时,分钟,秒和毫秒)。因此,如果您要测试某件事是否为“今天”,则可以将Date对象设置为在0:00:00.000的时间为“ today”,而将另一个日期为0:00:00.000的“明天”。我Calendar为此使用类,但是我敢肯定,如果您使用JodaTime或较新的基本Java类,则还有更好的方法。
Paul Tomblin

26

tl; dr

ZoneId z = ZoneId.of( "America/Montreal" );  // A date only has meaning within a specific time zone. At any given moment, the date varies around the globe by zone.
LocalDate ld = 
    givenJavaUtilDate.toInstant()  // Convert from legacy class `Date` to modern class `Instant` using new methods added to old classes.
                     .atZone( z )  // Adjust into the time zone in order to determine date.
                     .toLocalDate();  // Extract date-only value.

LocalDate today = LocalDate.now( z );  // Get today’s date for specific time zone.
LocalDate kwanzaaStart = today.withMonth( Month.DECEMBER ).withDayOfMonth( 26 );  // Kwanzaa starts on Boxing Day, day after Christmas.
LocalDate kwanzaaStop = kwanzaaStart.plusWeeks( 1 );  // Kwanzaa lasts one week.
Boolean isDateInKwanzaaThisYear = (
    ( ! today.isBefore( kwanzaaStart ) ) // Short way to say "is equal to or is after".
    &&
    today.isBefore( kwanzaaStop )  // Half-Open span of time, beginning inclusive, ending is *exclusive*.
)

半开

日期时间工作通常采用“半开放”方法来定义时间跨度。刚开始是包容性的,而结局是独家。因此,从星期一开始的一周将运行至(但不包括)下一个星期一。

java.time

Java 8和更高版本java.time内置了该框架。取代旧的麻烦类,包括java.util.Date/.CalendarSimpleDateFormat。受成功的Joda-Time库启发。由JSR 310定义。由ThreeTen-Extra项目扩展。

一个Instant是在时间轴上的一个时刻UTC纳秒的分辨率。

Instant

将您的java.util.Date对象转换为Instant对象。

Instant start = myJUDateStart.toInstant();
Instant stop = …

如果java.sql.Timestamp通过JDBC从数据库获取对象,请类似的方式转换为java.time.Instant。Ajava.sql.Timestamp已经在UTC中,因此无需担心时区。

Instant start = mySqlTimestamp.toInstant() ;
Instant stop = …

获取当前时刻进行比较。

Instant now = Instant.now();

使用方法isBefore,isAfter和equals进行比较。

Boolean containsNow = ( ! now.isBefore( start ) ) && ( now.isBefore( stop ) ) ;

LocalDate

也许您只想使用日期而不是时间。

LocalDate级表示日期,只值,没有时间的天,没有时区。

LocalDate start = LocalDate.of( 2016 , 1 , 1 ) ;
LocalDate stop = LocalDate.of( 2016 , 1 , 23 ) ;

要获取当前日期,请指定一个时区。在任何给定时刻,今天的日期都会随时区变化。例如,巴黎的新天要比蒙特利尔早。

LocalDate today = LocalDate.now( ZoneId.of( "America/Montreal" ) );

我们可以使用isEqualisBeforeisAfter方法比较。在日期时间工作中,我们通常使用半开放式方法,其中时间跨度的开始是包含在内的,而结束时间是排除的

Boolean containsToday = ( ! today.isBefore( start ) ) && ( today.isBefore( stop ) ) ;

Interval

如果选择将ThreeTen-Extra库添加到项目中,则可以使用Interval该类来定义时间跨度。该类提供了一些方法来测试间隔是否包含邻接封闭重叠 其他日期时间/间隔。

Interval类适用于Instant对象。该Instant级表示时间轴上的时刻UTC,分辨率为纳秒(最多小数的9个位数)。

LocalDate通过指定获取时区的时区,我们可以将调整为特定时刻,即一天中的第一时刻ZonedDateTime。从那里我们可以通过提取来返回UTC Instant

ZoneId z = ZoneId.of( "America/Montreal" );
Interval interval = 
    Interval.of( 
        start.atStartOfDay( z ).toInstant() , 
        stop.atStartOfDay( z ).toInstant() );
Instant now = Instant.now();
Boolean containsNow = interval.contains( now );

关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar,和SimpleDateFormat

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

要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

在哪里获得java.time课程?

ThreeTen-EXTRA项目扩展java.time与其他类。该项目是将来可能添加的试验场java.time。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多


16

那是正确的方法。日历的工作方式相同。根据您的示例,我能为您提供的最好的服务是:

boolean isWithinRange(Date testDate) {
    return testDate.getTime() >= startDate.getTime() &&
             testDate.getTime() <= endDate.getTime();
}

Date.getTime()返回格林尼治标准时间1970年1月1日00:00:00以来的毫秒数,它很长,因此很容易比较。


9

考虑使用Joda Time。我喜欢这个库,希望它能取代现有的Java Date和Calendar类的当前混乱局面。日期处理正确。

编辑:现在不再是2009年了,Java 8已经问世了。如Basil Bourque所述,请使用Java 8内置的基于Joda Time的java.time类。在这种情况下,您需要使用Period类,这是有关如何使用它的Oracle教程


3
您是否相信如果所有人都想做的那样,按照发问者的指示进行简单的比较,那么将Joda Time引入到项目中是
不合时宜的

3
我认为你的意思是过大。对于大多数图书馆的增加,我会同意你的看法。但是,Joda Time非常轻巧,并且由于其表示形式的不变性,可以帮助您编写更正确的日期处理代码,因此在我看来,引入它并不是一件坏事。
本·哈迪

25
另外,请不要忘记StackOverflow规则#46:如果有人提到Java中的日期或时间,则建议有人建议改用Joda Time。不相信我吗 尝试找到一个没有的问题。
Paul Tomblin,2009年

2
甚至Sun / Oracle也放弃了与最早的Java版本捆绑在一起的旧的日期时间类,并同意用java.time框架(由Joda-Time启发)代替旧的日期时间类。那些旧类的设计不佳,并且被证明是麻烦和令人困惑的。
罗勒·布尔克

4

一种简单的方法是将日期转换为1970年1月1日之后的毫秒数(使用Date.getTime()),然后比较这些值。


1

为了涵盖我的情况->我有一个范围开始和结束日期,并且日期列表可以部分地位于提供的范围内,也可以完全(重叠)。

测试涵盖的解决方案:

/**
 * Check has any of quote work days in provided range.
 *
 * @param startDate inclusively
 * @param endDate   inclusively
 *
 * @return true if any in provided range inclusively
 */
public boolean hasAnyWorkdaysInRange(LocalDate startDate, LocalDate endDate) {
    if (CollectionUtils.isEmpty(workdays)) {
        return false;
    }

    LocalDate firstWorkDay = getFirstWorkDay().getDate();
    LocalDate lastWorkDay = getLastWorkDay().getDate();

    return (firstWorkDay.isBefore(endDate) || firstWorkDay.equals(endDate))
            && (lastWorkDay.isAfter(startDate) || lastWorkDay.equals(startDate));
}

1

以来 Java 8

注意:Date不推荐使用所有(但只有一个)构造函数。Date不推荐使用的大多数方法。参考:日期:不推荐使用的方法。除Date::from(Instant)静态方法外,所有方法均已弃用。

因此,由于Java 8考虑使用Instant (不可变,线程安全,leap秒感知)类型而不是Date。REF:即时

  static final LocalTime MARKETS_OPEN = LocalTime.of(07, 00);
  static final LocalTime MARKETS_CLOSE = LocalTime.of(20, 00);

    // Instant utcTime = testDate.toInstant();
    var bigAppleTime = ZonedDateTime.ofInstant(utcTime, ZoneId.of("America/New_York"));

在包含范围内:

    return !bigAppleTime.toLocalTime().isBefore(MARKETS_OPEN)
        && !bigAppleTime.toLocalTime().isAfter(MARKETS_CLOSE);

在独家范围内:

    return bigAppleTime.toLocalTime().isAfter(MARKETS_OPEN)
        && bigAppleTime.toLocalTime().isBefore(MARKETS_CLOSE);

0

不在乎哪个日期边界是哪个。

Math.abs(date1.getTime() - date2.getTime()) == 
    Math.abs(date1.getTime() - dateBetween.getTime()) + Math.abs(dateBetween.getTime() - date2.getTime());

0

你可以这样使用

Interval interval = new Interval(date1.getTime(),date2.getTime());
Interval interval2 = new Interval(date3.getTime(), date4.getTime());
Interval overlap = interval.overlap(interval2);
boolean isOverlap = overlap == null ? false : true

0

这对我来说很清楚

// declare calendar outside the scope of isWithinRange() so that we initialize it only once
private Calendar calendar = Calendar.getInstance();

public boolean isWithinRange(Date date, Date startDate, Date endDate) {

    calendar.setTime(startDate);
    int startDayOfYear = calendar.get(Calendar.DAY_OF_YEAR); // first day is 1, last day is 365
    int startYear = calendar.get(Calendar.YEAR);

    calendar.setTime(endDate);
    int endDayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
    int endYear = calendar.get(Calendar.YEAR);

    calendar.setTime(date);
    int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
    int year = calendar.get(Calendar.YEAR);

    return (year > startYear && year < endYear) // year is within the range
            || (year == startYear && dayOfYear >= startDayOfYear) // year is same as start year, check day as well
            || (year == endYear && dayOfYear < endDayOfYear); // year is same as end year, check day as well

}

2
(A)Calendar不是线程安全的。因此,维护一个可由多个线程访问的实例是有风险的。(B)麻烦的旧类(例如java.util.Datejava.util.Calendar现在是legacy),已由java.time类取代。请参阅Oracle教程
罗勒·布尔克

@BasilBourque,感谢您指出问题。看来Android尚不支持新的DateTime类。:/
Dhunju_likes_to_Learn

1
大部分的java.time功能后移植到Java 6和7 ThreeTen,反向移植,并进一步用于安卓ThreeTenABP(见如何使用......)。并且,此问题与Android无关。
罗勒·布尔克

0
  public class TestDate {

    public static void main(String[] args) {
    // TODO Auto-generated method stub

    String fromDate = "18-FEB-2018";
    String toDate = "20-FEB-2018";

    String requestDate = "19/02/2018";  
    System.out.println(checkBetween(requestDate,fromDate, toDate));
}

public static boolean checkBetween(String dateToCheck, String startDate, String endDate) {
    boolean res = false;
    SimpleDateFormat fmt1 = new SimpleDateFormat("dd-MMM-yyyy"); //22-05-2013
    SimpleDateFormat fmt2 = new SimpleDateFormat("dd/MM/yyyy"); //22-05-2013
    try {
     Date requestDate = fmt2.parse(dateToCheck);
     Date fromDate = fmt1.parse(startDate);
     Date toDate = fmt1.parse(endDate);
     res = requestDate.compareTo(fromDate) >= 0 && requestDate.compareTo(toDate) <=0;
    }catch(ParseException pex){
        pex.printStackTrace();
    }
    return res;
   }
 }

2
请添加一些单词以简要描述/解释此代码为何以及如何回答该问题。
Yannis

1
很高兴您能有所贡献,谢谢。(1)请不要教年轻人使用已经过时且臭名昭著的SimpleDateFormat课程。至少不是第一选择。也并非毫无保留。今天,我们java.time在现代Java日期和时间API及其功能方面有了很多改进DateTimeFormatter。(2)仅代码答案很少有帮助。我们都从附带的解释中学到了东西。
Ole VV

1
在2018年建议使用这些几年前被取代的非常麻烦的类是不明智的建议。此外,您的代码忽略了时区的关键问题。
罗勒·布尔克

-1

您的逻辑会很好。正如您提到的,您从数据库获取的日期都在时间戳中,您只需要先将时间戳转换为日期,然后再使用此逻辑即可。

同样不要忘记检查空日期。

在这里,我分享了一些从时间戳转换为日期的信息。

公共静态日期convertTimeStamptoDate(String val)引发异常{

    DateFormat df = null;
    Date date = null;

    try {
        df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        date = df.parse(val);
        // System.out.println("Date Converted..");
        return date;
    } catch (Exception ex) {
        System.out.println(ex);
        return convertDate2(val);
    } finally {
        df = null;
        date = null;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.