0

I have a logic in python that I am converting into Java code. The logic is I need to read from a timestamp attribute in JSON file and convert it into ISO date format. Python query:

datetime.datetime.fromtimestamp(jsonMsg["time"]).isoformat(timespec='seconds')

Here is the code I wrote in Java 1627065646.444 is an example of the value I get from JSON script

long timestamp = (long) 1627065646.444 * 1000;
        Timestamp time = new Timestamp(timestamp);
        Date d = new Date(time.getTime());

        DateFormat df = new SimpleDateFormat();
        String dateToString = df.format(d);
        
        LocalDateTime datetime = LocalDateTime.parse(dateToString, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        ZoneOffset offset = ZoneOffset.UTC;
        String formattedTimeStamp = datetime.atOffset(offset).toString();

When I run the code I get compile error "Text '7/23/21 11:40 AM' could not be parsed at index 0 at java.time.format.DateTimeFormatter.parseResolved0" This exception occurs at LocalDateTime.parse(dateToString, DateTimeFormatter.ISO_LOCAL_DATE_TIME. Can someone please help me in understanding what I am doing wrong here.

perplexedDev
  • 857
  • 4
  • 17
  • 49
  • @MartinZeitler same here... – Turing85 Aug 11 '21 at 21:43
  • The provided java code doesn't compile. The first line casts a String to a long which isn't how java works. You say: "I get the exception ... " - no, you don't. You'd get a compiler error. Please fix your question, paste the _actual_ code you have with the _actual_ error you get. – rzwitserloot Aug 11 '21 at 21:45
  • Sorry, my bad that was a typo. It is a compile error. I will update the question. – perplexedDev Aug 11 '21 at 21:49
  • I have also updated the code which I was trying on my dev box. I was using a constant value from the JSON for testing instead of reading from JSON to get the logic working first – perplexedDev Aug 11 '21 at 21:55
  • 2
    [`final long nowMillis = (long) 1627065646.444 * 1000; System.out.println(Instant.ofEpochMilli(nowMillis));` will print `2021-07-23T18:40:46Z`](https://ideone.com/LLJ7bl) [(ISO 8601 format)](https://en.wikipedia.org/wiki/ISO_8601). – Turing85 Aug 11 '21 at 21:56
  • 1
    You are mixing old and modern. It’s a very bad idea. When you can use `LocalDateTime` and `ZoneOffset` from java.time, the modern Java date and time API, don’t also mix in the outdated and troublesome `Timestamp`, `Date`, `DateFormat` and `SimpleDateFormat`. It only brings needless complication. java.time offers all the functionality you need. – Ole V.V. Aug 12 '21 at 04:25
  • I get `Exception in thread "main" java.time.format.DateTimeParseException: Text '2021-07-23 20:40' could not be parsed at index 10`. – Ole V.V. Aug 12 '21 at 04:32
  • `new SimpleDateFormat()` without any arguments gave you some default format for your locale, whatever that was. Apparently `7/23/21 11:40 AM`. In any case not the ISO 8601 format that `DateTimeFormatter.ISO_LOCAL_DATE_TIME` expects. This caused your exception (and no compile error). But as I said, it was already a troublesome detour that you should avoid completely. – Ole V.V. Aug 12 '21 at 04:36
  • @Turing85 That answers the question. You convert to `long` before multiplying. We will want to do it after to preserve precision. So `(long) (1627065646.444 * 1000)`. Then we get `2021-07-23T18:40:46.444Z` with decimal fraction on the seconds. – Ole V.V. Aug 12 '21 at 05:00

1 Answers1

3

java.time

The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.

Solution using java.time, the modern Date-Time API:

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        long timestamp = (long) (1627065646.444 * 1000);

        Instant instant = Instant.ofEpochMilli(timestamp);
        System.out.println(instant);

        ZonedDateTime zdt = instant.atZone(ZoneOffset.UTC);
        LocalDateTime ldt = zdt.toLocalDateTime();
        System.out.println(ldt);

        // A custom format
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("M/d/u h:m:s a", Locale.ENGLISH);
        String formatted = dtf.format(zdt);
        System.out.println(formatted);
    }
}

Output:

2021-07-23T18:40:46.444Z
2021-07-23T18:40:46.444
7/23/2021 6:40:46 PM

ONLINE DEMO

The Z in the output is the timezone designator for zero-timezone offset. It stands for Zulu and specifies the Etc/UTC timezone (which has the timezone offset of +00:00 hours).

Learn more about the modern Date-Time API* from Trail: Date Time.

Apart from this, what else is wrong with your code?

You have done

long timestamp = (long) 1627065646.444 * 1000;

in which 1627065646.444 will be cast to long resulting in 1627065646 and thus the result of the multiplication will be 1627065646000, not 1627065646444 what you are expecting. You need to cast to long after performing the multiplication.

A valuable comment by Ole V.V.:

I’d use Math.round(1627065646.444 * 1000) to make sure that floating-point inaccuracy is handled.


* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • I’d use `Math.round(1627065646.444 * 1000)` to make sure that floating point inaccuracy is handled. I haven’t seen any need why we should convert to `LocalDateTime`. A good answer. – Ole V.V. Aug 12 '21 at 04:27
  • @OleV.V. - Thanks for the suggestion for improvement and the encouraging words. The result of `1627065646.444 * 1000` is already `1627065646444` and therefore I thought to cast it simply to `long`. However, if the input would have been something like `1627065646.444567`, I would also do it in the way you have suggested. – Arvind Kumar Avinash Aug 12 '21 at 09:01
  • You are right: the simple “cast” conversion seems to work for other values too. So using `round()` is only be to assure dumb code readers like me. :-) – Ole V.V. Aug 12 '21 at 15:33