Порівняння двох java.util.Dates, щоб побачити, чи є вони в один і той же день


250

Мені потрібно порівняти два Dates (наприклад, date1і date2) і придумати те, boolean sameDayщо відповідає дійсності двох Dates в той же день, і false, якщо вони не є.

Як я можу це зробити? Тут, мабуть, виникає вихор плутанини ... і я хотів би уникати інших залежностей поза JDK, якщо це можливо.

уточнити: якщо date1і date2ділити один і той же рік, місяць і день, то sameDayце правда, інакше це неправда. Я розумію, що для цього потрібні знання часового поясу ... було б добре пройти в часовому поясі, але я можу жити з GMT або місцевим часом, доки я знаю, що таке поведінка.

ще раз, щоб уточнити:

date1 = 2008 Jun 03 12:56:03
date2 = 2008 Jun 03 12:59:44
  => sameDate = true

date1 = 2009 Jun 03 12:56:03
date2 = 2008 Jun 03 12:59:44
  => sameDate = false

date1 = 2008 Aug 03 12:00:00
date2 = 2008 Jun 03 12:00:00
  => sameDate = false

Просто для уточнення - ви хочете знати, чи потрапляють два об’єкти дати в один і той же день тижня?
Роб Хайзер

Ви хочете порівняти повну дату (день, місяць, рік) або лише місяць місяця?
XpiritO

1
@Rob: ні, в той же день / місяць / рік ... уточню.
Jason S

то чому б ти не використав "рівний"?
XpiritO

7
Тому що вони не рівні, якщо година / хвилина / секунда різні.
Jason S

Відповіді:


416
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.setTime(date1);
cal2.setTime(date2);
boolean sameDay = cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) &&
                  cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR);

Зауважте, що "той самий день" не є таким простим поняттям, як звучить, коли можуть бути задіяні різні часові пояси. Код, наведений вище, для обох дат обчислює день щодо часового поясу, який використовується комп'ютером, на якому він працює. Якщо це не те, що вам потрібно, вам потрібно передати відповідний часовий пояс (и) на Calendar.getInstance()дзвінки, після того як ви вирішили, що саме ви маєте на увазі під «тим же днем».

І так, Joda Time зробив LocalDateби цю справу набагато чистішою та легшою (хоча ті самі труднощі, що стосуються часових поясів, були б)


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

1
@Jason Це може бути, а може і не бути хорошою ідеєю. Основна проблема Календаря полягає в тому, що це дуже важкий клас з великою кількістю внутрішнього стану, частина з яких використовується в його рівній () реалізації. Якщо ви не підготуєте дати для рівності і не введете їх у HashMaps, вам слід добре.
Майкл Боргвардт

класно, дякую, я просто використовую тут задану логіку порівняння поточного та попереднього дня. функції "рівний" та "хеш-код" ніколи не повинні викликатись.
Jason S

1
@UmerHayat: код порівнює день року, а не день місяця. Одне менш необхідне порівняння, коротший код
Майкл Боргвардт

2
Пропозиція: Порівнюйте DAY_OF_YEAR спочатку, не доведеться перевіряти рік. Гаразд, це не так, як порівнювати інт - це дуже дорого, але ...
Мартін П.

332

Як щодо:

SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");
return fmt.format(date1).equals(fmt.format(date2));

Ви також можете встановити часовий пояс у SimpleDateFormat, якщо це необхідно.


2
(Я фактично використовую SimpleDateFormat в будь-якому випадку в моєму випадку, тому це видається доречним.)
Jason S

8
Я насправді здивований, бачачи це, але навіть з точки зору продуктивності, цей SimpleDateFormatметод насправді швидший, ніж інший, згаданий тут, за допомогою Calendars. В середньому це займає половину часу як Calendarметод. (Принаймні, в моїй системі). Кудо!
Майкл Плаутз

Більшість витрат на цю відповідь припадає на створення SimpleDateFormat. Покладіть його в ниткуМісцеве поле в одиночному, якщо ви хочете ще кращої продуктивності
Тьєррі

