【问题标题】:How do I check if a date is within a certain range?Java:如何检查日期是否在一定范围内?
【发布时间】:2010-10-04 09:32:06
【问题描述】:

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

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

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

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

【问题讨论】:

标签: java date


【解决方案1】:
boolean isWithinRange(Date testDate) {
   return !(testDate.before(startDate) || testDate.after(endDate));
}

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

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

所以即使 testDate 完全等于最终情况之一,它也可以工作。

【讨论】:

  • 使用此代码,以下代码无效:date is 01-01-2014 and the range is from 01-01-2014 to 31-01-2014 如何使其有效?
  • @Shahe 我要做的第一件事就是检查日期的时间部分。您的 date 很可能是清晨,而 startDate 在它之后。
  • 这个技巧与否定或七年后仍然很好。 :)
  • 当 testDate 是今天的当前日期时它不起作用。如何在上述建议的解决方案中处理这种情况。
  • @Swift Date 包含时间(小时、分钟、秒和毫秒)。因此,如果您想测试某事是否是“今天”,那么您可以在时间 0:00:00.000 创建一个“今天”的 Date 对象,并在时间 0:00:00.000 创建一个“明天”的 Date 对象。我为此使用了 Calendar 类,但如果您使用 JodaTime 或更新的 Java 基类,我相信会有更好的方法。
【解决方案2】:

tl;博士

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*.
)

半开

日期时间工作通常采用“半开放”方法来定义时间跨度。开头是inclusive,而结尾是exclusive。因此,从星期一开始的一周一直持续到但不包括下一个星期一。

java.time

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

InstantUTC 时间线上的一个时刻,分辨率为纳秒级。

Instant

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

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

如果通过 JDBC 从数据库中获取 java.sql.Timestamp 对象,convert to java.time.Instant 以类似的方式。 java.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 类来定义时间跨度。该类提供了测试间隔 containsabutsenclosesoverlaps 是否为其他日期时间/间隔的方法。

Interval 类适用于 Instant 对象。 Instant 类代表UTC 时间线上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。

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

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 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

从哪里获得java.time 类?

ThreeTen-Extra 项目扩展了 java.time 并添加了其他类。该项目是未来可能添加到java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

    【解决方案3】:

    这是正确的方法。日历的工作方式相同。我能给你的最好的(基于你的例子)是这样的:

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

    Date.getTime() 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来的毫秒数,并且很长,因此很容易比较。

    【讨论】:

      【解决方案4】:

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

      编辑:现在已经不是 2009 年了,Java 8 已经出现很久了。使用 Java 8 内置的基于 Joda Time 的 java.time 类,正如上面提到的 Basil Bourque。在这种情况下,您需要 Period 类,这里是 Oracle's tutorial 以了解如何使用它。

      【讨论】:

      • 如果大家只想像提问者所说的那样做一个简单的比较,你不认为将 Joda Time 引入一个项目已经过时了吗?-
      • 我假设你的意思是矫枉过正。在大多数图书馆添加的情况下,我会同意你的看法。然而 Joda Time 非常轻巧,并且由于其表示的不变性,它可以帮助您编写更正确的日期处理代码,因此在我看来引入它并不是一件坏事。
      • 我们不要忘记 StackOverflow 规则 #46:如果有人在 Java 中提到日期或时间,则必须有人建议切换到 Joda Time。不相信我?试着找出一个没有的问题。
      • 甚至 Sun/Oracle 也放弃了与早期 Java 版本捆绑的旧日期时间类,同意用 java.time 框架(受 Joda-Time 启发)取代旧类。那些旧的类设计得很糟糕,并且被证明是麻烦和混乱的。
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        Java 8

        注意:除一个之外,Date 的所有构造函数均已弃用。大多数Date 的方法已被弃用。参考:Date: Deprecated Methods。除了Date::from(Instant),所有静态方法都已弃用。

        因此,由于 java 8 考虑使用 Instant(不可变、线程安全、闰秒感知) 类型而不是 Date。编号:Instant

          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);
        

        【讨论】:

        • 知道闰秒,嗯,有点,不完全,真的。
        【解决方案7】:

        这对我来说更清楚,

        // 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
        
        }
        

        【讨论】:

        【解决方案8】:

        为了涵盖我的案例 -> 我有一个范围开始和结束日期,日期列表可以部分在提供的范围内,也可以完全(重叠)。

        包含测试的解决方案:

        /**
         * 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));
        }
        

        【讨论】:

          【解决方案9】:

          如果您想与LocalDate 进行包容性比较,请使用此代码。

          LocalDate from = LocalDate.of(2021,8,1);
          LocalDate to = LocalDate.of(2021,8,1);
          LocalDate current = LocalDate.of(2021,8,1);
          
          boolean isDateInRage = ( ! (current.isBefore(from.minusDays(1)) && current.isBefore(to.plusDays(1))) );
          

          【讨论】:

          • 有点复杂,也许正因为如此也不太正确。我将current更改为LocalDate.of(2021, 7, 31);,仍然得到true,这是不对的。使用LocalDateisBefore() 是个好主意。
          【解决方案10】:

          不关心哪个日期边界是哪个。

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

          【讨论】:

            【解决方案11】:

            你可以这样使用

            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
            

            【讨论】:

              【解决方案12】:
                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;
                 }
               }
              

              【讨论】:

              • 请添加一些词来简要描述/解释这段代码为什么以及如何回答这个问题。
              • 很高兴您愿意贡献,谢谢。 (1)请不要教年轻人使用陈旧且臭名昭著的SimpleDateFormat类。至少不是第一选择。而且不是没有任何保留。今天我们在java.time, the modern Java date and time API 和它的DateTimeFormatter 中做得更好。 (2) 只有代码的答案很少有帮助。我们都从随附的解释中了解到。
              • 在 2018 年建议使用这些多年前被取代的非常麻烦的课程是一个糟糕的建议。此外,您的代码忽略了时区的关键问题。
              【解决方案13】:

              你的逻辑可以正常工作。正如您提到的,您从数据库中获取的日期在时间戳中,您只需先将时间戳转换为日期,然后使用此逻辑。

              别忘了检查空日期。

              这里 m 分享一点以将时间戳转换为日期。

              public static Date convertTimeStamptoDate(String val) throws Exception {

                  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;
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 2015-02-13
                • 2010-11-01
                • 1970-01-01
                • 1970-01-01
                • 2017-01-28
                • 1970-01-01
                • 2016-02-15
                • 1970-01-01
                相关资源
                最近更新 更多