关键 log
I/ConsoleReporter: [1/1 armeabi-v7a CtsIcuTestCases HZC75C7N] android.icu.dev.test.format.DateTimeGeneratorTest#TestOptions fail: junit.framework.AssertionFailedError: Failure: TestOptions(android.icu.dev.test.format.DateTimeGeneratorTest), due to 6 error(s)
Error: (DateTimeGeneratorTest.java:1249) Locale da, skeleton Hmm, options ==0, expected pattern HH.mm, got HH:mm
Error: (DateTimeGeneratorTest.java:1249) Locale da, skeleton HHmm, options ==0, expected pattern HH.mm, got HH:mm
Error: (DateTimeGeneratorTest.java:1249) Locale da, skeleton hhmm, options ==0, expected pattern h.mm a, got h:mm a
Error: (DateTimeGeneratorTest.java:1249) Locale da, skeleton Hmm, options !=0, expected pattern H.mm, got H:mm
Error: (DateTimeGeneratorTest.java:1249) Locale da, skeleton HHmm, options !=0, expected pattern HH.mm, got HH:mm
Error: (DateTimeGeneratorTest.java:1249) Locale da, skeleton hhmm, options !=0, expected pattern hh.mm a, got hh:mm a
at android.icu.junit.IcuFrameworkTest.test_for_TestFmwk_Run(IcuFrameworkTest.java:117)
at android.icu.junit.IcuFrameworkTest.run(IcuFrameworkTest.java:63)
at android.icu.junit.IcuTestFmwkRunner$2.evaluate(IcuTestFmwkRunner.java:126)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at android.icu.junit.IcuTestFmwkRunner.runChild(IcuTestFmwkRunner.java:130)
at android.icu.junit.IcuTestFmwkRunner.runChild(IcuTestFmwkRunner.java:117)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
此问题首先分析 log 可以看到 DateTimeGeneratorTest 文件 1249 打印出来的报错信息,通过 openGrok 搜索一下,然后看1249
/alps/external/icu/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateTimeGeneratorTest.java
.....
.....
private final class TestOptionsItem {
public String locale;
public String skeleton;
public String expectedPattern;
public int options;
// Simple constructor
public TestOptionsItem(String loc, String skel, String expectedPat, int opts) {
locale = loc;
skeleton = skel;
expectedPattern = expectedPat;
options = opts;
}
}
public void TestOptions() {
final TestOptionsItem[] testOptionsData = {
new TestOptionsItem( "en", "Hmm", "HH:mm", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en", "HHmm", "HH:mm", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en", "hhmm", "h:mm a", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en", "Hmm", "HH:mm", DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH ),
new TestOptionsItem( "en", "HHmm", "HH:mm", DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH ),
new TestOptionsItem( "en", "hhmm", "hh:mm a", DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH ),
new TestOptionsItem( "da", "Hmm", "HH.mm", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "da", "HHmm", "HH.mm", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "da", "hhmm", "h.mm a", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "da", "Hmm", "H.mm", DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH ),
new TestOptionsItem( "da", "HHmm", "HH.mm", DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH ),
new TestOptionsItem( "da", "hhmm", "hh.mm a", DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH ),
//
new TestOptionsItem( "en", "yyyy", "yyyy", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en", "YYYY", "YYYY", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en", "U", "y", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=japanese", "yyyy", "y G", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=japanese", "YYYY", "Y G", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=japanese", "U", "y G", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=chinese", "yyyy", "r(U)", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=chinese", "YYYY", "Y(Y)", DateTimePatternGenerator.MATCH_NO_OPTIONS ), // not a good result, want r(Y) or r(U)
new TestOptionsItem( "en@calendar=chinese", "U", "r(U)", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=chinese", "Gy", "r(U)", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=chinese", "GU", "r(U)", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=chinese", "ULLL", "MMM U", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=chinese", "yMMM", "MMM r(U)", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "en@calendar=chinese", "GUMMM", "MMM r(U)", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "zh@calendar=chinese", "yyyy", "rU\u5E74", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "zh@calendar=chinese", "YYYY", "YY\u5E74", DateTimePatternGenerator.MATCH_NO_OPTIONS ), // not a good result, want r(Y) or r(U)
new TestOptionsItem( "zh@calendar=chinese", "U", "rU\u5E74", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "zh@calendar=chinese", "Gy", "rU\u5E74", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "zh@calendar=chinese", "GU", "rU\u5E74", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "zh@calendar=chinese", "ULLL", "U\u5E74MMM", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "zh@calendar=chinese", "yMMM", "rU\u5E74MMM", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
new TestOptionsItem( "zh@calendar=chinese", "GUMMM", "rU\u5E74MMM", DateTimePatternGenerator.MATCH_NO_OPTIONS ),
};
for (int i = 0; i < testOptionsData.length; ++i) {
// 这里可以看到用 locale 参数 创建了一个 ULocale, 这个东西首先不看的话 也能猜到 基本就是一个 Locale 一些参数配置的存放类
//关键类位置 alps/external/icu/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java
ULocale uloc = new ULocale(testOptionsData[i].locale);
// 根据 uloc 对象,去创建 Pattern, 这个就有点像我们的正则了吧,(这里就是重点了, 去看一下他是怎么创建这个匹配方式的)
DateTimePatternGenerator dtpgen = DateTimePatternGenerator.getInstance(uloc);
String pattern = dtpgen.getBestPattern(testOptionsData[i].skeleton, testOptionsData[i].options);
if (pattern.compareTo(testOptionsData[i].expectedPattern) != 0) {
//在这里报错了 输出了error的信息
errln("Locale " + testOptionsData[i].locale + ", skeleton " + testOptionsData[i].skeleton +
", options " + ((testOptionsData[i].options != 0)? "!=0": "==0") +
", expected pattern " + testOptionsData[i].expectedPattern + ", got " + pattern);
}
}
}
可以看到log 是 Locale da, skeleton Hmm, options ==0, expected pattern HH.mm, got HH:mm
所以是中间 定义的 local 为 ‘da’ 的项
继续看格式化正则那里的代码跟进去
/alps/external/icu/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java
.....
.....
public static DateTimePatternGenerator getInstance(ULocale uLocale) {
return getFrozenInstance(uLocale).cloneAsThawed();
}
@Deprecated
public static DateTimePatternGenerator getFrozenInstance(ULocale uLocale) {
String localeKey = uLocale.toString();
DateTimePatternGenerator result = DTPNG_CACHE.get(localeKey);
if (result != null) {
return result;
}
result = new DateTimePatternGenerator();
PatternInfo returnInfo = new PatternInfo();
String shortTimePattern = null;
// first load with the ICU patterns
for (int i = DateFormat.FULL; i <= DateFormat.SHORT; ++i) {
// 其他的都不看了就看这一句, 是调用 android 系统提供的方法 根据 区域,获得这个时间格式的 Format
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(i, uLocale);
// 然后 添加这个规则
result.addPattern(df.toPattern(), false, returnInfo);
df = (SimpleDateFormat) DateFormat.getTimeInstance(i, uLocale);
result.addPattern(df.toPattern(), false, returnInfo);
if (i == DateFormat.SHORT) {
// keep this pattern to populate other time field
// combination patterns by hackTimes later in this method.
shortTimePattern = df.toPattern();
// use hour style in SHORT time pattern as the default
// hour style for the locale
FormatParser fp = new FormatParser();
fp.set(shortTimePattern);
List<Object> items = fp.getItems();
for (int idx = 0; idx < items.size(); idx++) {
Object item = items.get(idx);
if (item instanceof VariableField) {
VariableField fld = (VariableField)item;
if (fld.getType() == HOUR) {
result.defaultHourFormatChar = fld.toString().charAt(0);
break;
}
}
}
}
.......
......
所以说, 就肯定是对语言格式或者什么的修改, 违背了android 不同国家下的默认规则 的原则
最后查看 git 记录,确实有一个修改,修改了一个国家下的时间显示格
修改了丹麦语, 中 时间的显示格式
http://www.icu-project.org/apiref/icu4c/classicu_1_1DateTimePatternGenerator.html
http://www.icu-project.org/apiref/icu4c/classes.html
几种修改方式
0. 修改时间格式 分隔符
在文件中修改
gregorian{
AmPmMarkers{
"AM",
"PM",
}
DateTimePatterns{
"h:mm:ss a zzzz", //变成 "h.mm.ss a zzzz"
"h:mm:ss a z", // "h.mm.ss a z",
"h:mm:ss a", // "h.mm.ss a",
"h:mm a",
1.:将中文环境下的日期格式修改为”yyyy年 M月 d日,EEE”(EEE是星期)
L:
打开文件Donottranslate-cldr.xml (frameworks\base\core\res\res\values-zh-rcn)
找到numeric_date_format这项,修改成下面这样:
<string name="numeric_date_format">yyyy年 M月 d日,EEE</string>
这样默认日期格式就变成“yyyy年 M月 d日,EEE”了。
kk:
对于KK版本,这个格式使用的是底层ICU的定义
如是英文的定义的如下
android L 路径 external\icu\icu4c\source\data\locales\en.txt
android KK 路径 external/icu4c/locales/en.txt
gregorian{
AmPmMarkers{
"AM",
"PM",
}
DateTimePatterns{
"h:mm:ss a zzzz",
"h:mm:ss a z",
"h:mm:ss a",
"h:mm a",
"EEEE, MMMM d, y",
"MMMM d, y",
"MMM d, y",
"m/d/yy" //修改此处,如改为d/y/MMM
"{1},{0}"
对于希伯来语、印尼语以及意地绪语上层使用的语言编码和ICU定义的不同,要改的文件分别是:
希伯来语:he.txt
印尼语:id.txt
意地绪语:yi.txt
丹麦语: da.txt
波斯语: fa.txt
2. 如果没有设置默认系统的时间显示格式(12小时制或者24小时制),系统切换不同语言显示时间格式是不同的,
波斯语是24小时制,中文是12小时制,这是在哪里控制的呢?
这是icu的时间格式觉定的“H”代表24小时制,“h”代表12小时制,如波斯语如下
android kk external\icu4c\data\locales\fa.txt
android L external\icu\icu4c\source\data\locales\fa.txt
gregorian{
....
DateTimePatterns{
"H:mm:ss (zzzz)",
"H:mm:ss (z)",
"H:mm:ss",
"H:mm",
"EEEE d MMMM y",
"d MMMM y",
"d MMM y",
"y/M/d",
...
....
}
如果改成12小时制的话,只需把上面红色部分改成下面就行
"H:mm:ss (zzzz)",
"h:mm:ss (z)",
"h:mm:ss",
"h:mm",
还有好多其他的修改,还要编译 icu资源文件的方式等等,这里只是分析这个cts问题。