问题背景

我们在使用jenkins的过程中有没有发现 历史任务 那里的构建时间的格式 是不是怪怪的?

例如设置的晚上凌晨0点的 定时任务, 显示的时间会是 2020-10-22 上午12:00看着不习惯,感觉很怪。感觉显示的是12小时制的格式,但是又不太符合我们中国的风格?(这都是国外设计的,也怪不得我们.)

这里有个 issue 提问 , jenkins 简体中文插件中的, 参考这个 https://github.com/jenkinsci/localization-zh-cn-plugin/issues/79

由这个引发了以下的一些思考,特记录下来分享给大家.

问题1

这个日期时间格式在哪里控制的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
在这个文件中控制的 core/src/main/resources/hudson/widgets/HistoryWidget/entry.jelly
在48行左右: <i:formatDate value="${build.timestamp.time}" type="both" dateStyle="medium" timeStyle="medium" /> ${h.getUserTimeZonePostfix()}
jenkins 通过 这个 tag "<i:formatDate>" 来格式化日期的,
标签中有 datestyle 和 timestyle 来分别控制 日期 时间 显示的格式.
而且这个显示的格式还和本地浏览器语言设置有点关系的.

下面分别对 不同的 dateStyle 和 timeStyle 设置不同的值 做了对比:
dateStyle="medium" timeStyle="medium" will show 2020-8-19 17:27:01
dateStyle="medium" timeStyle="short" will show 2020-8-19 下午5:27
dateStyle="medium" timeStyle="long" will show 2020-8-19 下午05时27分01秒
dateStyle="long" timeStyle="long" will show 2020年8月19日 下午05时27分01秒
dateStyle="short" timeStyle="long" will show 20-8-19 下午05时27分01秒

火狐中文语言

在这里插入图片描述

火狐英式英语

在这里插入图片描述

火狐美式英语

在这里插入图片描述

1
2
3
通过设置浏览器不同的语言环境 得到的日期时间格式是不一样的。
之所以这么做,而不是统一的设置日期时间 格式为一个固定的 也是考虑到了本地化的 需求. 常见的本地化就是语言字符串翻译的.

问题2

这个 formatDate 来自哪里呢

1
2
3
4
5
6
7
8
9
10
11
<i:formatDate> 这个标签是 在jelly文件中的, jenkins的页面设计用的比较多的 都是 jelly 文件, 
这个很类似JSP页面, 里面有各种标签可以使用, 循环的, 判断的,变量赋值的等等.
也类似我们常常听说的 EL 表达式 等等.

这个 标签 对应的java 实现又在哪里呢? 哪里可以找到它的源码呢?
这个 <i:formatDate> 是 commons-jelly 提供的. 参考 https://github.com/apache/commons-jelly

源码见 https://github.com/apache/commons-jelly/blob/master/jelly-tags/fmt/src/main/java/org/apache/commons/jelly/tags/fmt/FormatDateTag.java



问题3

这个设置的timeStyle为short为什么显示那样的格式呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
下面我们来解决这个问题, 我们从代码来分析.
为什么
dateStyle="medium" timeStyle="medium" will show 2020-8-19 17:27:01
dateStyle="medium" timeStyle="short" will show 2020-8-19 下午5:27
medium 和 short 显示的是那样的格式, 而不是这样的格式.....
short 字面意思是短,感觉这里应该是想显示个短格式的时间的,那你把秒去掉不就得了,干嘛显示个中文 何来 短 呢? 感觉更长了呢...


总之我们最初的 问题是 哪个short 格式的为啥 显示的那么怪呢?下面我们看代码.
直接看 https://github.com/apache/commons-jelly/blob/master/jelly-tags/fmt/src/main/java/org/apache/commons/jelly/tags/fmt/FormatDateTag.java 中的代码.

