Comparando duas java.util.Dates para ver se elas estão no mesmo dia

250

Preciso comparar dois Dates (por exemplo, date1e date2) e criar um boolean sameDayque seja verdadeiro para os dois Dates compartilhar no mesmo dia e falso se não forem.

Como posso fazer isso? Parece haver um turbilhão de confusão aqui ... e eu gostaria de evitar puxar outras dependências além do JDK, se possível.

para esclarecer: se date1e date2compartilhar o mesmo ano, mês e dia, então sameDayé verdade, caso contrário, é falsa. Sei que isso requer conhecimento de um fuso horário ... seria bom passar um fuso horário, mas posso viver com a GMT ou a hora local, desde que saiba qual é o comportamento.

novamente, para esclarecer:

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
fonte
Apenas para esclarecer - você quer saber se dois objetos Date caem no mesmo dia da semana?
Rob Heiser
Deseja comparar a data completa (dia, mês, ano) ou apenas o dia do mês?
XpiritO
1
@ Rob: não, no mesmo dia / mês / ano ... Vou esclarecer.
Jason S
então por que você não usa "iguais"?
XpiritO
7
Porque eles não são iguais se a hora / minuto / segundo forem diferentes.
Jason S

Respostas:

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

Observe que "mesmo dia" não é um conceito tão simples quanto parece quando diferentes fusos horários podem estar envolvidos. O código acima calculará para as duas datas o dia em relação ao fuso horário usado pelo computador em que está sendo executado. Se não é isso que você precisa, você deve passar o (s) fuso horário (s) relevante (s) para as Calendar.getInstance()chamadas, depois de decidir o que exatamente você quer dizer com "no mesmo dia".

E sim, o Joda Time's LocalDatetornaria tudo muito mais limpo e fácil (embora as mesmas dificuldades que envolvem fusos horários estariam presentes).

Michael Borgwardt
fonte
Obrigado, parece que ele fará o que eu quero. No meu caso, estou comparando datas sucessivas em uma série, então parece que eu poderia usar instâncias do Calendário em vez de instâncias de Data da minha série.
Jason S
1
@ Jason Isso pode ou não ser uma boa idéia. O principal problema do Calendar é que é uma classe muito pesada com muito estado interno, alguns dos quais são usados ​​em sua implementação equals (). Se você não copmpare suas datas de igualdade e não as coloca no HashMaps, tudo bem.
Michael Borgwardt
legal, obrigado, estou apenas usando a lógica de comparar a corrente e o dia anterior solicitada aqui. as funções "equals" e "hashcode" nunca devem ser chamadas.
Jason S
1
@UmerHayat: o código compara o dia do ano, não o dia do mês. É necessário uma comparação a menos, código mais curto.
Michael Borgwardt
2
Sugestão: compare DAY_OF_YEAR primeiro, não será necessário verificar o ano. Ok não é como comparar int é realmente caro, mas ...
Martin P.
332

E se:

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

Você também pode definir o fuso horário como SimpleDateFormat, se necessário.

Binil Thomas
fonte
2
(Na verdade, estou usando SimpleDateFormat de qualquer maneira no meu caso, então parece meio apropriado.)
Jason S
8
Estou realmente surpreso ao ver isso, mas mesmo do ponto de vista de desempenho, esse SimpleDateFormatmétodo é realmente mais rápido do que o outro mencionado aqui usando Calendars. Em média, leva metade do tempo como Calendarmétodo. (Pelo menos no meu sistema). Parabéns!
Michael Plautz
A maior parte do custo desta resposta está na criação de SimpleDateFormat. Coloque-o em um campo threadLocal em um singleton, se você quiser um desempenho ainda melhor
Thierry
1
Eu também faço isso no Swift. É incrível como uma coisa tão simples exige o mesmo hack na maioria dos idiomas. C # é a exceção - se (d1.Date == d2.Date) ...
alpsystems.com
2
Isso é feio; surpreso ao ver um hack como este votado.
Zsolt Safrany
162

Eu uso o pacote "apache commons lang" para fazer isso (ou seja, org.apache.commons.lang.time.DateUtils )

