Java: як мені перевірити, чи є дата в певному діапазоні?


79

У мене є ряд діапазонів із датами початку та датами закінчення. Я хочу перевірити, чи не перебуває дата в цьому діапазоні.

Date.before () та Date.after () здаються трохи незручними у використанні. Мені дійсно потрібно щось на зразок цього псевдокоду:

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

Не впевнений, чи це актуально, але дати, які я знімаю з бази даних, мають позначки часу.


1
Подібне запитання з кількома відповідями: Як я можу визначити, чи є дата між двома датами в Java?
Василь Бурк

1
FYI, клопітні старі класи часу та часу, такі як java.util.Dateі java.util.Calendarзараз є застарілими , витіснені класами java.time . Див. Підручник Oracle .
Basil Bourque

Відповіді:


145
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як ви можете зробити його дійсним?
Шахе

4
@ Шахе перше, що я б зробив, це перевірити часові частини ваших дат. Швидше за все, dateце рано вранці і startDateпісля нього.
Пол Томблін,

цей трюк з запереченням чи все ще приємний після семи років. :)
Tosz

Це не працює, коли дата тесту - сьогоднішня поточна дата. Як обробити цей сценарій у запропонованому вище рішення.
Свіфт

@Swift Dateмістить час (години, хвилини, секунди та мілісекунди). Отже, якщо ви хочете перевірити, чи є щось "сьогодні", то ви повинні зробити об'єкт Date, який "сьогодні" в момент часу 0: 00: 00.000, і той, що "завтра" в час 0: 00: 00.000. Я використовую Calendarклас для цього, але я впевнений, що є кращий спосіб, якщо ви використовуєте JodaTime або новіші базові класи Java.
Пол Томблін,

26

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

Напіввідкритий

Для роботи часу і часу зазвичай використовується підхід "напіввідкритий" для визначення періоду часу. Початок є інклюзивним, а кінець ексклюзивним . Отже, тиждень, що починається в понеділок, доходить до наступного понеділка, але не включає його.

java.time

Java 8 і пізніших версій поставляється із java.timeвбудованою структурою. Витісняє старі клопітні класи, включаючи java.util.Date/.Calendarі SimpleDateFormat. Натхненний успішною бібліотекою Joda-Time. Визначено JSR 310. Розширено проектом ThreeTen-Extra.

А Instant- це момент на часовій шкалі в UTC з наносекундною роздільною здатністю.

Instant

Перетворіть свої java.util.Dateоб’єкти на миттєві об’єкти.

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

Якщо ви отримуєте java.sql.Timestampоб'єкти через JDBC з бази даних, перетворіть у java.time.Instant подібним чином. A java.sql.Timestampвже в UTC, тому не потрібно турбуватися про часові пояси.

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

Отримайте поточний момент для порівняння.

Instant now = Instant.now();

Порівняйте, використовуючи методи isBefore, isAfter і дорівнює.

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

Ми можемо використовувати isEqual, isBeforeі isAfterметоди порівняння. У роботі з датою та часом ми зазвичай використовуємо підхід напіввідкритого типу, коли початок періоду часу є включним, а закінчення ексклюзивним .

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.Date, Calendar, і SimpleDateFormat.

Проект Joda-Time , який зараз перебуває в режимі технічного обслуговування , радить перейти на java.timeкласи.

Щоб дізнатись більше, див. Підручник Oracle . І шукайте в Stack Overflow багато прикладів та пояснень. Специфікація - JSR 310 .

Де взяти java.timeкласи?

  • Java SE 8 і SE 9 і пізніших версій
  • Вбудований.
  • Частина стандартного Java API із комплексною реалізацією.
  • Java 9 додає деякі незначні функції та виправлення.
  • Java SE 6 і SE 7
  • Значна частина java.timeфункціональних можливостей перенесена на Java 6 і 7 у ThreeTen-Backport .
  • Android
  • Проект ThreeTenABP адаптує ThreeTen-Backport (згаданий вище) для Android.
  • Дивіться Як користуватися… .

Проект ThreeTen-Extra поширюється java.timeна додаткові класи. Цей проект є полігоном для можливих майбутніх доповнень до java.time. Ви можете знайти деякі корисні класи тут , такі як Interval, YearWeek, YearQuarter, і більш .


16

Це правильний спосіб. Календарі працюють однаково. Найкраще, що я міг би вам запропонувати (на основі вашого прикладу), це:

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

