Reputation: 21316
I'm not sure I'm getting the subtleties between Java Period
and Duration
.
When I read Oracle's explanation, it says that I can find out how many days since a birthday like this (using the example dates they used):
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1960, Month.JANUARY, 1);
Period birthdayPeriod = Period.between(birthday, today);
int daysOld = birthdayPeriod.getDays();
But as even they point out, this doesn't take into account the time zone you were born in and the time zone you are in now. But this is a computer and we can be precise, right? So would I use a Duration
?
ZoneId bornIn = ZoneId.of("America/New_York");
ZonedDateTime born = ZonedDateTime.of(1960, Month.JANUARY.getValue(), 1, 2, 34, 56, 0, bornIn);
ZonedDateTime now = ZonedDateTime.now();
Duration duration = Duration.between(born, now);
long daysPassed = duration.toDays();
Now the actual times are accurate, but if I understand this correctly, the days might not correctly represent calendar days, e.g. with DST and such.
So what am I do to to get a precise answer based upon my time zone? The only thing I can think of is to go back to using LocalDate
, but normalize the time zones first from the ZonedDateTime
values, and then use a Duration
.
ZoneId bornIn = ZoneId.of("America/New_York");
ZonedDateTime born = ZonedDateTime.of(1960, Month.JANUARY.getValue(), 1, 2, 34, 56, 0, bornIn);
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime nowNormalized=now.withZoneSameInstant(born.getZone());
Period preciseBirthdayPeriod = Period.between(born.toLocalDate(), nowNormalized.toLocalDate());
int preciseDaysOld = preciseBirthdayPeriod.getDays();
But that seems really complicated just to get a precise answer.
Upvotes: 21
Views: 14148
Reputation: 121
java.time.Period
is more friendly for human reading
java.time.Duration
is for a machine.
Upvotes: 0
Reputation: 12662
The main distinction between the two classes is :
java.time.Period
uses date-based values ( May 31, 2018)java.time.Duration
is more precise, it uses time-based values ( "2018-05-31T11:45:20.223Z" ) Upvotes: 1
Reputation: 44061
Your analysis regarding the Java-8-classes Period
and Duration
is more or less correct.
java.time.Period
is limited to calendar date precision.java.time.Duration
only handles second (and nanosecond) precision but treats days always as equivalent to 24 hours = 86400 seconds.Normally it is completely sufficient to ignore clock precision or timezones when calculating the age of a person because personal documents like passports don't document the exact time of day when someone was born. If so then the Period
-class does its job (but please handle its methods like getDays()
with care - see below).
But you want more precision and describe the result in terms of local fields taking into account timezones. Well, the first part (precision) is supported by Duration
, but not the second part.
It is also not helpful to use Period
because the exact time difference (which is ignored by Period
) can impact the delta in days. And furthermore (just printing the output of your code):
Period preciseBirthdayPeriod =
Period.between(born.toLocalDate(), nowNormalized.toLocalDate());
int preciseDaysOld = preciseBirthdayPeriod.getDays();
System.out.println(preciseDaysOld); // 13
System.out.println(preciseBirthdayPeriod); // P56Y11M13D
As you can see, it is quite dangerous to use the method preciseBirthdayPeriod.getDays()
in order to get the total delta in days. No, it is only a partial amount of the total delta. There are also 11 months and 56 years. I think it is wise to also print the delta not only in days because then people can easier imagine how big the delta is (see the often seen use-case of printed durations in social media like "3 years, 2 months, and 4 days").
Obviously, you rather need a way to determine a duration including calendar units as well as clock units in a special timezone (in your example: the timezone where someone has been born). The bad thing about Java-8-time-library is: It does not support any combination of Period
AND Duration
. And importing the external library Threeten-Extra-class Interval
will also not help because long daysPassed = interval.toDuration().toDays();
will still ignore timezone effects (1 day == 24 hours) and is also not capable of printing the delta in other units like months etc.
Summary:
You have tried the Period
-solution. The answer given by @swiedsw tried the Duration
-based solution. Both approaches have disadvantages with respect to precision. You could try to combine both classes in a new class which implements TemporalAmount
and realize the necessary time arithmetic yourself (not so trivial).
Side note:
I have myself already implemented in my time library Time4J what you look for, so it might be useful as inspiration for your own implementation. Example:
Timezone bornZone = Timezone.of(AMERICA.NEW_YORK);
Moment bornTime =
PlainTimestamp.of(1960, net.time4j.Month.JANUARY.getValue(), 1, 22, 34, 56).in(
bornZone
);
Moment currentTime = Moment.nowInSystemTime();
MomentInterval interval = MomentInterval.between(bornTime, currentTime);
MachineTime<TimeUnit> mt = interval.getSimpleDuration();
System.out.println(mt); // 1797324427.356000000s [POSIX]
net.time4j.Duration<?> duration =
interval.getNominalDuration(
bornZone, // relevant if the moments are crossing a DST-boundary
CalendarUnit.YEARS,
CalendarUnit.MONTHS,
CalendarUnit.DAYS,
ClockUnit.HOURS,
ClockUnit.MINUTES
);
// P56Y11M12DT12H52M (12 days if the birth-time-of-day is after current clock time)
// If only days were specified above then the output would be: P20801D
System.out.println(duration);
System.out.println(duration.getPartialAmount(CalendarUnit.DAYS)); // 12
This example also demonstrates my general attitude that using units like months, days, hours etc. is not really exact in strict sense. The only strictly exact approach (from a scientific point of view) would be using the machine time in decimal seconds (best in SI-seconds, also possible in Time4J after the year 1972).
Upvotes: 18
Reputation: 308
The JavaDoc of Period
states that it models:
A date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
I understand it has no reference to points in time.
You might want to check Interval
from project ThreeTen-Extra which models:
an immutable interval of time between two instants.
The project website states the project “[...] is curated by the primary author of the Java 8 date and time library, Stephen Colebourne”.
You can retrieve a Duration
from an Interval by invoking toDuration()
on it.
I shall transform your code to give an example:
ZoneId bornIn = ZoneId.of("America/New_York");
ZonedDateTime born = ZonedDateTime.of(1960, Month.JANUARY.getValue(), 1, 2, 34, 56, 0, bornIn);
ZonedDateTime now = ZonedDateTime.now();
Interval interval = Interval.of(born.toInstant(), now.toInstant());
long daysPassed = interval.toDuration().toDays();
Upvotes: 5