Search code examples
javatimestampdatetime-formatzoneinstant

How can I convert from a String to Epoch microseconds?


I have a String of this format: "2019-08-17T09:51:41.775+00:00". I need to convert it into Epoch microseconds, but my conversion is always an hour off.

This is my code at the moment:

String timestamp = "2019-08-17T09:51:41.775+00:00"
ZonedDateTime date = ZonedDateTime.parse(timestamp)
Long epoch = date.toInstant().toEpochMilli() * 1000

So when I run this code, I get the result 1566035501775000. Now if I put 1566035501775 in a converter like this one: https://www.freeformatter.com/epoch-timestamp-to-date-converter.html to reverse it, I get this date: 8/17/2019, 10:51:41 AM.

Why is it an hour off and how can I change that?

I also tried it with a DateTimeFormatter:

DateTimeFormatter pat = DateTimeFormatter.ofPattern("YYYY-MM-dd'T'hh:mm:ss.SSSXXX")
Instant.from(pat.parse(timestamp))

But this gives me this exception:

java.time.DateTimeException: Unable to obtain Instant from TemporalAccessor: {SecondOfMinute=41, MicroOfSecond=775000, DayOfMonth=17, HourOfAmPm=9, NanoOfSecond=775000000, MonthOfYear=8, OffsetSeconds=0, MinuteOfHour=51, MilliOfSecond=775, WeekBasedYear[WeekFields[SUNDAY,1]]=2019},ISO of type java.time.format.Parsed

    at java.time.Instant.from(Instant.java:378)
    at com.amazon.hailstonetracing.controller.chrome.WorkflowEventMapperTest.test(WorkflowEventMapperTest.kt:114)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: InstantSeconds
    at java.time.format.Parsed.getLong(Parsed.java:203)
    at java.time.Instant.from(Instant.java:373)
    ... 24 more

After researching I presume this is because I didn't define a ZoneId? I only have the offset from the timestamp String. But I get the same result if I specify .withZone(ZoneId.of("UTC")) on the DateTimeFormatter.

Thanks for your help!


Solution

  • Your microsecond value is correct.

    What happens is: The freeformatter.com Epoch & Unix Timestamp Converter detects your time zone and gives you your local time, 10:51:41 AM. Apparently you are (or your browser thinks that you are) in a time zone that is at offset +01:00 from UTC. It therefore adds an hour compared to the time in your string, which was specified to be at offset +00:00.

    I am in Europe/Copenhagen time zone, currently at offset +02:00, and when I try the same on freeformatter.com, I get 17.8.2019 11.51.41, the correct local time for my time zone.

    In your attempt with an explicit formatter you have the wrong case of a few of your format pattern letters. Since it works without constructing your own formatter, that’s certainly the solution I recommend.

    And you need no time zone ID. The offset in your string is fully sufficient.

    Since your string has an offset (+00:00) and no time zone (like for example Pacific/Tarawa) using ZonedDateTime is overkill. I suggest using OffsetDateTime instead. It goes in the same way:

        OffsetDateTime date = OffsetDateTime.parse(timestamp);
        long epoch = date.toInstant().toEpochMilli() * 1000;
    

    I also changed the type of epoch from a Long object to a primitive long.

    As an aside, had your string had a finer precision than milliseconds, it would have been lost in your conversion. Here’s a way to get microsecond precision through:

        String timestamp = "2019-08-17T09:51:41.7754321+00:00";
        OffsetDateTime date = OffsetDateTime.parse(timestamp);
        Instant asInstant = date.toInstant();
        long epoch = TimeUnit.SECONDS.toMicros(asInstant.getEpochSecond())
                + asInstant.get(ChronoField.MICRO_OF_SECOND);
        System.out.println(epoch);
    

    As you can see, I have added more decimals in your string for the demonstration. Output is:

    1566035501775432