Java 的 MessageFormat 没有本地化小写日期中的葡萄牙语月份

Dmi*_*i V 2 java localization date messageformat

月份名称以大写字母而不是小写字母开头,正如它们应该的那样

我在本地机器上运行的一些示例代码:

  Locale portugal = new Locale("pt");
  Locale brazil = new Locale("pt", "BR");
  Locale france = new Locale("fr", "FR");

  Object[] params = new Object[] { new Date() };
  String pattern = "Today is {0,date,long}!";

  MessageFormat ptFormat = new MessageFormat(pattern, portugal);
  MessageFormat brFormat = new MessageFormat(pattern, brazil);
  MessageFormat frFormat = new MessageFormat(pattern, france);

  StringBuffer ptResult = ptFormat.format(params, new StringBuffer(), new FieldPosition(0));
  StringBuffer brResult = brFormat.format(params, new StringBuffer(), new FieldPosition(0));
  StringBuffer frResult = frFormat.format(params, new StringBuffer(), null);

  System.out.println("Portugal result: " + ptResult.toString());
  System.out.println("Brazil result: " + brResult.toString());
  System.out.println("France result: " + frResult.toString());
Run Code Online (Sandbox Code Playgroud)

这就是我得到的:

Portugal result: Today is 10 de Julho de 2018!
Brazil result: Today is 10 de Julho de 2018!
France result: Today is 10 juillet 2018!
Run Code Online (Sandbox Code Playgroud)

所以法语是正确的,但由于某种原因,两个葡萄牙语变体不是。

更奇怪的是,我尝试添加与IDEAONE 片段完全相同的代码,但它根本无法本地化。

我在这里误解了什么?

Bas*_*que 5

tl;博士

  • Java 的不同实现的本地化规则可能有所不同。
  • 不同版本的 Java 实现的本地化规则可能有所不同。

对于 Oracle JDK 和 OpenJDK 项目,版本 9 及更高版本在其自己的规则集和Unicode CLDR定义的规则集之间切换(请参阅Wikipedia)。请参阅发行说明OpenJDK JEP 252

运行此代码:

Month.JULY.getDisplayName( TextStyle.FULL , new Locale( "pt" ) )
Run Code Online (Sandbox Code Playgroud)

在 Java 8 中,Oracle JDK 默认使用自己的本地化规则,我们得到了 initial-cap。

朱略

在 Java 10、Oracle JDK 中,默认情况下使用 Unicode CLDR规则,我们得到小写。

朱略

在 Java 10 中,Oracle JDK 在将 VM 选项设置-Djava.locale.providers=COMPAT,SPI为恢复到旧行为而不是使用 Unicode CLDR 后,我们得到了初始上限。

朱略

细节

  • 文化规范定义的格式规则可能因JVM实现和版本而异。
  • JVMIdeOne.com拒绝本地化。只有美国英语。见证明
  • 您使用了错误的类。使用java.time进行所有日期时间处理。

代码。

ZonedDateTime       // Use modern class for a moment seen from a particular time zone.
.now()              // Capture the current moment, using the JVM’s current default time zone. Better to specify a zone explicitly.
.format(            // Generate a `String` representing the value of our `ZonedDateTime` object.
    DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL )
    .withLocale( new Locale( "pt" , "BR" ) ) 
)                   // Returns a `String` object.
Run Code Online (Sandbox Code Playgroud)

11 de julho de 2018 17:57:36 NZST

文化规范

决定月份名称大写等问题取决于文化规范。当然,这些规范可能会有所不同,理性的人可能会不同意。

但在某些时候,必须做出决定。Java 实现必须有一套规则来制定这些本地化规则。

统一码

也许您使用的是 Java 8 或更早版本。我在这个答案中的代码是用 Java 10 生成的。

一个重要的区别是,从 Java 9 开始,对于 Oracle JDK 和 OpenJDK 项目,本地化规则的默认来源已更改为使用Unicode CLDR(请参阅维基百科)。在 Java 8 及更早版本中,每个 JVM 都提供了自己的一组规则。

另请注意,Unicode CLDR 会不时更新,某些规则可能会更改。

因此,您很可能会看到不同的本地化结果,具体取决于所使用的 Java 版本以及所使用的 Java 实现。

演示的上下文

也许这里的问题是上下文之一。在某些文化中,格式规则(例如月份名称的大小写)因月份是单独显示还是嵌入在日期中而异。

让我们试试 translate.Google.com:

  • July 产量 Julho
  • Early in the month of July 产量 no início do mês de julho
  • 12th of July, 2018 产量 12 de julho de 2018

所以谷歌似乎因上下文而异,但我不确定第一种情况是初始上限是怎么回事。

该上下文可以通过使用方法中使用的TextStyle枚举来指示Month::getDisplayName。该枚举提供了…STANDALONE变化。

Month.JULY.getDisplayName(
    TextStyle.FULL , 
    new Locale( "pt" )
)
Run Code Online (Sandbox Code Playgroud)

朱略

Month.JULY.getDisplayName(
    TextStyle.FULL_STANDALONE , 
    new Locale( "pt" )
)
Run Code Online (Sandbox Code Playgroud)

