Parsing error for French locale with SimpleDateFor

2019-08-21 07:23发布

问题:

I have a piece of code like this on my java side:

private static DateFormat getHourFormatter(){

        //DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(_locale);
        Locale locale = Locale.FRENCH; //locale : "fr"
        DateFormat hourFormatter = new SimpleDateFormat( "hh:mm a",locale); //hourFormatter: simpleDateFormat@103068 locale: "fr"
        hourFormatter.setTimeZone( TimeZone.getTimeZone("GMT") );
        return hourFormatter; //hourFormatter: SimpleDateFormat@103068
    }



protected static boolean isHoursTimeStringValid( String hourDisplay ) {
         try {
            getHourFormatter().parse( hourDisplay ); //hourDisplay: "01:01 Matin"
            return true;
         } catch (ParseException e) { //e: "java.text.ParseException: Upparseable date "01:01 Matin"
            return false; 
         }
    }

It is working fine for English locale if I change the locale value to US.

But for French locale it throwing parsing error.

java.text.ParseException: Upparseable date "01:01 Matin"

I have added the debug info as commented line for better understanding

回答1:

Without rewriting your existing code base, you may still introduce java.time, the modern Java date and time API, for this particular purpose. It does offer a solution for French:

    Map<Long, String> amPmText = new HashMap<>(4);
    amPmText.put(0L, "Matin");
    amPmText.put(1L, "Soir");
    DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder().appendPattern("hh:mm ")
            .appendText(ChronoField.AMPM_OF_DAY, amPmText)
            .toFormatter(Locale.FRENCH);

    System.out.println(LocalTime.parse("01:01 Matin", timeFormatter));
    System.out.println(LocalTime.parse("10:59 Soir", timeFormatter));

This prints

01:01
22:59

Mixing java.time and the outdated classes

So far our code base (which is old) is a funny mix of old and new date and time API use. Frankly we’re seldom rewriting any of the old and working code, but we always use the modern API for new code.

I do warmly recommend using java.time wherever you can. It is generally so much nicer to work with. Once you have embarked on using it, I’m sure you will not want to go back.

For a pure SimpleDateFormat solution see Meno Hochschild’s comment below.



回答2:

If and only if you have just two possible values (here AM/PM) then you can do it with SimpleDateFormat this way:

DateFormatSymbols dfs = DateFormatSymbols.getInstance(Locale.FRENCH);
dfs.setAmPmStrings(new String[] { "Matin", "Soir" });
SimpleDateFormat input = new SimpleDateFormat("hh:mm a", Locale.FRENCH);
input.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
input.setDateFormatSymbols(dfs);

Date parsed = input.parse("01:01 Matin");

// control of parsing
SimpleDateFormat output = new SimpleDateFormat("HH:mm");
output.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
System.out.println(output.format(parsed)); // 01:01 (24-hour-clock)

I have here set the timezone to GMT in order to prevent any zone effects. You can deviate from it if needed (but care is necessary).

As mentioned in some comments, I still don't think that using the AM/PM-field is really appropriate for other languages than English. French for example knows at least two or more values like "nuit" (=night) or "après-midi" (=afternoon). But that way is not possible with old API or new java.time-package (would require external libs like ICU4J or Time4J).



回答3:

Thank you guys for all of these answers.

As I mentioned earlier, I can't afford to change the code base.

So, what I have done is :

public void setBeginAMPM( String ampm ) {
    if(ampm.equals(new I18NStringFactory().getString("Calendar", _locale , "default.time.am" ))) {
        _beginAMPM = "AM";
    }
    else if(ampm.equals(new I18NStringFactory().getString("Calendar", _locale , "default.time.pm" ))) {
        _beginAMPM = "PM";
    }
    else{
        _beginAMPM = ampm;
    }
}


public void setEndAMPM( String ampm ) {
    if(ampm.equals(new I18NStringFactory().getString("Calendar", _locale , "default.time.am" ))) {
        _endAMPM = "AM";
    }
    else if(ampm.equals(new I18NStringFactory().getString("Calendar", _locale , "default.time.pm" ))) {
        _endAMPM = "PM";
    }
    else{
        _endAMPM = ampm;
    }
}

_locale value I am passing from Action class to From class. If it is other than English it will come into one of the if block or in case of English it will come to the else block by default. Based on the local value it is taking AM/PM value from the properties file and converting that accordingly.

I am just modifying the AM/PM value from other locale-specific languages to English, as SimpleDateFormat() only supports English.

You guys can call it an ugly hack, but guess what, It is solving my purpose.