web-dev-qa-db-fra.com

Java 8: Calculer la différence entre deux heures locales

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

La sortie doit être au 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};
    }
}

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

Epoch Converter

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

Mise à jour

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

Mise à jour

Comme j'ai obtenu deux résultats différents sur deux sites différents, je me demande si l'algorithme de 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);

Alors la sortie arrive: 29 years 8 months 25 days -1 hours -5 minutes -10 seconds.

À partir de ce lien , il devrait s'agir de 29 years 8 months 24 days 22 hours, 54 minutes and 50 seconds. Donc, l'algorithme doit aussi 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.

218
Tapas Bose

Malheureusement, il ne semble pas y avoir de classe de période qui couvre aussi le temps, vous devrez donc peut-être faire les calculs vous-même.

Heureusement, les classes de date et d’heure ont beaucoup de méthodes d’utilité 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éez une date de début temporaire et obtenez les années complètes jusqu'à la fin. Ensuite, ajustez cette date en fonction du nombre d'années afin que la date de début soit inférieure à un an à partir de la fin. Répétez cette opération pour chaque unité de temps dans l'ordre décroissant.

Enfin un disclaimer: je n'ai pas pris en compte les fuseaux horaires différents (les deux dates doivent être dans le même fuseau horaire) et je n'ai pas non plus testé/vérifié la façon dont l'heure d'été ou d'autres modifications d'un calendrier (comme les changements de fuseau horaire à Samoa) affecter ce calcul. Alors utilisez avec précaution.

138
Thomas

J'ai trouvé le meilleur moyen de faire cela avec ChronoUnit.

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

La documentation supplémentaire est ici: https://docs.Oracle.com/javase/tutorial/datetime/iso/period.html

421
satnam

Voici un exemple simple 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)));
34
Junior Batista

Ça devrait être plus simple!

Duration.between(startLocalDateTime, endLocalDateTime).toMillis();
20
Zon

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 explicité la déclaration de la fonction) 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 @Gennady Kolomoets, le code retourne 23 Hours.

Lorsque vous fournissez une liste d'unités, vous devez la trier par taille (la plus grande en premier):

def listOfUnits = [ChronoUnit.WEEKS, ChronoUnit.DAYS, ChronoUnit.HOURS]
// returns 2495 Weeks, 3 Days, 8 Hours
5
ChrLipp

Voici une réponse très simple à votre question. Ça marche.

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
SavedBeau

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 retourne 0 ans 0 mois 1 jours -1 heures 0 minutes 0 secondes.

Je pense que le bon résultat 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 Période et Durée. L'instance de durée est séparée du nombre de jours et de la valeur de l'heure tout au long de la journée (<24h) avec correction ultérieure de la valeur de la période. Lorsque la deuxième valeur LocalTime est antérieure à la valeur firstLocalTime, il est nécessaire de réduire la période d'un jour.

Voici comment 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 toute valeur de date et heure locale, 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 valeur LocalDateTime soit antérieure ou non à une valeur LocalTime.

3
Gennady Kolomoets