Converting duration to years in Java8 Date API? - java

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

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

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.

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

Related

Using instances of GregorianCalendar to determine time passed?

I am working on a project in my CIS 163 class that is effectively a campsite reservation system. The bulk of the code was provided and I just have to add certain functionalities to it. Currently I need to be able to determine how much time has passed between 2 different GregorianCalendar instances (one being the current date, the other being a predetermined "check out") represented by days. I haven't been able to figure out quite how to do this, and was hoping someone here might be able to help me out.
The GregorianCalendar is old and you shouldn't really use it anymore. It was cumbersome and was replaced by the "new" java.time module since Java 8.
Still, if you need to compare using GC instances, you could easily calculate time using milliseconds between dates, like this:
GregorianCalendar date1 = new GregorianCalendar();
GregorianCalendar date2 = new GregorianCalendar();
// Adding 15 days after the first date
date2.add(GregorianCalendar.DAY_OF_MONTH, 15);
long duration = (date2.getTimeInMillis() - date1.getTimeInMillis() )
/ ( 1000 * 60 * 60 * 24) ;
System.out.println(duration);
If you want to use the new Time API, the following code would work.
LocalDate date1 = LocalDate.now();
LocalDate date2 = date1.plusDays(15);
Period period = Period.between(date1, date2);
int diff = period.getDays();
System.out.println(diff);
If you need to convert between the types (e.g. you're working with legacy code), you can do it like this:
LocalDate date3 = gcDate1.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate date4 = gcDate2.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
Also I'm pretty sure this question must've been asked over and over again, so make sure you search properly before asking.
Since you have been forced to use the old and poorly designed GregorianCalendar class, the first thing you should do is convert each of the two GregorianCalendar objects to a modern type. Since Java 8 GregorianCalendar has a method that converts it to ZonedDateTime. Check the documentation, I include a link below.
Now that you’ve got two ZonedDateTime objects, there are different paths depending on your exact requirements. Often one will use Duration.between() for finding the duration, the amount of time between them in hours, minutes, seconds and fraction of second. If you know that you will always need just one of those time units, you may instead use for example ChronoUnit.HOURS.between() or ChronoUnit.MILLISECONDS.between(). If you need to count days, use ChronoUnit.DAYS.between().
If instead you need the time in months and days, you should instead use Period.between().
Links
Documentation:
GregorianCalendar (long outdated, don’t use unless forced to)
Duration
ChronoUnit
Period
Oracle tutorial: Date Time explaining how to use java.time, the modern Java date and time API to which ZonedDateTIme, Duration, ChronoUnit and Period belong.

Converting epoch time to the hour

Given an epoch time: eg (1513213212) I should get 1 since its 1 am right now UTC. How would I go about converting it into the hour of the day? Is it possible to do it just using math (division, mod)?
It would be close to impossible to do it by using maths only. (Leap year and all). It's better to use established APIs which will do all the hard work.
You can use following method to do this.
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(1513213212* 1000L);
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(cal.get(Calendar.HOUR));//12 hour clock
System.out.println(cal.get(Calendar.HOUR_OF_DAY));//24 hour clock
Use java.time, the modern Java date and time API also known as JSR-310:
LocalTime timeOfDay = Instant.ofEpochSecond(1513213212L)
.atOffset(ZoneOffset.UTC)
.toLocalTime();
System.out.println(timeOfDay);
int hourOfDay = timeOfDay.getHour();
System.out.println(hourOfDay);
This prints:
01:00:12
1
Even if you just wanted to do the math, I would still prefer to use standard library methods for it:
long epochSeconds = 1513213212L;
// convert the seconds to days and back to seconds to get the seconds in a whole number of days
long secondsInWholeDays = TimeUnit.DAYS.toSeconds(TimeUnit.SECONDS.toDays(epochSeconds));
long hourOfDay = TimeUnit.SECONDS.toHours(epochSeconds - secondsInWholeDays);
System.out.println(hourOfDay);
This too prints 1.
Your intention was “Given an epoch time: eg (1513213212) I should get 1 since it’s 1 AM right now UTC.” Which of the above code snippets in your opinion most clearly expresses this intention? This is what I would use for making my pick.
While MadProgrammer is surely correct in his/her comment that date and time arithmetic is complicated and that you should therefore leave it to the date and time API, I believe that this is one of the rare cases where not too complicated math gives the correct answer. It depends on it being safe to ignore the issue of leap seconds, and if going for the math solution, you should make sure to check this assumption. Personally I would not use it anyway.

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.
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).
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();
The main distinction between the two classes is :
that java.time.Period uses date-based values ( May 31, 2018)
while java.time.Duration is more precise, it uses time-based values ( "2018-05-31T11:45:20.223Z" )
java.time.Period is more friendly for human reading
for example Period between A and B is 2 years 3 months 3 days
java.time.Duration is for a machine.