boolean samedate = DateUtils.isSameDay(date1, date2);  //Takes either Calendar or Date objects
Brent Watson
fonte
2
Isso usa uma dependência externa ... mas é bom saber para o futuro.
Jason S
20
Basta copiar a fonte e chamá-lo um dia :)
Anton Kuzmin
Mas lança uma exceção de argumento ilegal se algum dia for nulo, o que não é ideal em alguns casos.
raviraja 18/09/19
1
Conforme comentado em stackoverflow.com/a/2517824/1665809, esta solução pode não ser apropriada se estiverem envolvidos fusos horários diferentes.
mrod 12/02
24

Você pode evitar dependências externas e o desempenho atingido ao usar o Calendário calculando o Número do dia juliano para cada uma das datas e comparando-os:

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
fonte
4
Mas lembre-se de que isso não leva em consideração as alterações na duração do dia devido ao horário de verão. Mas então straight Dates não encapsulam a noção de fuso horário, então não há como corrigir isso de maneira não arbitrária.
AyeJay
3
Essa é a solução mais rápida - muito obrigado, exatamente o que eu estava procurando; como eu tenho que verificar um monte de datas ... #
218
2
Nitpick: date1.getTime () não retorna o número do dia juliano, mas milissegundos desde 01-01-2009. Enorme diferença.
Per Lindberg
2
Isso realiza a comparação no fuso horário UTC. Se você queria o seu fuso horário local ou algum outro lugar do planeta, não pode saber se o resultado obtido está correto.
Ole VV
20

Joda-Time

Quanto à adição de uma dependência, receio que o java.util.Date e o .Calendar sejam tão ruins que a primeira coisa que faço em qualquer novo projeto é adicionar a biblioteca Joda-Time. No Java 8, você pode usar o novo pacote java.time, inspirado no Joda-Time.

O núcleo do Joda-Time é a DateTimeclasse. Ao contrário do java.util.Date, ele entende o fuso horário atribuído ( DateTimeZone). Ao converter a partir de juDate, atribua uma zona.

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

LocalDate

Uma maneira de verificar se duas datas estão na mesma data é converter para LocalDate objetos.

Essa conversão depende do fuso horário atribuído. CompararLocalDate objetos, eles devem ter sido convertidos com a mesma zona.

Aqui está um pequeno método utilitário.

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

Melhor seria outro argumento, especificando o fuso horário em vez de assumir que o fuso horário do primeiro objeto DateTime deve ser usado.

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

Representação de String

Outra abordagem é criar uma representação de string da parte da data de cada data e hora e comparar as strings.

Novamente, o fuso horário atribuído é crucial.

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;

Período de tempo

A solução generalizada é definir um período de tempo e perguntar se o período contém seu destino. Este código de exemplo está no Joda-Time 2.4. Observe que as classes relacionadas à "meia-noite" foram preteridas. Em vez disso, use o withTimeAtStartOfDaymétodo O Joda-Time oferece três classes para representar um período de tempo de várias maneiras: Intervalo, Período e Duração.

Usando a abordagem "semi-aberta", onde o início do período é inclusivo e o final exclusivo.

O fuso horário do destino pode ser diferente do fuso horário do intervalo.

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

O Java 8 e posterior vem com a estrutura java.time . Inspirado no Joda-Time, definido pelo JSR 310, e estendido pelo projeto ThreeTen-Extra. Veja o tutorial .

Os criadores do Joda-Time nos instruíram a mudar para java.time o mais rápido possível. Enquanto isso, o Joda-Time continua como um projeto mantido ativamente. Mas espere que o trabalho futuro ocorra apenas em java.time e ThreeTen-Extra, em vez de Joda-Time.

Para resumir java.time em poucas palavras… Um Instanté um momento na linha do tempo no UTC. Aplique um fuso horário ( ZoneId) para obter um ZonedDateTimeobjeto. Para mover para fora da linha do tempo, para obter a idéia indefinida vaga de uma data-hora, use as classes "locais": LocalDateTime, LocalDate,LocalTime .

A lógica discutida na seção Joda-Time desta resposta se aplica a java.time.