朱略

所以不,在使用 Java 10 时,上下文似乎不是问题。

时间

您正在使用现在被java.time类取代的麻烦的旧类。

而不是java.util.Date,使用java.time.Instant。两者都代表 UTC 中的一个时刻。现代Instant类使用更精细的纳秒分辨率而不是毫秒。

Instant instant = Instant.now() ;  // Capture the current moment in UTC.
Run Code Online (Sandbox Code Playgroud)

Instant.toString(): 2018-07-11T05:57:36.446917Z

从 UTC 调整到该地区的人们使用您想要的特定挂钟时间的时区。应用 aZoneId来获取一个ZonedDateTime对象。时区与语言环境完全正交。一个与当前的内容有关,另一个仅影响用于生成字符串以表示该内容的本地化。例如,您可以使用带有葡萄牙语语言环境的日本时区。

ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
Run Code Online (Sandbox Code Playgroud)

现在,我们想要生成代表那个时刻的字符串。首先,通过在方括号中附加时区名称来生成String标准ISO 8601格式。

String output = zdt.toString() ;  // Generate ISO 8601 format string.
Run Code Online (Sandbox Code Playgroud)

2018-07-11T17:57:36.446917+12:00[太平洋/奥克兰]

您想要本地化生成的String对象。使用DateTimeFormatter控制的格式String产生。指定一个FormatStyle来控制多长时间或缩写。

Locale l_pt = new Locale( "pt" ) ;
Locale l_ptBR = new Locale( "pt" , "BR" ) ;
Locale l_FR = Locale.FRANCE ;

DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ) ;

String output_pt = zdt.format( f.withLocale( l_pt ) ) ;
String output_ptBR = zdt.format( f.withLocale( l_ptBR ) ) ;
String output_FR = zdt.format( f.withLocale( l_FR ) ) ;
Run Code Online (Sandbox Code Playgroud)

quarta-feira, 11 de julho de 2018 17:57:36 Horário Padrão da Nova Zelândia

quarta-feira, 11 de julho de 2018 17:57:36 Horário Padrão da Nova Zelândia

mercredi 11 juillet 2018 à 17:57:36 heure normale de la Nouvelle-Zélande

为了好玩,让我们尝试FormatStyle.LONG代替FULL.

11 de julho de 2018 17:57:36 NZST

11 de julho de 2018 17:57:36 NZST

11 juillet 2018 à 17:57:36 NZSTe

IdeOne 拒绝本地化

我喜欢使用 IdeOne.com 来演示 Java 代码。不幸的是,它的 JVM 拒绝使用Locale除美国英语之外的任何其他语言。所以,不要去上面的代码。


关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat

现在处于维护模式Joda-Time项目建议迁移到java.time类。

要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310

您可以直接与您的数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC 驱动程序。不需要字符串,不需要类。java.sql.*

从哪里获得 java.time 类?

ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多


时间简史

回应对此答案的评论……

时间线:

  • Sun Microsystems推出了Java 1.0,其中包含java.util.DateIBM/ Taligent捐赠的一些日期时间类(等)。不幸的是,它们设计不佳且有缺陷,令人困惑且麻烦。
  • Sun 在 Java 1.1(java.util.Calendar等)中添加了更多类以尝试改进日期时间处理。但是这些也被证明是设计不佳和混乱的,而不是真正的改进。
  • 多年后,Stephen Colebourne创立了Joda-Time项目,以制作一个综合复杂的日期时间处理库,这在 IT 行业尚属首次。巨大的成功。最流行的 Java 库之一,经常添加到许多开发人员的项目中。
  • Sun、Oracle 和JCP 社区终于认识到,最早版本的 Java 附带的旧日期时间类是不够的。这种认可为同一位Stephen Colebourne 推出JSR 310敞开了大门,这是一种用于包含在 Java 平台中的全新日期时间 API 的规范。该项目基于 Joda-Time 的概念,但进行了全新的重写。新的重写建立在创造 Joda-Time 的经验之上,询问“如果我们当时知道我们现在所知道的,我们将如何设计 Joda-Time?”。
  • JSR 310 的实现是作为java.time包构建的,将包含在当时即将推出的 Java 8 中。
  • 为了确保更广泛接受的java.time API,斯蒂芬Colebourne对推出开源ThreeTen -反向移植项目,模仿几乎相同的API在java.time但能够在Java 6和Java 7运行不太所有的java的.time功能,但大部分。目标是使使用早期 Java 的开发人员能够开始使用 API。然后,当迁移到 Java 的更高版本时,他们只需将import语句从更改org.threeten.bp.*java.time.*
  • 随着 Java 8 的成功发布,Stephen Colebourne 等人。将 Joda-Time 项目转移到维护模式,仍然积极更新tzdata更改和错误修复,但没有进一步的功能工作要做。他们建议从 Joda-Time 迁移到java.time类。
  • Stephen Colebourne 还启动了开源ThreeTen-Extra项目,以处理最终可能会或可能不会提交到更高版本的 JSR 310(成为额外的java.time类)的附加功能。同时,这些额外的特性,对java.time功能的扩展,作为一个库提供给公众,供那些发现它们有用的开发人员使用。