395 Stimmen

Java 8: Unterschied zwischen zwei LocalDateTime in mehreren Einheiten

Ich versuche, den Unterschied zwischen zwei LocalDateTime zu berechnen.

Die Ausgabe muss im Format y Jahre m Monate d Tage h Stunden m Minuten s Sekunden sein. Hier ist, was ich geschrieben habe:

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() + " Jahre " + 
                period.getMonths() + " Monate " + 
                period.getDays() + " Tage " +
                time[0] + " Stunden " +
                time[1] + " Minuten " +
                time[2] + " Sekunden.");

    }

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

Die Ausgabe, die ich erhalte, ist 29 Jahre 8 Monate 24 Tage 12 Stunden 0 Minuten 50 Sekunden. Ich habe mein Ergebnis von dieser Website überprüft (mit den Werten 16.12.1984 07:45:55 und 09.09.2014 19:46:45). Der folgende Screenshot zeigt die Ausgabe:

Epoch Converter

Ich bin ziemlich sicher, dass die Felder nach dem Monatswert in meinem Code falsch sind. Jeder Vorschlag wäre sehr hilfreich.

Aktualisierung

Ich habe mein Ergebnis von einer anderen Website getestet und das Ergebnis, das ich erhalten habe, ist anders. Hier ist es: Berechnen Sie die Dauer zwischen zwei Daten (Ergebnis: 29 Jahre, 8 Monate, 24 Tage, 12 Stunden, 0 Minuten und 50 Sekunden).

Aktualisierung

Da ich von zwei verschiedenen Websites zwei verschiedene Ergebnisse erhalten habe, frage ich mich, ob der Algorithmus meiner Berechnung legitim ist oder nicht. Wenn ich die folgenden zwei LocalDateTime-Objekte verwende:

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

Dann lautet die Ausgabe: 29 Jahre 8 Monate 25 Tage -1 Stunden -5 Minuten -10 Sekunden.

Wie aus diesem Link hervorgeht, sollte es 29 Jahre 8 Monate 24 Tage 22 Stunden, 54 Minuten und 50 Sekunden sein. Daher muss der Algorithmus auch mit negativen Zahlen umgehen können.

Bitte beachten Sie, dass es nicht um die Frage geht, welche Site mir welches Ergebnis gegeben hat. Ich möchte den richtigen Algorithmus kennen und die richtigen Ergebnisse haben.

6voto

Es gibt ein Problem für den Code von Tapas Bose und Thomas. Wenn die Zeitdifferenz negativ ist, werden negative Werte im Array angezeigt. Zum Beispiel, wenn

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

ist das Ergebnis 0 Jahre 0 Monate 1 Tag -1 Stunde 0 Minuten 0 Sekunden.

Ich denke, das richtige Ergebnis ist: 0 Jahre 0 Monate 0 Tage 23 Stunden 0 Minuten 0 Sekunden.

Ich schlage vor, die LocalDateTime-Instanzen auf LocalDate- und LocalTime-Instanzen aufzuteilen. Danach können wir die Java 8 Period- und Duration-Instanzen erhalten. Die Duration-Instanz wird in die Anzahl der Tage und den ganztägigen Zeitswert (< 24h) mit anschließender Korrektur des Periodenwerts aufgeteilt. Wenn der zweite LocalTime-Wert vor dem ersten LocalTime-Wert liegt, muss die Periode um einen Tag reduziert werden.