1
Я роблю це і у Свіфта. Дивно, як для такої простої речі потрібен той самий злом на більшості мов. C # є винятком - якщо (d1.Date == d2.Date) ...
alpsystems.com

2
Це некрасиво; здивований, побачивши такий хак, як це викликано.
Zsolt Safrany

162

Для цього я використовую пакет "apache commons lang" (а саме org.apache.commons.lang.time.DateUtils )

boolean samedate = DateUtils.isSameDay(date1, date2);  //Takes either Calendar or Date objects

2
Для цього використовується зовнішня залежність ... але це добре знати на майбутнє.
Jason S

20
Просто скопіюйте джерело і зателефонуйте йому за день :)
Антон Кузьмін

Але це викидає незаконне виключення аргументу, якщо будь-який з днів є недійсним, що не є ідеальним у деяких випадках.
raviraja

1
Як коментується stackoverflow.com/a/2517824/1665809, це рішення може бути невідповідним, якщо задіяні різні часові пояси.
mrod

24

Ви можете уникнути зовнішніх залежностей та результативності використання календаря, обчисливши номер Джуліанського дня для кожної дати та порівнявши їх

public static boolean isSameDay(Date date1, Date date2) {

    // Strip out the time part of each date.
    long julianDayNumber1 = date1.getTime() / MILLIS_PER_DAY;
    long julianDayNumber2 = date2.getTime() / MILLIS_PER_DAY;

    // If they now are equal then it is the same day.
    return julianDayNumber1 == julianDayNumber2;
}

4
Але майте на увазі, що це не враховує зміни тривалості дня через економію денного світла. Але тоді прямі Dates не інкапсулюють поняття часових поясів, тому немає можливості самостійно це виправити.
AyeJay

3
Це, безумовно, найшвидше рішення - велике спасибі, саме те, що я шукав; як я маю перевірити багато дат ...
Ridcully

2
Nitpick: date1.getTime () не повертає номер Джуліанського дня, а мілісекунди з 1970-01-01. Величезна різниця.
Пер Ліндберг

2
Це виконує порівняння у часовому поясі UTC. Якщо ви хотіли, щоб ваш місцевий часовий пояс чи якесь інше місце на планеті, ви не можете знати, чи отриманий результат правильний.
Оле ВВ

20

Joda-Time

Що стосується додавання залежності, я боюся, що java.util.Date & .Calendar насправді настільки поганий, що перше, що я роблю до будь-якого нового проекту, - це додати бібліотеку Joda-Time. У Java 8 ви можете використовувати новий пакет java.time, натхненний Joda-Time.

Ядром Joda-Time є DateTimeклас. На відміну від java.util.Date, він розуміє призначений йому часовий пояс ( DateTimeZone). При перетворенні з juDate призначте зону.

DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
DateTime dateTimeQuébec = new DateTime( date , zone );

LocalDate

Один із способів переконатися, що два дати-раз приземляються на одну і ту ж дату - це перетворення на LocalDateоб’єкти.

Ця конверсія залежить від призначеного часового поясу. Для порівняння LocalDateоб'єктів вони повинні бути перетворені з однією зоною.

Ось невеликий корисний метод.

static public Boolean sameDate ( DateTime dt1 , DateTime dt2 )
{
    LocalDate ld1 = new LocalDate( dt1 );
    // LocalDate determination depends on the time zone.
    // So be sure the date-time values are adjusted to the same time zone.
    LocalDate ld2 = new LocalDate( dt2.withZone( dt1.getZone() ) );
    Boolean match = ld1.equals( ld2 );
    return match;
}

Краще було б ще одним аргументом, що вказує часовий пояс, а не передбачає використання часового поясу першого об'єкта DateTime.

static public Boolean sameDate ( DateTimeZone zone , DateTime dt1 , DateTime dt2 )
{
    LocalDate ld1 = new LocalDate( dt1.withZone( zone ) );
    // LocalDate determination depends on the time zone.
    // So be sure the date-time values are adjusted to the same time zone.
    LocalDate ld2 = new LocalDate( dt2.withZone( zone ) );
    return ld1.equals( ld2 );
}

