Porównywanie dwóch dat java.util.Data, aby sprawdzić, czy są tego samego dnia

249

Muszę porównać dwa Dates (np. date1I date2) i wymyślić, boolean sameDayco jest prawdą o dwóch Datesach tego samego dnia, i fałszywe, jeśli nie są.

W jaki sposób mogę to zrobić? Wydaje się, że panuje tutaj zamieszanie ... i chciałbym unikać wciągania innych zależności poza JDK, jeśli to w ogóle możliwe.

wyjaśnić: jeśli date1i date2dzielić ten sam rok, miesiąc i dzień, wówczas sameDayjest to prawda, w przeciwnym razie jest to fałsz. Zdaję sobie sprawę, że wymaga to wiedzy o strefie czasowej ... byłoby miło przejść w strefie czasowej, ale mogę żyć z czasem GMT lub czasem lokalnym, o ile wiem, jakie jest zachowanie.

ponownie, aby wyjaśnić:

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
Jason S.
źródło
Żeby wyjaśnić - chcesz wiedzieć, czy dwa obiekty Data wypadną tego samego dnia tygodnia?
Rob Heiser,
Czy chcesz porównać pełną datę (dzień, miesiąc, rok) czy tylko dzień miesiąca?
XpiritO,
1
@Rob: nie, ten sam dzień / miesiąc / rok ... wyjaśnię.
Jason S
to dlaczego nie używasz „równych”?
XpiritO,
7
Ponieważ nie są równe, jeśli godzina / minuta / sekunda są różne.
Jason S

Odpowiedzi:

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

Pamiętaj, że „ten sam dzień” nie jest tak prostym pojęciem, jak się wydaje, gdy można zaangażować różne strefy czasowe. Powyższy kod dla obu dat oblicza dzień w zależności od strefy czasowej używanej przez komputer, na którym działa. Jeśli nie jest to potrzebne, musisz przekazać odpowiednie strefy czasowe do Calendar.getInstance()połączeń, po tym, jak zdecydujesz, co dokładnie masz na myśli, mówiąc „tego samego dnia”.

I tak, Joda Time LocalDatesprawiłoby, że całość byłaby o wiele czystsza i łatwiejsza (chociaż występowałyby te same trudności dotyczące stref czasowych).

Michael Borgwardt
źródło
Dzięki, wygląda na to, że zrobi to, co chcę. W moim przypadku porównuję kolejne daty w serii, więc wygląda na to, że mógłbym po prostu użyć instancji kalendarza zamiast instancji daty w mojej serii.
Jason S
1
@Jason To może, ale nie musi być dobrym pomysłem. Głównym problemem związanym z Kalendarzem jest to, że jest to bardzo ciężka klasa z wieloma stanami wewnętrznymi, z których część jest używana w implementacji equals (). Jeśli nie skopiujesz swoich dat dla równości i nie umieścisz ich w HashMaps, wszystko powinno być w porządku.
Michael Borgwardt,
fajnie, dziękuję, używam tylko logiki porównania obecnego i poprzedniego dnia, o którą tutaj zapytałem. funkcje „equals” i „hashcode” nigdy nie powinny być wywoływane.
Jason S
1
@UmerHayat: kod porównuje dzień roku, a nie dzień miesiąca. Konieczne jest jedno mniejsze porównanie, krótszy kod.
Michael Borgwardt
2
Sugestia: najpierw porównaj DAY_OF_YEAR, nie będzie trzeba sprawdzać roku. Ok, to nie jest tak, że porównywanie int jest naprawdę drogie, ale ...
Martin P.
332

Co powiesz na:

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

W razie potrzeby możesz także ustawić strefę czasową na SimpleDateFormat.

Binil Thomas
źródło
2
(Właściwie używam SimpleDateFormat w moim przypadku, więc wydaje się to odpowiednie).
Jason S
8
Zaskakuje mnie to, ale nawet z punktu widzenia wydajności ta SimpleDateFormatmetoda jest w rzeczywistości szybsza niż druga wspomniana tutaj za pomocą Calendars. CalendarMetoda ta zajmuje średnio połowę czasu . (Przynajmniej w moim systemie). Sława!
Michael Plautz
Większość kosztów tej odpowiedzi dotyczy tworzenia SimpleDateFormat. Umieść to w polu ThreadLocal w singletonie, jeśli chcesz jeszcze lepszej wydajności
Thierry
1
Robię to również w Swift. To niesamowite, że tak prosta rzecz wymaga takiego samego włamania w większości języków. C # jest wyjątkiem - jeśli (d1.Date == d2.Date) ...
alpsystems.com
2
To jest brzydkie; zaskoczony widząc, że taki hack jest pozytywnie oceniany.
Zsolt Safrany
162