A antiga classe java.util.Date possui um novo toInstantmétodo de conversão para java.time.

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

Determinar uma data requer um fuso horário.

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

Aplicamos esse objeto de fuso horário ao Instantpara obter um ZonedDateTime. A partir disso, extraímos um valor somente de data (a LocalDate), pois nosso objetivo é comparar datas (não horas, minutos etc.).

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

Faça o mesmo com o segundo java.util.Dateobjeto que precisamos para comparação. Vou usar o momento atual.

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

Use o isEqualmétodo especial para testar o mesmo valor de data.

Boolean sameDate = localDate1.isEqual( localDate2 );
Basil Bourque
fonte
java.time: Ou você pode simplesmente fazerLocalDate localDate = LocalDate.fromDateFields(yourJavaUtilDate);
CyberMew
1
@CyberMew Er? Não há fromDateFields()na minha LocalDateclasse .
Ole VV
1
Desculpe, eu deveria ter sido mais claro. O comentário é relevante para a classe Joda-Time LocalDate .
26417 CyberMew
7

Converta datas para Java 8 java.time.LocalDate, como visto aqui .

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
fonte
Esta é a minha resposta favorita. Se você deseja controlar o fuso horário usado, em vez de depender das configurações do computador, é fácil inserir, por exemplo, ZoneId.of("America/Phoenix") ou em ZoneId.of("Europe/Budapest")vez do padrão do sistema.
Ole VV
6

Java 8

Se você estiver usando o Java 8 em seu projeto e comparando java.sql.Timestamp, poderá usar a LocalDateclasse:

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

Se você estiver usando java.util.Date, dê uma olhada na resposta do Istvan, que é menos ambígua.

amanteaux
fonte
Usar o Java 8 é uma boa ideia. Você está assumindo date1e date2é java.sql.Timestamp? De acordo com a pergunta que eles são Date, eu entenderia java.util.Date. Além disso, seu código usa a configuração de fuso horário do computador, o que para muitos propósitos será bom; ainda assim, prefiro deixar esse fato explícito no código.
Ole VV
@ OleV.V. obrigado pelo seu comentário, minha resposta original foi realmente sobre java.sql.Timestamp. Como você disse, geralmente é melhor definir explicitamente o fuso horário.
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
fonte
5

PARA USUÁRIOS DA ANDROID:

Você pode usar DateUtils.isToday(dateMilliseconds) para verificar se a data especificada é o dia atual ou não.

Referência da API: https://developer.android.com/reference/android/text/format/DateUtils.html#isToday(long)

Hemant Kaushik
fonte
1
Este método usa o objeto Time, que está obsoleto desde a API 22: developer.android.com/reference/android/text/format/Time.html
Oleksandr Bodashko
2
+1 para desenvolvedor Android. este método usa o primitivo long as parâmetro, basta usar DateUtils.isToday(x.getTime())(x é uma instância java.util.date) fará
jackycflau
1

além da solução 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;
}

uso

    isOnSameDay(date1,date2,date3 ...);
//or 
    isOnSameDay(mydates);
Alnamrouti com tarifa
fonte
1

Para os desenvolvedores do Kotlin, esta é a versão com a comparação da abordagem de strings formatadas:

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

Não é o melhor caminho, mas é curto e funcional.

Micer
fonte
1
A SimpleDateFormatclasse foi substituída anos atrás pela java.time.DateTimeFormatterclasse moderna definida no JSR 310. Sugerir seu uso em 2019 é um péssimo conselho.
Basil Bourque
2
Este código ignora a questão crucial do fuso horário. Por um determinado momento, a data varia em todo o mundo por zona.
Basil Bourque
-4

você pode aplicar a mesma lógica que a solução SimpleDateFormat sem depender do SimpleDateFormat

date1.getFullYear()*10000 + date1.getMonth()*100 + date1.getDate() == 
date2.getFullYear()*10000 + date2.getMonth()*100 + date2.getDate()
jawa
fonte
6
Esses métodos foram descontinuados em java.util.Date. Você deve usar o Calendário para fazer isso. Também é getYear, não getFullYear
Kris