38

I would appreciate any help with finding bug for this exception:

java.text.ParseException: Unparseable date: "2007-09-25T15:40:51.0000000Z"

and following code:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date date = sdf.parse(timeValue);
long mills = date.getTime();
this.point.time = String.valueOf(mills);

It throws expcetion with Date date = sdf.parse(timeValue); .

timeValue = "2007-09-25T15:40:51.0000000Z"; , as in exception.

Thanks.

Jacob
  • 14,341
  • 18
  • 50
  • 70
  • Do you even need to parse for `.SSSZ`? If all you want is date or time, then remove the `.SSSZ`. – IgorGanapolsky Oct 14 '15 at 20:27
  • For new readers to this question I recommend you don’t use `SimpleDateFormat` and `Date`. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. Instead just use `Instant` from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Dec 04 '21 at 09:19

3 Answers3

86

Z represents the timezone character. It needs to be quoted:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Reimeus
  • 155,977
  • 14
  • 207
  • 269
  • 2
    Or possibly use X instead of Z so that Z is accepted as an ISO8601 timezone, for which "Z" is parsed as the UTC time zone designator – DNA Nov 23 '13 at 22:30
  • Using X works for me, BUT seems to require an exact number of S (millisecond) characters in the patterns, which is strange - see my answer... – DNA Nov 23 '13 at 22:41
  • It's in the [javadoc](http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html) _Text can be quoted using single quotes (') to avoid interpretation_ – Reimeus Nov 23 '13 at 23:49
  • @Reimeus This solution didn't work for me. I tried the `'Z'`, and it didn't get parsed. It only worked when I removed the Z. – IgorGanapolsky Oct 14 '15 at 21:50
5

In Java 7 you can also use the X pattern to match an ISO8601 timezone, which includes the special Z (UTC) value:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSX");
Date date = sdf.parse("2007-09-25T15:40:51.0000000Z");

However, it seems to require an exact number of millisecond characters in the pattern, which is not required for the 'Z' character pattern, and is rather inconvenient. I think this is because the ISO8601 definition also includes "two-digit hours", which are just numbers, so cannot be distinguished by the parser from the preceding milliseconds.

So this version would be fine for timestamps down to second precision, less so for milliseconds.

DNA
  • 41,067
  • 12
  • 101
  • 141
  • `IllegalArgumentException: Unknown pattern character 'X'` – IgorGanapolsky Oct 14 '15 at 20:24
  • 1
    Igor - what version of Java gives that error? The 'X' pattern is [clearly documented](https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html) for Java 7, and works for me under Java 8 too. – DNA Oct 14 '15 at 22:13
  • I am using ThreeTenABP library in my Android project (Java 8). – IgorGanapolsky Oct 14 '15 at 22:13
  • 2
    @IgorGanapolsky sounds like you need to post a new question will full details of your code & environment – Reimeus Oct 16 '15 at 16:43
  • @IgorGanapolsky See [my answer](https://stackoverflow.com/a/70224524/5772882) (works with ThreeTenABP too). – Ole V.V. Dec 04 '21 at 09:45
0

java.time

I recommend that you use java.time, the modern Java date and time API, for your date and time work. Your string is in ISO 8601 format and can be directly parsed by the java.time.Instant class without us specifying any formatter:

    String timeValue = "2007-09-25T15:40:51.0000000Z";
    
    Instant i = Instant.parse(timeValue);
    long mills = i.toEpochMilli();
    String time = String.valueOf(mills);
    
    System.out.println(time);

Output:

1190734851000

May use a formatter for output if desired

If we know for a fact that the millisecond value will never be negative, java.time can format it into a string for us. This saves the explicit conversion to milliseconds first.

private static final DateTimeFormatter EPOCH_MILLI_FORMATTER
        = new DateTimeFormatterBuilder().appendValue(ChronoField.INSTANT_SECONDS)
                .appendValue(ChronoField.MILLI_OF_SECOND, 3)
                .toFormatter(Locale.ROOT);

Now formatting is trivial:

    assert ! i.isBefore(Instant.EPOCH) : i;
    String time = EPOCH_MILLI_FORMATTER.format(i);

And output is still the same:

1190734851000

In particular if you need to format Instant objects to strings in more places in your program, I recommend the latter approach.

What went wrong in your code?

First of all, there is no way that SimpleDateFormat can parse 7 decimals of fraction of second correctly. As long as the fraction is zero, the result will happen to come out correct anyway, but imagine a time that is just one tenth of a second after the full second, for example, 2007-09-25T15:40:51.1000000Z. In this case SimpleDateFormat would parse the fraction into a million milliseconds, and your result would be more than a quarter of an hour off. For greater fractions the error could be several hours.

Second as others have said format pattern letter Z does not match the offset of Z meaning UTC or offset zero from UTC. This caused the exception that you observed. Putting Z in quotes as suggested in the accepted answer is wrong too since it will cause you to miss this crucial information from the string, again leading to an error of several hours (in most time zones).

Link

Oracle tutorial: Date Time explaining how to use java.time.

Ole V.V.
  • 76,217
  • 14
  • 120
  • 142