Tarin Gamberini

A software engineer and a passionate java programmer

Come calcolare la differenza fra due date in Java (JDK 1.5)

Come calcolare la differenza fra due date in Java?

Una domanda che sento porre periodicamente ed alla quale raramente è risposto con chiarezza, forse anche a causa della documentazione relativa alle classi java.util.Date e java.util.GregorianCalendar, presenti nella JDK 1.5, che in alcuni punti lascia un po' a desiderare.

Nella documentazione troviamo che il metodo getTime() della classe java.util.Date, oppure il metodo getTimeInMillis() della classe java.util.GregorianCalendar, restituisce il numero di millisecondi a partire dall'epoca 1 Gennaio 1970 00:00:00.000 GMT (Gregoriano). Ciò che invece è omesso è che sia getTime() che getTimeInMillis() nel ritornare il numero di millisecondi tengono conto: degli anni bisestili e dei cambi d'ora legale e solare.

Indice

Algoritmo intuitivo

Uno dei modi più intuitivi per calcolare i giorni di differenza che intercorrono fra due date è il seguente.

Definiamo le due date dallaData < allaData:

GregorianCalendar dallaData = null;
GregorianCalendar allaData = null;

convertiamole in millisecondi:

long dallaDataMilliSecondi = dallaData.getTimeInMillis();
long allaDataMilliSecondi = allaData.getTimeInMillis();

calcoliamone la differenza:

long millisecondiFraDueDate = allaDataMilliSecondi - dallaDataMilliSecondi;

ed infine convertiamo i millisecondi di differenza in giorni, utilizzando la divisione fra interi:

//1 giorno = 1000*60*60*24 ms = 86400000 ms
double giorniFraDueDate = millisecondiFraDueDate / 86400000;

L'errore

L'errore commesso nel convertire, da millisecondi a giorni, la differenza fra le due date consiste nell'aver implicitamente supposto che ogni giorno duri 24 ore. Sotto tale ipotesi, infatti, la divisione fra interi darebbe effettivamente i giorni di differenza fra due date. In realtà l'ipotesi è errata poichè non tutti i giorni durano 24 ore. Infatti il giorno di cambio dall'ora solare a quella legale gs→l dura 23 ore, mentre il giorno di cambio dall'ora legale a quella solare gl→s dura 25 ore.

Ecco che la divisione fra interi produce un risultato errato nel caso i millisecondi di differenza non siano un multiplo intero di 24 ore. Invece che affermare che ogni giorno duri 24 ore è più corretto asserire che il giorno medio in un anno dura 24 ore. La media su 365 giorni da esattamente 24 ore poichè l'ora in meno del gl→s è compensata dall'ora in più del gs→l:

1   ...      gls     ...      gsl     ...  365
24  ...  24  23  24  ...  24  25  24  ...  24

Il gs→l del 2005 cade il 27/03 e provando a calcolare, con la divisione intera, i giorni che intercorrono dalla data 27/03/2005 alla data 28/03/2005 si ottiene il risultato giorniFraDueDate = 0.0. Risultato sorprendente per chi pensa che ogni giorno duri 24 ore. Tuttavia la divisione intera tronca le 23 ore, pari a circa 0.9 giorni, così sembra che ci siano zero giorni fra il 27 ed il 28.

Ricalcolando i giorni di differenza fra il 27/03/2005 ed il 28/03/2005 con la divisione fra reali:

// 1 giorno medio = 1000*60*60*24 ms = 86400000 ms
double giorniFraDueDate = millisecondiFraDueDate / 86400000.0;

otteniamo che giorniFraDueDate = 0.9583333333333334 pari a 23.0000000000000016 ore.

Analogamente il gl→s del 2005 cade il 30/10 e provando a calcolare, con la divisione intera, i giorni che intercorrono dalla data 30/10/2005 alla data 31/10/2005 si ottiene il risultato giorniFraDueDate = 1.0. Risultato corretto per chi pensa che ogni giorno duri 24 ore.