Струнне представлення

Інший підхід полягає у створенні рядкового подання частини дати кожної дати, а потім порівняння рядків.

Знову ж таки, призначений часовий пояс є вирішальним.

DateTimeFormatter formatter = ISODateTimeFormat.date();  // Static method.
String s1 = formatter.print( dateTime1 );
String s2 = formatter.print( dateTime2.withZone( dt1.getZone() )  );
Boolean match = s1.equals( s2 );
return match;

Проміжок часу

Узагальнене рішення - визначити проміжок часу, а потім запитати, чи містить цей проміжок ціль. Цей приклад код знаходиться в Joda-Time 2.4. Зауважте, що класи, пов'язані з "опівночі", застарілі. Замість цього використовуйте withTimeAtStartOfDayметод. Joda-Time пропонує три класи, які представляють проміжок часу різними способами: інтервал, період та тривалість.

Використовуючи підхід "напіввідкрито", коли початок проміжку є інклюзивним, а закінчення ексклюзивним.

Часовий пояс цілі може бути різним, ніж часовий пояс інтервалу.

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );
DateTime target = new DateTime( 2012, 3, 4, 5, 6, 7, timeZone );
DateTime start = DateTime.now( timeZone ).withTimeAtStartOfDay();
DateTime stop = start.plusDays( 1 ).withTimeAtStartOfDay();
Interval interval = new Interval( start, stop );
boolean containsTarget = interval.contains( target );

java.time

Java 8 і пізніші версії поставляються з java.time . Натхненний Joda-Time, визначений JSR 310, і розширений проектом ThreeTen-Extra. Дивіться Підручник .

Творці Joda-Time доручили нам усі перейти на java.time, як тільки зручно. Тим часом Joda-Time продовжується як активно підтримуваний проект. Але очікуйте, що майбутня робота відбуватиметься лише у java.time та ThreeTen-Extra, а не Joda-Time.

Підводячи підсумок java.time у двох словах… InstantМомент на часовій шкалі в UTC. Застосуйте часовий пояс ( ZoneId), щоб отримати ZonedDateTimeоб’єкт. Для переходу від тимчасової шкали, щоб отримати неясне невизначене уявлення про дату-часу, використовувати «локальні» класів: LocalDateTime, LocalDate, LocalTime.

Логіка, обговорювана в розділі Joda-Time у цій відповіді, стосується java.time.

Старий клас java.util.Date має новий toInstantметод перетворення в java.time.

Instant instant = yourJavaUtilDate.toInstant(); // Convert into java.time type.

Визначення дати потребує часового поясу.

ZoneId zoneId = ZoneId.of( "America/Montreal" );

Ми застосовуємо цей об’єкт часового поясу до параметра Instanta ZonedDateTime. Виходячи з цього, ми отримуємо значення (а LocalDate) лише для дати, оскільки нашою метою є порівняння дат (а не годин, хвилин тощо).

ZonedDateTime zdt1 = ZonedDateTime.ofInstant( instant , zoneId );
LocalDate localDate1 = LocalDate.from( zdt1 );

Зробіть те саме з другим java.util.Dateоб'єктом, який нам потрібен для порівняння. Я просто використаю замість цього поточний момент.

ZonedDateTime zdt2 = ZonedDateTime.now( zoneId );
LocalDate localDate2 = LocalDate.from( zdt2 );

Використовуйте спеціальний isEqualметод для тестування на однакове значення дати.

Boolean sameDate = localDate1.isEqual( localDate2 );

java.time: Або ви просто можете зробитиLocalDate localDate = LocalDate.fromDateFields(yourJavaUtilDate);
CyberMew

1
@CyberMew Er? Існує не fromDateFields()в моєму LocalDateкласі .
Оле ВВ

1
Вибачте, я мав би бути зрозумілішим. Коментар стосується класу Joda -Time LocalDate .
CyberMew

7

Перетворення дат на Java 8 java.time.LocalDate, як показано тут .

