web-dev-qa-db-fra.com

Comparer un objet Date avec un TimeStamp dans Java

Quand je teste ce code:

Java.util.Date date = new Java.util.Date();
Java.util.Date stamp = new Java.sql.Timestamp(date.getTime());

assertTrue(date.equals(stamp));
assertTrue(date.compareTo(stamp) == 0);
assertTrue(stamp.compareTo(date) == 0);
assertTrue(stamp.equals(date));

Je m'attends à un vrai, vrai, vrai, faux. À cause de ce:

Dans le javadoc pour Java.sql.Timestamp, il indique:

Remarque: ce type est un composite de Java.util.Date et d'une valeur de nanosecondes distincte. Seules les secondes intégrales sont stockées dans le composant Java.util.Date. Les secondes fractionnaires - les nanos - sont séparées. La méthode Timestamp.equals (Object) ne renvoie jamais true lorsqu'elle reçoit une valeur de type Java.util.Date car le composant nanos d'une date est inconnu. Par conséquent, la méthode Timestamp.equals (Object) n'est pas symétrique par rapport à la méthode Java.util.Date.equals (Object). De plus, la méthode hashcode utilise l'implémentation Java.util.Date sous-jacente et n'inclut donc pas les nanos dans son calcul.

En raison des différences entre la classe Timestamp et la classe Java.util.Date mentionnées ci-dessus, il est recommandé que le code ne visualise pas les valeurs Timestamp de manière générique comme une instance de Java.util.Date. La relation d'héritage entre Timestamp et Java.util.Date dénote vraiment l'héritage d'implémentation, et non l'héritage de type.

Mais à la place, j'obtiendrai un vrai, un faux, un vrai, un faux. Des idées?

EDIT: Ce problème apparaît quand je vérifiais deux dates avec la méthode equals, mais l'un des objets Date provient d'une classe Hibernate et en déboguant je vois que l'objet contient un TimeStamp. Donc, la méthode égale est évaluée à faux, alors j'ai trouvé ceci: http://mattfleming.com/node/141

Mais quand j'essaie le code, j'obtiens des résultats différents ... si je ne peux pas utiliser ni égal ni comparerPour ce que je dois utiliser pour vérifier si 2 dates sont identiques?!?!

27
Torres

tl; dr

Utilisez les classes modernes Java.time au lieu de ces classes de date-heure héritées gênantes.

myPreparedStatement.setObject(
    … , 
    Instant.now()                // Capture the current moment in UTC.
)

Anciennes classes de date-heure mal conçues

Autrement dit, les classes Java.sql.Timestamp/.Date/.Time sont un hack, un mauvais hack. Comme Java.util.Date/.Calendar, ils sont le résultat de mauvais choix de conception.

Les types Java.sql doivent être utilisés aussi brièvement que possible, utilisés uniquement pour le transfert de données dans/hors de la base de données. Ne pas utiliser pour la logique métier et les travaux ultérieurs.

Java.time

Les anciennes classes date-heure ont été remplacées par le framework Java.time intégré à Java 8 et versions ultérieures. Ces nouvelles classes sont définies par JSR 310, inspiré par le bibliothèque Joda-Time très réussie et étendue par le projet ThreeTen-Extra.

Finalement, nous devrions voir les pilotes JDBC mis à jour pour fonctionner directement avec ces types Java.time. Mais jusqu'à ce jour, nous devons convertir vers/à partir des types Java.sql. Pour de telles conversions, appelez nouvelles méthodes ajoutées aux anciennes classes .

Un Instant est un moment sur la timeline en UTC à une résolution de nanosecondes .

Instant instant = myJavaSqlTimestamp.toInstant();

Pour aller dans l'autre sens:

Java.sql.Timestamp ts = Java.sql.Timestamp.valueOf( instant );

Appliquez un fuseau horaire pour obtenir heure de l'horloge murale .

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