Ricalcolando i giorni di differenza fra il 30/10/2005 ed il 31/10/2005 con la divisione fra reali otteniamo che giorniFraDueDate = 1.0416666666666667 pari a 25.0000000000000008 ore.

Riassumendo

Con la divisione fra interi nel calcolare la differenza fra due date si perde un giorno se fra di esse è compreso almeno un gs→l. In tutti gli altri casi il calcolo è corretto.

Con la divisione fra reali sono introdotti dei decimali nella differenza fra due date quando fra di esse è compreso un gs→l, oppure un gl→s. Mentre non sono introdotti decimali se fra le due date cadono sia il gs→l che il gl→s, poichè l'ora in meno del gl→s è compensata dall'ora in più del gs→l.

Per generalizzare i risultati ottenuti, per la divisione fra reali, a due date appartenenti ad anni diversi si può pensare al tempo come ad un susseguirsi di periodi, nei quali vige l'ora solare (winter time) o l'ora legale (summer time), intervallati da giorni di cambio d'ora:

... oraSolare gsl oraLegale gls oraSolare gsl oraLegale ...

I giorni di differenza fra due date sono un numero intero se queste ultime appartengono ad uno stesso tipo di periodo (Solare o Legale) poiché contengono un numero pari di coppie gs→l gl→s. Mentre i giorni di differenza fra due date sono un numero razionale se queste ultime appartengono a diversi tipi di periodo (Solare o Legale) poiché, oltre a contenere un numero pari di coppie gs→l gl→s, contengono anche un solo gs→l, oppure un solo gl→s.

Una soluzione

Una fra le soluzioni possibili è quella di arrotondare il risultato della conversione in giorni calcolato con la divisione fra reali:

// 1 giorno medio = 1000*60*60*24 ms = 86400000 ms
double giorniFraDueDate = Math.round(millisecondiFraDueDate / 86400000.0);

Algoritmo di test DifferenzaDate1.java

java DifferenzaDate1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.taringamberini.util.date.test;

import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;



/**
 * <p>
 * Calcola i giorni di differenza fra un giorno ed il giorno immediatamente
 * successivo. Il calcolo è ripetuto per tutte le coppie successive di date dal
 * 01/01/2003 alla 31/12/2006.
 * </p>
 *
 * @author Tarin Gamberini - www.taringamberini.com
 */
public class DifferenzaDate1 {

  /**
  * <p>
  * Calcola i giorni di differenza nell'intervallo ] dallaDataGC, allaDataGC ].
  * </p>
  *
  * @param dallaDataGC
  * @param allaDataGC
  * @return Una stringa contenete i giorni fra due date calcolati con la
  *         divisione intera, con la divisione reale e con l'arrotondamento
  *         della divisione reale.
  */
  public String giorniFraDueDate( GregorianCalendar dallaDataGC, GregorianCalendar allaDataGC ) {

      // conversione in millisecondi
      long dallaDataMilliSecondi = dallaDataGC.getTimeInMillis();
      long allaDataMilliSecondi = allaDataGC.getTimeInMillis();

      long millisecondiFraDueDate = allaDataMilliSecondi - dallaDataMilliSecondi;

      // conversione in giorni con la divisione intera
      double giorniFraDueDate_DivInt = millisecondiFraDueDate / 86400000;

      // conversione in giorni con la divisione reale
      double giorniFraDueDate_DivReal = millisecondiFraDueDate / 86400000.0;

      // conversione in giorni con arrotondamento della divisione reale
      double giorniFraDueDate_DivRealRound = Math.round( millisecondiFraDueDate / 86400000.0 );

      return giorniFraDueDate_DivInt + "\t" +
              giorniFraDueDate_DivReal + "\t" +
              giorniFraDueDate_DivRealRound;
  }

