Java 8: Calculer la différence entre deux LocalDateTime


J'essaie de calculer la différence entre deux LocalDateTime.

La sortie doit être du format y years m months d days h hours m minutes s seconds. Voici ce que j'ai écrit:

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;

public class Main {

    static final int MINUTES_PER_HOUR = 60;
    static final int SECONDS_PER_MINUTE = 60;
    static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;

    public static void main(String[] args) {
        LocalDateTime toDateTime = LocalDateTime.of(2014, 9, 9, 19, 46, 45);
        LocalDateTime fromDateTime = LocalDateTime.of(1984, 12, 16, 7, 45, 55);

        Period period = getPeriod(fromDateTime, toDateTime);
        long time[] = getTime(fromDateTime, toDateTime);

        System.out.println(period.getYears() + " years " + 
                period.getMonths() + " months " + 
                period.getDays() + " days " +
                time[0] + " hours " +
                time[1] + " minutes " +
                time[2] + " seconds.");


    }

    private static Period getPeriod(LocalDateTime dob, LocalDateTime now) {
        return Period.between(dob.toLocalDate(), now.toLocalDate());
    }

    private static long[] getTime(LocalDateTime dob, LocalDateTime now) {
        LocalDateTime today = LocalDateTime.of(now.getYear(),
                now.getMonthValue(), now.getDayOfMonth(), dob.getHour(), dob.getMinute(), dob.getSecond());
        Duration duration = Duration.between(today, now);

        long seconds = duration.getSeconds();

        long hours = seconds / SECONDS_PER_HOUR;
        long minutes = ((seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE);
        long secs = (seconds % SECONDS_PER_MINUTE);

        return new long[]{hours, minutes, secs};
    }
}

La sortie que j'obtiens est 29 years 8 months 24 days 12 hours 0 minutes 50 seconds. J'ai vérifié mon résultat à partir de ce site (avec les valeurs 12/16/1984 07:45:55 et 09/09/2014 19:46:45). La capture d'écran suivante montre la sortie:

Convertisseur d'époque

Je suis à peu près sûr que les champs après la valeur du mois proviennent mal de mon code. Toute suggestion serait très utile.

Mise à jour

J'ai testé mon résultat à partir d'un autre site Web et le résultat que j'ai obtenu est différent. La voici: Calculer la durée entre deux dates (résultat: 29 ans, 8 mois, 24 jours, 12 heures, 0 minute et 50 secondes).

Mise à jour

Puisque j'ai obtenu deux résultats différents de deux sites différents, je me demande si l'algorithme de mon calcul est légitime ou non. Si j'utilise les deux objets LocalDateTime suivants:

LocalDateTime toDateTime = LocalDateTime.of(2014, 9, 10, 6, 40, 45);
LocalDateTime fromDateTime = LocalDateTime.of(1984, 12, 16, 7, 45, 55);

Puis la sortie est à venir: 29 years 8 months 25 days -1 hours -5 minutes -10 seconds.

De cette lien, il devrait être 29 years 8 months 24 days 22 hours, 54 minutes and 50 seconds. L'algorithme doit donc également gérer les nombres négatifs.

Notez que la question ne concerne pas le site qui m'a donné quel résultat, j'ai besoin de connaître le bon algorithme et d'avoir les bons résultats.

Author: satnam, 2014-09-09

6 answers

Malheureusement, il ne semble pas y avoir de classe period qui s'étend également sur le temps, vous devrez donc peut-être faire les calculs vous-même.

Forunately les classes date et time ont beaucoup de méthodes utilitaires qui simplifient cela dans une certaine mesure. Voici un moyen de calculer la différence, mais pas nécessairement le plus rapide:

LocalDateTime fromDateTime = LocalDateTime.of(1984, 12, 16, 7, 45, 55);
LocalDateTime toDateTime = LocalDateTime.of(2014, 9, 10, 6, 40, 45);

LocalDateTime tempDateTime = LocalDateTime.from( fromDateTime );

long years = tempDateTime.until( toDateTime, ChronoUnit.YEARS);
tempDateTime = tempDateTime.plusYears( years );

long months = tempDateTime.until( toDateTime, ChronoUnit.MONTHS);
tempDateTime = tempDateTime.plusMonths( months );

long days = tempDateTime.until( toDateTime, ChronoUnit.DAYS);
tempDateTime = tempDateTime.plusDays( days );


long hours = tempDateTime.until( toDateTime, ChronoUnit.HOURS);
tempDateTime = tempDateTime.plusHours( hours );

long minutes = tempDateTime.until( toDateTime, ChronoUnit.MINUTES);
tempDateTime = tempDateTime.plusMinutes( minutes );

long seconds = tempDateTime.until( toDateTime, ChronoUnit.SECONDS);

System.out.println( years + " years " + 
        months + " months " + 
        days + " days " +
        hours + " hours " +
        minutes + " minutes " +
        seconds + " seconds.");

//prints: 29 years 8 months 24 days 22 hours 54 minutes 50 seconds.

L'idée de base est la suivante: créer une date de début temporaire et obtenir les années complètes jusqu'à la fin. Ajustez ensuite cette date par le nombre d'années afin que le la date de début est inférieure à un an de la fin. Répéter pour chaque unité de temps dans l'ordre décroissant.

Enfin un avertissement: Je n'ai pas pris en compte différents fuseaux horaires (les deux dates doivent être dans le même fuseau horaire) et je n'ai pas non plus testé/vérifié comment l'heure d'été ou d'autres changements dans un calendrier (comme les changements de fuseau horaire à Samoa) affectent ce calcul. Donc à utiliser avec précaution.