主入口 应该在 public void doTag(XMLOutput output) throws JellyTagException { 这个方法上.

分析发现 在哪里做格式化日期时间呢? 在 152 行 DateFormat formatter = createFormatter(locale);

下面我们看 createFormatter 方法, 其中的locale 参数取值就是和浏览器语言对应的. 中文简体 zh_CN 这个. 中文台湾 zh_TW, 中文香港 zh_HK
private DateFormat createFormatter(Locale loc) throws JellyTagException {
DateFormat formatter = null;

if ((etype == null) || DATE.equalsIgnoreCase(etype)) {
formatter = DateFormat.getDateInstance(
getStyle(edateStyle, "FORMAT_DATE_INVALID_DATE_STYLE"),
loc);
} else if (TIME.equalsIgnoreCase(etype)) {
formatter = DateFormat.getTimeInstance(
getStyle(etimeStyle, "FORMAT_DATE_INVALID_TIME_STYLE"),
loc);
} else if (DATETIME.equalsIgnoreCase(etype)) {
formatter = DateFormat.getDateTimeInstance(
getStyle(edateStyle, "FORMAT_DATE_INVALID_DATE_STYLE"),
getStyle(etimeStyle, "FORMAT_DATE_INVALID_TIME_STYLE"),
loc);
} else {
throw new JellyTagException("Date format invalue");
}

return formatter;
}
这里if else 什么的 分了 3 类, 只 格式 date的, 只格式 time的, 格式date和time的. 我们这里看 date和time的.
formatter = DateFormat.getDateTimeInstance(
getStyle(edateStyle, "FORMAT_DATE_INVALID_DATE_STYLE"),
getStyle(etimeStyle, "FORMAT_DATE_INVALID_TIME_STYLE"),
loc);
这里 getStyle() 把 标签 <i:formatDate>中设置的 dateStyle 和 timeStyle 做个转换, 由字符串类型 转换成 对应的 DateFormat.SHORT, 其实是个整数.
其实 dateStyle="medium" timeStyle="short" 就会转换成 DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, locale)

其实 代码 走到这里 这个 格式 已经 和 这个 commons-jelly 没关系了, 对应的 转到 JDK 中的 代码了.

下面我们先做几组实验, 测试测试 medium, short 等等格式在不同语言下面的效果.

medium + short 格式的 格式化之后 medium + medium 格式的 格式化之后
Thu Oct 22 10:56:52 CST 2020 Thu Oct 22 10:55:15 CST 2020
medium_short_zh 2020-10-22 上午10:56 medium_medium_zh 2020-10-22 10:55:15
medium_short_zh_CN 2020-10-22 上午10:56 medium_medium_zh_CN 2020-10-22 10:55:15
medium_short_zh_TW 2020/10/22 上午 10:56 medium_medium_zh_TW 2020/10/22 上午 10:55:15
medium_short_zh_HK 2020年10月22日 上午10:56 medium_medium_zh_HK 2020年10月22日 上午10:55:15
medium_short_ja 2020/10/22 10:56 medium_medium_ja 2020/10/22 10:55:15
medium_short_ja_JP 2020/10/22 10:56 medium_medium_ja_JP 2020/10/22 10:55:15
medium_short_fr 22 oct. 2020 10:56 medium_medium_fr 22 oct. 2020 10:55:15
medium_short_fr_FR 22 oct. 2020 10:56 medium_medium_fr_FR 22 oct. 2020 10:55:15
medium_short_fr_CA 2020-10-22 10:56 medium_medium_fr_CA 2020-10-22 10:55:15
medium_short_de 22.10.2020 10:56 medium_medium_de 22.10.2020 10:55:15
medium_short_de_DE 22.10.2020 10:56 medium_medium_de_DE 22.10.2020 10:55:15
medium_short_it 22-ott-2020 10.56 medium_medium_it 22-ott-2020 10.55.15
medium_short_it_IT 22-ott-2020 10.56 medium_medium_it_IT 22-ott-2020 10.55.15
medium_short_ko 2020. 10. 22 오전 10:56 medium_medium_ko 2020. 10. 22 오전 10:55:15
medium_short_ko_KR 2020. 10. 22 오전 10:56 medium_medium_ko_KR 2020. 10. 22 오전 10:55:15
medium_short_en Oct 22, 2020 10:56 AM medium_medium_en Oct 22, 2020 10:55:15 AM
medium_short_en_GB 22-Oct-2020 10:56 medium_medium_en_GB 22-Oct-2020 10:55:15
medium_short_en_US Oct 22, 2020 10:56 AM medium_medium_en_US Oct 22, 2020 10:55:15 AM
medium_short_en_CA 22-Oct-2020 10:56 AM medium_medium_en_CA 22-Oct-2020 10:55:15 AM