Date.getTime () повертає кількість мілісекунд, починаючи з 1/1/1970 00:00:00 GMT, і є довгим, тому його легко порівняти.


9

Подумайте про використання Joda Time . Мені подобається ця бібліотека, і я хотів би, щоб вона замінила поточний жахливий безлад, який є існуючими класами Java Date і Calendar. Це обробка дат зроблена правильно.

РЕДАГУВАТИ: Це вже не 2009 рік, і Java 8 випускається віками. Використовуйте вбудовані в Java 8 класи Java.time, які базуються на Joda Time, як згадував вище Василь Бурк. У цьому випадку вам потрібен клас Period, і ось посібник Oracle, як ним користуватися.


3
чи не вірите ви, що введення Joda Time у проект з часом, якщо все, що хочеться, - це зробити просте порівняння, як вказав запитувач? -
hhafez

3
Я припускаю, ви маєте на увазі надмірну кількість. У випадку більшості доповнень бібліотеки я б погодився з вами. Однак Joda Time досить легкий і допомагає вам писати більш правильний код обробки дат завдяки незмінності його подань, тому, на мій погляд, це непогана річ.
Ben Hardy

25
І не забуваємо про правило StackOverflow # 46: Якщо хтось згадує дати чи час у Java, обов’язково для когось запропонувати перейти на Joda Time. Не вірите мені? Спробуйте знайти запитання, якого немає.
Пол Томблін,

2
Навіть Sun / Oracle відмовився від старих класів часу та часу, що входять до складу найдавніших версій Java, погодившись витіснити старі класи з фреймворком java.time (натхненний Joda-Time). Ці старі класи були погано розроблені, і виявились складними та заплутаними.
Василь Бурк

4

Простий спосіб - перетворити дати в мілісекунди після 1 січня 1970 року (використовуйте Date.getTime ()), а потім порівняти ці значення.


2

Оскільки Java 8

ПРИМІТКА. Усі Dateконструктори , крім одного, застаріли. Більшість Dateметодів Росії застаріли. REF: Дата: Застарілі методи . Усі, але Date::from(Instant)статичні методи застаріли.

Отже, оскільки Java 8 розглядає можливість використання типу Instant (незмінний, безпечний для потоків, високоскорості секунди), а не 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"));

в межах діапазону INCLUSIVE:

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

в межах ЕКСКЛЮЗИВ:

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

Другий стрибок усвідомлюю, ну, трохи, не зовсім, насправді.
Оле В. В.

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

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.Dateі java.util.Calendarзараз є застарілими , витіснені класами java.time . Див. Підручник Oracle .
Basil Bourque

@BasilBourque, дякую, що вказали на проблеми. Схоже, Android ще не підтримує новий клас DateTime. : /
Dhunju_likes_to_Learn

1
Значна частина функцій java.time переноситься назад на Java 6 і 7 у ThreeTen-Backport і надалі адаптується до Android у 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
Будь ласка, додайте кілька слів, щоб коротко описати / пояснити, чому і як цей код відповідає на питання.
Янніс,

1
Приємно, що ви хочете внести свій внесок, дякую. (1) Будь ласка, не вчіть молодих користуватися давно застарілим і, як відомо, неприємним SimpleDateFormatзаняттям. Принаймні не як перший варіант. І не без будь-яких застережень. Сьогодні у нас набагато кращий java.timeсучасний API дати та часу Java та його DateTimeFormatter. (2) Відповіді лише на код дуже рідко допомагають. Із супровідного пояснення ми всі дізнаємось.
Оле В. В.

1
Запропонувати в 2018 році використання цих страшних проблемних класів, які були замінені багато років тому, є поганою порадою. Крім того, ваш код ігнорує важливу проблему часового поясу.
Василь Бурк

0

якщо ви хочете інклюзивне порівняння, 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, що не може бути правильно. Використовувати LocalDateі isBefore()є хорошою ідеєю.
Оле В. В.

-1

ваша логіка спрацювала б нормально. Як ви вже згадували дати, які ви отримуєте з бази даних, у мітці часу, вам просто потрібно спочатку перетворити мітку часу на дату, а потім використовувати цю логіку.

Також не забудьте перевірити наявність нульових дат.

тут я обмінююсь трохи, щоб перетворити з Timestamp на сьогоднішній день.

відкрита статична дата convertTimeStamptoDate (рядок 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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.