Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subtleties between Java Period and Duration

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.

like image 500
Garret Wilson Avatar asked Dec 13 '16 14:12

Garret Wilson


People also ask

How is duration defined in Java?

A Duration represents a directed distance between two points on the time-line. A negative duration is expressed by the negative sign of the seconds part. A duration of -1 nanosecond is stored as -1 seconds plus 999,999,999 nanoseconds.

How do you pass duration in Java?

Java Date Time - Duration toString() exampleDuration toString() returns a string representation of this duration using ISO-8601 seconds based representation, such as PT8H6M12. 345S. The format of the string will be PTnHnMnS , where n is the relevant hours, minutes or seconds part of the duration.

How do you set a date range in Java?

The idea is quite simple, just use Calendar class to roll the month back and forward to create a “date range”, and use the Date. before() and Date.


1 Answers

Your analysis regarding the Java-8-classes Period and Duration is more or less correct.

  • The class java.time.Period is limited to calendar date precision.
  • The class 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).

like image 132
Meno Hochschild Avatar answered Sep 17 '22 15:09

Meno Hochschild