Java 8: Różnica między dwoma LocalDateTime w wielu jednostkach

266

Próbuję obliczyć różnicę między dwoma LocalDateTime .

Dane wyjściowe muszą mieć format y years m months d days h hours m minutes s seconds. Oto co napisałem:

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

Otrzymuję wynik 29 years 8 months 24 days 12 hours 0 minutes 50 seconds. Sprawdziłem swój wynik z tej strony (z wartościami 12/16/1984 07:45:55i 09/09/2014 19:46:45). Poniższy zrzut ekranu pokazuje dane wyjściowe:

Konwerter Epoki

Jestem prawie pewien, że pola po wartości miesiąca są niepoprawne z mojego kodu. Wszelkie sugestie byłyby bardzo pomocne.

Aktualizacja

Przetestowałem swój wynik z innej strony internetowej i otrzymałem inny wynik. Oto on: Oblicz czas trwania między dwiema datami (wynik: 29 lat, 8 miesięcy, 24 dni, 12 godzin, 0 minut i 50 sekund).

Aktualizacja

Ponieważ otrzymałem dwa różne wyniki z dwóch różnych witryn, zastanawiam się, czy algorytm moich obliczeń jest prawidłowy, czy nie. Jeśli użyję następujących dwóch LocalDateTimeobiektów:

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

Potem nadchodzi wynik: 29 years 8 months 25 days -1 hours -5 minutes -10 seconds.

Z tego linku powinno być 29 years 8 months 24 days 22 hours, 54 minutes and 50 seconds. Dlatego algorytm musi również obsługiwać liczby ujemne.

Uwaga: pytanie nie dotyczy tego, która strona dała mi jaki wynik, muszę znać odpowiedni algorytm i mieć właściwe wyniki.

Tapas Bose
źródło
Tylko zgadnij, ale może Period.between()zastosować jakieś zaokrąglenie?
Thomas
3
Właśnie spojrzałem na kod jeszcze raz i wygląda na to, że strona jest zła (spróbuj samemu się przeliczyć). Jeśli pominiesz datę, tj. Różnice w roku, miesiącu i dniu, otrzymasz godzinę rozpoczęcia 7:45:55i godzinę zakończenia 19:46:45(lub 7:46:45PM). Różnica między tymi dwoma czasami wynosi 12 godzin, 0 minut i 50 sekund, a nigdy 23 godziny, 34 minuty i 12 sekund. Więc twoje obliczenia wydają się być poprawne, przynajmniej w części czasu.
Thomas
1
Ciekawe zjawisko na tej stronie: dodaj 10 lat do daty rozpoczęcia, a różnica godzin zmienia się z 23 na 8 - z pewnością oznaka błędu.
Thomas
8
Pamiętaj, że ponieważ LocalDateTimenie ma strefy czasowej, odpowiedź może nie być jednoznaczna. Nawet jeśli założysz, że początkowa i końcowa strefa czasowa są takie same, w niektórych strefach daty, takie jak 2014-09-09 będą miały czas letni lub letni, a w innych nie. To może zrzucić rzeczy o godzinę. Zatem obliczenie różnicy w stosunku do drugiego jest bez znaczenia, chyba że zostanie to rozwiązane.
Stuart Marks
2
Czy rozumiesz, że używanie LocalDateTimedaje nierealistyczne wyniki, ponieważ tej klasie celowo brakuje jakiejkolwiek koncepcji strefy czasowej lub przesunięcia względem UTC? Aby uzyskać realistyczne wartości, przypisz strefę czasową za ZoneIdpomocą ZonedDateTime.
Basil Bourque

Odpowiedzi:

163

Niestety nie wydaje się, że istnieje okres, który obejmuje również czas, więc może być konieczne samodzielne wykonanie obliczeń.

Na szczęście klasy daty i godziny mają wiele metod użyteczności, które w pewnym stopniu upraszczają to. Oto sposób na obliczenie różnicy, choć niekoniecznie najszybszej:

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.