 98
Author: Thomas, 2014-09-10 12:35:42

J'ai trouvé que la meilleure façon de le faire est avec ChronoUnit.

long minutes = ChronoUnit.MINUTES.between(fromDate, toDate);
long hours = ChronoUnit.HOURS.between(fromDate, toDate);

Documentation supplémentaire est ici: https://docs.oracle.com/javase/tutorial/datetime/iso/period.html

 315
Author: satnam, 2014-11-16 07:34:42

Voici un seul exemple utilisant Duration et TimeUnit pour obtenir le format 'hh:mm:ss'.

                Duration dur = Duration.between(LocalDateTimeIni, LocalDateTimeEnd);
                long millis = dur.toMillis();

                String.format("%02d:%02d:%02d", 
                        TimeUnit.MILLISECONDS.toHours(millis),
                        TimeUnit.MILLISECONDS.toMinutes(millis) - 
                        TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)),
                        TimeUnit.MILLISECONDS.toSeconds(millis) - 
                        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
 16
Author: Junior Batista, 2017-05-23 18:53:52

Et la version de @Thomas dans Groovy avec prend les unités souhaitées dans une liste au lieu de coder en dur les valeurs. Cette implémentation (qui peut facilement être portée sur Java - j'ai rendu la déclaration de fonction explicite) rend l'approche de Thomas plus réutilisable.

def fromDateTime = LocalDateTime.of(1968, 6, 14, 0, 13, 0)
def toDateTime = LocalDateTime.now()
def listOfUnits = [
    ChronoUnit.YEARS, ChronoUnit.MONTHS, ChronoUnit.DAYS,
    ChronoUnit.HOURS, ChronoUnit.MINUTES, ChronoUnit.SECONDS,
    ChronoUnit.MILLIS]

println calcDurationInTextualForm(listOfUnits, fromDateTime, toDateTime)    

String calcDurationInTextualForm(List<ChronoUnit> listOfUnits, LocalDateTime ts, LocalDateTime to)
{
    def result = []

    listOfUnits.each { chronoUnit ->
        long amount = ts.until(to, chronoUnit)
        ts = ts.plus(amount, chronoUnit)

        if (amount) {
            result << "$amount ${chronoUnit.toString()}"
        }
    }

    result.join(', ')
}

, Au moment d'écrire ces lignes,le code ci-dessus renvoie 47 Years, 8 Months, 9 Days, 22 Hours, 52 Minutes, 7 Seconds, 140 Millis. Et, pour l'entrée @ Gennady Kolomoets, le code renvoie 23 Hours.

Lorsque vous fournissez une liste d'unités, elle doit être triée par taille des unités (la plus grande premier):

def listOfUnits = [ChronoUnit.WEEKS, ChronoUnit.DAYS, ChronoUnit.HOURS]
// returns 2495 Weeks, 3 Days, 8 Hours
 5
Author: ChrLipp, 2016-04-11 06:29:12

Voici une réponse très simple à votre question. Elle fonctionne.

import java.time.*;
import java.util.*;
import java.time.format.DateTimeFormatter;
public class MyClass {
    public static void main(String args[]) {
       DateTimeFormatter T = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
       Scanner h = new Scanner(System.in);

       System.out.print("Enter date of birth[dd/mm/yyyy hh:mm]: ");
       String b = h.nextLine();

       LocalDateTime bd = LocalDateTime.parse(b,T);
       LocalDateTime cd = LocalDateTime.now();

       int hr = cd.getHour() - bd.getHour();
       int mn = cd.getMinute() - bd.getMinute();

       Period time = Period.between(bd.toLocalDate(),cd.toLocalDate());

       System.out.print("Age is: "+time.getYears()+ " years,"+time.getMonths()+ " months, " +time.getDays()+ " days, "+hr+ " hours, " +mn+ " minutes old");
    }
}
 4
Author: Tinashe Kadiki, 2017-11-24 01:40:13

Il y a un problème pour le code Tapas Bose et le code Thomas. Si la différence de temps est négative, array obtient les valeurs négatives. Par exemple si

LocalDateTime toDateTime = LocalDateTime.of(2014, 9, 10, 6, 46, 45);
LocalDateTime fromDateTime = LocalDateTime.of(2014, 9, 9, 7, 46, 45);

Il renvoie 0 ans 0 mois 1 jours -1 heures 0 minutes 0 secondes.

Je pense que la bonne sortie est: 0 ans 0 mois 0 jours 23 heures 0 minutes 0 secondes.

Je propose de séparer les instances LocalDateTime sur les instances LocalDate et LocalTime. Après cela, nous pouvons obtenir les instances Java 8 Period et Duration. Le L'instance Duration est séparée sur le nombre de jours et la valeur de temps tout au long de la journée (

Voici ma façon de calculer la différence LocalDateTime:

private void getChronoUnitForSecondAfterFirst(LocalDateTime firstLocalDateTime, LocalDateTime secondLocalDateTime, long[] chronoUnits) {
    /*Separate LocaldateTime on LocalDate and LocalTime*/
    LocalDate firstLocalDate = firstLocalDateTime.toLocalDate();
    LocalTime firstLocalTime = firstLocalDateTime.toLocalTime();

    LocalDate secondLocalDate = secondLocalDateTime.toLocalDate();
    LocalTime secondLocalTime = secondLocalDateTime.toLocalTime();

    /*Calculate the time difference*/
    Duration duration = Duration.between(firstLocalDateTime, secondLocalDateTime);
    long durationDays = duration.toDays();
    Duration throughoutTheDayDuration = duration.minusDays(durationDays);
    Logger.getLogger(PeriodDuration.class.getName()).log(Level.INFO,
            "Duration is: " + duration + " this is " + durationDays
            + " days and " + throughoutTheDayDuration + " time.");

    Period period = Period.between(firstLocalDate, secondLocalDate);

    /*Correct the date difference*/
    if (secondLocalTime.isBefore(firstLocalTime)) {
        period = period.minusDays(1);
        Logger.getLogger(PeriodDuration.class.getName()).log(Level.INFO,
                "minus 1 day");
    }

    Logger.getLogger(PeriodDuration.class.getName()).log(Level.INFO,
            "Period between " + firstLocalDateTime + " and "
            + secondLocalDateTime + " is: " + period + " and duration is: "
            + throughoutTheDayDuration
            + "\n-----------------------------------------------------------------");

    /*Calculate chrono unit values and  write it in array*/
    chronoUnits[0] = period.getYears();
    chronoUnits[1] = period.getMonths();
    chronoUnits[2] = period.getDays();
    chronoUnits[3] = throughoutTheDayDuration.toHours();
    chronoUnits[4] = throughoutTheDayDuration.toMinutes() % 60;
    chronoUnits[5] = throughoutTheDayDuration.getSeconds() % 60;
}

La méthode ci-dessus peut être utilisée pour calculer la différence de toutes les valeurs de date et d'heure locales, par exemple:

public long[] getChronoUnits(String firstLocalDateTimeString, String secondLocalDateTimeString) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    LocalDateTime firstLocalDateTime = LocalDateTime.parse(firstLocalDateTimeString, formatter);
    LocalDateTime secondLocalDateTime = LocalDateTime.parse(secondLocalDateTimeString, formatter);

    long[] chronoUnits = new long[6];
    if (secondLocalDateTime.isAfter(firstLocalDateTime)) {
        getChronoUnitForSecondAfterFirst(firstLocalDateTime, secondLocalDateTime, chronoUnits);
    } else {
        getChronoUnitForSecondAfterFirst(secondLocalDateTime, firstLocalDateTime, chronoUnits);
    }
    return chronoUnits;
}

