3

I am using the below code to format millisecond resolution date strings. It works for 2018-09-14T13:05:21.329Z but not 2018-09-14T13:05:21.3Z. Can anybody suggest the reason and how to correct it?

DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
SimpleDateFormat sdfDestination = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
    Date parsedDate = formatter.parse(date);
    String destDate = sdfDestination.format(parsedDate);
    return destDate;
} catch (java.text.ParseException parseException) {
    logger.error("Parse Exception occured while converting publication time to date "
            + "format 'yyyy-MM-dd HH:mm:ss'", parseException);
}

I get below exception:

java.text.ParseException: Unparseable date: "2018-09-14T13:05:21.3Z"
    at java.text.DateFormat.parse(Unknown Source) ~[na:1.8.0_181]
    at com.noordpool.api.implementation.utility.Utility.parseDate(Utility.java:136) [classes/:na]
    at com.noordpool.api.implementation.utility.Utility.parseMessage(Utility.java:77) [classes/:na]
Donald Duck
  • 8,409
  • 22
  • 75
  • 99
Shankar Guru
  • 1,071
  • 2
  • 26
  • 46
  • Maybe that is due to your `SimpleDateFormat` expecting 3 digits before the `Z` at the end and your failing `String` having only 1? Have you tried filling it up with two additional zeros before the `Z` (manually)? Does it fail then, too? – deHaar Sep 14 '18 at 13:28
  • I recommend you avoid the `SimpleDateFormat` class. It is not only long outdated, it is also notoriously troublesome. Today we have so much better in [`java.time`, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Sep 14 '18 at 15:00
  • Related: [ISO 8601 String to Date/Time object in Android](https://stackoverflow.com/questions/3941357/iso-8601-string-to-date-time-object-in-android) – Ole V.V. Sep 14 '18 at 15:23
  • @DonaldDuck Please see [When is it okay to edit answers for “Code Formatting?”](https://meta.stackoverflow.com/questions/263115/when-is-it-okay-to-edit-answers-for-code-formatting) (I suggest the answer there goes for questions too). – Ole V.V. Sep 15 '18 at 05:52
  • @OleV.V. I've already read that according to [this answer](https://meta.stackoverflow.com/a/263120/4284627), my edit is OK: "**unneeded** and incorrect indentation" (emphasis mine). – Donald Duck Sep 15 '18 at 07:40
  • FYI, the terribly troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/10/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/10/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes built into Java 8 and later. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Sep 16 '18 at 03:20

4 Answers4

2

Your only problem is that you are using a wrong pattern for SimpleDateFormat, you need to change:

DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");

To:

DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

Because the Z used in the date string means "zero hour offset" so you just need to pass it as 'Z' in your pattern.

This is a working demo with the right pattern.

Edit:

And to make things work with different Locales and Timezones, you need to use the appropriate Locale when you are creating the SimpleDateFormat instance, this is how should be the code:

DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
cнŝdk
  • 31,391
  • 7
  • 56
  • 78
1

The only possible issue I can see is that you're passing in milliseconds incorrectly and the program doesn't know what to do about it.

So the last part of the formatter indicates with milliseconds and a timezone as .SSSX

But how does it evaluate 3Z for the input into this? I mean, do you say it's 300 timezone Z, or say it's 003 timezone Z, or worse, try and parse it as 3Z, which hopefully you see that you cannot turn '3Z' into a number.

To remedy this, I'd validate your input 'date' and ensure the milliseconds part is always 3 digits long, this removes the ambiguity and the program always knows that you mean '300 milliseconds, timezone Z'.

DubDub
  • 1,277
  • 1
  • 10
  • 24
1

There is a problem in java 8 where the number of characters that you specified with the formatter should be an exact match (which is not specified in the documentation). You can use three different Formatters and use nested exception as follows:

DateFormat format1 = new SimpleDateFormat("y-M-d'T'H:m:s.SX");
DateFormat format2 = new SimpleDateFormat("y-M-d'T'H:m:s.SSX");
DateFormat format3 = new SimpleDateFormat("y-M-d'T'H:m:s.SSSX");
Date parsedDate;

try {
    // Parsing for the case - 2018-09-14T13:05:21.3Z 
    parsedDate  = format1.parse(date); 
} catch (ParseException e1) {

    try {
         // Parsing for the case - 2018-09-14T13:05:21.32Z 
         parsedDate = format2.parse(date); 
    } catch (ParseException e2) {

          try {
              // Parsing for the case - 2018-09-14T13:05:21.329Z 
              parsedDate = format3.parse(date);
          } catch (ParseException e2) {
             //The input date format is wrong
             logger.error("Wrong format for date - " + date);      
          }

    }
}
Jahnavi Paliwal
  • 1,721
  • 1
  • 12
  • 20
  • The analysis is correct. I have reproduced the asker’s issue on Java 1.8.0_60 and 1.8.0_131, but not on Java 9.0.4. Since using Java 8 I suggest the solution is to throw the long outdated and notoriously troublesome `SImpleDateFormat` out and use java.time instead: `Instant.parse("2018-09-14T13:05:21.3Z")`. No need for an explicit formatter. – Ole V.V. Sep 14 '18 at 15:12
  • While the output will be as expected when applying your chained attempts to parse in the asker’s context, you are in fact parsing the example string from the question into a `Date` equal to `2018-09-14T13:05:21.003Z`, that is, you have parsed the `.3` seconds as 3 milliseconds where it does mean 3 tenths of a second. There is no way that a `SimpleDateFormat` can do better, though. – Ole V.V. Sep 14 '18 at 15:49
  • Correctly stated. Java time is a better choice for the .3 scenario where it means 3 tenths of a second. Will try the same. – Jahnavi Paliwal Sep 15 '18 at 13:09
1

java.time

    DateTimeFormatter dtfDestination
            = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
    String date = "2018-09-14T13:05:21.3Z";
    String destDate = Instant.parse(date)
            .atZone(ZoneId.of("Indian/Comoro"))
            .format(dtfDestination);
    System.out.println(destDate);

Output from this snippet is:

2018-09-14 16:05:21

Please substitute your correct time zone if it didn’t happen to be Indian/Comoro, since correct output depends on using the correct time zone. If you want to use your JVM’s default time zone, specify ZoneId.systemDefault(), but be aware that the default can be changed at any time from other parts of your program or other programs running in the same JVM.

I am exploiting the fact that your string, "2018-09-14T13:05:21.3Z", is in ISO 8601 format, the format that the classes of java.time parse as their default, that is, without any explicit formatter. Instant.parse accepts anything from 0 through 9 decimals on the seconds, so there is no problem giving it a string with just 1 decimal, as you did. In comparison there is no way that an old-fashioned SimpleDateFormat can parse 1 decimal on the seconds with full precision since it takes pattern letter (uppercase) S to mean milliseconds, so .3 will be parsed as 3 milliseconds, not 3 tenths of a second, as it means.

Jahnavi Paliwal has already correctly diagnosed and explained the reason for the exception you got.

The date-time classes that you used, DateFormat, SimpleDateFormat and Date, are all long outdated and SimpleDateFormat in particular is notoriously troublesome. Since you seem to be using Java 8 (and even if you didn’t), I suggest you avoid those classes completely and use java.time instead.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161