Java 8: Difference between two LocalDateTime in multiple units

后端 未结 10 1667
后悔当初
后悔当初 2020-11-22 11:36

I am trying to calculate the difference between two LocalDateTime.

The output needs to be of the format y years m months d days h hours m minutes

相关标签:
10条回答
  • 2020-11-22 11:54

    Unfortunately, there doesn't seem to be a period class that spans time as well, so you might have to do the calculations on your own.

    Fortunately, the date and time classes have a lot of utility methods that simplify that to some degree. Here's a way to calculate the difference although not necessarily the fastest:

    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.
    

    The basic idea is this: create a temporary start date and get the full years to the end. Then adjust that date by the number of years so that the start date is less then a year from the end. Repeat that for each time unit in descending order.

    Finally a disclaimer: I didn't take different timezones into account (both dates should be in the same timezone) and I also didn't test/check how daylight saving time or other changes in a calendar (like the timezone changes in Samoa) affect this calculation. So use with care.

    0 讨论(0)
  • 2020-11-22 11:54

    It should be simpler!

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

    You can convert millis to whatever unit you like:

    String.format("%d minutes %d seconds", 
      TimeUnit.MILLISECONDS.toMinutes(millis),
      TimeUnit.MILLISECONDS.toSeconds(millis) - 
      TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
    
    0 讨论(0)
  • 2020-11-22 12:04

    There is some problem for Tapas Bose code and Thomas code. If time differenсe is negative, array gets the negative values. For example if

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

    it returns 0 years 0 months 1 days -1 hours 0 minutes 0 seconds.

    I think the right output is: 0 years 0 months 0 days 23 hours 0 minutes 0 seconds.

    I propose to separate the LocalDateTime instances on LocalDate and LocalTime instances. After that we can obtain the Java 8 Period and Duration instances. The Duration instance is separated on the number of days and throughout-the-day time value (< 24h) with subsequent correction of the period value. When the second LocalTime value is before the firstLocalTime value, it is necessary to reduce the period for one day.

    Here's my way to calculate the LocalDateTime difference:

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

    The above method can be used to calculate the difference of any local date and time values, for example:

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

    It is convenient to write a unit test for the above method (both of them are PeriodDuration class members). Here's the 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);
    }
    

    }

    All tests are successful whether or not the value of the first LocalDateTime is before and for any LocalTime values.

    0 讨论(0)
  • 2020-11-22 12:09

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

    and then use the methods period.getYears(), period.getMonths(), period.getDays(), duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart().


    Expanded answer

    I'll answer the original question, i.e. how to get the time difference between two LocalDateTimes in years, months, days, hours & minutes, such that the "sum" (see note below) of all the values for the different units equals the total temporal difference, and such that the value in each unit is smaller than the next bigger unit—i.e. minutes < 60, hours < 24, and so on.

    Given two LocalDateTimes start and end, e.g.

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

    we can represent the absolute timespan between the two with a Duration—perhaps using Duration.between(start, end). But the biggest unit we can extract out of a Duration is days (as a temporal unit equivalent to 24h)—see the note below for an explanation. To use larger units (months, years) we can represent this Duration with a pair of (Period, Duration), where the Period measures the difference up to a precision of days and the Duration represents the remainder:

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

    Now we can simply use the methods defined on Period and Duration to extract the individual units:

    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
    

    or, using the default format:

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

    Note on years, months & days

    Note that, in java.time's conception, "units" like "month" or "year" don't represent a fixed, absolute temporal value—they're date- and calendar-dependent, as the following example illustrates:

    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
    
    0 讨论(0)
提交回复
热议问题