Formatting a Duration in Java 8 / jsr310

I am transitioning a project from Joda-Time to java8's native time libraries, and I have run into a snag.
I have been unable to find a formatter for Duration. I would like to have a custom String format of, for instance, HHH+MM, where a Duration of 75 hours and 15 minutes would format as "75+15".
This was easy to do with Joda-Time by converting to period, and using a PeriodFormatter, but I have been unable to find this type of class in Java8. Am I missing something?
Java 9 and later: Duration::to…Part methods
In Java 9 the Duration class gained new to…Part methods for returning the various parts of days, hours, minutes, seconds, milliseconds/nanoseconds. See this pre-release OpenJDK source code.
Given a duration of 49H30M20.123S…
toNanosPart() = 123000000
toMillisPart() = 123
toSecondsPart() = 20
toMinutesPart() = 30
toHoursPart() = 1
toDaysPart() = 2
Remember that “days” here means chunks of 24-hours, ignoring dates on a calendar. If you care about dates, use Period class instead.
I do not know if any additional formatter features are added. But at least you will be able to more conveniently generate your own strings from numbers obtained via these new getter methods.
Java 8
Oddly enough, no convenient getter methods for these values were included in the first edition release of java.time in Java 8. One of very few oversights in the otherwise excellent design of the java.time framework.
See the related Question: Why can't I get a duration in minutes or hours in java.time?.
There is no period/duration-formatter in jsr-310, different from JodaTime. Not every feature of JodaTime was ported to JSR-310 (for example also not PeriodType). And in reverse JSR-310 has some features which are not available in JodaTime (for example localized weekday numbers or the strategy pattern approach with adjusters).
It might happen that Java 9 will introduce some kind of built-in period formatting (read something about this from S. Colebourne).
Conclusion: JSR-310 and JodaTime are not fully compatible to each other, so a lot of work can be required. I would not be so keen on migration as soon as possible. Do you need special features of JSR-310 which are not offered by JodaTime?
Additional note: You should also be aware of the fact that joda period (which includes all units from years to seconds) is not fully compatible with jsr310-period (only years, months, days) or jsr310-duration (only hours, minutes, seconds and fraction seconds).
There is no built-in method but you can access the number of hours/minutes without having to calculate them manually. Your specific format could look like:
Duration d = Duration.of(75, HOURS).plusMinutes(15);
long hours = d.toHours(); //75
long minutes = d.minusHours(hours).toMinutes(); //15
String HH_PLUS_MM = hours + "+" + minutes; //75+15
System.out.println(HH_PLUS_MM);
If the duration is guaranteed to be less than 24 hours, you can also use this trick:
String hhPlusMm = LocalTime.MIDNIGHT.plus(d).format(DateTimeFormatter.ofPattern("HH+mm"));
you can use the DurationFormatUtils from commons-lang3-time (for minutes you have to use "mm" as the format is the same as in SimpleDateFormat):
DurationFormatUtils.formatDuration(interval.toMillis(), "HHH+mm")
Sadly I found no way to exclude empty parts, like in my case days or hours could be 0, so I still had to roll my own.
Update: I have opened an issue for this on Apache commons.lang.time.DurationFormatUtils JIRA.
I know this is an old question but I recently ran into the same thing. There really should be a better solution than this, but this worked for me:
public static String millisToElapsedTime(long millis){
DateFormat fmt = new SimpleDateFormat(":mm:ss.SSS");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
return (millis/3600000/*hours*/)+fmt.format(new Date(millis));
}
Then, you could add this:
public static String durationToElapsedTime(Duration d){
return millisToElapsedTime(d.toMillis());
}

Calculating difference in dates in Java

