Search code examples
javatimestamptimezoneepoch

localTimeDate formula conversion


I came across following formula in which localTimeDay is being retrieved from TimeStamp and Timezone:

long localTimeDay = (timeStamp + timeZone) / (24 * 60 * 60 * 1000) * (24 * 60 * 60 * 1000);

I am not able to understand what is the meaning of Local Time Day and how this formula "magically" converts based on TS and TZ.


Solution

    • timestamp is a count of milliseconds since the epoch of Jan 1, 1970 at 00:00 UTC.
    • timeZone is the offset in milliseconds from UTC. It may be positive, negative or zero. It must have its sign reversed, though, for the formula to work, as far as I can see: an offset of +01:00 should be given as minus 3 600 000 milliseconds. When I add the offset, I get a different point in time where the date and time of day in UTC is the same as the original date and time of day at that UTC offset.
    • Dividing by 24 * 60 * 60 * 1000 converts from milliseconds to days since the epoch. Any fraction of a day, that is, any time of the day, is discarded.
    • Multiplying by 24 * 60 * 60 * 1000 converts back from days to milliseconds. Since the time of day was discarded, we now have the time at 00:00 UTC on that day.

    So it’s a way of converting from a point in time at some UTC offset to the date alone represented in UTC.

    It’s not code that you would want to have in your program. You should leave such conversions to proven library methods. It might however be code that could be found inside such a tested and proven library.

    Edit: Why do I believe that the sign of the offset has been reversed? Couldn’t it be the opposite conversion, from UTC to local? The variable name localTimeDay seems to suggest this? The division and multiplication only works in UTC. Since the epoch is at 00:00 UTC, the formula necessarily gives you the start of a day in UTC, it cannot give you the start of a day at any non-zero offset. Therefore I figure that the conversion must be from local to UTC.

    How to do in your code

    Here’s a nice way of doing the same conversion using java.time, the modern Java date and time API

        // The point in time given in UTC
        Instant time = Instant.parse("2020-04-23T05:16:45Z");
        // The UTC offset, +02:00
        ZoneOffset offset = ZoneOffset.ofHours(2);
    
        ZonedDateTime startOfDayInUtc = time.atOffset(offset)
                .toLocalDate()
                .atStartOfDay(ZoneOffset.UTC);
        System.out.println(startOfDayInUtc);
        long epochMillis = startOfDayInUtc.toInstant().toEpochMilli();
        System.out.println(epochMillis);
    

    Output:

    2020-04-23T00:00Z
    1587600000000
    

    We can check that it gives the same result as your code line:

        long timeStamp = time.toEpochMilli();
        // Reverse sign of offset
        long timeZone = -Duration.ofSeconds(offset.getTotalSeconds()).toMillis();
        long localTimeDay = (timeStamp + timeZone) / (24 * 60 * 60 * 1000) * (24 * 60 * 60 * 1000);
    
        System.out.println(localTimeDay);
        System.out.println("Agree? " + (localTimeDay == epochMillis));
    
    1587600000000
    Agree? true
    

    Edit: you asked in a comment:

    I still have this doubt: There is fixed number of millis that have elapsed since Jan 1 1970, so every country should get same number. Does localTimeDate mean "date for my country" or to the country where this data came from? For example: Country-A , Country-B; they will see same millis since Epoch. Suppose data processing is happening in Country-A and origin of data is Country-B. So when we say "localTimeDate" , does it pertain to Country-A or Country-B.

    It will work for both countries. It all depends on the timeZone value that enters into the formula. If that is the UTC offset for country A (sign reversed), the conversion will be from country A time to UTC date. If it’s country B’s offset, the conversion will be from country B time. And you are fully correct, you will get two different dates if it’s not the same date in countries A and B, which could easily be the case.