Unable to parse DateTime-string with AM/PM marker

Viewed 40485

The string I want to format looks like this: String datetime = "9/1/10 11:34:35 AM"

Following pattern for SimpleDateFormat works:

SimpleDateFormat sdf = SimpleDateFormat("M/d/yy h:mm:ss");
Date d = sdf.parse(datetime);
System.out.println(d);

Output> [Wed Sep 01 11:34:35 CEST 2010]

However I need to parse the AM/PM marker as well, and when I add that to the pattern I receive an exception.

Pattern that doesn't work:

SimpleDateFormat sdf = SimpleDateFormat("M/d/yy h:mm:ss a");

I have tried with this also with same exception:

SimpleDateFormat sdf = SimpleDateFormat("M/d/yy h:mm:ss aa");

Exception:

java.text.ParseException: Unparseable date: "9/1/10 11:34:35 AM"

I have looked through the API at http://download.oracle.com/javase/1.4.2/docs/api/java/text/SimpleDateFormat.html#text but canät seem to find where I do wrong.

Any suggestions?

6 Answers

java.time

You can build a case-insensitive parser using DateTimeFormatterBuilder. Since a date-time parsing/formatting type (e.g. DateTimeFormatter, SimpleDateFormat etc.) is Locale-sensitive, you should always use a Locale with such a type. I've used Locale.ENGLISH because your date-time string has AM/PM marker in English.

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

public class Main {
    public static void main(String args[]) {
        DateTimeFormatter dtf = new DateTimeFormatterBuilder()
                                .parseCaseInsensitive()
                                .appendPattern("M/d/uu H:m:s a")
                                .toFormatter(Locale.ENGLISH);
        
        //Test
        Stream.of(
                "9/1/10 11:34:35 AM",
                "9/1/10 11:34:35 am",
                "09/1/10 11:34:35 AM",
                "9/01/10 11:34:35 Am"
        ).forEach(s -> System.out.println(LocalDateTime.parse(s, dtf)));;
    }
}

Output:

2010-09-01T11:34:35
2010-09-01T11:34:35
2010-09-01T11:34:35
2010-09-01T11:34:35

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


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

Just a note about Locale:
the symbols used for AM/PM depend on Locale!

This affects parsing strings, eventually causing errors if the used AM/PM field does not match the predefined symbols. (obviously also affects formatting)

java.time.format.DateTimeFormatter and java.text.SimpleDateFormat accept an optional Locale when being created. If none is given. the systems default one is used.


Warning: using DateTimeFormatter the case of the AM/PM flag is also relevant when parsing, at least for some locales.

As an example, the Indian Locale requires the AM/PM flag being lowercase (am, pm), while some other locales (ROOT, ITALY, US, GERMANY) only accept uppercase AM/PM.

This throws DateTimeParseException: Text '2021-03-31 10:15:30 AM +05:30' could not be parsed at index 20

ZonedDateTime.parse("2021-03-31 10:15:30 AM +05:30",
                    DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss a Z", 
                                                new Locale("en", "IN"))) 

This results in 2021-03-31T10:15:30+05:30

ZonedDateTime.parse("2021-03-31 10:15:30 am +05:30",
                    DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss a Z", 
                                                new Locale("en", "IN")))

Using Locale.US, Locale.GERMANY or Locale.ROOT, the results are inverted.

Note: case of AM/PM does not matter when parsing with SimpleDateFormat (I am not recommending its use, I prefer DateTimeFormatter)

Related