Podstawowa idea jest następująca: utwórz tymczasową datę rozpoczęcia i uzyskaj pełne lata do końca. Następnie dostosuj tę datę o liczbę lat, aby data rozpoczęcia była krótsza niż rok od końca. Powtórz to dla każdej jednostki czasu w kolejności malejącej.

Wreszcie zastrzeżenie : Nie brałem pod uwagę różnych stref czasowych (obie daty powinny znajdować się w tej samej strefie czasowej), a także nie testowałem / sprawdzałem, jak czas letni lub inne zmiany w kalendarzu (takie jak zmiany stref czasowych w Samoa) wpływa na to obliczenie. Więc używaj ostrożnie.

Tomasz
źródło
1
Masz ten sam podstawowy pomysł, dlatego moja opinia, ale proszę spróbuj użyć tego samego wejścia, w przeciwnym razie inny wynik jest mylący.
Meno Hochschild,
@MenoHochschild to jest to samo wejście, po prostu wzięte z aktualizacji, gdzie PO ma problemy z negatywnymi czasów. ;)
Thomas
1
Dobry algorytm, ale niewłaściwy typ (patrz wyłączenie odpowiedzialności Thomasa). Przed wykonaniem obliczeń należy przekonwertować zmienne LocalDateTime na ZonedDateTime, używając na przykład domyślnej strefy czasowej (lub dowolnej strefy czasowej, której potrzebujesz): ZonedDateTime fromZonedDateTime = fromDateTime.atZone (ZoneId.systemDefault ());
Tristan
2
@Thomas Twój przykład używa także Java 8 - pytanie jest oznaczone jako java-8. W rzeczywistości twój przykład używa nawet ChronoUnit:), ale wykonuje pracę „ręcznie”.
Eugene Beresovsky,
3
@EugeneBeresovsky o tak, masz rację. Całkowicie tego przegapiłem;) - Poza tym nadal będziesz musiał odjąć większe jednostki od interwału, ponieważ na przykład long minutes = ChronoUnit.MINUTES.between(fromDate, toDate);zwróci liczbę większą niż 59 dla dowolnego interwału trwającego co najmniej 1 godzinę - i nie tego chce OP. Biorąc to pod uwagę, nie można wykonać 6 połączeń ChronoUnit#between()i wykonać.
Thomas
503

Znalazłem najlepszy sposób, aby to zrobić ChronoUnit.

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

Dodatkowa dokumentacja znajduje się tutaj: https://docs.oracle.com/javase/tutorial/datetime/iso/period.html

satnam
źródło
8
Nie widzę prawdziwej poprawy. W porównaniu z odpowiedzią zaakceptowaną Thomasa po prostu zastąpić tempDateTime.until( toDateTime, ChronoUnit.YEARS)z ChronoUnit.YEARS.between(fromDate, toDate). Ale ważnych dodatkowych linii, takich jak tempDateTime.plusYears( years )itp., Całkowicie brakuje w twojej odpowiedzi, więc nie pomaga OP. Cały algorytm ma znaczenie!
Meno Hochschild,
9
Lubię to bardziej, ponieważ jest bardziej zwięzłe i bardziej czytelne. To najlepszy sposób na znalezienie różnicy między dwiema datami.
Somaiah Kumbera
7
@SomaiahKumbera Nie, całkowicie przegapisz punkt krytyczny. OP nie chce czasu trwania tylko w jednej jednostce, ale w wielu jednostkach , a zatem ta odpowiedź wcale nie jest prawdziwa. Problem PO nie został po prostu rozwiązany. Prosimy o ponowne przeczytanie pytania. (Wydaje się, że wielu zwycięzców źle zrozumiało pytanie).
Meno Hochschild,
125
@MenoHochschild, myślę, że tak naprawdę brakuje ci punktu krytycznego. OP otrzymał odpowiedź ponad rok temu. 36 tysięcy osób, które oglądają to pytanie, nie dba o konkretne pytanie PO. Prowadził ich tu Google, a moja odpowiedź zapewniła im to, czego szukali - czyste i proste rozwiązanie pozwalające uzyskać różnicę między dwiema datami.
satnam
10
Potrzebowałem różnicy sekund między dwiema datami i zacząłem wdrażać własne rozwiązanie. Wkrótce wydaje się to skomplikowane i zdecydowałem się na Google. odpowiedź satnam może nie odpowiedzieć na pytanie OP, ale na pewno bardzo mi pomogła i założę się, że wielu innych takich jak ja.
Julian
43

