Cannot parse date using JodaTime

2.2k views Asked by At

I've been trying to parse a simple date using JodaTime since yesterday and so far I keep on failing.....

Here's a (random) date I'm trying to parse: 2017-Sept-14 (Even with S in upper case doesn't change anything...)

Here's my code

 DateTimeFormatter dateTimeFormat = DateTimeFormat.forPattern("yyyy-MM-dd");
 // The variable 'parsed' is a dynamic string. For now I set it to 2017-sept-14 
 DateTime dateTime = dateTimeFormat.parseDateTime(parsed);
 Log.d(TAG, "Parsed date = "+ dateTime.toString());

And here's the exception I have:

java.lang.IllegalArgumentException: Invalid format: "2017-sept-14" is malformed at "sept-14" at org.joda.time.format.DateTimeFormatter.parseDateTime(DateTimeFormatter.java:945)

What am I missing here ??

UPDATE: Actually what I get from my textfield is in the form above i.e date-month-day (the month is 3 or 4 characters long depending on the month....) So what I want to get as output when I have 2017-sept-14 is simply 2017-09-14

4

There are 4 answers

12
Claude Hangui On

So I don't know know if it's the right way but this worked for me (I ended up using SimpleDateFormat...) :

SimpleDateFormat sdfSource = new SimpleDateFormat("yyyy-MMM-dd");
SimpleDateFormat sdfDestination = new SimpleDateFormat("yyyy-MM-dd");
try {
      Date date = sdfSource.parse(parsed);
      String strOutput = sdfDestination.format(date);
      Log.d(TAG, "Parsed date = "+ strOutput);
    } catch (ParseException e) {
        e.printStackTrace();
    }
4
AudioBubble On

Joda-Time accepts the month name in a short format (in most languages, it's usually with 3 letters) or long format (with the full name). Your input seems to be in English and with 4 letters, which is not supported.

If it's possible to manipulate the input, you can remove the extra characters and make sure the month name contains just 3 letters.

I also use a java.util.Locale to specify that the month name is in English. If you don't specify a locale, it uses the system default, and it's not guaranteed to always be English, so it's better to specify one.

I also parse it to a LocalDate, because its toString() method already produces the output you want:

String input = "2017-Sept-14";
input = input.replace("Sept", "Sep");
DateTimeFormatter dateTimeFormat = DateTimeFormat.forPattern("yyyy-MMM-dd").withLocale(Locale.ENGLISH);
LocalDate dateTime = dateTimeFormat.parseLocalDate(input);
System.out.println(dateTime);

The output is:

2017-09-14

I was assuming that the locale was English, but in Estonia locale the short month name for September is "sept", so you could also do:

String input = "2017-Sept-14";
input = input.toLowerCase(); // et_EE locale accepts only "sept"
DateTimeFormatter dateTimeFormat = DateTimeFormat.forPattern("yyyy-MMM-dd")
    .withLocale(new Locale("et", "EE"));
LocalDate dateTime = dateTimeFormat.parseLocalDate(input);
System.out.println(dateTime);

Or you can try with your system's default (based on your comments that SimpleDateFormat works with French locale, so there's a chance of the code above to also work).


Java new Date/Time API

Joda-Time is in maintainance mode and is being replaced by the new APIs, so I don't recommend start a new project with it. Even in joda's website it says: "Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).".

If you can't (or don't want to) migrate from Joda-Time to the new API, you can ignore this section.

In Android you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. To make it work, you'll also need the ThreeTenABP (more on how to use it here).

You can create a formatter, set the locale and parse it to a LocalDate:

import org.threeten.bp.LocalDate;
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.DateTimeFormatterBuilder;

DateTimeFormatter f = new DateTimeFormatterBuilder()
    // case insensitive (so it accepts Sept, sept, and so on)
    .parseCaseInsensitive()
    // pattern
    .appendPattern("yyyy-MMM-dd")
    // set locale
    .toFormatter(new Locale("et", "EE"));
System.out.println(LocalDate.parse("2017-Sept-14", f));

The output is:

2017-09-14

Or just try with your system's default locale (just call toFormatter() without arguments and it'll use the system default).


Optionally, you can create a map of custom month names and use it in the formatter. The only detail is that you have to fill it with values for all months. I put Sept in September, and you can fill the other months accordingly:

// map of custom names for month
Map<Long, String> monthNames = new HashMap<>();
// put the names used in your input
monthNames.put(1L, "Jan");
monthNames.put(2L, "Feb");
monthNames.put(3L, "Mar");
monthNames.put(4L, "Apr");
monthNames.put(5L, "May");
monthNames.put(6L, "Jun");
monthNames.put(7L, "Jul");
monthNames.put(8L, "Aug");
monthNames.put(9L, "Sept");
monthNames.put(10L, "Oct");
monthNames.put(11L, "Nov");
monthNames.put(12L, "Dec");

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // case insensitive (so it accepts Sept, sept, and so on)
    .parseCaseInsensitive()
    // year
    .appendPattern("yyyy-")
    // month, using custom names
    .appendText(ChronoField.MONTH_OF_YEAR, monthNames)
    // day
    .appendPattern("-dd")
    // create formatter
    .toFormatter();

String input = "2017-Sept-14";
System.out.println(LocalDate.parse(input, fmt));
3
Imen Gharsalli On

You need a two digits number for the month (09) if you want to keep the pattern yyyy-MM-dd. Otherwise change your pattern to yyyy-MMMM-dd.

1
Meno Hochschild On

Joda-Time does not offer a simple way. The only (complex) way would be to define your own implementation of DateTimeParser. You might find some inspiration how to implement it in another old SO-post from me and then do:

DateTimeParser monthParser = ...; // left as exercise to you

DateTimeFormatter joda = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyy-")
  .append(monthParser)
  .append("-dd")
  .toFormatter();
LocalDate ld = joda.parseLocalDate("2017-sept-14");

I am not sure why the answer of Hugo suggesting a map-based lookup of customized names does not work for you. Maybe the month names are variable and not fixed (for the same month). Maybe you just want to check if the month names start with the same prefix. If so then my lib Time4J might help you because it manages much more formatting/parsing attributes to control parsing, see following short example (it also manages additional trailing dots if present):

String s = "2017-sept.-14";
ChronoFormatter<PlainDate> f =
  ChronoFormatter
    .setUp(PlainDate.class, new java.util.Locale("fr", "FR"))
    .addPattern("uuuu-MMM", PatternType.CLDR)
    .skipUnknown(c -> c == '.', 1)
    .addPattern("-dd", PatternType.CLDR)
    .build()
    .with(net.time4j.format.Attributes.PARSE_CASE_INSENSITIVE, true)
    .with(net.time4j.format.Attributes.PARSE_PARTIAL_COMPARE, true);
System.out.println(f.parse(s)); // 2017-09-14

For Android, you would replace Time4J by Time4A and also replace the given lambda-expression by an anonymous class implementing the ChronoCondition-interface.

By the way, a lookup-table for fixed month names is possible with every library. For Joda, see my older SO-post mentioned above as link. For Java-8 or the threeten-backport see the answer of Hugo. For SimpleDateFormat see how to set a suitable instance of DateFormatSymbols. For Time4J, see the builder-API (similar to Java-8).

A final word about SimpleDateFormat. Even if it seems to work for you now (just because of lenient parsing which works for old Java and Time4J but interestingly not for java.time-API), I would still not trust it in every situation, and this old parser class is not thread-safe, too.