Les classes Java.time ont une conception de classe propre et judicieusement choisie. Vous pouvez donc utiliser les equals et compareTo comme prévu. Notez que les classes avec un décalage par rapport à UTC ou un fuseau horaire proposent également des méthodes isEqual, isBefore et isAfter. Ces méthodes se comparent en considérant les moments de la chronologie, leur ordre chronologique. Les méthodes equals et compareTo tiennent également compte du décalage ou du fuseau horaire.

Minimiser l'utilisation de Java.sql tout en maximisant l'utilisation de Java.time rend les problèmes de la question sans objet.

Dans Hibernate, utilisez des convertisseurs pour Java.time.

JDBC 4.2

À partir de JDBC 4.2 et versions ultérieures, vous n'avez plus besoin d'utiliser les classes héritées. Vous pouvez échanger directement des objets Java.time avec votre base de données via les méthodes getObject & setObject.

myPreparedStatement.setObject( … , instant ) ;

Et la récupération.

Instant instant = myResultSet.getObject( … , Instant.class ) ;

Notez que de nombreuses bases de données ne peuvent pas stocker un moment avec une résolution aussi fine que les nanosecondes utilisées dans Java.time . Vous voudrez peut-être tronquer explicitement plutôt que de laisser votre pilote JDBC le faire implicitement.

Instant instant = Instant.now().truncatedTo( ChronoUnit.MILLIS ) ; // Lop off any nanoseconds & microseconds, keeping only the milliseconds, to match limitations of database. 

À propos de Java.time

Le cadre Java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent l'ancien hérité classes date-heure telles que Java.util.Date , Calendar , & SimpleDateFormat .

Le projet Joda-Time , maintenant en mode maintenance , conseille la migration vers Java.time = classes.

Pour en savoir plus, consultez le Oracle Tutorial . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 31 .

Vous pouvez échanger des objets Java.time directement avec votre base de données. Utilisez un pilote JDBC compatible avec JDBC 4.2 ou version ultérieure. Pas besoin de chaînes, pas besoin de Java.sql.* Des classes.

Où obtenir les classes Java.time?

Le projet ThreeTen-Extra étend Java.time avec des classes supplémentaires. Ce projet est un terrain d'essai pour de futurs ajouts possibles à Java.time. Vous pouvez trouver ici des classes utiles telles que Interval , YearWeek , YearQuarter , et plus .

6
Basil Bourque

Nican a expliqué la partie equals, à propos de compareTo:

  • Timestamp possède une méthode compareTo(Date) qui la convertit en Timestamp en interne
  • Date fait la comparaison par downcasting (puisque Timestamp en est une sous-classe); mais comme l'indique le javadoc: "La relation d'héritage entre Timestamp et Java.util.Date dénote vraiment l'héritage d'implémentation, et non l'héritage de type"

Ce qui bien sûr est une horrible idée, à mon avis.

6
Viruzzo

J'ai eu le même problème lors d'un test où je voulais comparer Java.util.Date et Java.sql.Timestamp objets.

Je les ai convertis en LocalDate et cela a fonctionné:

import org.Apache.commons.lang.ObjectUtils;

// date1 and date2 can be Java.util.Date or Java.sql.Timestamp
boolean datesAreEqual = ObjectUtils.equals(toLocalDate(date1), toLocalDate(date2));

toLocalDate est:

import org.joda.time.LocalDate;
import Java.util.Date;

