This question has a philosophical nature and spans few problems like time measurements, and date format conventions.
LocalDate
is an implementation of ISO 8601 date exchange standard.
Java Doc states explicitly that this class does not represent time but provides only standard date notation.
The API provides only simple operations on the notation itself and all calculations are done by incrementing the Year, or Month, or Day of a given date.
In other words, when calling LocalDate.plusYears()
you are adding conceptual years of 365 days each, rather than the exact amount of time within a year.
This makes Day the lowest unit of time which one can add to a date expressed by LocalDate
.
In human understanding, date is not a moment in time, but it is a period.
It starts with 00h 00m 00s (...) and finishes with 23h 59m 59s (...).
LocalDate
however avoids problems of time measurement and vagueness of human time units (hour, day, month, and a year can all have different length) and models date notation simply as a tuple of:
(years, months within a year, days within a month )
calculated since the beginning of the era.
In this interpretation, it makes sense that Day is the smallest unit affecting the date.
As an example following:
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusSecond = date.plus(1, ChronoUnit.SECONDS);
returns
java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
... which shows, that using LocalDate
and adding the number of seconds (or smaller units to drive the precision), you could not overcome the limitation listed in your question.
Looking at the implementation you find that LocalDate.plusYears()
after adding the years, calls resolvePreviousValid()
. This method then checks for leap year and modifies the day field in the following manner:
day = Math.min(day, IsoChronology.INSTANCE.isLeapYear((long)year)?29:28);
In other words it corrects it by effectively deducting 1 day.
You could use Year.length()
which returns the number of days for given year and will return 366 for leap years. So you could do:
LocalDate plusYear = date.plus(Year.of(date.getYear()).length(), ChronoUnit.DAYS);
You will still run into following oddities (call to Year.length()
replaced with the day counts for brevity):
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plus(365, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
between.getMonths() + "m " +
between.getDays() + "d");
returns
1997-02-28
0y 11m 30d
then
LocalDate date = LocalDate.of(1996, 3, 29);
LocalDate plusYear = date.plus(365, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
between.getMonths() + "m " +
between.getDays() + "d");
returns
1997-03-29
1y 0m 0d
and finally:
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plus(366, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
between.getMonths() + "m " +
between.getDays() + "d");
returns:
1997-03-01
1y 0m 1d
Please note that moving the date by 366 instead of 365 days increased the period from 11 months and 30 days to 1 year and 1 day (2 days increase!).