TimeUnit conversion from Milliseconds to Days not working for me

13k Views Asked by At

I'm trying to get the difference of two timestamps in days, and TimeUnit is returning completely incorrect results for me.

Here is my code:

long ts = 1522242239952L;
long now = 1527274162820L;
long difference = now - ts;
int ageInDays = (int) TimeUnit.MILLISECONDS.convert(difference, TimeUnit.DAYS);
int ageInDays2 = (int) (difference/(1000 * 60 * 60 * 24));
System.out.println(ageInDays);
System.out.println(ageInDays2);

Output is:

-1756844032
58

Why is the TimeUnit calculation so incorrect ?

3

There are 3 best solutions below

0
On

Why is the TimeUnit calculation so incorrect ?

The other answers are correct: you were performing the opposite conversion of the one you intended. TimeUnit.MILLISECONDS.convert(difference, TimeUnit.DAYS); converts from days to milliseconds.

But how come you got a negative number?? This is because of int overflow. The opposite conversion was performed correctly and yielded 434 758 135 795 200 000. This is much larger than what a 32 bit int can hold. So when you cast to int, the most significant bits are chopped off. By chance, bit that ended up being the sign bit of the int was 1, meaning negative. I usually avoid making such a cast without a range check first. You may want to acquire the same habit. Math.toIntExact() is very helpful here.

How to fix: java.time

The other answers are still basically correct. Still I would like to supply a couple of suggestions.

If you consider that the time from, say, 16:04 one day to 16:04 the next day is 1 day, then you need to take time zone into account to get the correct result. For example:

    ZoneId zone = ZoneId.of("Africa/Mogadishu");
    ZonedDateTime birth = Instant.ofEpochMilli(ts).atZone(zone);
    long ageInDays = ChronoUnit.DAYS.between(birth, ZonedDateTime.now(zone));
    System.out.println(ageInDays);

In this case the result of running the code just now is also the one that you had expected:

58

If on the other hand you define 1 day as 24 hours no matter what the wall clock reads, here’s a way to get the age with sub-second precision. You can always convert to days afterward.

    Instant birth = Instant.ofEpochMilli(ts);
    Duration age = Duration.between(birth, Instant.now());
    System.out.println(age);
    System.out.println(age.toDays());

Output:

PT1398H52M13.994841S
58

The first of the two lines says that the age is 1398 hours 52 minutes 13.994841 seconds. The second line agrees with the result from before.

The thoughts behind the two snippets can also be combined and mixed. All in all I think that java.time gives some possibilities that would be harder to get with TimeUnit, and some pretty clear and self-explanatory code where you wouldn’t easily make the same mistake as in the question.

Links:

0
On

You misunderstood the TimeUnit documentation:

long convert(long sourceDuration, TimeUnit sourceUnit)

Convert the given time duration in the given unit to this unit.

Your source units are MILLISECONDS, and you want DAYS so the line should read

int ageInDays = (int) TimeUnit.DAYS.convert(difference, TimeUnit.MILLISECONDS);
0
On

Because you're using TimeUnit.convert backwards. Try

TimeUnit.DAYS.convert(difference, TimeUnit.MILLISECONDS);

or just

TimeUnit.MILLISECONDS.toDays(difference);

Ref: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/TimeUnit.html#convert(long,%20java.util.concurrent.TimeUnit)