SimpleDateFormat Illegal pattern character error with localized pattern
Asked Answered
E

2

7

I'm trying to understand some SimpleDateFormat code. In particular I'm trying to use localized pattern strings in SimpleDateFormat. From the javadoc:

SimpleDateFormat also supports localized date and time pattern strings. In these strings, the pattern letters described above may be replaced with other, locale dependent, pattern letters.

It also specifies a SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) constructor:

Constructs a SimpleDateFormat using the given pattern and date format symbols.

However, although getLocalPatternChars() instance is presenting the expected pattern characters, SimpleDateFormat's constructors are rejecting patterns containing those characters:

public void run() {
    Locale loc = new Locale("de", "de");
    DateFormatSymbols dfs = new DateFormatSymbols(loc);
    String sym = dfs.getLocalPatternChars();
    System.out.println(sym);
    SimpleDateFormat datefmt = new SimpleDateFormat("tt.MM.uuuu", dfs);
}

produces output:

GuMtkHmsSEDFwWahKzZ
Exception in thread "main" java.lang.IllegalArgumentException: Illegal pattern character 't'
    at java.text.SimpleDateFormat.compile(SimpleDateFormat.java:845)
    ...

I get the same output if I replace the last line with "... new SimpleDateFormat("tt.MM.uuuu", loc);".

On the other hand, if I create a SimpleDateFormat instance using any Anglicized pattern string, then call "applyLocalizedPattern("tt.MM.uuuu")", the localized pattern is accepted.

So it appears that one cannot use a localized pattern string in SimpleDateFormat's constructors, and need this two-step initialization. Is this intentional behaviour?

Exergue answered 20/6, 2016 at 11:25 Comment(3)
The constructor doesn't call translatePattern (applyLocalizedPattern does that), so this is either a bug or a bad JavaDoc which doesn't clearly explain the usage of the constructor.Trousseau
@Trousseau is right. The pattern parameter of the constructor only refers to the unlocalized date-time-pattern-characters.Penetralia
See also https://mcmap.net/q/333072/-how-do-i-get-localized-date-pattern-stringPenetralia
P
6

Unfortunately the documentation of how to handle localized patterns is horrible. So I studied the source code and made my own investigations. Result:

The constructor of SimpleDateFormat accepting a pattern string only refers to the unlocalized pattern characters whose definition is documented as given in the javadoc header of class SimpleDateFormat. These unlocalized pattern characters are also defined as constant in DateTimeFormatSymbols:

/**
 * Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
 * All locales use the same these unlocalized pattern characters.
 */
static final String  patternChars = "GyMdkHmsSEDFwWahKzZYuXL";

Three steps are necessary in order to use localized patterns (like "tt.MM.uuuu" what you believe to be German - but is NOT German, it should rather be "TT.MM.JJJJ" - example for wrong JDK resources):

  1. Define the localized pattern characters via DateFormatSymbols.setLocalPatternChars(...).
  2. Use the customized date-format-symbols on your SimpleDateFormat-object.
  3. Apply the localized date-time-pattern via SimpleDateFormat.applyLocalizedPattern(...)

Then the localized pattern will be translated to the internal and official pattern character definition.

Example of usage (using the correct German pattern TT.MM.JJJJ):

SimpleDateFormat sdf = new SimpleDateFormat(); // uses default locale (here for Germany)
System.out.println(sdf.toPattern()); // dd.MM.yy HH:mm
System.out.println(sdf.toLocalizedPattern()); // tt.MM.uu HH:mm

DateFormatSymbols dfs = DateFormatSymbols.getInstance(Locale.GERMANY);
dfs.setLocalPatternChars("GJMTkHmsSEDFwWahKzZYuXL");
sdf.setDateFormatSymbols(dfs);
sdf.applyLocalizedPattern("TT.MM.JJJJ");

System.out.println(sdf.toPattern()); // dd.MM.yyyy
System.out.println(sdf.toLocalizedPattern()); // TT.MM.JJJJ
System.out.println(sdf.format(new Date())); // 20.06.2016

Side note: I have changed the appropriate pattern chars y and d to J and T in the string "GyMdkHmsSEDFwWahKzZYuXL" to make a localized definition.

Unfortunately the JDK resources are obviously not reliable so my personal view is that the whole feature can only be used in an awkward way and is not very useful in practice.

Penetralia answered 20/6, 2016 at 16:20 Comment(2)
I have also searched within the CLDR data for these localized date-time-patterns-characters like "GyMdkHmsSEDFwWahKzZYuXL" but not yet found. So it is not clear for me how Sun/Oracle gets these data. Is it their own guess-work? Maybe someone knows more about the source of the localized data?Penetralia
Ah okay, the old versions of CLDR-data know the element <localizedPatternChars>. It has been deprecated and is no longer filled (in CLDR v29). So the JDK seems to use old data. See also this ICU-ticketPenetralia
P
0

Although I agree with @MenoHochschild's analysis of the problem, the solution presented seems more complicated than necessary. There is a method on SimpleDateFormat called applyLocalizedPattern which I believe will achieve the same result.

String localizedPattern = ... // whatever localized pattern you have e.g. TT.MM.JJJJ
Locale locale = ... // whatever locale you are expecting the localized symbols to be, e.g. GERMAN

// Start with empty pattern (always valid)
SimpleDateFormat df = new SimpleDateFormat("", locale);

// Set the localized pattern (not possible via constructor)
df.applyLocalizedPattern(localizedPattern);

// Now, do whatever you want with that DateFormat object
String canonicalPattern = df.toPattern();

As long as the localizedPattern and locale agree with each other, this technique should work for this use-case.

Pelagia answered 11/12, 2019 at 15:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.