  /**
  * <p>
  * Calcola i giorni di differenza fra un giorno ed il giorno immediatamente
  * successivo. Il calcolo è ripetuto per tutte le coppie successive di date
  * dal 01/01/2003 alla 31/12/2006.
  * </p>
  * <p>
  * Interessanti i risultati nei giorno di cambio fra ora solare/legale e
  * legale/solare:
  * <p>
  * <ul>
  * * ] 30/03/2003, 31/03/2003 ]
  * * ] 26/10/2003, 27/10/2003 ]
  * * ] 28/03/2004, 29/03/2004 ]
  * * ] 31/10/2004, 01/11/2004 ]
  * * ] 27/03/2005, 28/03/2005 ]
  * * ] 30/10/2005, 31/10/2005 ]
  * * ] 26/03/2006, 27/03/2006 ]
  * * ] 29/10/2006, 30/10/2006 ]
  * </ul>
  */
  public DifferenzaDate1() {
      SimpleDateFormat sdf = new SimpleDateFormat( "dd/MM/yyyy" );

      // dalla data
      GregorianCalendar dallaDataGC = new GregorianCalendar( 2003, GregorianCalendar.JANUARY, 1 );
      // alla data
      GregorianCalendar allaDataGC = new GregorianCalendar( 2003, GregorianCalendar.JANUARY, 2 );

      System.out.println( "]  dallaData,   allaData ]\t" + "int" + "\t" + "real" + "\t" + "round" );
      while ( !sdf.format( allaDataGC.getTime() ).equals( "31/12/2006" ) ) {

          // calcolo i giorni di differenza fra due date
          String numGiorni = giorniFraDueDate( dallaDataGC, allaDataGC );

          System.out.println( "] " +
                  sdf.format( dallaDataGC.getTime() ) + ", " +
                  sdf.format( allaDataGC.getTime() ) + " ]\t" +
                  numGiorni );

          // incremento di un giorno l'intervallo ]dallaData, allaData]
          dallaDataGC.add( GregorianCalendar.DATE, 1 );
          allaDataGC.add( GregorianCalendar.DATE, 1 );
      }
  }

  public static void main( String[] args ) {
      new DifferenzaDate1();
  }

}

Calcola i giorni di differenza fra un giorno ed il giorno immediatamente successivo. Il calcolo è ripetuto per tutte le coppie successive di date dal 01/01/2003 alla 31/12/2006. Interessanti i risultati per i giorni gs→l e gl→s:

]   da   ,    a    ] int real round
]27/03/04, 28/03/04] 1.0 1.0  1.0
]28/03/04, 29/03/04] 0.0 0.9583333334 1.0
]29/03/04, 30/03/04] 1.0 1.0  1.0
...
]30/10/04, 31/10/04] 1.0 1.0  1.0
]31/10/04, 01/11/04] 1.0 1.0416666667 1.0
]01/11/04, 02/11/04] 1.0 1.0  1.0

Algoritmo di test DifferenzaDate2.java

java DifferenzaDate1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package com.taringamberini.util.date.test;

import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;



/**
 * <p>
 * Calcola i giorni di differenza fra un giorno ed il giorno immediatamente
 * successivo. Il calcolo è ripetuto per tutte le coppie successive di date dal
 * 01/01/2003 alla 31/12/2006.
 * </p>
 *
 * @author Tarin Gamberini - www.taringamberini.com
 */
public class DifferenzaDate2 {

  /**
  * <p>
  * Calcola i giorni di differenza dal 01/01/2003 per ogni giorno fino al
  * 31/12/2006.
  * </p>
  *
  * @param dallaDataGC
  * @param allaDataGC
  * @return Una stringa contenete i giorni fra due date calcolati con la
  *         divisione intera, con la divisione reale e con l'arrotondamento
  *         della divisione reale.
  */
  public String giorniFraDueDate( GregorianCalendar dallaDataGC, GregorianCalendar allaDataGC ) {

      // conversione in millisecondi
      long dallaDataMilliSecondi = dallaDataGC.getTimeInMillis();
      long allaDataMilliSecondi = allaDataGC.getTimeInMillis();

      long millisecondiFraDueDate = allaDataMilliSecondi - dallaDataMilliSecondi;

      // conversione in giorni con la divisione intera
      double giorniFraDueDate_DivInt = millisecondiFraDueDate / 86400000;

      // conversione in giorni con la divisione reale
      double giorniFraDueDate_DivReal = millisecondiFraDueDate / 86400000.0;

      // conversione in giorni con arrotondamento della divisione reale
      double giorniFraDueDate_DivRealRound = Math.round( millisecondiFraDueDate / 86400000.0 );

      return giorniFraDueDate_DivInt + "\t" +
              giorniFraDueDate_DivReal + "\t" +
              giorniFraDueDate_DivRealRound;
  }

