Pourquoi la différence entre le 30 mars et le 1er mars 2020 donne-t-elle par erreur 28 jours au lieu de 29?

124
TimeUnit.DAYS.convert(
   Math.abs(
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("30-03-2020 00:00:00").getTime() - 
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("1-03-2020 00:00:00").getTime()
   ),
   TimeUnit.MILLISECONDS)

Le résultat est 28, alors qu'il devrait être 29.

Le fuseau horaire / l'emplacement pourrait-il être le problème?

Joe
la source
17
Remarque: veuillez ne pas utiliser SimpleDateFormatplus longtemps, car il est obsolète. Utilisez java.timeplutôt des packages de . Dans SimpleDateFormatle cas, utilisez DateTimeFormatter. Dans le cas de Java 7, voir le commentaire d'Andy Turner ci-dessous.
MC Emperor
28
Ne faites pas de maths à temps. Utilisez une bibliothèque de temps appropriée ( java.time[bien que je note que vous êtes sur Java 7], ThreeTenBp , Joda).
Andy Turner
14
J'aurais adoré être à la réunion où quelqu'un va "Ok, maintenant que nous avons des fuseaux horaires un peu compris, allons à 100% en mode cul et implémentons cette chose appelée l'heure d'été qui m'est venue dans un rêve après mon voyage à l'acide hier soir."
MonkeyZeus
5
@gmauch TimeUnitne prétend rien savoir de l'heure d'été. Comme le dit le javadoc: Une nanoseconde est définie comme un millième de microseconde, une microseconde comme un millième de milliseconde, une milliseconde comme un millième de seconde, une minute comme soixante secondes, une heure comme soixante minutes et un jour comme vingt-quatre heures . --- Étant donné que l'heure d'été fait que 2 jours de l'année ne sont pas exactement 24 heures, TimeUnitil se trompe lorsque l'heure d'été est impliquée.
Andreas
1
Ce problème se produit uniquement sur les ordinateurs qui se trouvent dans des fuseaux horaires avec l'heure d'été. Il donne le nombre correct de jours (29) dans les fuseaux horaires qui n'ont pas d'heure d'été!
Gopinath

Réponses:

207

Le problème est qu'en raison du décalage horaire (le dimanche 8 mars 2020), il y a 28 jours et 23 heures entre ces dates. TimeUnit.DAYS.convert(...) tronque le résultat à 28 jours.

Pour voir le problème (je suis dans le fuseau horaire US Eastern):

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

System.out.println(diff);
System.out.println("Days: " + TimeUnit.DAYS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Hours: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Days: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS) / 24.0);

Production

2502000000
Days: 28
Hours: 695
Days: 28.958333333333332

Pour résoudre ce problème, utilisez un fuseau horaire sans heure d'été, par exemple UTC :

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

Production

2505600000
Days: 29
Hours: 696
Days: 29.0
Andreas
la source
121
"Pour réparer" ne faites pas de maths avec le temps. Utilisez une bibliothèque de date / heure appropriée.
Andy Turner
62
@AndyTurner Pour corriger, en utilisant les API Java 7 intégrées. Puisqu'il peut être corrigé, montrant comment est une réponse valide. Forcer quelqu'un à inclure une bibliothèque complète (Joda-Time, ThreeTen, etc.) juste pour faire ce seul calcul serait exagéré. Bien sûr, l'utilisation d'une bibliothèque serait recommandée, mais n'est pas obligatoire .
Andreas
38
Je suis respectueusement en désaccord. Si vous voulez faire un calcul, faites-le bien; payer le coût pour bien faire les choses.
Andy Turner
16
Mon 0,02 €: La "bonne" façon de faire en Java 7 sans bibliothèque externe serait d'utiliser un GregorianCalendarobjet et d'ajouter 1 jour à la fois jusqu'à la date de fin. Je paierais un prix élevé pour éviter cela. Et ajouter le backport d'une bibliothèque qui fait déjà partie de Java 8, 9, 10, 11, 12, 13,… n'est pas cher. Au contraire, la prochaine fois que vous devrez faire quelque chose avec la date ou l'heure, ce sera déjà un gain.
Ole VV
27
Même en UTC, le dernier jour de juin est parfois une seconde trop courte, sans véritable avertissement ni prévisibilité. Utilisez toujours une bibliothèque date-heure.
Affe
41

La cause de ce problème est déjà mentionnée dans la réponse d'Andreas .

La question est de savoir exactement ce que vous voulez compter. Le fait que vous déclariez que la différence réelle devrait être de 29 au lieu de 28 et que vous demandez si «l'heure de l'emplacement / de la zone pourrait être un problème» révèle ce que vous voulez réellement compter. Apparemment, vous voulez vous débarrasser de toute différence de fuseau horaire.

Je suppose que vous souhaitez uniquement calculer les jours, sans heure ni fuseau horaire.

Java 8

Ci-dessous, dans l'exemple de la façon dont le nombre de jours entre les deux pourrait être calculé correctement, j'utilise une classe qui représente exactement cela - une date sans heure ni fuseau horaire - LocalDate.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MM-yyyy HH:mm:ss");
LocalDate start = LocalDate.parse("1-03-2020 00:00:00", formatter);
LocalDate end = LocalDate.parse("30-03-2020 00:00:00", formatter);

long daysBetween = ChronoUnit.DAYS.between(start, end);

Notez que ChronoUnit, DateTimeFormatteret LocalDatenécessitent au moins Java 8, qui n'est pas disponible pour vous, selon la balise . Cependant, c'est peut-être pour les futurs lecteurs.

Comme mentionné par Ole VV, il y a aussi le backport ThreeTen , qui rétroporte les fonctionnalités de l'API date et heure Java 8 vers Java 6 et 7.

Empereur MC
la source
2
@ OleV.V. Je sais qu'il y a ThreeTen, certains utilisateurs l'ont peut-être mentionné à plusieurs reprises. (J'étais sur le point de créer un lien vers une requête SEDE renvoyant tous les messages et commentaires de l'utilisateur 5772882 contenant le texte ThreeTen;-), mais malheureusement, il est hors ligne au moment de la rédaction.) Je mettrai à jour le message.
MC Emperor
2
@OleVV Ce n'est pas du tout une mauvaise chose. Je pense que la plupart des gens sont tout simplement ignorants java.time, car à l'école, ils utilisent toujours les anciennes classes. Mais l'API Java 8 Date and Time est très bien conçue - ce serait une perte de ne pas l'utiliser.
MC Emperor