Il est pratique d'écrire un test unitaire pour la méthode ci-dessus (les deux sont des membres de la classe PeriodDuration). Voici le code:

@RunWith(Parameterized.class)
public class PeriodDurationTest {

private final String firstLocalDateTimeString;
private final String secondLocalDateTimeString;
private final long[] chronoUnits;

public PeriodDurationTest(String firstLocalDateTimeString, String secondLocalDateTimeString, long[] chronoUnits) {
    this.firstLocalDateTimeString = firstLocalDateTimeString;
    this.secondLocalDateTimeString = secondLocalDateTimeString;
    this.chronoUnits = chronoUnits;
}

@Parameters
public static Collection<Object[]> periodValues() {
    long[] chronoUnits0 = {0, 0, 0, 0, 0, 0};
    long[] chronoUnits1 = {0, 0, 0, 1, 0, 0};
    long[] chronoUnits2 = {0, 0, 0, 23, 0, 0};
    long[] chronoUnits3 = {0, 0, 0, 1, 0, 0};
    long[] chronoUnits4 = {0, 0, 0, 23, 0, 0};
    long[] chronoUnits5 = {0, 0, 1, 23, 0, 0};
    long[] chronoUnits6 = {29, 8, 24, 12, 0, 50};
    long[] chronoUnits7 = {29, 8, 24, 12, 0, 50};
    return Arrays.asList(new Object[][]{
        {"2015-09-09 21:46:44", "2015-09-09 21:46:44", chronoUnits0},
        {"2015-09-09 21:46:44", "2015-09-09 22:46:44", chronoUnits1},
        {"2015-09-09 21:46:44", "2015-09-10 20:46:44", chronoUnits2},
        {"2015-09-09 21:46:44", "2015-09-09 20:46:44", chronoUnits3},
        {"2015-09-10 20:46:44", "2015-09-09 21:46:44", chronoUnits4},
        {"2015-09-11 20:46:44", "2015-09-09 21:46:44", chronoUnits5},
        {"1984-12-16 07:45:55", "2014-09-09 19:46:45", chronoUnits6},
        {"2014-09-09 19:46:45", "1984-12-16 07:45:55", chronoUnits6}
    });
}

@Test
public void testGetChronoUnits() {
    PeriodDuration instance = new PeriodDuration();
    long[] expResult = this.chronoUnits;
    long[] result = instance.getChronoUnits(this.firstLocalDateTimeString, this.secondLocalDateTimeString);
    assertArrayEquals(expResult, result);
}

}

Tous les tests réussissent, que la valeur de la première LocalDateTime soit antérieure ou non et pour toutes les valeurs LocalTime.

 2
Author: Gennady Kolomoets, 2016-03-13 06:56:06