  /**
  * <p>
  * Calcola i giorni di differenza dal 01/01/2003 per ogni giorno fino al
  * 31/12/2006.
  * </p>
  * <p>
  * Interessanti i risultati nei giorno di cambio fra ora solare/legale e
  * legale/solare:
  * <p>
  * <ul>
  * * ] 30/03/2003, 31/03/2003 ]
  * * ] 26/10/2003, 27/10/2003 ]
  * * ] 28/03/2004, 29/03/2004 ]
  * * ] 31/10/2004, 01/11/2004 ]
  * * ] 27/03/2005, 28/03/2005 ]
  * * ] 30/10/2005, 31/10/2005 ]
  * * ] 26/03/2006, 27/03/2006 ]
  * * ] 29/10/2006, 30/10/2006 ]
  * </ul>
  */
  public DifferenzaDate2() {
      SimpleDateFormat sdf = new SimpleDateFormat( "dd/MM/yyyy" );

      // dalla data
      GregorianCalendar dallaDataGC = new GregorianCalendar( 2003, GregorianCalendar.JANUARY, 1 );
      // alla data
      GregorianCalendar allaDataGC = new GregorianCalendar( 2003, GregorianCalendar.JANUARY, 2 );

      System.out.println( "]  dallaData,   allaData ]\t" + "int" + "\t" + "real" + "\t" + "round" );
      while ( !sdf.format( allaDataGC.getTime() ).equals( "31/12/2006" ) ) {

          // calcolo i giorni di differenza fra due date
          String numGiorni = giorniFraDueDate( dallaDataGC, allaDataGC );

          System.out.println( "] " +
                  sdf.format( dallaDataGC.getTime() ) + ", " +
                  sdf.format( allaDataGC.getTime() ) + " ]\t" +
                  numGiorni );

          // incremento solo l'estremo destro dell'intervallo ]dallaData, allaData]
          allaDataGC.add( GregorianCalendar.DATE, 1 );
      }
  }

  public static void main( String[] args ) {
      new DifferenzaDate2();
  }

}

Calcola i giorni di differenza fra due date: la prima è fissata al 01/01/2003, la seconda parte dal giorno successivo e viene incrementata fino al 31/12/2006. Interessanti i risultati per i giorni gs→l e gl→s:

]   da   ,    a    ] int   real  round
]01/01/03, 30/03/03] 88.0  88.0  88.0
]01/01/03, 31/03/03] 88.0  88.958333333 89.0
]01/01/03, 01/04/03] 89.0  89.958333333 90.0
...
]01/01/03, 26/10/03] 297.0 297.95833333 298.0
]01/01/03, 27/10/03] 299.0 299.0 299.0
]01/01/03, 28/10/03] 300.0 300.0 300.0

Link di approfondimento

  • Joda è un progetto che punta al miglioramento delle funzionalità base di Java. In particolare Joda Time rimpiazza le classi java Data e Time
  • La data Giuliana è un altro sistema di rappresentazione del tempo
  • Alcune informazioni, meno tecniche delle precedenti, sulla data Giuliana
  • L'articolo di un collega, Daniele Cremonini, sintetizza l'approccio classico all'utilizzo delle date in Java

Invia un commento

Un commento è inviato attraverso una normalissima e-mail. Il tuo indirizzo e-mail non sarà pubblicato o diffuso.

Questo blog è moderato, per cui alcuni commenti potrebbero non essere pubblicati. I commenti sono solitamente approvati dal moderatore in uno/tre giorni.