下面我们再来debug,一步一步的揭开这层神秘面纱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   @Test
public void test () {
Calendar calendar = Calendar.getInstance();
Date calendarTime = calendar.getTime();
DateFormat medium_short_zh = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.CHINESE);
System.out.println( "|medium_short_zh| " + medium_short_zh.format(calendarTime) + "|");

}
设置断点, 我们可以查看到 medium_short_zh 内容.
medium_short_zh = {SimpleDateFormat@803}
serialVersionOnStream = 1
pattern = "yyyy-M-d ah:mm"
originalNumberFormat = null
从这里可以看到格式的那个样式了, pattern = "yyyy-M-d ah:mm"了. 剩下的就是追踪 这个值是怎么设置上去的, 怎么初始化的 ?
1
2
3
4
5
6
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.CHINESE);  
进入到
public final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)
{
return get(timeStyle, dateStyle, 3, aLocale); // 注意此时的 timeStyle=3, dateStyle=2, aLocale=zh
}
1
2
3
4
进入到 
private static DateFormat get(int timeStyle, int dateStyle,int flags, Locale loc) {// 注意此时的 timeStyle=3, dateStyle=2, flags=3, aLocale=zh
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DateFormatProvider.class, loc);
DateFormat dateFormat = get(adapter, timeStyle, dateStyle, loc); // 大概走到这里,adapter (slot_4) = {JRELocaleProviderAdapter@808}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
进入到  java/text/DateFormat.java文件中的
private static DateFormat get(LocaleProviderAdapter adapter, int timeStyle, int dateStyle, Locale loc) {
// adapter 是一个 JRELocaleProviderAdapter 类型的.
DateFormatProvider provider = adapter.getDateFormatProvider();
这里的 provoider是 provider (slot_4) = {DateFormatProviderImpl@813}
type = {LocaleProviderAdapter$Type@821} "JRE"
langtags = {HashSet@822} size = 137

if (timeStyle == -1) { // 几个if 判断来决定获取那种类型的 DateFormat
dateFormat = provider.getDateInstance(dateStyle, loc);
} else {
if (dateStyle == -1) { // 几个if 判断来决定获取那种类型的 DateFormat
dateFormat = provider.getTimeInstance(timeStyle, loc);
} else { // 几个if 判断来决定获取那种类型的 DateFormat
dateFormat = provider.getDateTimeInstance(dateStyle, timeStyle, loc);// 我们的格式是有日期和时间的,所以进入到这里,
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
进入到  /sun/util/locale/provider/DateFormatProviderImpl.java 参考了 https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/util/locale/provider/DateFormatProviderImpl.java 源码
@Override
public DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) {
return getInstance(dateStyle, timeStyle, locale);
}

进入到 /sun/util/locale/provider/DateFormatProviderImpl.java
private DateFormat getInstance(int dateStyle, int timeStyle, Locale locale) {
if (locale == null) {
throw new NullPointerException();
}

SimpleDateFormat sdf = new SimpleDateFormat("", locale);
Calendar cal = sdf.getCalendar();
try {
String pattern = LocaleProviderAdapter.forType(type)
.getLocaleResources(locale).getDateTimePattern(timeStyle, dateStyle,
cal);
sdf.applyPattern(pattern);
} catch (MissingResourceException mre) {
// Specify the fallback pattern
sdf.applyPattern("M/d/yy h:mm a");
}

return sdf;
}
主要看try catch 那里, 最后返回的是一个SimpleDateFormat 的实例. 主要关心的就是 pattern 的 值.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
主要看这里
String pattern = LocaleProviderAdapter.forType(type).getLocaleResources(rg).getDateTimePattern(timeStyle, dateStyle, cal);
timeStyle, dateStyle 这2个值还是最开始的.
这里的type的值:
type = {LocaleProviderAdapter$Type@821} "JRE"
UTIL_RESOURCES_PACKAGE = "sun.util.resources"
TEXT_RESOURCES_PACKAGE = "sun.text.resources"
name = "JRE"
ordinal = 0
这里的rg就是locale
rg = {Locale@798} "zh"
baseLocale = {BaseLocale@832} "language=zh"
localeExtensions = null
hashCodeValue = 115767826
languageTag = "zh"
这里的cal是通过SimpleDateFormat 的getCalendar() 得到的. 一个 java.util.GregorianCalendar 类型实例. 表示 公历

LocaleProviderAdapter.forType(type) 能获取到 JRELocaleProviderAdapter 的一个实例. 这里type 是 JRE.
result = {JRELocaleProviderAdapter@808}
langtagSets = {ConcurrentHashMap@810} size = 4
localeResourcesMap = {ConcurrentHashMap@811} size = 3
localeData = {LocaleData@812}
breakIteratorProvider = null
collatorProvider = null
dateFormatProvider = {DateFormatProviderImpl@813}
dateFormatSymbolsProvider = {DateFormatSymbolsProviderImpl@868}
decimalFormatSymbolsProvider = {DecimalFormatSymbolsProviderImpl@814}
numberFormatProvider = {NumberFormatProviderImpl@869}
currencyNameProvider = {CurrencyNameProviderImpl@815}
localeNameProvider = null
timeZoneNameProvider = {TimeZoneNameProviderImpl@816}
calendarDataProvider = {CalendarDataProviderImpl@817}
calendarNameProvider = null
calendarProvider = {CalendarProviderImpl@818}

public static LocaleProviderAdapter forType(Type type) { //来自文件 sun/util/locale/provider/LocaleProviderAdapter.java
switch (type) {
case JRE:
return jreLocaleProviderAdapter;
case CLDR:
return cldrLocaleProviderAdapter;
case SPI:
return spiLocaleProviderAdapter;
case HOST:
return hostLocaleProviderAdapter;
case FALLBACK:
return fallbackLocaleProviderAdapter;
default:
throw new InternalError("unknown locale data adapter type");
}
}
通过 forType() 方法我们知道 还有其他几个类型的 adapter,适配器, JRE的 CLDR的, SPI 的, HOST的,

LocaleProviderAdapter.forType(type).getLocaleResources(locale) 能获取 LocaleResources 实例.
result = {LocaleResources@872}
locale = {Locale@798} "zh"
localeData = {LocaleData@812}
type = {LocaleProviderAdapter$Type@821} "JRE"
cache = {ConcurrentHashMap@875} size = 4
referenceQueue = {ReferenceQueue@876}

@Override
public LocaleResources getLocaleResources(Locale locale) { // 来自文件 sun/util/locale/provider/JRELocaleProviderAdapter.java
LocaleResources lr = localeResourcesMap.get(locale);
if (lr == null) {
lr = new LocaleResources(this, locale);
LocaleResources lrc = localeResourcesMap.putIfAbsent(locale, lr);
if (lrc != null) {
lr = lrc;
}
}
return lr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
进入到 sun/util/locale/provider/LocaleResources.java 文件的 getDateTimePattern(int timeStyle, int dateStyle, Calendar cal) 方法中
public String getDateTimePattern(int timeStyle, int dateStyle, Calendar cal) {
if (cal == null) {
cal = Calendar.getInstance(locale);
}
return getDateTimePattern(null, timeStyle, dateStyle, cal.getCalendarType());
}
进入到 getDateTimePattern(String prefix, int timeStyle, int dateStyle, String calType) 方法中,
private String getDateTimePattern(String prefix, int timeStyle, int dateStyle, String calType) { // 其中 calType是 gregory, 表示公历, 当然还有其他日历的实现,

进入到 getDateTimePattern(String prefix, String key, int styleIndex, String calendarType) 方法中, 这个方法和上面的 方法 参数个数一样, 类型不一样了.
private String getDateTimePattern(String prefix, String key, int styleIndex, String calendarType)

ResourceBundle r = (prefix != null) ? getJavaTimeFormatData() : localeData.getDateFormatData(locale);
1
2
3
4
进入到 sun\util\resources\LocaleData.class 
public ResourceBundle getDateFormatData(Locale var1) {
return getBundle(this.type.getTextResourcesPackage() + ".FormatData", var1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
进入到  java/util/ResourceBundle.java
private static ResourceBundle getBundleImpl(String baseName, Locale locale, ClassLoader loader, Control control)
if (locale == null || control == null) {
throw new NullPointerException();
}

// We create a CacheKey here for use by this call. The base
// name and loader will never change during the bundle loading
// process. We have to make sure that the locale is set before
// using it as a cache key.
CacheKey cacheKey = new CacheKey(baseName, locale, loader);
ResourceBundle bundle = null;

// Quick lookup of the cache.
BundleReference bundleRef = cacheList.get(cacheKey);
if (bundleRef != null) {
bundle = bundleRef.get();
bundleRef = null;
}
cacheKey是 CacheKey[sun.text.resources.FormatData, lc=zh, ldr=java.util.ResourceBundle$RBClassLoader@66048bfd(format=null)]
通过 cacheList.get(cacheKey); 就能得到
result = {ResourceBundle$BundleReference@1149}
cacheKey = {ResourceBundle$CacheKey@1148} "CacheKey[sun.text.resources.FormatData, lc=zh, ldr=java.util.ResourceBundle$RBClassLoader@66048bfd(format=java.class)]"
timestamp = 67899702
referent = {FormatData_zh@1172}
lookup = {ConcurrentHashMap@1397} size = 21
"MonthNames" -> {String[13]@1425}
"standalone.DayNarrows" -> {String[7]@1427}
"standalone.DayAbbreviations" -> {String[7]@1429}
"AmPmMarkers" -> {String[2]@1430}
"TimePatterns" -> {String[4]@1431}
key = "TimePatterns"
value = {String[4]@1431}
0 = "ahh'时'mm'分'ss'秒' z"
1 = "ahh'时'mm'分'ss'秒'"
2 = "H:mm:ss"
3 = "ah:mm"
"DatePatterns" -> {String[4]@1432}
key = "DatePatterns"
value = {String[4]@1432}
0 = "yyyy'年'M'月'd'日' EEEE"
1 = "yyyy'年'M'月'd'日'"
2 = "yyyy-M-d"
3 = "yy-M-d"
其中的 TimePatterns 对应的那个字符串数组 就是我 要的结果了. 其中的 第三个 3 = "ah:mm" 就是我们 short 格式出来的样子了.
其中cacheList是一个map, 静态的成员变量, 关键就是看怎么往这个map中存数据了.
private static final ConcurrentMap<CacheKey, BundleReference> cacheList
= new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
到了 这里我们好像快揭开这层面纱了, 已经看到了 "ah:mm" 这样的模式了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class ResourceBundle 这个是个抽象类, 发现他有很多子类.
sun.text.resources.cldr.zh.FormatData_zh
sun.text.resources.zh.FormatData_zh

在文件 https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/text/resources/zh/FormatData_zh.java
public class FormatData_zh extends ParallelListResourceBundle {
public FormatData_zh() {
}

protected final Object[][] getContents() {
String[] rocEras = new String[]{"民国前", "民国"};
return new Object[][]
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
我们选取几个 语言下面的,TimePatterns 和 DatePatterns的值比较看看. 这2个值就是格式日期时间的.
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/text/resources/zh/FormatData_zh.java
{ "TimePatterns",
new String[] {
"ahh'\u65f6'mm'\u5206'ss'\u79d2' z", // full time pattern
"ahh'\u65f6'mm'\u5206'ss'\u79d2'", // long time pattern
"H:mm:ss", // medium time pattern
"ah:mm", // short time pattern
}
},
{ "DatePatterns",
new String[] {
"yyyy'\u5e74'M'\u6708'd'\u65e5' EEEE", // full date pattern
"yyyy'\u5e74'M'\u6708'd'\u65e5'", // long date pattern
"yyyy-M-d", // medium date pattern
"yy-M-d", // short date pattern
}
},
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/text/resources/zh/FormatData_zh_HK.java
{ "TimePatterns",
new String[] {
"ahh'\u6642'mm'\u5206'ss'\u79d2' z", // full time pattern
"ahh'\u6642'mm'\u5206'ss'\u79d2'", // long time pattern
"ahh:mm:ss", // medium time pattern
"ah:mm", // short time pattern
}
},
{ "DatePatterns",
new String[] {
"yyyy'\u5e74'MM'\u6708'dd'\u65e5' EEEE", // full date pattern
"yyyy'\u5e74'MM'\u6708'dd'\u65e5' EEEE", // long date pattern
"yyyy'\u5e74'M'\u6708'd'\u65e5'", // medium date pattern
"yy'\u5e74'M'\u6708'd'\u65e5'", // short date pattern
}
},

https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/text/resources/zh/FormatData_zh_TW.java
{ "TimePatterns",
new String[] {
"ahh'\u6642'mm'\u5206'ss'\u79d2' z", // full time pattern
"ahh'\u6642'mm'\u5206'ss'\u79d2'", // long time pattern
"a hh:mm:ss", // medium time pattern
"a h:mm", // short time pattern
}
},
{ "DatePatterns",
new String[] {
"yyyy'\u5e74'M'\u6708'd'\u65e5' EEEE", // full date pattern
"yyyy'\u5e74'M'\u6708'd'\u65e5'", // long date pattern
"yyyy/M/d", // medium date pattern
"yyyy/M/d", // short date pattern
}
},


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
再选取其他国家语言的
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/text/resources/ja/FormatData_ja.java
{ "TimePatterns",
new String[] {
"H'\u6642'mm'\u5206'ss'\u79d2' z", // full time pattern
"H:mm:ss z", // long time pattern
"H:mm:ss", // medium time pattern
"H:mm", // short time pattern
}
},
{ "DatePatterns",
new String[] {
"yyyy'\u5e74'M'\u6708'd'\u65e5'", // full date pattern
"yyyy/MM/dd", // long date pattern
"yyyy/MM/dd", // medium date pattern
"yy/MM/dd", // short date pattern
}
},
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/text/resources/en/FormatData_en_GB.java
{ "TimePatterns",
new String[] {
"HH:mm:ss 'o''clock' z", // full time pattern
"HH:mm:ss z", // long time pattern
"HH:mm:ss", // medium time pattern
"HH:mm", // short time pattern
}
},
{ "DatePatterns",
new String[] {
"EEEE, d MMMM yyyy", // full date pattern
"dd MMMM yyyy", // long date pattern
"dd-MMM-yyyy", // medium date pattern
"dd/MM/yy", // short date pattern
}
},

1
2
3
4
5
6
7
以上是对这个日期格式化的 分析, 最后基本上就定位到了 jdk中的源码部分了, 参考 
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/text/resources/zh/FormatData_zh_TW.java 这里,
从中查看git log 提交历史, 发现有个CLDR 这个, 这个CLDR 又是什么呢? 然后问题又来了, 为什么jdk 中要选择这么恶心的 12 小时制呢? 有没有一个我们国家的标准呢?


CLDR 是 http://cldr.unicode.org/

遗留问题

通过继续网上搜索发现 知乎上有个 关键CLDR的问题.

为什么CLDR 提供的中文本地化格式中时间坚持使用十二小时制而非二十四小时制?
https://www.zhihu.com/question/67699402/answer/402268304

这里回答虽然说明了一些, 但是 让人还是不能理解的,

1

CLDR上也有一个问题追踪单.

1
2
3
4
https://unicode-org.atlassian.net/browse/CLDR-10560

For Chinese (zh), use 12-hour formats with day periods

jdk上也有人对这个提出疑问, 但是最后都不了了之了,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
https://bugs.openjdk.java.net/browse/JDK-7087304

[zh_CN]DateFormat: Incorrect hour representation in standard time format pattern
Description
Java: 5, 6, 7 all have this problem. It's not a regression.

In S. Chinese(zh_CN), When the time of the date is at 0:00, the midnight of a day, and using DateFormat to format the date object, the output woulde be
ShangWu 12:00, rather than 00:00. This is very misleading in Chinese, because in China, there is no covention that localized 12:00 am means 0:00 at midnight.

In FormatData_zh_CN.java, ah is used in DateTimePattern. This follows the standard time pattern in CLDR. I check the national standard for date and time in China, GB/T 7408-2005. There is no hour based on 1 to 12. hh means 00 to 24, not 1 to 12 or 0 to 11. So, in zh_CN, HH should be used to replace ah in standard time pattern. This change also fits the national standard.

I also reported the defect to unicode.org via http://www.unicode.org/reporting.html.

Attached is the source code of the program that can reproduce the problem in zh_CN, zh_TW, and ko. I am sure that it's a defect in zh_CN because I am the native speaker and check the national standard published by the government, but for zh_TW and ko, I am not sure.

The bug report I submitted to unicode is also attached.
Confirmed with language specialists that this problem is zh_CN only. Current time format is OK in zh_TW and ko.


Activity
All
Comments
Work Log
History
Activity
Ascending order - Click to sort in descending order
Permalink
yhuang
Yong Huang (Inactive) added a comment - 2011-09-06 00:08
BT2:EVALUATION

To fix the problem, FormatData_zh_CN.java needs to be updated. ah should be replace by HH.

I need to further confirm if it's a defect in zh_TW and ko.
Permalink
yhuang
Yong Huang (Inactive) added a comment - 2011-11-22 18:25
BT2:EVALUATION

Set the status to imcomplete, because current implementation in Java is in accordance with CLDR.

The resource file will not be modified until any change is published in CLDR.
Permalink
yhuang
Yong Huang (Inactive) added a comment - 2011-11-23 17:55
BT2:EVALUATION

Will wait until http://unicode.org/cldr/trac/ticket/4108 is solved in CLDR.
Permalink
yhuang
Yong Huang (Inactive) added a comment - 2013-01-06 19:21
According to the CLDR ticket, this will not be fixed in CLDR:


"I talked to three of our Chinese speaking engineers without getting any consensus that this was an actual problem. It seemed to be converging more on being a user preference than a real issue.

This pattern is also all over the zh patterns. Given this, I won't kick it back, but will defer it."

Close the issue as won't fix.