LocalDate localDate1 = date1.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate localDate2 = date2.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

// compare dates
assertTrue("Not on the same day", localDate1.equals(localDate2));

Це моя улюблена відповідь. Якщо ви хочете , щоб контролювати часовий пояс , який використовується замість того , щоб покладатися на настройки комп'ютера, це просто покласти, наприклад , ZoneId.of("America/Phoenix") або ZoneId.of("Europe/Budapest")замість системи по замовчуванню.
Оле ВВ

6

Java 8

Якщо ви використовуєте Java 8 у своєму проекті та порівнюєте java.sql.Timestamp, ви можете використовувати LocalDateклас:

sameDate = date1.toLocalDateTime().toLocalDate().equals(date2.toLocalDateTime().toLocalDate());

Якщо ви користуєтесь java.util.Date, подивіться на відповідь Іштвана, яка є менш неоднозначною.


Використання Java 8 - хороша ідея. Ви припускаєте date1і date2є java.sql.Timestamp? За питанням, яке вони є Date, я зрозумів би java.util.Date. Також ваш код використовує налаштування часового поясу комп'ютера, що для багатьох цілей буде добре; все-таки я вважаю за краще зробити цей факт явним у коді.
Оле ВВ

@ OleV.V. дякую за ваш коментар, справді була моя оригінальна відповідь java.sql.Timestamp. Як ви сказали, це, як правило, краще чітко встановити часовий пояс.
amanteaux

5
private boolean isSameDay(Date date1, Date date2) {
        Calendar calendar1 = Calendar.getInstance();
        calendar1.setTime(date1);
        Calendar calendar2 = Calendar.getInstance();
        calendar2.setTime(date2);
        boolean sameYear = calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
        boolean sameMonth = calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH);
        boolean sameDay = calendar1.get(Calendar.DAY_OF_MONTH) == calendar2.get(Calendar.DAY_OF_MONTH);
        return (sameDay && sameMonth && sameYear);
    }

5

Для користувачів Android

Ви можете DateUtils.isToday(dateMilliseconds)перевірити, чи вказана дата є поточним днем ​​чи ні.

Посилання на API: https://developer.android.com/reference/android/text/format/DateUtils.html#isToday(long)


1
Цей метод використовує об’єкт Час, який застарів з API 22: developer.android.com/reference/android/text/format/Time.html
Олександр Бодашко

2
+1 для андроїд розробника. цей метод використовує примітивний long як параметр, просто скористайтеся DateUtils.isToday(x.getTime())(x - це екземпляр java.util.date)
jackycflau

1

крім рішення Бініла Томаса

public static boolean isOnSameDay(Timestamp... dates) {
    SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");
    String date1 = fmt.format(dates[0]);
    for (Timestamp date : dates) {
        if (!fmt.format(date).equals(date1)) {
            return false;
        }
    }
    return true;
}

використання

    isOnSameDay(date1,date2,date3 ...);
//or 
    isOnSameDay(mydates);

1

Для Kotlin devs це версія з порівнянням підходу форматованих рядків:

val sdf = SimpleDateFormat("yyMMdd")
if (sdf.format(date1) == sdf.format(date2)) {
    // same day
}

Це не найкращий спосіб, але він короткий і робочий.


1
SimpleDateFormatКлас був витіснений років тому сучасного java.time.DateTimeFormatterкласу , визначеного в JSR 310. Радити його використання в 2019 році погана порада.
Василь Бурк

2
Цей код ігнорує вирішальну проблему часового поясу. Для будь-якого моменту дата змінюється по всьому світу за зоною.
Василь Бурк

-4

ви можете застосувати ту ж логіку, що і для рішення SimpleDateFormat, не покладаючись на SimpleDateFormat

date1.getFullYear()*10000 + date1.getMonth()*100 + date1.getDate() == 
date2.getFullYear()*10000 + date2.getMonth()*100 + date2.getDate()

6
Ці методи застаріли в java.util.Date. Для цього слід використовувати Календар. Також це getYear, не getFullYear
Kris
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.