Używam do tego pakietu „apache commons lang” (mianowicie org.apache.commons.lang.time.DateUtils )

boolean samedate = DateUtils.isSameDay(date1, date2);  //Takes either Calendar or Date objects
Brent Watson
źródło
2
Wykorzystuje to zewnętrzną zależność ... ale dobrze jest wiedzieć na przyszłość.
Jason S
20
Po prostu skopiuj źródło i nazwij to dniem :)
Anton Kuzmin
Zgłasza jednak wyjątek dotyczący niezgodnego z prawem argumentu, jeśli którykolwiek z dni ma wartość zerową, co w niektórych przypadkach nie jest idealne.
raviraja
1
Jak skomentowano w stackoverflow.com/a/2517824/1665809 to rozwiązanie może nie być odpowiednie, jeśli w grę wchodzą różne strefy czasowe.
mrod
24

Możesz uniknąć zewnętrznych zależności i pogorszenia wydajności korzystania z Kalendarza, obliczając liczbę dni Juliana dla każdej z dat, a następnie porównując te:

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
źródło
4
Ale uwaga: nie uwzględnia to zmian długości dnia z powodu oszczędności w świetle dziennym. Ale wtedy proste Datenie zawierają pojęcia stref czasowych, więc nie ma sposobu, aby to arbitralnie naprawić.
AyeJay
3
To przy okazji najszybsze rozwiązanie - wielkie dzięki, dokładnie to, czego szukałem; ponieważ muszę sprawdzić wiele dat ...
Ridcully
2
Nitpick: date1.getTime () nie zwraca Julian Day Number, ale milisekundy od 1970-01-01. Duża różnica.
Per Lindberg,
2
Wykonuje to porównanie w strefie czasowej UTC. Jeśli chcesz mieć lokalną strefę czasową lub inne miejsce na planecie, nie możesz wiedzieć, czy otrzymany wynik jest poprawny.
Ole VV,
20

Joda-Time

Jeśli chodzi o dodawanie zależności, obawiam się, że java.util.Date & .Calendar są tak złe, że pierwszą rzeczą, którą robię w każdym nowym projekcie, jest dodanie biblioteki Joda-Time. W Javie 8 możesz użyć nowego pakietu java.time, zainspirowanego Joda-Time.

Rdzeniem Joda-Time jest DateTimeklasa. W przeciwieństwie do java.util.Date, rozumie przypisaną strefę czasową ( DateTimeZone). Podczas konwersji z juDate przypisz strefę.

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

LocalDate

Jednym ze sposobów sprawdzenia, czy dwie daty wylądują w tym samym dniu, jest konwersja na LocalDateobiekty.

Ta konwersja zależy od przypisanej strefy czasowej. Aby porównać LocalDateobiekty, muszą zostać przekonwertowane na tę samą strefę.

Oto mała metoda użyteczności.

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

Lepszym rozwiązaniem byłby kolejny argument, określający strefę czasową zamiast zakładania, że ​​należy użyć strefy czasowej pierwszego obiektu 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 );
}

Reprezentacja łańcucha

Innym podejściem jest utworzenie ciągu reprezentującego część daty dla każdej daty i godziny, a następnie porównanie ciągów.

Ponownie, przypisana strefa czasowa ma kluczowe znaczenie.

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;

Przedział czasu

Uogólnione rozwiązanie polega na zdefiniowaniu przedziału czasu, a następnie zapytaniu, czy przedział zawiera cel. Ten przykładowy kod znajduje się w Joda-Time 2.4. Zauważ, że klasy związane z „północą” są przestarzałe. Zamiast tego użyj withTimeAtStartOfDaymetody. Joda-Time oferuje trzy klasy reprezentujące przedział czasu na różne sposoby: Interwał, Okres i Czas trwania.

Stosując podejście „półotwarte”, w którym początek zakresu jest włączający, a zakończenie wyłączny.

Strefa czasowa celu może być inna niż strefa czasowa przedziału.

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 i nowsze wersje są dostarczane ze środowiskiem java.time . Zainspirowany przez Joda-Time, zdefiniowany przez JSR 310 i rozszerzony przez projekt ThreeTen-Extra. Zobacz samouczek .

Twórcy Joda-Time poinstruowali nas wszystkich, abyśmy jak najszybciej przenieśli się na java.time. W międzyczasie Joda-Time kontynuuje działalność jako aktywny projekt. Ale oczekuj, że przyszłe prace pojawią się tylko w java.time i ThreeTen-Extra, a nie w Joda-Time.

Podsumowując java.time w pigułce… InstantTo moment na osi czasu w UTC. Zastosuj strefę czasową ( ZoneId), aby uzyskać ZonedDateTimeobiekt. Aby przenieść się na osi czasu, aby uzyskać mgliste pojęcie o nieokreśloną datę czasie, korzystać z „lokalnych” klas: LocalDateTime, LocalDate, LocalTime.

