java.text.ParseException: Unparseable date: java.text.DateFormat.parse(DateFormat.java:579)

I have problem with SimpleDateFormat.

SimpleDateFormat dtfmt=new SimpleDateFormat("dd MMM yyyy hh:mm a", Locale.getDefault());
Date dt=dtfmt.parse(deptdt);

In Android Emulator works fine but in phone I have this error:

W/System.err: java.text.ParseException: Unparseable date: "24 Oct 2016 7:31 pm" (at offset 3) W/System.err: at java.text.DateFormat.parse(DateFormat.java:579)

Any solution?


Solution 1:

Never use SimpleDateFormat or DateTimeFormatter without a Locale

Since the given date-time is in English, you should use Locale.ENGLISH with your date-time parser; otherwise the parsing will fail in a system (computer, phone etc.) which is using a non-English type of locale.

Also, note that the date-time API of java.util 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.

  • 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.

Demo:

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

public class Main {
    public static void main(String[] args) {
        final String strDateTime = "24 Oct 2016 7:31 pm";
        DateTimeFormatter dtf = new DateTimeFormatterBuilder()
                .parseCaseInsensitive()             // For case-insensitive (e.g. am, Am, AM) parsing 
                .appendPattern("d MMM uuuu h:m a")  // Pattern conforming to the date-time string
                .toFormatter(Locale.ENGLISH);       // Locale
        LocalDateTime ldt = LocalDateTime.parse(strDateTime, dtf);
        System.out.println(ldt);
    }
}

Output:

2016-10-24T19:31

By default, DateTimeFormatter#ofPattern uses the default FORMAT locale which the JVM sets during startup based on the host environment. Same is the case with SimpleDateFormat. I have tried to illustrate the problem through the following demo:

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

public class Main {
    public static void main(String[] args) {
        final String strDateTime = "24 Oct 2016 7:31 pm";           
        DateTimeFormatter dtfWithDefaultLocale = null;          

        System.out.println("JVM's Locale: " + Locale.getDefault());
        // Using DateTimeFormatter with the default Locale
        dtfWithDefaultLocale = getDateTimeFormatterWithDefaultLocale();
        System.out.println("DateTimeFormatter's Locale: " + dtfWithDefaultLocale.getLocale());
        System.out.println(
                "Parsed with JVM's default locale: " + LocalDateTime.parse(strDateTime, dtfWithDefaultLocale));

        // Setting the JVM's default locale to Locale.FRANCE
        Locale.setDefault(Locale.FRANCE);
        
        // Using DateTimeFormatter with Locale.ENGLISH explicitly (recommended)
        DateTimeFormatter dtfWithEnglishLocale = getDateTimeFormatterWithEnglishLocale();
        System.out.println("JVM's Locale: " + Locale.getDefault());
        System.out.println("DateTimeFormatter's Locale: " + dtfWithEnglishLocale.getLocale());
        LocalDateTime zdt = LocalDateTime.parse(strDateTime, dtfWithEnglishLocale);
        System.out.println("Parsed with Locale.ENGLISH: " + zdt);

        
        System.out.println("JVM's Locale: " + Locale.getDefault());
        // Using DateTimeFormatter with the default Locale
        dtfWithDefaultLocale = getDateTimeFormatterWithDefaultLocale();
        System.out.println("DateTimeFormatter's Locale: " + dtfWithDefaultLocale.getLocale());
        System.out.println(
                "Parsed with JVM's default locale: " + LocalDateTime.parse(strDateTime, dtfWithDefaultLocale));
    }
    
    static DateTimeFormatter getDateTimeFormatterWithDefaultLocale() {
        return new DateTimeFormatterBuilder()
                .parseCaseInsensitive()             
                .appendPattern("d MMM uuuu h:m a") 
                .toFormatter(); // Using default Locale
    }
    
    static DateTimeFormatter getDateTimeFormatterWithEnglishLocale() {
        return new DateTimeFormatterBuilder()
                .parseCaseInsensitive()             
                .appendPattern("d MMM uuuu h:m a") 
                .toFormatter(Locale.ENGLISH); // Using Locale.ENGLISH
    }
}

