Abhijit
Abhijit

Reputation: 63777

ParseException while trying to parse string timestamp

I am trying to parse a timestamp with a SimpleDateFormat but it raises ParseException in Java 1.8. The same code works flawlessly in Java 9 or higher. The example code snippet

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.text.SimpleDateFormat;
import java.text.ParseException;
public class TestStrftime{

     public static void main(String []args) throws ParseException {
        String TS_FMT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
        SimpleDateFormat dateFormat = new SimpleDateFormat(TS_FMT);
        String szSableTimeStamp = "2020-01-16T19:32:13.540000Z";
        Instant sableTimestamp = dateFormat
                        .parse(szSableTimeStamp)
                        .toInstant().truncatedTo(ChronoUnit.SECONDS);
        System.out.println(sableTimestamp);
     }
}

and it raises the exception

Exception in thread "main" java.text.ParseException: Unparseable date: "2020-01-16T19:32:13.540000Z"
    at java.text.DateFormat.parse(DateFormat.java:366)
    at TestStrftime.main(TestStrftime.java:12)

but with Java 9 or higher, its able to parse the date with the desired output

2020-01-16T19:41:13Z

Upvotes: 1

Views: 601

Answers (2)

Andreas
Andreas

Reputation: 159165

The issue with SSSXXX not matching 540000Z was fixed by JDK-8072099: Format "ha" is unable to parse hours 10-12 in Java 9.

The real issue is that SSS represents millisecond, while 540000 is microseconds, so even the Java 9 parser gets it wrong by parsing that to 9 minutes (540 seconds).

Since you want an Instant object, do not use the old flawed SimpleDateFormat. Use DateTimeFormatter instead:

String TS_FMT = "uuuu-MM-dd'T'HH:mm:ss.SSSSSSXXX";
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(TS_FMT);
String szSableTimeStamp = "2020-01-16T19:32:13.540000Z";
Instant sableTimestamp = Instant.from(dateFormat.parse(szSableTimeStamp))
                .truncatedTo(ChronoUnit.SECONDS);
System.out.println(sableTimestamp);

However, since your format happens to match the ISO-8601 format, which is the natural format of an Instant, just use the Instant.parse() method, without any need for a formatter:

String szSableTimeStamp = "2020-01-16T19:32:13.540000Z";
Instant sableTimestamp = Instant.parse(szSableTimeStamp)
                .truncatedTo(ChronoUnit.SECONDS);
System.out.println(sableTimestamp);

Upvotes: 4

Basil Bourque
Basil Bourque

Reputation: 339845

tl;dr

Instant                             // The modern way to represent a moment in UTC. Resolves to nanoseconds.
.parse(                             // Parse text in standard ISO 8601 format without specifying any formatting pattern.
    "2020-01-16T19:32:13.540000Z"
)                                   // Returns an `Instant` object.
.truncatedTo(                       // Lop off part of the data.
    ChronoUnit.SECONDS              // Keep whole seconds and larger. Lose the fractional second, if any.
)                                   // Returns a new fresh second `Instant` object, per immutable objects pattern. Original `Instant` is left unaffected.
.toString()                         // Generate text in standard ISO 8601 format.

See this code run live at IdeOne.com.

2020-01-16T19:32:13Z

Details

You are mixing the terrible legacy date-time classes such as SimpleDateFormat with their modern replacements. Do not do this. Use only classes from the java.time packages.

The Instant class knows how to parse strings in standard ISO 8601 format such as your input string. No need to specify a formatting pattern.

String input = "2020-01-16T19:32:13.540000Z" ;  // Using standard ISO 8601 format.
Instant instant = Instant.parse( input ) ;      // By default parses ISO 8601 strings. No need to specify a formatting pattern.

If you want to drop any fractional second, truncate.

Instant instantWholeSeconds = instant.truncatedTo( ChronoUnit.SECONDS ) ;

Generate text in standard ISO 8601 format by calling Instant::toString().

String output = instantWholeSeconds.toString() ;  // Generate text in standard ISO 8601 format.

If you want a count of seconds since the epoch reference of the first moment of 1970 in UTC, interrogate the Instant object by calling getEpochSecond.

long secondsSinceEpoch = instant.getEpochSecond() ;

am trying to parse a timestamp with a SimpleDateFormat

Never use SimpleDateFormat again. Obsolete with the adoption of JSR 310.

The legacy date-time classes are a bloody awful mess. Avoid them like the plague.

Upvotes: 4

Related Questions