converting epoch to ZonedDateTime in Java

Tags: , , ,



How to convert epoch like 1413225446.92000 to ZonedDateTime in java?

The code given expects long value hence this will throw NumberFormatException for the value given above.

ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(dateInMillis)), ZoneId.of(TIME_ZONE_PST));

Answer

java.time can directly parse your string

Edit: If your millisecond value is always non-negative, the following DateTimeFormatter can parse it.

private static final String TIME_ZONE_PST = "America/Los_Angeles";
private static final DateTimeFormatter epochFormatter = new DateTimeFormatterBuilder()
        .appendValue(ChronoField.INSTANT_SECONDS, 1, 19, SignStyle.NEVER)
        .optionalStart()
        .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
        .optionalEnd()
        .toFormatter()
        .withZone(ZoneId.of(TIME_ZONE_PST));

Now parsing into a ZonedDateTime is just one method call:

    ZonedDateTime zdt = ZonedDateTime.parse(dateInMillis, epochFormatter);
    System.out.println(zdt);

Output is:

2014-10-13T11:37:26.920-07:00[America/Los_Angeles]

It will not work correctly with a negative value: the fraction would still be parsed as positive, which I am assuming would be incorrect. To be sure to be notified in case of a negative value I have specified in the formatter that the number cannot be signed.

A more general solution: use BigDecimal

If you need a more general solution, for example including negative numbers, I think it’s best to let BigDecinmal parse the number and do the math.

    BigDecimal bd = new BigDecimal(dateInMillis);
    BigDecimal[] wholeAndFractional = bd.divideAndRemainder(BigDecimal.ONE);
    long seconds = wholeAndFractional[0].longValueExact();
    int nanos = wholeAndFractional[1].movePointRight(9).intValue();
    ZonedDateTime zdt = Instant.ofEpochSecond(seconds, nanos)
            .atZone(ZoneId.of(TIME_ZONE_PST));

Output is the same as before. Only now we can also handle negative numbers according to expectations:

    String dateInMillis = "-1.5";

1969-12-31T15:59:58.500-08:00[America/Los_Angeles]

Even scientific notation is accepted:

    String dateInMillis = "1.41322544692E9";

2014-10-13T11:37:26.920-07:00[America/Los_Angeles]

If finer precision than nanoseconds is possible in the string, consider how you want to truncate or round, and instruct BigDecimal accordingly, there are a number of options.

Original answer

Basil Bourque’s answer is a good one. Taking out the nanoseconds from the fractional part into an integer for nanoseconds may entail a pitfall or two. I suggest:

    String dateInMillis = "1413225446.92000";
    String[] secondsAndFraction = dateInMillis.split("\.");
    int nanos = 0;
    if (secondsAndFraction.length > 1) { // there’s a fractional part
        // extend fractional part to 9 digits to obtain nanoseconds
        String nanosecondsString
                = (secondsAndFraction[1] + "000000000").substring(0, 9);
        nanos = Integer.parseInt(nanosecondsString);
        // if the double number was negative, the nanos must be too
        if (dateInMillis.startsWith("-")) {
            nanos = -nanos;
        } 
    }
    ZonedDateTime zdt = Instant
            .ofEpochSecond(Long.parseLong(secondsAndFraction[0]), nanos)
            .atZone(ZoneId.of("Asia/Manila"));
    System.out.println(zdt);

This prints

2014-10-14T02:37:26.920+08:00[Asia/Manila]

We don’t need 64 bits for the nanoseconds, so I am just using an int.

Assumption: I have assumed that your string contains a floating-point number and that it may be signed, for example -1.50 would mean one and a half seconds before the epoch. If one day your epoch time comes in scientific notation (1.41322544692E9), the above will not work.

Please substitute your desired time zone in the region/city format if it didn’t happen to be Asia/Manila, for example America/Vancouver, America/Los_Angeles or Pacific/Pitcairn. Avoid three letter abbreviations like PST, they are ambiguous and often not true time zones.



Source: stackoverflow