1

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

Claude Hangui
  • 426
  • 3
  • 8
  • 29
  • I think "MM" in the pattern expects "Sep" as abbreviation for the month. And "MMM" leads to "September". So "Sept" is your problem. – IQV Sep 14 '17 at 13:12
  • But what if I entered May/may instead ?? Will I still have to chop-off the last character ?? (I recall that my date is dynamic....) – Claude Hangui Sep 14 '17 at 13:20
  • I don't know. But I think you have just to ensure, that month is 3 characters long. – IQV Sep 14 '17 at 13:21
  • I just tried your proposal..still the same crash: java.lang.IllegalArgumentException: Invalid format: "2017-sep-14" is malformed at "sep-14" – Claude Hangui Sep 14 '17 at 13:26

4 Answers4

1

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));
  • Hi, I did try the ThreeTen Backport at first, though I confess I did not have to write all what you just did above...Actually I did something lie this : DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder(); dateTimeFormatterBuilder.parseCaseSensitive(); dateTimeFormatterBuilder.appendPattern("yyyy-MMM-dd"); DateTimeFormatter dateTimeFormatter = dateTimeFormatterBuilder.toFormatter(); LocalDate localDate = LocalDate.parse(parsed, dateTimeFormatter); But I didn't work so I had to switch to the plain SimpleDateFormat... – Claude Hangui Sep 14 '17 at 15:56
  • @ClaudeHangui That's strange. With your code, I can't parse `2017-Sept-14`, just `sep`. What's your default locale? –  Sep 14 '17 at 15:58
  • And like I said before chopping-off one character doesn't seem to be the best idea...since something like May has only 3 characters...Of course one can make a switch statement to decide when to remove the last character, but that seems tedious (at least in my opinion..)..Using SimpleDateFormat solves the problem in 4-5 lines – Claude Hangui Sep 14 '17 at 16:00
  • @ClaudeHangui I've just added a few comments about the locale, but I'm afraid I can't add much more to this issue. I simply can't reproduce the same behaviour in my environment, so that's all the help I can provide. But good you found a solution with `SimpleDateFormat`. –  Sep 14 '17 at 17:30
1

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.

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
  • Great! I didn't think about extending `DateTimeParser`, that seems to be the only way with Joda-Time. –  Sep 14 '17 at 17:50
0

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.

Imen Gharsalli
  • 367
  • 2
  • 13
  • I get the 2014-Sept-14 from a text field, So I just tried to change to yyyy-MMM-dd, but still have no results so far...I still have the same issue: java.lang.IllegalArgumentException: Invalid format: "2017-sept-14" is malformed at "sept-14" – Claude Hangui Sep 14 '17 at 13:32
  • The pattern for " 2014-Sept-14 " is `yyyy-MMMM-dd` not `yyyy-MMM-dd` – Imen Gharsalli Sep 14 '17 at 13:58
  • `MMMM` parses only "September", not "Sept" –  Sep 14 '17 at 15:50
0

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();
    }
Claude Hangui
  • 426
  • 3
  • 8
  • 29
  • Which java version are you using? I tested with Java 7 and 8, and none worked. –  Sep 14 '17 at 15:28
  • I'm using Java 7....Did you actually tried 2017-sept-14 ??... Cuz I just tested it with other months of the calendar and so far everything's fine for me ! – Claude Hangui Sep 14 '17 at 15:52
  • Yes, I tried with `2017-sept-14` and `SimpleDateFormat` didn't work. Anyway, I've posted [an answer](https://stackoverflow.com/a/46223441/7605325), take a look there... –  Sep 14 '17 at 15:54
  • Here's what I have.. SimpleDateFormat sdfSource = new SimpleDateFormat("yyyy-MMM-dd"); SimpleDateFormat sdfDestination = new SimpleDateFormat("yyyy-MM-dd"); try { Date date = sdfSource.parse(parsed); return sdfDestination.format(date); } catch (ParseException e) { e.printStackTrace(); } – Claude Hangui Sep 14 '17 at 16:02
  • What do you get if you print `Locale.getDefault()`? –  Sep 14 '17 at 16:04
  • With the code above, I don't need to....I even tried specifying a locale SimpleDateFormat sdfSource = new SimpleDateFormat("yyyy-MMM-dd", Locale.FRENCH); I still get the desired result...SO WITH OR WITHOUT THE LOCALE I HAVE THE SAME RESULT – Claude Hangui Sep 14 '17 at 16:06
  • I'm trying to understand the problem and help you. Locale sensitive data (like month names) are environment-dependent and sometimes hard to figure out. There's no need to shout in ALL CAPS. –  Sep 14 '17 at 16:08
  • lol....I wasn't trying to argue/shout..That wasn't my intent..I was merely insisting on the fact that adding (or not) the Locale gave me the same result :)..It's surprising you don't have the same result – Claude Hangui Sep 14 '17 at 16:11
  • Maybe I'm not using the same exact JVM version as yours (or some other configuration/detail that I'm missing). Anyway, I'll check this out and update the answer if I find something else. What do you get if you print `Locale.getDefault()`? Just to make sure I have the same configs here. –  Sep 14 '17 at 16:13
  • `SimpleDateFormat` only works for me if I set the default locale to Estonia: `Locale.setDefault(new Locale("et", "EE"))` - that's why I'm asking what's your default locale. –  Sep 14 '17 at 16:16
  • Ok, so my default locale is fr_FR – Claude Hangui Sep 14 '17 at 16:19
  • Ok, I think I get it. In my JVM, French locale produces "sept." (with a dot `.` in the end). Probably in your JVM the result is just "sept", and that's why it works. So ThreeTen backport should work, but Joda-Time probably not (as it seems to be only case sensitive and parsing just lowercase `s`). Anyway, as I can't reproduce the same environment, my answer stays the same. I don't have much more to add, sorry for not being able to help more.. –  Sep 14 '17 at 17:07