【问题标题】:Java's MessageFormat Not Localizing Portuguese Months in Dates in LowercaseJava 的 MessageFormat 没有将葡萄牙语月份本地化为小写日期
【发布时间】:2023-04-02 06:23:01
【问题描述】:

月份名称以大写字母而不是小写字母开头,as they should

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

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

这就是我得到的:

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!

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

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

我在这里误会了什么?

【问题讨论】:

  • 在我的 Java 9 上,我得到了你想要的结果:11 de julho de 2018,等等。在我的 Java 8 上,我得到了你不想要的大写月份。在 Java 8 上,如果您将系统属性 java.locale.providers 定义为 CLDR,COMPAT(或其他一些 CLDR 在 COMPAT 或 JRE 之前的组合),您可以获得所需的小写字母。
  • 在我看来你理解的一切都是正确的,但是那些为 Java 定义葡萄牙语月份名称的人误解了。
  • 这也适用于 Java 7 吗?
  • 来自the documentation of LocaleServiceProvider:“从JDK8开始,可以使用“java.locale.providers”系统属性配置区域敏感服务的搜索顺序。”所以显然不是。如果你想确定,你可以试试。
  • @DmitriV 在您使用的 Java 版本的问题上添加标签。

标签: java date localization messageformat


【解决方案1】:

tl;博士

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

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

运行此代码:

Month.JULY.getDisplayName( TextStyle.FULL , new Locale( "pt" ) )

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

朱罗

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

胡歌

在 Java 10 中,Oracle JDK 在设置 VM 选项 -Djava.locale.providers=COMPAT,SPI 以恢复到传统行为而不是使用 Unicode CLDR 后,我们得到 initial-cap。

朱罗

详情

  • 文化规范定义的格式规则可能因JVM 实现和版本而异。
  • IdeOne.com 中的JVM 拒绝本地化。只有美国英语。 See proof
  • 您使用了错误的类。 使用 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.

2018 年 7 月 11 日 17:57:36 新西兰标准时间

文化规范

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

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

Unicode CLDR

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

一个重要的区别是,从 Java 9 开始,对于 Oracle JDK 和 OpenJDK 项目,localization rules changed 的默认源使用Unicode CLDR(请参阅Wikipedia)。在 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

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

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

Month.JULY.getDisplayName(
    TextStyle.FULL , 
    new Locale( "pt" )
)

胡歌

Month.JULY.getDisplayName(
    TextStyle.FULL_STANDALONE , 
    new Locale( "pt" )
)

胡歌

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

java.time

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

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

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

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

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

ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

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

String output = zdt.toString() ;  // Generate ISO 8601 format string.

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

quarta-feira,2018 年 7 月 11 日 17:57:36 Horário Padrão da Nova Zelândia

quarta-feira,2018 年 7 月 11 日 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

2018 年 7 月 11 日 17:57:36 新西兰标准时间

2018 年 7 月 11 日 17:57:36 新西兰标准时间

11 juillet 2018 à 17:57:36 NZSTe

IdeOne 拒绝本地化

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


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

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

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

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore


时间简史

在这个答案上回复 cmets…

时间线:

  • Sun Microsystems 启动 Java 1.0,其中包含一些由 IBM/Taligent 捐赠的日期时间类(java.util.Date 等)。不幸的是,它们设计不佳且存在缺陷,令人困惑且麻烦。
  • Sun 在 Java 1.1 中添加了更多类(java.util.Calendar 等),以尝试改进日期时间处理。但这些也被证明设计不佳且令人困惑,而不是真正的改进。
  • 几年后,一个人 Stephen Colebourne 创立了 Joda-Time 项目,以创建一个综合复杂的日期时间处理库,这在 IT 行业中尚属首次。巨大的成功。最受欢迎的 Java 库之一,经常添加到许多开发人员的项目中。
  • Sun,然后是 Oracle,以及 JCP community 终于认识到,早期 Java 版本附带的旧日期时间类是不够的。这一认可为同一个 Stephen Colebourne 推出 JSR 310 开启了大门,这是一个全新的日期时间 API 规范,作为 Java 平台的一部分。这个项目基于 Joda-Time 的概念,但是是一个全新的重写。新的重写建立在创建 Joda-Time 的经验之上,询问“如果我们当时知道我们现在所知道的,我们将如何设计 Joda-Time?”。
  • JSR 310 的实现被构建为 java.time 包,将包含在即将推出的 Java 8 中。
  • 为确保更广泛地接受 java.time API,Stephen Colebourne 启动了开源 ThreeTen-Backport 项目,模仿了与 java.time 中几乎相同的 API但能够在 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 功能的扩展,作为库提供给那些认为它们有用的开发人员。

【讨论】:

  • 非常感谢您提供的所有信息。我们确实导入了 Joda time 2.2 JAR,但我尝试使用它并仍然得到相同的结果:Locale portugal = new Locale("pt");LocalDate now = LocalDate.now();DateTimeFormatter formatter = DateTimeFormat.longDate();System.out.println("Portugal v2: " + formatter.withLocale(portugal).print(now)); 结果:Portugal v2: 11 de Julho de 2018 这不应该工作,因为现在我正在使用java.time 类?
  • @DmitriV 仔细重新阅读我的答案的关于 java.time 部分。对于 Java 6 和 Java 7,将 Joda-Time 替换为 ThreeTen-Backport。API 与 Java 中内置的 java.time 类几乎相同8 及以后。当您最终迁移到 Java 8 或更高版本时,您只需更改 import 语句即可。
  • @DmitriV 我添加了一个“时间简史”部分,可以为您澄清一些事情。
  • @DmitriV 您在最后 2 个 cmets 中引用的输出有什么问题?对我来说看起来是正确的(不是我说葡萄牙语)。 Google says 英语中的 July 是葡萄牙语中的 Julho。您的问题仅仅是您声称葡萄牙语的文化规范是月份名称为小写吗?如果是这样,显然编辑Unicode CLDR 的人不同意你的观点。 Java 9 及更高版本 switched to using CLDR by default 用于本地化。
  • @DmitriV 这个大写-小写问题变得令人困惑但很有趣。我现在正在更新我的答案。我怀疑您和我所看到的差异是由于格式中使用的文化规范规则发生了变化。您是在 9 之前还是之后使用 Java?从 9 开始,默认使用 Unicode CLDR 规则,在 Oracle JDK 和 OpenJDK 项目中的列表中。我正在使用 Java 10,这是适用于 macOS 的 Oracle JDK 实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-15
  • 1970-01-01
相关资源
最近更新 更多