public static void LocalDate toLocalDate(Date date)
{
    return date != null ? LocalDate.fromDateFields(date) : null;
}
5
Ferran Maylinch
  1. date.equals (stamp) renvoie true ET stamp equals (date) renvoie false. [~ # ~] raison [~ # ~] : la date néglige la partie nanoseconde de l'horodatage et puisque les autres parties se trouvent être égales, le résultat est égal. Les secondes fractionnaires - les nanos - sont distinctes. La méthode Timestamp.equals (Object) ne renvoie jamais la valeur true lorsqu'elle reçoit une valeur de type Java.util.Date car le composant nanos d'une date est inconnu. Voir ici pour plus de détails

    1. date.compareTo (stamp) == 0 renvoie false ET stamp.compareTo (date) == 0 renvoie true. [~ # ~] raison [~ # ~] : Selon cela bug la fonction compareTo se comportera comme elle le fait.
3
Saurabh

La valeur en nanos de Timestamp n'est PAS le nombre de nanosecondes - c'est un nombre de millisecondes de résolution en nanosecondes (c'est-à-dire des fractions de seconde). En tant que tel, dans le constructeur Timestamp, il définit l'heure sur le super pour qu'il soit sans millisecondes. Par conséquent, le Timestamp aura toujours une valeur inférieure pour le membre fastTime (utilisé dans la compareTo() de Date) que le Date correspondant (sauf, bien sûr, , il n'a pas de fraction de seconde).

Vérifiez la source de l'horodatage à la ligne 110.

2
sarumont

Essayez de recréer la date de l'objet en utilisant des "chaînes" comme ceci

Date date = new Date();
Date stamp = Timestamp(date.getTime());

SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String date1String = ft.format(date);
String date2String = ft.format(stamp);

Date date1 = ft.parse(date1String);
Date date2 = ft.parse(date2String);


assertTrue(date1.equals(date2)); // true
assertTrue(date1.compareTo(date2) == 0); //true
assertTrue(date2.compareTo(date1) == 0); //true
assertTrue(date2.equals(date1)); // true
1
Marco Mendão

J'ai résolu de convertir à la fois Date et TimeStamp en un objet Calendar, puis j'ai comparé les propriétés de l'agenda simple:

Calendar date = Calendar.getInstance();
date.setTimeInMillis(dateObject.getTime());
Calendar timestamp = Calendar.getInstance();
timestamp.setTimeInMillis(timestampObject.getTime());
if (effettoAppendice.get(Calendar.DAY_OF_MONTH) == timestamp.get(Calendar.DAY_OF_MONTH) &&
    effettoAppendice.get(Calendar.MONTH) == timestamp.get(Calendar.MONTH) &&
    effettoAppendice.get(Calendar.YEAR) == timestamp.get(Calendar.YEAR)) {
    System.out.println("Date and Timestamp are the same");
} else {
    System.out.println("Date and Timestamp are NOT the same");
}

J'espère que cela t'aides.

1
Pasquale Ciuffreda

Malheureusement, la classe Timestamp surcharge la méthode equals(Object) avec equals(Timestamp) donc les comparaisons sur les horodatages sont difficiles à faire.

Dans la equals(Object) javadocs dit:

Teste pour voir si cet objet Timestamp est égal à l'objet donné. Cette version de la méthode equals a été ajoutée pour corriger la signature incorrecte de Timestamp.equals (Timestamp) et pour préserver la compatibilité descendante avec les fichiers de classe existants. Remarque: Cette méthode n'est pas symétrique par rapport à la méthode equals (Object) dans la classe de base.

Ma règle d'or est de ne jamais comparer les horodatages pour l'égalité (ce qui est à peu près inutile de toute façon), mais si vous devez vérifier l'égalité, comparez-les en utilisant le résultat de getTime() (le nombre de millisecondes à partir de janvier 1er 1970, 00h00).

0
Gabriel Belingueres

Une petite note sur l'héritage d'implémentation et l'héritage de type.

"La classe d'un objet définit la façon dont l'objet est implémenté. En revanche, le type d'un objet se réfère uniquement à son interface. L'héritage de classe (héritage d'implémentation) définit l'implémentation d'un objet en termes d'implémentation d'un autre objet. L'héritage de type décrit quand un objet peut être utilisé à la place d'un autre. "

Les classes d'horodatage et de date ont un héritage d'implémentation, comme l'a dit JAVADOC.

0
Shanmugavel

Jetez un œil à la méthode de comparaison du code source pour l'horodatage:

public boolean equals(Java.lang.Object ts) {
  if (ts instanceof Timestamp) {
    return this.equals((Timestamp)ts);
  } else {
    return false;
  }
}

http://www.docjar.com/html/api/Java/sql/Timestamp.Java.html

Il ne retournera vrai que si l'objet de comparaison est un horodatage. En outre, voici le code source de Date: http://www.docjar.com/html/api/Java/util/Date.Java.html , et puisque Timestamp hérite de Date, il peut le comparer .

0
Nican