Oto pojedynczy przykład użycia Duration i TimeUnit, aby uzyskać 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)));
Junior Batista
źródło
2
Jest to rzeczywiście możliwe, aby to zrobić tak: String.format("%02d:%02d:%02d",dur.toHoursPart(), dur.toMinutesPart(), dur.toSecondsPart());. Metody części dają odpowiednie liczby do zbudowania łańcucha. Myślę, że to bardziej czytelne.
Daantie
Uwaga dodatkowa do mojego poprzedniego komentarza: metody części są dostępne tylko od JDK 9 i wyższych.
Daantie
40

To powinno być prostsze!

Duration.between(startLocalDateTime, endLocalDateTime).toMillis();
Strefa
źródło
1
Wierzę, że jest to właściwie poprawne semantycznie rozwiązanie, począwszy od Java 8. A przed tą wersją JodaTime robi to samo.
Vrakfall
9

TL; DR

Duration duration = Duration.between(start, end);
duration = duration.minusDays(duration.toDaysPart()); // essentially "duration (mod 1 day)"
Period period = Period.between(start.toLocalDate(), end.toLocalDate());

a następnie za pomocą metody period.getYears(), period.getMonths(), period.getDays(), duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart().


Rozszerzona odpowiedź

Odpowiem na pierwotne pytanie, tj. Jak uzyskać różnicę czasu między dwoma LocalDateTimesw latach, miesiącach, dniach, godzinach i minutach, tak aby „suma” (patrz uwaga poniżej) wszystkich wartości dla różnych jednostek była równa sumie różnica czasowa, i takie, że wartość dla każdego modułu jest mniejszy niż kolejnego większa jednostka-IE minutes < 60,hours < 24 i tak dalej.

Biorąc pod uwagę dwa LocalDateTimes starti endnp

LocalDateTime start = LocalDateTime.of(2019, 11, 29, 17, 15);
LocalDateTime end = LocalDateTime.of(2020, 11, 30, 18, 44);

możemy reprezentować bezwzględny przedział czasowy między nimi za pomocą - Durationmoże za pomocą Duration.between(start, end). Ale największą jednostką, z której możemy wydobyć, Durationsą dni (jako jednostka czasowa równoważna 24 godzinom) - wyjaśnienie znajduje się poniżej. Aby użyć większych jednostek (miesięcy, lat), możemy to przedstawić Durationza pomocą pary ( Period, Duration), gdzie Periodmiara różnicy do dokładności dni, a Durationreprezentuje resztę:

Duration duration = Duration.between(start, end);
duration = duration.minusDays(duration.toDaysPart()); // essentially "duration (mod 1 day)"
Period period = Period.between(start.toLocalDate(), end.toLocalDate());

Teraz możemy po prostu użyć zdefiniowanych metod Periodi Durationwyodrębnić poszczególne jednostki:

System.out.printf("%d years, %d months, %d days, %d hours, %d minutes, %d seconds",
        period.getYears(), period.getMonths(), period.getDays(), duration.toHoursPart(),
        duration.toMinutesPart(), duration.toSecondsPart());
1 years, 0 months, 1 days, 1 hours, 29 minutes, 0 seconds

lub używając domyślnego formatu:

System.out.println(period + " + " + duration);
P1Y1D + PT1H29M

Uwaga na lata, miesiące i dni

