Skip
Skip

Reputation: 6521

Converting duration to years in Java8 Date API?

I have a date in the far past.
I found out what the duration is between this date and now.
Now I would like to know - how much is this in years?

I came up withthis solution using Java8 API.
This is a monstrous solution, since I have to convert the duration to Days manually first, because there will be an UnsupportedTemporalTypeException otherwise - LocalDate.plus(SECONDS) is not supported for whatever reason.
Even if the compiler allows this call.

Is there a less verbous possibility to convert Duration to years?

LocalDate dateOne = LocalDate.of(1415, Month.JULY, 6);
Duration durationSinceGuss1 = Duration.between(LocalDateTime.of(dateOne, LocalTime.MIDNIGHT),LocalDateTime.now());

long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDate.now(), 
        LocalDate.now().plus(
                TimeUnit.SECONDS.toDays(
                        durationSinceGuss1.getSeconds()), 
                ChronoUnit.DAYS) );

/*
 * ERROR - 
 * LocalDate.now().plus(durationSinceGuss1) causes an Exception. 
 * Seconds are not Supported for LocalDate.plus()!!!
 * WHY OR WHY CAN'T JAVA DO WHAT COMPILER ALLOWS ME TO DO?
 */
//long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDate.now(), LocalDate.now().plus(durationSinceGuss) );

/*
 * ERROR - 
 * Still an exception! 
 * Even on explicitly converting duration to seconds. 
 * Everything like above. Seconds are just not allowed. Have to convert them manually first e.g. to Days?!
 * WHY OR WHY CAN'T YOU CONVERT SECONDS TO DAYS OR SOMETHING AUTOMATICALLY, JAVA?
 */
//long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDate.now(), LocalDate.now().plus(durationSinceGuss.getSeconds(), ChronoUnit.SECONDS) );

Upvotes: 4

Views: 7858

Answers (3)

JVon
JVon

Reputation: 694

Use Period to get the number of years between two LocalDate objects:

    LocalDate before    = LocalDate.of(1415, Month.JULY, 6);
    LocalDate now       = LocalDate.now();
    Period    period    = Period.between(before, now);

    int yearsPassed     = period.getYears();

    System.out.println(yearsPassed);

Upvotes: 5

Meno Hochschild
Meno Hochschild

Reputation: 44061

Although the accepted answer of @Matt Ball tries to be clever in usage of the Java-8-API, I would throw in following objection:

Your requirement is not exact because there is no way to exactly convert seconds to years.

Reasons are:

  • Most important: Months have different lengths in days (from 28 to 31).
  • Years have sometimes leap days (29th of February) which have impact on calculating year deltas, too.
  • Gregorian cut-over: You start with a year in 1415 which is far before first gregorian calendar reform which cancelled full ten days, in England even 11 days and in Russia more. And years in old Julian calendar have different leap year rules.
  • Historic dates are not defined down to second precision. Can you for example describe the instant/moment of the battle of Hastings? We don't even know the exact hour, just the day. Assuming midnight at start of day is already a rough and probably wrong assumption.
  • Timezone effects which have impact on the length of day (23h, 24h, 25h or even different other lengths).
  • Leap seconds (exotic)

And maybe the most important objection to your code:

I cannot imagine that the supplier of the date with year 1415 has got the intention to interprete such a date as gregorian date.

I understand the wish for conversion from seconds to years but it can only be an approximation whatever you choose as solution. So if you have years like 1415 I would just suggest following very simple approximation:

Duration d = ...;
int approximateYears = (int) (d.toDays() / 365.2425);

For me, it is sufficient in historic context as long as we really want to use a second-based duration for such an use-case. It seems you cannot change the input you get from external sources (otherwise it would be a good idea to contact the duration supplier and ask if the count of days can be supplied instead). Anyway, you have to ask yourself what kind of year definition you want to apply.

Side notes:

Your complaint "WHY OR WHY CAN'T JAVA DO WHAT COMPILER ALLOWS ME TO DO?" does not match the character of new java.time-API.

You expect the API to be type-safe, but java.time (JSR-310) is not designed as type-safe and heavily relies on runtime-exceptions. The compiler will not help you with this API. Instead you have to consult the documentation in case of doubt if any given time unit is applicable on any given temporal type. You can find such an answer in the documentation of any concrete implementation of Temporal.isSupported(TemporalUnit). Anyway, the wish for compile-safety is understandable (and I have myself done my best to implement my own time library Time4J as type-safe) but the design of JSR-310 is already set in stone.

There is also a subtile pitfall if you apply a java.time.Duration on either LocalDateTime or Instant because the results are not exactly comparable (seconds of first type are defined on local timeline while seconds of Instant are defined on global timeline). So even if there is no runtime exception like in the accepted answer of @Matt Ball, we have to carefully consider if the result of such a calculation is reasonable and trustworthy.

Upvotes: 3

Matt Ball
Matt Ball

Reputation: 359776

Have you tried using LocalDateTime or DateTime instead of LocalDate? By design, the latter does not support hours/minutes/seconds/etc, hence the UnsupportedTemporalTypeException when you try to add seconds to it.

For example, this works:

LocalDateTime dateOne = LocalDateTime.of(1415, Month.JULY, 6, 0, 0);
Duration durationSinceGuss1 = Duration.between(dateOne, LocalDateTime.now());
long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDateTime.now(), LocalDateTime.now().plus(durationSinceGuss1) );
System.out.println(yearsSinceGuss); // prints 600

Upvotes: 7

Related Questions