So berechne ich den Unterschied zwischen LocalDateTime-Werten:

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

    /*Berechne den Zeitunterschied*/
    Duration duration = Duration.between(firstLocalDateTime, secondLocalDateTime);
    long durationDays = duration.toDays();
    Duration throughoutTheDayDuration = duration.minusDays(durationDays);
    Logger.getLogger(PeriodDuration.class.getName()).log(Level.INFO,
            "Dauer ist: " + duration + " das sind " + durationDays
            + " Tage und " + throughoutTheDayDuration + " Zeit.");

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

    /*Korrigiere den Datumsunterschied*/
    if (secondLocalTime.isBefore(firstLocalTime)) {
        period = period.minusDays(1);
        Logger.getLogger(PeriodDuration.class.getName()).log(Level.INFO,
                "minus 1 Tag");
    }

    Logger.getLogger(PeriodDuration.class.getName()).log(Level.INFO,
            "Periode zwischen " + firstLocalDateTime + " und "
            + secondLocalDateTime + " ist: " + period + " und die Dauer beträgt: "
            + throughoutTheDayDuration
            + "\n-----------------------------------------------------------------");

    /*Berechne die Chrono-Einheiten und schreibe sie in das 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;
}

Die obige Methode kann verwendet werden, um den Unterschied zwischen beliebigen lokalen Datum- und Zeitwerten zu berechnen, zum Beispiel:

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

Es ist praktisch, einen Unittest für die obige Methode zu schreiben (beide sind Mitglieder der Klasse PeriodDuration). Hier ist der 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 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);
}

}

Alle Tests sind erfolgreich, unabhängig davon, ob der Wert des ersten LocalDateTime vorliegt und für beliebige LocalTime-Werte.

6voto

ChrLipp Punkte 15255

Und die Version von @Thomas in Groovy nimmt die gewünschten Einheiten in einer Liste anstelle von festcodierten Werten entgegen. Diese Implementierung (die leicht nach Java portiert werden kann - ich habe die Funktionsdeklaration explizit gemacht) macht Thomas' Ansatz wiederverwendbarer.

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 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(', ')
}

Zum Zeitpunkt dieser Schreibens liefert der obige Code 47 Jahre, 8 Monate, 9 Tage, 22 Stunden, 52 Minuten, 7 Sekunden, 140 Millis zurück. Und für die Eingabe von @Gennady Kolomoets liefert der Code 23 Stunden zurück.

Wenn Sie eine Liste von Einheiten bereitstellen, muss diese nach Größe der Einheiten sortiert sein (größte zuerst):

def listOfUnits = [ChronoUnit.WEEKS, ChronoUnit.DAYS, ChronoUnit.HOURS]
// gibt 2495 Wochen, 3 Tage, 8 Stunden zurück

4voto

Nach mehr als fünf Jahren beantworte ich meine Frage. Ich denke, dass das Problem mit einer negativen Dauer durch eine einfache Korrektur gelöst werden kann:

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

Period period = Period.between(fromDateTime.toLocalDate(), toDateTime.toLocalDate());
Duration duration = Duration.between(fromDateTime.toLocalTime(), toDateTime.toLocalTime());

if (duration.isNegative()) {
    period = period.minusDays(1);
    duration = duration.plusDays(1);
}
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);
long time[] = {hours, minutes, secs};
System.out.println(period.getYears() + " Jahre "
            + period.getMonths() + " Monate "
            + period.getDays() + " Tage "
            + time[0] + " Stunden "
            + time[1] + " Minuten "
            + time[2] + " Sekunden.");

Hinweis: Die Website https://www.epochconverter.com/date-difference berechnet jetzt korrekt die Zeitdifferenz.

Vielen Dank für eure Diskussion und Anregungen.

2voto

Robert Niestroj Punkte 14014

Wenn Sie keine Jahre und Monate benötigen, können Sie DurationFormatUtils von Apache Commons Lang 3 verwenden. Es verwendet Millisekunden als Dauer. Sie können die Millisekunden von der java.time-Dauerklasse erhalten.

LocalDateTime start = LocalDateTime.of(2023,10,1,7,14,3);
LocalDateTime end = LocalDateTime.of(2023,10,1,8,55,22);
Duration d = Duration.between(start, end);
DurationFormatUtils.formatDuration(d.toMillis(), "d 'Tage' H 'Stunden' m 'Minuten' s 'Sekunden'", true);

 // "0 Tage 1 Stunde 41 Minuten 19 Sekunden"

Weitere Möglichkeiten finden Sie in der JavaDoc.

1voto

Kushal Punkte 7429

Joda-Time

Da viele der Antworten API 26 Unterstützung erforderten und meine minimale API 23 war, löste ich dies mit folgendem Code:

import org.joda.time.Days

LocalDate startDate = Something
LocalDate endDate = Something
// Der Unterschied wäre ausschließlich von beiden Daten, 
// also in den meisten Anwendungsfällen müssen wir ihn um 1 erhöhen
Days.daysBetween(startDate, endDate).days

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X