Perché la differenza tra il 30 marzo e il 1 marzo 2020 dà erroneamente 28 giorni anziché 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)

Il risultato è 28, mentre dovrebbe essere 29.

Il fuso orario / la posizione potrebbe essere il problema?


17
Nota: non utilizzare SimpleDateFormatpiù, poiché è obsoleto. Usa java.timeinvece i pacchetti da . Nel SimpleDateFormatcaso, utilizzare DateTimeFormatter. Nel caso di Java 7, vedere il commento di Andy Turner di seguito.
MC Emperor

28
Non fare matematica a volte. Utilizzare una libreria temporale corretta ( java.time[anche se noto che sei su Java 7], ThreeTenBp , Joda).
Andy Turner,

14
Mi piacerebbe essere stato all'incontro in cui qualcuno va "Ok, ora che abbiamo fusi orari un po 'capito andiamo in modalità culo al 100% e implementiamo questa cosa chiamata ora legale che mi è venuta in sogno dopo il mio viaggio acido la notte scorsa."
MonkeyZeus

5
@gmauch TimeUnitnon pretende di sapere nulla dell'ora legale. Come dice il javadoc: un nanosecondo è definito come un millesimo di microsecondo, un microsecondo come un millesimo di millisecondo, un millisecondo come un millesimo di secondo, un minuto come sessanta secondi, un'ora come sessanta secondi e un giorno come ventiquattro ore . --- Poiché l'ora legale fa sì che 2 giorni all'anno non siano esattamente 24 ore, TimeUnitsi sbaglia nel caso dell'ora legale.
Andreas,

1
Questo problema si verifica solo su computer che si trovano in fusi orari con l'ora legale. Fornisce il numero corretto di giorni (29) in fusi orari che non hanno l'ora legale!
Gopinath,

Risposte:


207

Il problema è che, a causa dello spostamento dell'ora legale (domenica 8 marzo 2020), ci sono 28 giorni e 23 ore tra quelle date. TimeUnit.DAYS.convert(...) tronca il risultato a 28 giorni.

Per vedere il problema (sono nel fuso orario degli Stati Uniti orientali):

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

Produzione

2502000000
Days: 28
Hours: 695
Days: 28.958333333333332

Per risolvere, usa un fuso orario che non ha l'ora legale, ad esempio 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();

Produzione

2505600000
Days: 29
Hours: 696
Days: 29.0

121
"Per risolvere" non fare matematica con i tempi. Utilizzare una libreria data / ora corretta.
Andy Turner,

62
@AndyTurner Per risolvere, utilizzando le API Java 7 integrate. Dal momento che può essere risolto, mostrando come è una risposta valida. Costringere qualcuno a includere una libreria completa (Joda-Time, ThreeTen, ecc.) Solo per fare questo calcolo sarebbe eccessivo. Certo, si consiglia di utilizzare una libreria, ma non è necessario .
Andreas,

38
Rispetto rispettosamente. Se vuoi fare un calcolo, fallo bene; pagare il costo per farlo bene.
Andy Turner,

16
Il mio € 0,02: il modo "giusto" di fare in Java 7 senza alcuna libreria esterna sarebbe quello di utilizzare un GregorianCalendaroggetto e aggiungere 1 giorno alla volta fino al raggiungimento della data di fine. Pagherei un prezzo elevato per evitarlo. E l'aggiunta del backport di una libreria che fa già parte di Java 8, 9, 10, 11, 12, 13, ... non è un prezzo elevato. Al contrario, la prossima volta che devi fare qualcosa con la data o l'ora, sarà già un guadagno.
Ole VV,

27
Anche in UTC l'ultimo giorno di giugno è occasionalmente un secondo troppo breve, senza alcun preavviso o prevedibilità. Utilizzare sempre una libreria di data e ora.
Affe il

41

La causa di questo problema è già menzionata nella risposta di Andreas .

La domanda è che cosa vuoi esattamente contare. Il fatto che tu affermi che la differenza effettiva dovrebbe essere 29 invece di 28 e chiedi se "l'ora della posizione / zona potrebbe essere un problema" , rivela ciò che vuoi effettivamente contare. Apparentemente, vuoi sbarazzarti di qualsiasi differenza di fuso orario.

Presumo che tu voglia solo calcolare i giorni, senza tempo e fuso orario.

Java 8

Di seguito, nell'esempio di come il numero di giorni in mezzo potrebbe essere calcolato correttamente, sto usando una classe che rappresenta esattamente quello - una data senza ora e fuso orario - 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);

Si noti che ChronoUnit, DateTimeFormattere LocalDaterichiedono almeno Java 8, che non è a disposizione di voi, secondo il tag. Tuttavia, forse è per i futuri lettori.

Come menzionato da Ole VV, c'è anche il ThreeTen Backport , che esegue il backport della funzionalità API di data e ora di Java 8 su Java 6 e 7.


2
@ OleV.V. So che c'è ThreeTen, alcuni utenti potrebbero averlo menzionato alcune volte. (Stavo per collegarmi a una query SEDE che restituiva tutti i post e i commenti dell'utente 5772882 contenenti il ​​testo ThreeTen;-), ma sfortunatamente non è in linea al momento della scrittura.) Aggiornerò il post.
MC Emperor

2
@OleVV Non è affatto una brutta cosa. Penso che la maggior parte delle persone sia semplicemente ignorante java.time, perché a scuola usano ancora le vecchie classi. Ma l'API Date and Time di Java 8 è molto ben progettata: sarebbe una perdita non usarla.
MC Emperor
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.