Logika omówiona w części Joda-Time tej odpowiedzi dotyczy java.time.

Stara klasa java.util.Date ma nową toInstantmetodę konwersji na java.time.

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

Określenie daty wymaga strefy czasowej.

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

Stosujemy ten obiekt strefy czasowej w Instantcelu uzyskania ZonedDateTime. Z tego wyodrębniamy wartość tylko daty (a LocalDate), ponieważ naszym celem jest porównywanie dat (nie godzin, minut itp.).

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

Zrób to samo z drugim java.util.Dateobiektem, którego potrzebujemy do porównania. Zamiast tego wykorzystam bieżący moment.

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

Użyj specjalnej isEqualmetody, aby przetestować tę samą wartość daty.

Boolean sameDate = localDate1.isEqual( localDate2 );
Basil Bourque
źródło
java.time: Albo możesz to zrobićLocalDate localDate = LocalDate.fromDateFields(yourJavaUtilDate);
CyberMew
1
@CyberMew Er? Nie ma fromDateFields()w mojej LocalDateklasie .
Ole VV,
1
Przepraszam, powinienem był być jaśniejszy. Komentarz dotyczy klasy LocalDate Joda-Time .
CyberMew
7

Konwertuj daty na Java 8 java.time.LocalDate, jak pokazano tutaj .

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));
Istvan
źródło
To moja ulubiona odpowiedź. Jeśli chcesz kontrolować używaną strefę czasową zamiast polegać na ustawieniach komputera, łatwo jest ją wprowadzić, na przykład ZoneId.of("America/Phoenix") lub ZoneId.of("Europe/Budapest")zamiast domyślnych ustawień systemowych.
Ole VV
6

Java 8

Jeśli używasz Java 8 w swoim projekcie i porównujesz java.sql.Timestamp, możesz użyć LocalDateklasy:

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

Jeśli używasz java.util.Date, spójrz na odpowiedź Istvan, która jest mniej dwuznaczna.

amanteaux
źródło
Korzystanie z Java 8 to dobry pomysł. Zakładasz date1i tak date2jest java.sql.Timestamp? DateZrozumiałbym, zgodnie z pytaniem java.util.Date. Również twój kod wykorzystuje ustawienia strefy czasowej komputera, co w wielu przypadkach będzie w porządku; nadal wolę wyrazić ten fakt w kodzie.
Ole VV,
@ OleV.V. dziękuję za komentarz, moja pierwotna odpowiedź rzeczywiście była java.sql.Timestamp. Jak powiedziałeś, ogólnie lepiej jest wyraźnie ustawić strefę czasową.
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
źródło
5

DLA UŻYTKOWNIKÓW ANDROIDA:

Możesz użyć, DateUtils.isToday(dateMilliseconds)aby sprawdzić, czy podana data jest aktualna, czy nie.

Dokumentacja interfejsu API: https://developer.android.com/reference/android/text/format/DateUtils.html#isToday(long)

Hemant Kaushik
źródło
1
Ta metoda wykorzystuje obiekt Czas, który jest przestarzały od API 22: developer.android.com/reference/android/text/format/Time.html
Oleksandr Bodashko
2
+1 dla programisty Androida. ta metoda używa prymitywnej długości jako parametru, wystarczy użyć DateUtils.isToday(x.getTime())(x to instancja java.util.date) zrobi
jackycflau
1

oprócz rozwiązania Binil Thomas

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

stosowanie

    isOnSameDay(date1,date2,date3 ...);
//or 
    isOnSameDay(mydates);
Fareed Alnamrouti
źródło
1

Dla twórców Kotlin jest to wersja z porównywaniem sformatowanych ciągów znaków:

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

To nie jest najlepszy sposób, ale jest krótki i działa.

Micer
źródło
1
SimpleDateFormatKlasa została wyparta lat temu przez nowoczesne java.time.DateTimeFormatterklasy zdefiniowanej w JSR 310. Sugerowanie jego wykorzystania w 2019 jest słaba rada.
Basil Bourque,
2
Ten kod ignoruje kluczową kwestię strefy czasowej. W danym momencie data zmienia się na całym świecie w zależności od strefy.
Basil Bourque,
-4

możesz zastosować tę samą logikę, co rozwiązanie SimpleDateFormat, nie polegając na SimpleDateFormat

date1.getFullYear()*10000 + date1.getMonth()*100 + date1.getDate() == 
date2.getFullYear()*10000 + date2.getMonth()*100 + date2.getDate()
Jawa
źródło
6
Te metody są nieaktualne w pliku java.util.Date. Aby to zrobić, użyj kalendarza. Również jest getYear, a nie getFullYear
Kris,