Comparaison de deux java.util.Dates pour voir s'ils sont dans la même journée

250

Je dois comparer deux Dates (par exemple date1et date2) et trouver unboolean sameDay qui est vrai pour les deux Dates partagent le même jour, et faux s'ils ne le sont pas.

Comment puis-je faire ceci? Il semble y avoir un tourbillon de confusion ici ... et je voudrais éviter de tirer dans d'autres dépendances au-delà du JDK si possible.

pour clarifier: si date1et date2partager la même année, le même mois et le même jour, alors sameDayc'est vrai, sinon c'est faux. Je me rends compte que cela nécessite la connaissance d'un fuseau horaire ... ce serait bien de passer dans un fuseau horaire, mais je peux vivre avec l'heure GMT ou locale tant que je sais quel est le comportement.

encore une fois, pour clarifier:

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
la source
Juste pour clarifier - vous voulez savoir si deux objets Date tombent le même jour de la semaine?
Rob Heiser
Voulez-vous comparer la date complète (jour, mois, année) ou seulement le jour du mois?
XpiritO
1
@Rob: non, le même jour / mois / année ... je vais clarifier.
Jason S
alors pourquoi n'utilisez-vous pas "égal"?
XpiritO
7
Parce qu'ils ne sont pas égaux si les heures / minutes / secondes sont différentes.
Jason S

Réponses:

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

Notez que «même jour» n'est pas un concept aussi simple qu'il y paraît lorsque différents fuseaux horaires peuvent être impliqués. Le code ci-dessus calculera pour les deux dates le jour par rapport au fuseau horaire utilisé par l'ordinateur sur lequel il s'exécute. Si ce n'est pas ce dont vous avez besoin, vous devez transmettre le (s) fuseau (s) horaire (s) correspondant (s) aux Calendar.getInstance()appels, après avoir décidé ce que vous entendez exactement par "le même jour".

Et oui, Joda Time LocalDaterendrait le tout beaucoup plus propre et plus facile (bien que les mêmes difficultés impliquant les fuseaux horaires soient présentes).

Michael Borgwardt
la source
Merci, on dirait que ça fera ce que je veux. Dans mon cas, je compare des dates successives dans une série, il semble donc que je puisse simplement utiliser des instances de calendrier au lieu d'instances de date dans ma série.
Jason S
1
@Jason Cela peut ou peut ne pas être une bonne idée. Le principal problème avec Calendar est qu'il s'agit d'une classe très lourde avec beaucoup d'état interne, dont une partie est utilisée dans son implémentation equals (). Si vous ne cophargez pas vos dates pour l'égalité et ne les mettez pas dans HashMaps, ça devrait aller.
Michael Borgwardt
cool, merci, j'utilise simplement la logique de comparaison du courant et de la veille demandée ici. les fonctions "equals" et "hashcode" ne devraient jamais être appelées.
Jason S
1
@UmerHayat: le code compare le jour de l'année, pas le jour du mois. Une comparaison de moins nécessaire, code plus court.
Michael Borgwardt,
2
Suggestion: comparez DAY_OF_YEAR d'abord, il ne sera pas nécessaire de vérifier l'année. D'accord, ce n'est pas comme comparer un int, c'est vraiment cher mais ...
Martin P.
332

Que diriez-vous:

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

Vous pouvez également définir le fuseau horaire sur SimpleDateFormat, si nécessaire.

Binil Thomas
la source
2
(J'utilise en fait SimpleDateFormat de toute façon dans mon cas, donc cela semble plutôt approprié.)
Jason S
8
Je suis en fait surpris de voir cela, mais même du point de vue des performances, cette SimpleDateFormatméthode est en fait plus rapide que l'autre mentionnée ici en utilisant Calendars. En moyenne, cela prend la moitié du temps comme Calendarméthode. (Au moins sur mon système). Gloire!
Michael Plautz
La plupart du coût de cette réponse est sur la création de SimpleDateFormat. Mettez-le dans un champ threadLocal dans un singleton si vous voulez des performances encore meilleures
Thierry
1
Je le fais aussi dans Swift. C'est incroyable de voir comment une chose aussi simple nécessite le même hack dans la plupart des langues. C # étant l'exception - si (d1.Date == d2.Date) ...
alpsystems.com
2
C'est moche; surpris de voir un hack comme celui-ci voté.
Zsolt Safrany
162

J'utilise le package "apache commons lang" pour ce faire (à savoir org.apache.commons.lang.time.DateUtils )

boolean samedate = DateUtils.isSameDay(date1, date2);  //Takes either Calendar or Date objects
Brent Watson
la source
2
Cela utilise une dépendance externe ... mais c'est bon à savoir pour l'avenir.
Jason S
20
Copiez simplement la source et appelez-la un jour :)
Anton Kuzmin
Mais il lève une exception d'argument illégal si l'un des jours est nul, ce qui n'est pas idéal dans certains cas.
raviraja
1
Comme indiqué dans stackoverflow.com/a/2517824/1665809, cette solution peut ne pas être appropriée si différents fuseaux horaires sont impliqués.
mrod
24

