Сравнение двух java.util.Dates, чтобы видеть, находятся ли они в тот же день

250

Мне нужно сравнить два Dates (например, date1и date2) и придумать a, boolean sameDayкоторое верно для этих двух Dateакций в один и тот же день, и false, если это не так.

Как я могу это сделать? Здесь, кажется, вихрь путаницы ... и я бы хотел, если это вообще возможно, избежать других зависимостей за пределами JDK.

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

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

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
Джейсон С
источник
Просто чтобы уточнить - вы хотите знать, попадают ли два объекта Date в один и тот же день недели?
Роб Хайзер
Хотите сравнить полную дату (день, месяц, год) или только месяц?
XpiritO
1
@Rob: нет, в тот же день / месяц / год ... Я уточню.
Джейсон С
тогда почему вы не используете "равно"?
XpiritO
7
Потому что они не равны, если час / минута / секунда разные.
Джейсон С

Ответы:

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сделает все это намного чище и проще (хотя будут присутствовать те же трудности с часовыми поясами).

Майкл Боргвардт
источник
Спасибо, похоже, он будет делать то, что я хочу. В моем случае я сравниваю последовательные даты в серии, поэтому, похоже, я мог бы просто использовать экземпляры Calendar вместо экземпляров Date в моей серии.
Джейсон С
1
@ Джейсон Это может быть или не быть хорошей идеей. Основная проблема с Calendar состоит в том, что это очень тяжеловесный класс с большим внутренним состоянием, некоторые из которых используются в его реализации equals (). Если вы не ограничиваете свои даты для равенства и не помещаете их в HashMaps, у вас все будет хорошо.
Майкл Боргвардт
круто, спасибо, я просто использую логику сравнения текущего и предыдущего дня, заданную здесь. функции equals и hashcode никогда не должны вызываться.
Джейсон С
1
@UmerHayat: код сравнивает день года, а не день месяца. Необходимо меньшее сравнение, более короткий код.
Майкл Боргвардт
2
Предложение: сначала сравните DAY_OF_YEAR, не нужно проверять год. Хорошо, это не то же самое, что сравнивать int - это действительно дорого, но ...
Мартин П.
332

Как насчет:

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

При необходимости вы также можете установить часовой пояс на SimpleDateFormat.

Бинил Томас
источник
2
(Во всяком случае, в моем случае я использую SimpleDateFormat, так что это кажется уместным.)
Джейсон С
8
Я на самом деле удивлен, увидев это, но даже с точки зрения производительности, этот SimpleDateFormatметод на самом деле быстрее, чем другой, упомянутый здесь с использованием Calendars. В среднем Calendarметод занимает половину времени . (По крайней мере, в моей системе). Престижность!
Майкл Плаутц
Большая часть стоимости этого ответа лежит на создании SimpleDateFormat. Поместите его в поле ThreadLocal в синглтоне, если вы хотите еще большей производительности
Тьерри
1
Я делаю это и в Swift. Удивительно, как такая простая вещь требует такого же взлома на большинстве языков. C # является исключением - if (d1.Date == d2.Date) ...
alpsystems.com
2
Это безобразно; удивлен, увидев взлом, как это проголосовало.
Жолт Сафрани
162

Для этого я использую пакет «apache commons lang» (а именно org.apache.commons.lang.time.DateUtils )

boolean samedate = DateUtils.isSameDay(date1, date2);  //Takes either Calendar or Date objects
Брент Уотсон
источник
2
Это использует внешнюю зависимость ... но это хорошо знать на будущее.
Джейсон С
20
Просто скопируйте источник и назовите это день :)
Антон Кузьмин
Но он выдает исключение недопустимого аргумента, если любой из дней равен нулю, что в некоторых случаях не идеально.
Равираджа
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;
}
AyeJay
источник
4
Но имейте в виду, что это не учитывает изменения продолжительности дня из-за перехода на летнее время. Но тогда прямые Dates не инкапсулируют понятие часовых поясов, поэтому нет способа произвольно это исправить.
AyeJay
3
Это, кстати, самое быстрое решение - большое спасибо, именно то, что я искал; как я должен проверить много дат ...
Ridcully
2
Nitpick: date1.getTime () не возвращает юлианский номер дня, но миллисекунды с 1970-01-01. Огромная разница.
За Линдберга
2
Это выполняет сравнение в часовом поясе UTC. Если вам нужен местный часовой пояс или какое-то другое место на планете, вы не можете знать, является ли полученный вами результат правильным.
Оле В.В.
20

Joda времени

Что касается добавления зависимости, я боюсь, что 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" );

Мы применяем этот объект часового пояса к, Instantчтобы получить ZonedDateTime. Из этого мы извлекаем значение только для даты (a 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, посмотрите на ответ Иштвана, который является менее двусмысленным.

amanteaux
источник
Использование 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);
    }
evya
источник
5

ДЛЯ ПОЛЬЗОВАТЕЛЕЙ ANDROID:

Вы можете использовать, DateUtils.isToday(dateMilliseconds)чтобы проверить, является ли данная дата текущим днем ​​или нет.

Справочник по API: https://developer.android.com/reference/android/text/format/DateUtils.html#isToday(long)

Хемант Каушик
источник
1
Этот метод использует объект Time, который устарел начиная с API 22: developer.android.com/reference/android/text/format/Time.html
Александр Бодашко,
2
+1 для разработчика Android. этот метод использует примитив 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 это версия со сравнением форматированных строк:

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

Это не лучший способ, но он короткий и работает.

Micer
источник
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()
Jawa
источник
6
Эти методы устарели в java.util.Date. Вы должны использовать Календарь, чтобы сделать это. Также это getYear, а не getFullYear
Крис