Output:

JVM's Locale: en_GB
DateTimeFormatter's Locale: en_GB
Parsed with JVM's default locale: 2016-10-24T19:31
JVM's Locale: fr_FR
DateTimeFormatter's Locale: en
Parsed with Locale.ENGLISH: 2016-10-24T19:31
JVM's Locale: fr_FR
DateTimeFormatter's Locale: fr_FR
Exception in thread "main" java.time.format.DateTimeParseException: Text '24 Oct 2016 7:31 pm' could not be parsed at index 3
    at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
    at java.base/java.time.LocalDateTime.parse(LocalDateTime.java:492)
    at Main.main(Main.java:34)

The following demo, using SimpleDateFormat, is just for the sake of completeness:

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 {
        final String strDateTime = "24 Oct 2016 7:31 pm";
        SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy h:m a", Locale.ENGLISH);
        Date date = sdf.parse(strDateTime);
        System.out.println(date);
    }
}

Output:

Mon Oct 24 19:31:00 BST 2016

Solution 2:

Your deptdt contains Oct which looks like an English month name. But your Locale.getDefault() probably gives a non-english locale. Replace it by Locale.ENGLISH or Locale.US:

SimpleDateFormat dtfmt=new SimpleDateFormat("dd MMM yyyy hh:mm a", Locale.ENGLISH);
Date dt=dtfmt.parse(deptdt);

Solution 3:

It probably happens because the phone's default locale is not English, and the month name in your input is (Oct).

The solution is to explicity use the English locale:

SimpleDateFormat dtfmt = new SimpleDateFormat("dd MMM yyyy hh:mm a", Locale.ENGLISH);
Date dt = dtfmt.parse("24 Oct 2016 7:31 pm");

Instead of directly working with SimpleDateFormat (as this old API has lots of problems and design issues), you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. To use it in Android, you'll also need the ThreeTenABP (more on how to use it here).

The main classes to be used are org.threeten.bp.LocalDateTime (it seems the best choice, as you have date and time fields in your input) and org.threeten.bp.format.DateTimeFormatter (to parse the input). I also use java.util.Locale class to make sure it parse the month names in English, and the org.threeten.bp.format.DateTimeFormatterBuilder to make sure it parses pm (make it case insensitive, as the default is PM):

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // case insensitive to parse "pm"
    .parseCaseInsensitive()
    // pattern
    .appendPattern("dd MMM yyyy h:mm a")
    // use English locale to parse month name (Oct)
    .toFormatter(Locale.ENGLISH);
// parse input
LocalDateTime dt = LocalDateTime.parse("24 Oct 2016 7:31 pm", fmt);
System.out.println(dt); // 2016-10-24T19:31

The output will be:

2016-10-24T19:31

If you need to convert this to a java.util.Date, you can use the org.threeten.bp.DateTimeUtils class. But you also need to know what timezone will be used to convert this. In the example below, I'm using "UTC":

Date date = DateTimeUtils.toDate(dt.atZone(ZoneOffset.UTC).toInstant());

To change to a different zone, you can do:

Date date = DateTimeUtils.toDate(dt.atZone(ZoneId.of("Europe/London")).toInstant());

Note that the API uses IANA timezones names (always in the format Continent/City, like America/Sao_Paulo or Europe/Berlin). Avoid using the 3-letter abbreviations (like CST or PST) because they are ambiguous and not standard. To find the timezone that better suits each region, use the ZoneId.getAvailableZoneIds() method and check which one fits best for your use cases.

PS: the last example above (dt.atZone(ZoneId.of("Europe/London"))) will create the date/time 2016-10-24T19:31 in London timezone. But if what you want is 2016-10-24T19:31 in UTC, then convert it to another timezone, then you should do:

Date date = DateTimeUtils.toDate(dt
    // first convert it to UTC
    .toInstant(ZoneOffset.UTC)
    // then convert to LondonTimezone
    .atZone(ZoneId.of("Europe/London")).toInstant());