Vous pouvez éviter les dépendances externes et l'impact sur les performances de l'utilisation de Calendar en calculant le numéro du jour julien pour chacune des dates, puis en les comparant:

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
la source
4
Mais attention, cela ne prend pas en compte les modifications de la durée de la journée en raison de l'heure d'été. Mais les Dates droites n'encapsulent pas la notion de fuseaux horaires, il n'y a donc aucun moyen de résoudre ce problème de manière non arbitraire.
AyeJay
3
C'est en fait la solution la plus rapide - merci beaucoup, exactement ce que je cherchais; car je dois vérifier beaucoup de dates ...
Ridcully
2
Nitpick: date1.getTime () ne renvoie pas le numéro du jour julien, mais les millisecondes depuis le 01/01/1970. Énorme différence.
Per Lindberg
2
Ceci effectue la comparaison dans le fuseau horaire UTC. Si vous vouliez votre fuseau horaire local ou un autre endroit sur la planète, vous ne pouvez pas savoir si le résultat que vous obtenez est correct.
Ole VV
20

Joda-Time

Quant à l'ajout d'une dépendance, je crains que les java.util.Date et .Calendar soient vraiment si mauvais que la première chose que je fais à tout nouveau projet est d'ajouter la bibliothèque Joda-Time. Dans Java 8, vous pouvez utiliser le nouveau package java.time, inspiré de Joda-Time.

Le cœur de Joda-Time est la DateTimeclasse. Contrairement à java.util.Date, il comprend le fuseau horaire qui lui est attribué ( DateTimeZone). Lors de la conversion de juDate, attribuez une zone.

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

LocalDate

Une façon de vérifier si deux dates-heures atterrissent à la même date est de les convertir en LocalDateobjets.

Cette conversion dépend du fuseau horaire attribué. Pour comparer des LocalDateobjets, ils doivent avoir été convertis avec la même zone.

Voici une petite méthode utilitaire.

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

Mieux serait un autre argument, spécifiant le fuseau horaire plutôt que de supposer que le fuseau horaire du premier objet DateTime devrait être utilisé.

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

Représentation des chaînes

Une autre approche consiste à créer une représentation sous forme de chaîne de la partie date de chaque date-heure, puis à comparer les chaînes.

Encore une fois, le fuseau horaire attribué est 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;

Durée du temps

La solution généralisée consiste à définir un intervalle de temps, puis demandez si l'intervalle contient votre cible. Cet exemple de code est dans Joda-Time 2.4. Notez que les classes liées à "minuit" sont obsolètes. Utilisez plutôt la withTimeAtStartOfDayméthode. Joda-Time propose trois classes pour représenter un laps de temps de différentes manières: Intervalle, Période et Durée.

Utilisation de l'approche "Half-Open" où le début de la plage est inclusif et la fin exclusive.

Le fuseau horaire de la cible peut être différent du fuseau horaire de l'intervalle.

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 et versions ultérieures sont fournies avec le framework java.time . Inspiré par Joda-Time, défini par JSR 310, et étendu par le projet ThreeTen-Extra. Voir le tutoriel .

Les créateurs de Joda-Time nous ont tous demandé de passer à java.time dès que possible. Dans l'intervalle, Joda-Time continue d'être un projet activement maintenu. Mais attendez-vous à ce que les travaux futurs se produisent uniquement dans java.time et ThreeTen-Extra plutôt que Joda-Time.

Pour résumer java.time en quelques mots… An Instantest un moment sur la timeline en UTC. Appliquez un fuseau horaire ( ZoneId) pour obtenir un ZonedDateTimeobjet. Pour se déplacer hors de la ligne de temps, pour obtenir l'idée vague indéfinie d'une date-heure, utiliser les classes « locales »: LocalDateTime, LocalDate, LocalTime.