Zauważ, że w java.timekoncepcji „jednostki”, takie jak „miesiąc” lub „rok”, nie reprezentują stałej, bezwzględnej wartości czasowej - zależą one od daty i kalendarza, co ilustruje poniższy przykład:

LocalDateTime
        start1 = LocalDateTime.of(2020, 1, 1, 0, 0),
        end1 = LocalDateTime.of(2021, 1, 1, 0, 0),
        start2 = LocalDateTime.of(2021, 1, 1, 0, 0),
        end2 = LocalDateTime.of(2022, 1, 1, 0, 0);
System.out.println(Period.between(start1.toLocalDate(), end1.toLocalDate()));
System.out.println(Duration.between(start1, end1).toDays());
System.out.println(Period.between(start2.toLocalDate(), end2.toLocalDate()));
System.out.println(Duration.between(start2, end2).toDays());
P1Y
366
P1Y
365
Anakhand
źródło
5

Istnieje pewien problem z kodem Tapas Bose i kodem Thomas. Jeśli różnica czasu jest ujemna, tablica pobiera wartości ujemne. Na przykład jeśli

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

zwraca 0 lat 0 miesięcy 1 dni -1 godzin 0 minut 0 sekund.

Myślę, że właściwym wyjściem jest: 0 lat 0 miesięcy 0 dni 23 godzin 0 minut 0 sekund.

Proponuję oddzielić instancje LocalDateTime w instancjach LocalDate i LocalTime. Następnie możemy uzyskać instancje okresu i czasu trwania Java 8. Instancja Duration jest rozdzielana według liczby dni i wartości czasu w ciągu dnia (<24 godziny) z późniejszą korektą wartości okresu. Gdy druga wartość LocalTime jest wcześniejsza niż wartość firstLocalTime, konieczne jest skrócenie tego okresu o jeden dzień.

Oto mój sposób na obliczenie różnicy 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;
}

Powyższą metodę można wykorzystać do obliczenia różnicy lokalnych wartości daty i godziny, na przykład:

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

Dla powyższej metody wygodnie jest napisać test jednostkowy (oba są członkami klasy PeriodDuration). Oto kod:

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

}

Wszystkie testy zakończone powodzeniem niezależnie od tego, czy wartość pierwszego LocalDateTime jest wcześniejsza, czy też nie.

Giennadij Kolomoets
źródło
Nie mogę odtworzyć twojego oświadczenia, że ​​kod Thomasa przy użyciu twojego powyższego wejścia wytwarza mieszane znaki. Moje wyjście to: „0 lat 0 miesięcy 0 dni 23 godzin 0 minut 0 sekund.”. I właśnie przetestowałem.
Meno Hochschild,
Dziękuje za komentarz. Dokładnie przetestowałem kod Thomasa, rzeczywiście działa poprawnie! Magia jest wykonywana przez LocalDateTime, dopóki metoda nie koryguje jednostek chrono. Wartości ujemne w kodzie Thomasa pojawiają się, jeśli pierwszy DateTime jest późniejszy niż drugi. Ale można to łatwo naprawić, na przykład tak jak w powyższym kodzie. Dzięki jeszcze raz.
Giennadij Kolomoets,
Cóż, kod Thomasa i moja biblioteka Time4J używająca tego samego algorytmu do obliczania czasów trwania mogą wytwarzać znaki ujemne, ale tylko przez cały czas trwania. To jest kluczowy punkt! Znak odnosi się do całego czasu trwania, a zatem opisuje, czy początek jest późniejszy niż koniec i nigdy nie jest związany z elementami o pojedynczym czasie trwania. Mieszane znaki nie są tutaj możliwe i uniemożliwiłyby taką interpretację początku w stosunku do końca (kontrprzykładem jest czas Joda lub gdy java.time.Periodużytkownicy mogą wymuszać / produkować takie mieszane znaki ze względu na inny projekt wewnętrzny).
Meno Hochschild,
5

