javaandroidsimpledateformat

Android SimpleDateFormatter Parse Exception


I have some old Java code that attempts to parse a string using SimpleDateFormat. It is frequently throwing a ParseException:

Non-fatal Exception: java.text.ParseException:
Unparseable date: "06:00:00 PM" at java.text.DateFormat.parse(DateFormat.java:400)

I can't see what is going wrong here. Seems simple. Hoping somebody can spot it.

public static String toTimeString(String time, String inTimeFormat, String outTimeFormat) {
    DateFormat df1 = new SimpleDateFormat(inTimeFormat, Locale.getDefault());
    DateFormat df2 = new SimpleDateFormat(outTimeFormat, Locale.getDefault());
    String timeString = "";

    try {
        Date date = df1.parse(time); //<-- exception thrown here
        _12HrTime = df2.format(date);
    } catch (ParseException e) {
        Crashlytics.recordException(e);
    }
    return timeString;
}

Here is how it is being called when it fails:

toTimeString("06:00:00 PM", "hh:mm:ss a", "h:mm a")

Note that the string 06:00:00 PM comes from a server response.

Every time I run this, it works correctly. But Crashlytics reports this error frequently. What am I missing??


Solution

  • tl;dr

    Specify Locale to be used in parsing localized text PM.

    Use modern java.time classes, not legacy classes. Specifically java.time.LocalTime.

    LocalTime
        .parse( 
            "06:00:00 PM" ,
            DateTimeFormatter
                .ofPattern( "hh:mm:ss a" )
                .withLocale( Locale.of( "en" , "US" ) )  // ⬅️ Specify locale to be used in parsing localized text `PM`. 
        )
    

    Avoid legacy classes

    You are using terribly flawed legacy classes that were years ago supplanted by the modern java.time classes defined in JSR 310.

    Appropriate class: LocalTime

    And you are attempting to represent a time-of-day value with a class that represents a date with time-of-day — square peg in a round hole. Instead use the appropriate class: LocalTime.

    Locale

    Specify a Locale to determine the human language and cultural norms to be used in parsing the localized PM. Some cultures use lowercase, pm, some use punctuation, p.m.. And some use entirely different text.

    If you omit the locale, the JVM’s current default locale will be used implicitly. That particular locale may not be able to make sense of your text PM.

    Example code

    String input = "06:00:00 PM" ;
    Locale locale = Locale.of( "en" , "US" ) ;
    DateTimeFormatter f = DateTimeFormatter.ofPattern( "hh:mm:ss a" ).withLocale( locale ) ;
    LocalTime time = LocalTime.parse( input , f ) ;
    

    See an older version of this code run at Ideone.com.

    time.toString() = 18:00

    Android

    Android 26+ bundles an implementation of the java.time classes. For earlier Android, the latest tooling provides most of the functionality via “API desugaring”.

    ISO 8601

    Tip: Avoid using localized text when exchanging/storing date-time values. Use only ISO 8601 standard values.

    For a time-of-day that would be 24-hour clock, with zero seconds being optional, with leading & trailing padded zeros. So, 18:00 for 6 PM.