- Where am I going wrong in my calculations 41 years and a negative number?
Apart from using the notoriously troublesome and long outdated SimpleDateFormat
class and the just as outdated Date
there are the following bugs in your code:
- You are parsing
08.0
as 8 seconds 0 seconds. On my JDK-11 SimpleDateFormat
opts for the 0 seconds and discards the 8 seconds that I think are correct. SimpleDateFormat
cannot parse one decimal on the seconds (only exactly three decimals), so the solution to this bug is discarding SimpleDateFormat
altogether.
- As others have said you have an
int
overflow in your multiplications. For example, 30 * 24 * 60 * 60 * 1000
should give 2 592 000 000, but since an int
cannot hold this number, you get -1 702 967 296
instead. Since this is a negative number, the following division gives you a negative number of months.
- As Solomon Slow pointed out in a comment, a month may be 28, 29, 30 or 31 days. When setting all months to 30 days you risk incorrect numbers of days and months and in the end also years. When I ran your code today, the correct answer would have been 1 year, 4 months, 13 days, but I got 19 days instead, 6 days too much.
- You are not taking summer time (DST) and other time anomalies into account. These may cause a day to be for example 23 or 25 hours, giving an error.
Or to sum up: Your error was that you tried to do the calculation “by hand”. Date and time math is too complex and error-prone to do this. You should always leave it to well-proven library classes instead.
- Is there a better way for me to do this? My current setup does not consider leap years or a 365 day year, and I need to take these into
account.
Yes, there is a much better way. The best way may be to use the PeriodDuration
class from the ThreeTen Extra project, see the link below. I am not going to install that library in my computer right now, so I will just show the good and modern solution using built-in classes:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.S");
LocalDateTime currentDateTime = LocalDateTime.now(ZoneId.of("Australia/Sydney"));
String earliestRunTime = "2017-12-16 01:30:08.0";
LocalDateTime earliestDateTime = LocalDateTime.parse(earliestRunTime, dtf);
// We want to find a period (years, months, days) and a duration (hours, minutes, seconds).
// To do that we cut at the greatest possible whole number of days
// and then measure the period before the cut and the duration after it.
LocalDateTime cut = earliestDateTime.plusDays(
ChronoUnit.DAYS.between(earliestDateTime, currentDateTime));
Period p = Period.between(earliestDateTime.toLocalDate(), cut.toLocalDate());
Duration d = Duration.between(cut, currentDateTime);
String result = String.format("%s years, %s months, %s days, %s hours, %s minutes, %s seconds",
p.getYears(), p.getMonths(), p.getDays(),
d.toHours(), d.toMinutesPart(), d.toSecondsPart());
System.out.println(result);
When I ran the code just now I got:
1 years, 4 months, 13 days, 19 hours, 26 minutes, 7 seconds
In java.time, the modern Java date and time API, a Period
is an amount of years, months and days, and a Duration
is an amount of hours, minutes, seconds and fraction of second (down to nanoseconds). Since you wanted both, I am using both classes.
The toXxxPart
methods of Duration
I am using were introduced in Java 9. If you are using Java 8 (or the ThreeTen Backport) printing the minutes and seconds is a little bit more complicated. Search for java format duration
or similar to learn how.
I am still not taking summer time into account. To do that we would need to know the time zone of the earliest run time string and then use ZonedDateTime
instead of LocalDateTime
. The code would otherwise be very similar.
Links