La logique discutée dans la section Joda-Time de cette réponse s'applique à java.time.

L'ancienne classe java.util.Date a une nouvelle toInstantméthode de conversion en java.time.

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

La détermination d'une date nécessite un fuseau horaire.

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

Nous appliquons cet objet de fuseau horaire au Instantpour obtenir un ZonedDateTime. De là, nous extrayons une valeur de date uniquement (a LocalDate) car notre objectif est de comparer les dates (pas les heures, les minutes, etc.).

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

Faites de même pour le deuxième java.util.Dateobjet dont nous avons besoin pour la comparaison. Je vais juste utiliser l'instant présent à la place.

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

Utilisez la isEqualméthode spéciale pour tester la même valeur de date.

Boolean sameDate = localDate1.isEqual( localDate2 );
Basil Bourque
la source
java.time: Ou vous pouvez simplement le faireLocalDate localDate = LocalDate.fromDateFields(yourJavaUtilDate);
CyberMew
1
@CyberMew Er? Il n'y fromDateFields()en a pas dans ma LocalDateclasse .
Ole VV
1
Désolé, j'aurais dû être plus clair. Le commentaire est pertinent pour la classe Joda-Time LocalDate .
CyberMew
7

Convertissez les dates en Java 8 java.time.LocalDate comme on le voit ici .

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
la source
Ceci est ma réponse préférée. Si vous souhaitez contrôler le fuseau horaire utilisé plutôt que de vous fier aux paramètres de l'ordinateur, il est simple de les insérer, par exemple ZoneId.of("America/Phoenix") ou à la ZoneId.of("Europe/Budapest")place du système par défaut.
Ole VV
6

Java 8

Si vous utilisez Java 8 dans votre projet et comparez java.sql.Timestamp, vous pouvez utiliser la LocalDateclasse:

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

Si vous utilisez java.util.Date, jetez un œil à la réponse d'Istvan qui est moins ambiguë.

amanteaux
la source
Utiliser Java 8 est une bonne idée. Supposez-vous date1et date2êtes-vous java.sql.Timestamp? Selon la question qu'ils sont Date, je comprendrais java.util.Date. De plus, votre code utilise le paramètre de fuseau horaire de l'ordinateur, qui à de nombreuses fins conviendra; je préférerais quand même rendre ce fait explicite dans le code.
Ole VV
@ OleV.V. merci pour ton commentaire, ma réponse originale était bien sur java.sql.Timestamp. Comme vous l'avez dit, il est généralement préférable de définir explicitement le fuseau horaire.
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
la source
5

POUR LES UTILISATEURS ANDROID:

Vous pouvez utiliser DateUtils.isToday(dateMilliseconds) pour vérifier si la date donnée est la journée en cours ou non.

Référence de l'API: https://developer.android.com/reference/android/text/format/DateUtils.html#isToday(long)

Hemant Kaushik
la source
1
Cette méthode utilise l'objet Time qui est obsolète depuis l'API 22: developer.android.com/reference/android/text/format/Time.html
Oleksandr Bodashko
2
+1 pour le développeur Android. cette méthode utilise la primitive tant que le paramètre, utilisez simplement DateUtils.isToday(x.getTime())(x est une instance de java.util.date) fera l'affaire
jackycflau
1

en plus de la solution 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;
}

usage

    isOnSameDay(date1,date2,date3 ...);
//or 
    isOnSameDay(mydates);
Alnamrouti à prix réduit
la source
1

Pour les développeurs Kotlin, c'est la version avec la comparaison de l'approche des chaînes formatées:

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

Ce n'est pas la meilleure façon, mais c'est court et ça marche.

Micer
la source
1
La SimpleDateFormatclasse a été supplantée il y a des années par la java.time.DateTimeFormatterclasse moderne définie dans la JSR 310. Suggérer son utilisation en 2019 est un mauvais conseil.
Basil Bourque
2
Ce code ignore la question cruciale du fuseau horaire. Pour un moment donné, la date varie dans le monde par zone.
Basil Bourque
-4

vous pouvez appliquer la même logique que la solution SimpleDateFormat sans compter sur SimpleDateFormat

date1.getFullYear()*10000 + date1.getMonth()*100 + date1.getDate() == 
date2.getFullYear()*10000 + date2.getMonth()*100 + date2.getDate()
jawa
la source
6
Ces méthodes sont obsolètes dans java.util.Date. Vous devez utiliser Calendrier pour ce faire. C'est aussi getYear, pas getFullYear
Kris