-1

I have an example code as below

final String pattern = "dd-MMM-yy hh.mm.ss.SSS000 a";
final SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
final String formatted = dateFormat.format(new Date());
final Date date = dateFormat.parse(formatted); // throws ParseException 

The errorIndex is 25 which is the whitespace between the last zero and the 'AM/PM' phrase.

Exception in thread "main" java.text.ParseException: Unparseable date: "09-Jun-21 04.40.45.898000 PM"
    at java.text.DateFormat.parse(DateFormat.java:366)

But when I change the pattern as below (which is not fitting my case), everything is ok.

final String pattern = "dd-MMM-yy hh.mm.ss.SSSSSS a";

Can anyone tell me please what is going on?

The UMA
  • 44
  • 3
  • 1
    SimpleDateFormat is outdated, and not recommended for use anymore. It is NOT thread-safe and has many other problems that will NOT be fixed. Starting from Java 8 there is a new java.time package and class java.time.format.DateTimeFormatter. If you can switch to it ASAP – Michael Gantman Jun 09 '21 at 14:11
  • And no, everything certainly is not OK with `dd-MMM-yy hh.mm.ss.SSSSSS a`. It formats 45.898 seconds into 45.000898, so approx. 897 milliseconds too early. Just one of the very many confusing traits of `SimpleDateFormat`. Avoid that class. – Ole V.V. Jun 09 '21 at 17:13

3 Answers3

2

I think this is an issue with the parser. It does not necessarily take the number of characters in the pattern literally. So it will try to parse 898000 as milliseconds.

SimpleDateFormat is anyway quite outdated, maybe you could try the java.time library?

  LocalDateTime date = LocalDateTime.ofInstant((new Date()).toInstant(), ZoneId.systemDefault());
  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yy hh.mm.ss.SSS'000' a");
  String text = date.format(formatter);
  Date parsedDate = Date.from(LocalDateTime.parse(text, formatter).atZone(ZoneId.systemDefault()).toInstant());

You can skip the conversion to and from Date if you can work with LocalDateTime as well:

  LocalDateTime date = LocalDateTime.now();
  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yy hh.mm.ss.SSS'000' a");
  String text = date.format(formatter);
  LocalDateTime parsedDate = LocalDateTime.parse(text, formatter);
Brtlb
  • 46
  • 5
  • Good answer. java.time can also offer greater precision if one wants: `dd-MMM-yy hh.mm.ss.SSSSSS a`. I would expect it to be OK in the asker’s context. – Ole V.V. Jun 09 '21 at 16:11
1

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*, released in March 2014 as part of Java SE 8 standard library.

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

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        String strDateTime = "09-Jun-21 04.40.45.898000 PM";
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("d-MMM-uu h.m.s.SSSSSS a", Locale.ENGLISH);
        LocalDateTime ldt = LocalDateTime.parse(strDateTime, dtf);
        System.out.println(ldt);
    }
}

Output:

0021-06-09T16:40:45.898

ONLINE DEMO

Here, you can use y instead of u but I prefer u to y.

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

Solution using the legacy API:

SimpleDateFormat does not handle fraction-of-second beyond three digits in the millisecond part correctly. You need to adjust your Date-Time string to comply with this limitation i.e. you need to remove digits from the millisecond part to keep it up to three digits. An easy way to do this is to use the Regex replacement.

Demo:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class Main {
    public static void main(String[] args) throws ParseException {
        String strDateTime = "09-Jun-21 04.40.45.898000 PM";
        strDateTime = strDateTime.replaceAll("(.*(?<=\\.)\\d{3})(\\d+)(\\s[AP]M)", "$1$3");

        SimpleDateFormat sdf = new SimpleDateFormat("d-MMM-y h.m.s.SSS a", Locale.ENGLISH);
        Date date = sdf.parse(strDateTime);
        System.out.println(date);
    }
}

Output in my timezone:

Wed Jun 09 16:40:45 BST 2021

ONLINE DEMO


* 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
-1

The culprit is the SSS in your format string. When printing, it indicates that you want to print at least 3 digits for the milliseconds. But when parsing, it simply indicates that you want to parse milliseconds. From the documentation:

For formatting, the number of pattern letters is the minimum number of digits, and shorter numbers are zero-padded to this amount. For parsing, the number of pattern letters is ignored unless it's needed to separate two adjacent fields.

That is, when parsing, the SSS pattern parses the whole 898000 as the milliseconds, and then does not find the zeros after that.

fishinear
  • 6,101
  • 3
  • 36
  • 84