提问人:Spencer Shellman 提问时间:10/4/2023 最后编辑:Arun SudhakaranSpencer Shellman 更新时间:11/22/2023 访问量:238
DateFormat.getDateTimeInstance().format(new Date()) 的 Java 21 问题
Java 21 problem with DateFormat.getDateTimeInstance().format(new Date())
问:
这是我的代码
import java.util.Date;
import java.text.DateFormat;
class DateTime {
public static void main(String[] args) {
String dt = DateFormat.getDateTimeInstance().format(new Date());
System.out.println(dt);
}
}
当使用 Java 21 编译和执行时,对 'format()' 的调用将返回一个包含无效字节的字符串,用问号表示:UTF-16
2023年10月3日, 7:01:17?下午
有没有人看到这个问题?谢谢。
答:
JDK 20 中进行了更改,从 Unicode Common Locale Data Repository 升级到 CLDR 数据版本 42,该版本更改为不间断空格 (nbsp),又名 NARROW NO-BREAK SPACE。
Bug 8304925 已提交,但列出的解决方法包括:习惯它,要求 Unicode 恢复更改(不太可能),或者
通过在启动器命令行中指定 -Djava.locale.providers=COMPAT 来使用旧版区域设置数据。(不过,此选项限制了一些较新的功能。
新功能,不是错误
大卫·康拉德(David Conrad)的答案是正确的。您看到的是一个新功能,而不是一个错误。
CLDR 的新版本
Unicode 联盟的通用区域设置数据存储库 (CLDR) 中定义的本地化规则在不断发展。现代 Java 依赖于 CLDR 作为其本地化规则的主要来源。因此,新版本的 CLDR 在 Java 中带来了新的行为。
这就是现实世界中的生活。永远不要固化你对本地化价值观的期望。这些本地化可能会在 CLDR、Java 和人类区域性的未来版本中更改。
如果本地化行为对代码中的某些逻辑至关重要,请编写单元测试来验证该行为。
检测 NNBSP 字符
我们可以验证 Conrad 的说法,即您确实看到了 .让我们检查一下输出中的每个字符。U+202F NARROW NO-BREAK SPACE (NNBSP)
我们可以检查每个字符,以获得由Unicode联盟分配的编号,即其代码点。我们的 NNBSP 字符的码位为 8,239 十进制,202F 十六进制。
String dt = DateFormat.getDateTimeInstance ( ).format ( new Date ( ) );
System.out.println ( dt );
String codePoints = dt.codePoints ( ).boxed ( ).toList ( ).toString ( );
System.out.println ( "codePoints = " + codePoints );
运行时:
Oct 3, 2023, 6:02:35 PM
codePoints = [79, 99, 116, 32, 51, 44, 32, 50, 48, 50, 51, 44, 32, 54, 58, 48, 50, 58, 51, 53, 8239, 80, 77]
果然,我们看到我们的 NNBSP 是倒数第三位,在 和 之前。8239
P
M
改变是好的
我想对 CLDR 中的这一变化添加一个注释:这个变化是一个很好的变化,而且是有道理的。在逻辑排版思维中,一天中时间的 / 永远不应该与小时-分钟-秒分开。将 AM/PM 换行在另一行上会使读取变得笨拙。使用不间断空格而不是普通的断开空格是有意义的。“瘦”是我会留给排版专家的判断,但我收集也是有道理的。AM
PM
解决方案:修复主机
出现替换字符问题的直接解决方案是👉🏾更改控制台应用的字符编码。您使用的任何控制台应用程序(您在问题中忽略了这一点)显然都配置为一些古老的字符编码,而不是精通 Unicode 的现代字符编码,例如 UTF-8。?
更改控制台应用的字符编码。比你的错误应该以真实的性格出现,一个薄薄的不间断空间。?
避免使用旧版日期时间类
您正在使用有严重缺陷的日期时间类,这些类在几年前被 JSR 310 中定义的现代 java.time 所取代。应避免使用传统的日期时间类,而应使用 java.time 进行日期时间工作。
您选择的旧课程不是问题特定问题的一个因素。但仅供参考,让我向您展示代码的现代版本。
对象表示以 UTC 为单位的某个时刻,即与 UTC 的偏移量为 0 小时-分钟-秒。您可以将该时刻调整为时区,从而获得 .时间轴上的同一点,但挂钟时间/日历不同。Instant
ZonedDateTime
Instant instant = Instant.now ( ); // `java.util.Date` was years ago replaced by `java.time.Instant`.
ZoneId z = ZoneId.of ( "Asia/Tokyo" ); // Or, `ZoneId.systemDefault`.
ZonedDateTime zdt = instant.atZone ( z );
Locale locale = Locale.US;
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime ( FormatStyle.MEDIUM ).withLocale ( locale );
String output = zdt.format ( f );
System.out.println ( "output = " + output );
System.out.println ( output.codePoints ( ).boxed ( ).toList ( ).toString ( ) );
运行时。
output = Oct 4, 2023, 10:21:32 AM
[79, 99, 116, 32, 52, 44, 32, 50, 48, 50, 51, 44, 32, 49, 48, 58, 50, 49, 58, 51, 50, 8239, 65, 77]
我们在 和 之前看到相同的内容。8239
A
M
我们可以通过其官方 Unicode 名称来检查这些字符。
output.codePoints ( ).mapToObj ( Character :: getName ).forEach ( System.out :: println );
运行时:
LATIN CAPITAL LETTER O
LATIN SMALL LETTER C
LATIN SMALL LETTER T
SPACE
DIGIT FIVE
COMMA
SPACE
DIGIT TWO
DIGIT ZERO
DIGIT TWO
DIGIT THREE
COMMA
SPACE
DIGIT ONE
DIGIT ZERO
COLON
DIGIT ZERO
DIGIT TWO
COLON
DIGIT TWO
DIGIT SIX
NARROW NO-BREAK SPACE
LATIN CAPITAL LETTER A
LATIN CAPITAL LETTER M
请注意倒数第三个。NARROW NO-BREAK SPACE
我们可以用十六进制而不是十进制的代码点来检查字符。
output.codePoints ( ).mapToObj ( ( int codePoint ) -> String.format ( "U+%04X" , codePoint ) ).forEach ( System.out :: println );
运行时:
U+004F
U+0063
U+0074
U+0020
U+0035
U+002C
U+0020
U+0032
U+0030
U+0032
U+0033
U+002C
U+0020
U+0031
U+0030
U+003A
U+0030
U+0035
U+003A
U+0031
U+0037
U+202F
U+0041
U+004D
请注意倒数第三个。U+202F
对于Unicode极客
对于像我这样的Unicode极客来说,这个话题被证明是一个有趣的蠕虫。
Unicode Consortium 文档的第 1 节 Proposal to synchronize the Core Specification 解释说,字符被错误地描述为 的窄版本。这意味着维基百科上不间断空格页面的宽度变化部分不正确。该Unicode文档认为NNBSP实际上是一个不间断的版本。U+202F NARROW NO-BREAK SPACE (NNBSP)
U+00A0 NO-BREAK SPACE
U+2009 THIN SPACE
该文件中另一个有趣的说明是,NNBSP字符主要有两个目的。我引用(我的项目符号):
- NNBSP 可用于表示法语排版中标点符号周围出现的狭窄空间,这被称为“espace fine inécable”。
- 特别是在蒙古语文本中,在某些语法后缀之前,它提供了一个小间隙,不仅可以防止断词和换行,还可以触发这些后缀的特殊形状。
显然,我们现在可以在此用途中添加第三个主要用途:以 CLDR 定义的日期时间格式进行格式化。
评论
dt.codePoints().mapToObj(Character::getName).toList()
dt.codePoints().mapToObj(cp -> String.format("U+%04X", cp)).toList()
output.codePoints().forEach(codePoint -> System.out.printf( "%s (U+%04X)%n" , Character.getName(codePoint), codePoint));
评论
java.time.*
API 而不是较旧的 APIDate
Calendar
?
System.out.prinln
Terminal.app