SimpleDateFormat使用本地化模式时出现非法的模式字符错误

7
我正在尝试理解一些SimpleDateFormat代码。特别是,我正在尝试在SimpleDateFormat中使用本地化的模式字符串。根据javadoc的说明:

SimpleDateFormat还支持本地化日期和时间模式字符串。在这些字符串中,上面描述的模式字母可以用其他依赖于语言环境的模式字母替换。

它还指定了一个SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)构造函数:

使用给定的模式和日期格式符号构造SimpleDateFormat。

然而,尽管getLocalPatternChars()实例显示预期的模式字符,但SimpleDateFormat的构造函数仍会拒绝包含这些字符的模式:

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);
}

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

如果我将最后一行替换为"... new SimpleDateFormat("tt.MM.uuuu", loc);",将得到相同的输出结果。
另一方面,如果我使用任何英语化模式字符串创建SimpleDateFormat实例,然后调用"applyLocalizedPattern("tt.MM.uuuu")",则本地化模式将被接受。
因此,似乎不能在SimpleDateFormat的构造函数中使用本地化模式字符串,而需要进行两步初始化。这是有意为之的行为吗?

2
构造函数不调用 translatePatternapplyLocalizedPattern 执行此操作),因此这可能是一个错误或者是一个糟糕的 JavaDoc,没有清楚地解释构造函数的使用方法。 - Tom
@Tom 是正确的。构造函数的模式参数仅指未本地化的日期时间模式字符。 - Meno Hochschild
请参见https://dev59.com/y2445IYBdhLWcg3w_PK3#24128930。 - Meno Hochschild
2个回答

6

不幸的是,关于如何处理本地化模式的文档非常糟糕。因此我研究了源代码并进行了自己的调查。结果:

SimpleDateFormat 的构造函数只接受一个模式字符串,并且仅引用未本地化的模式字符,其定义在 SimpleDateFormat 类的 javadoc 头部中有所说明。这些未本地化的模式字符也被定义为 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";

为了使用本地化模式(例如“tt.MM.uuuu”,您认为它是德语,但实际上不是德语,应该是“TT.MM.JJJJ” - 错误的JDK资源示例),需要三个步骤:
  1. 通过DateFormatSymbols.setLocalPatternChars(...)定义本地化模式字符。
  2. SimpleDateFormat对象上使用自定义日期格式符号。
  3. 通过SimpleDateFormat.applyLocalizedPattern(...)应用本地化日期时间模式。
然后,本地化模式将被转换为内部和官方模式字符定义。
以下是正确使用德语模式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

顺便提一下:我已经将字符串“GyMdkHmsSEDFwWahKzZYuXL”中的适当模式字符y和d更改为J和T,以进行本地化定义。

不幸的是,JDK资源显然不可靠,因此我的个人观点是整个功能只能以笨拙的方式使用,在实践中并不是非常有用。


我还在CLDR数据中搜索了这些本地化的日期时间模式字符,例如“GyMdkHmsSEDFwWahKzZYuXL”,但尚未找到。因此,对于Sun/Oracle如何获取这些数据,我并不清楚。它是他们自己的猜测吗?也许有人更了解本地化数据的来源? - Meno Hochschild
啊,好的。旧版本的CLDR数据中包含元素<localizedPatternChars>。它已经被弃用并且在CLDR v29中不再填充。所以JDK似乎在使用旧数据。也可以参考此ICU票据 - Meno Hochschild

0
虽然我同意@MenoHochschild对问题的分析,但所呈现的解决方案似乎比必要的更复杂。在SimpleDateFormat上有一个名为applyLocalizedPattern的方法,我相信它将实现相同的结果。
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();

只要 localizedPatternlocale 相互匹配,这种技术就可以用于此用例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接