Wersja @ Thomas w Groovy z zajmuje żądane jednostki na liście zamiast na stałe wpisywać wartości. Ta implementacja (którą można łatwo przenieść na Javę - wyraźnie podałem deklarację funkcji) sprawia, że ​​podejście Thomasa jest bardziej użyteczne.

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

W momencie pisania tego tekstu powyższy kod zwraca 47 Years, 8 Months, 9 Days, 22 Hours, 52 Minutes, 7 Seconds, 140 Millis. I dla danych wejściowych @Gennady Kolomoets kod jest zwracany23 Hours .

Po podaniu listy jednostek należy je posortować według wielkości jednostek (najpierw największe):

def listOfUnits = [ChronoUnit.WEEKS, ChronoUnit.DAYS, ChronoUnit.HOURS]
// returns 2495 Weeks, 3 Days, 8 Hours
ChrLipp
źródło
4

Oto bardzo prosta odpowiedź na twoje pytanie. To działa.

import java.time.*;
import java.util.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

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

    long minutes = ChronoUnit.MINUTES.between(bd, cd);
    long hours = ChronoUnit.HOURS.between(bd, cd);

    System.out.print("Age is: "+hours+ " hours, or " +minutes+ " minutes old");
}
}
SavedBeau
źródło
1
Powinienem wspomnieć, że to nie jest dokładne. spróbuj tego. mówi 1-minutowa różnica. LocalDateTime bd = LocalDateTime.of (2019, 11, 26, 15, 03, 55); LocalDateTime cd = LocalDateTime.of (2019, 11, 26, 15, 04, 45);
Mark Amabile
Wczoraj wieczorem wszedłem w czas i zrozumiałem Age is: 0 years,0 months, 1 days, -10 hours, -48 minutes old. Nie sądzę, żeby tego było pożądane.
Ole VV
@ OleV.V. Naprawiono ten problem
SavedBeau
1

Joda-Time

Ponieważ wiele odpowiedzi wymagało obsługi interfejsu API 26, a mój minimalny interfejs API wynosił 23, rozwiązałem go za pomocą poniższego kodu:

import org.joda.time.Days

LocalDate startDate = Something
LocalDate endDate = Something
// The difference would be exclusive of both dates, 
// so in most of use cases we may need to increment it by 1
Days.daysBetween(startDate, endDate).days
Kushal
źródło
1
Projekt Joda-Time jest w trybie konserwacji, a jego twórca Stephen Colebourne zaczął tworzyć klasy java.time zdefiniowane w JSR 310. Większość funkcji java.time została przeniesiona z powrotem do Java 6 i 7 w ThreeTen- Projekt backport . Dalsze dostosowanie do wczesnego Androida w projekcie ThreeTenABP .
Basil Bourque,
1

Po ponad pięciu latach odpowiadam na moje pytanie. Myślę, że problem z ujemnym czasem trwania można rozwiązać prostą korektą:

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() + " years "
            + period.getMonths() + " months "
            + period.getDays() + " days "
            + time[0] + " hours "
            + time[1] + " minutes "
            + time[2] + " seconds.");

Uwaga: Witryna https://www.epochconverter.com/date-difference poprawnie teraz oblicza różnicę czasu.

Dziękujemy wszystkim za dyskusję i sugestie.

Giennadij Kolomoets
źródło
Dziękuję za odpowiedź. Do godziny i mniejszych można korzystać z toHours, toMinutesi toSecondsmetody Duration. Od Java 9 jeszcze więcej od toMinutesPart()i toSecondsPart(). I nie widzę sensu w zwijaniu godzin, minut i sekund w tablicę tylko po to, aby je ponownie wyjąć. Ogólnie jednak dobra odpowiedź.
Ole VV
Po ponad pięciu latach odpowiadam na moje pytanie. Pytanie zostało wysłane z konta o innej nazwie, tytule i lokalizacji, nie wydaje się zbyt podobne? Zastanawiam się, nie ma to żadnego znaczenia.
Ole VV
„Po ponad pięciu latach odpowiadam na moje pytanie”. Przypuszczam, że byłam OP :)
Tapas Bose