I find it funny that Java (or the java.util library) does not have a built-in function to calculate difference in dates. I want to subtract one date from another to get the elapsed time between them. What is the best way to do this?
I know the simple way is to take the difference of the time in milliseconds and then convert that into days. However, I wanted to know if this works in all cases (with daylight saving, etc.).
Java's not missing much, if you look at open source: try Joda-Time.
I know the simple way is to take the
difference of the time in milliseconds
and then convert that into days.
However, i wanted to know if this
works in all cases (with daylight
saving, etc.).
If your times are derived from UTC dates, or they are just the difference between two calls to System.getCurrentTimeMillis() measured on the same system, you will get a valid number of milliseconds as the difference, independent of any timezone issues. (which is why everything should be using UTC as a storage format -- it's much easier to go from UTC->local time; if you try to go the other way then you need to store the local timezone along with the local time -- or attempt to infer it, gack!)
As for turning this into a number of days, you should just be able to divide by 86400000... with the caveat that there is an occasional leap second every other year or so.
Use either Joda-Time or the new java.time package in Java 8.
Both frameworks use the Half-Open approach where the beginning is inclusive while the ending is exclusive. Sometimes notated as [). This is generally the best approach for defining spans of time.
java.time
The java.time framework built into Java 8 and later has a Period class to represent a span of time as a number of years, a number of months, and a number of days. But this class is limited to whole days, no representation of hours, minutes, and seconds.
Note that we specify a time zone, crucial for determining a date. For example, a new day dawns earlier in Paris than in Montréal.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
LocalDate now = LocalDate.now( zoneId );
LocalDate then = LocalDate.of( 2001, 1, 1 );
Period period = Period.between( then, now );
Then: 2001-01-01. Now: 2015-09-07. Period: P14Y8M6D. Days: 5362
For whole days, then Daylight Saving Time (DST) is irrelevant.
If you want a count of total days, use the ChronoUnit enum which includes some calculation methods. Notice the calculations return a long.
long days = ChronoUnit.DAYS.between( then, now ); // "5362" seen above.
I have asked about doing a full period in java.time, including hours, minutes, seconds. Not possible as of Java 8. A surprising workaround using the bundled libraries was suggested by Meno Hochschild: Use a Duration class found in the javax.xml.datatype package.
Joda-Time
Here is some example code in Joda-Time 2.3.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );
DateTime start = new DateTime( 2014, 1, 2, 3, 4, 5, timeZone );
DateTime stop = new DateTime( 2014, 5, 2, 3, 4, 5, timeZone );
Period period = new Period( start, stop );
Calling toString will get you a string representation in the form defined by the ISO 8601 standard, PnYnMnDTnHnMnS.
With the date4j library:
int numDaysBetween = oneDate.numDaysFrom(anotherDate);
There is simple way to implement it. We can use Calendar.add method with loop.
For example as below,
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date beginDate = dateFormat.parse("2013-11-29");
Date endDate = dateFormat.parse("2013-12-4");
Calendar beginCalendar = Calendar.getInstance();
beginCalendar.setTime(beginDate);
Calendar endCalendar = Calendar.getInstance();
endCalendar.setTime(endDate);
The minus days between beginDate and endDate, and the code as below,
int minusDays = 0;
while (true) {
minusDays++;
// Day increasing by 1
beginCalendar.add(Calendar.DAY_OF_MONTH, 1);
if (dateFormat.format(beginCalendar.getTime()).
equals(dateFormat.format(endCalendar).getTime())) {
break;
}
}
System.out.println("The substractation between two days is " + (minusDays + 1));
Have Fun! #.#
I disagree with the claim that Java doesn't have a mechanism for calculating the difference between dates.
Java was designed for global use. It was designed so that there isn't a concept of date, there is only a concept of "time in milliseconds". Any interpretation of such a universal time as the time-and-date in a specific location under a specific convention is merely a projection or a view.
The calendar class is used to turn this sort of absolute time into dates. You can also add or subtract date components, if you really need to. The only way to provide a difference in term of components between two times would be Calendar generated and specific. Thus, you could argue that the standard library does not include a smart enough Gregorian Calendar, and I would agree that it leaves some to be desired.
That being said, there are numerous implementations of this kind of functionality, I see others have provided examples.
Java's implementation of dates is poor. If you find Joda-Time too complicated, try my little contribution to open source:
http://calendardate.sourceforge.net/javadoc/index.html

Categories

Resources