Java's MessageFormat Not Localizing Portuguese Months in Dates in Lowercase

落爺英雄遲暮 提交于 2019-12-07 22:48:16

问题


The month names start with an uppercase letter instead of lowercase, as they should.

Some sample code I ran on my local machine:

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

And this is what I got:

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!

So the French is correct, but for some reason the two Portuguese variants are not.

Even weirder, I tried adding the exact same code as an IDEAONE snippet, and it wouldn't localize at all.

What am I misunderstanding here?


回答1:


tl;dr

  • Different implementations of Java may vary in their rules for localization.
  • Different versions of an implementation of Java may vary in their rules for localization.

For the Oracle JDK & OpenJDK projects, version 9 and later switched between its own set of rules and the set of rules defined by the Unicode CLDR (see Wikipedia). See Release Notes and see OpenJDK JEP 252.

Run this code:

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

In Java 8, Oracle JDK, by default using its own localization rules, we get initial-cap.

Julho

In Java 10, Oracle JDK, by default using Unicode CLDR rules, we get lowercase.

julho

In Java 10, Oracle JDK after setting the VM option -Djava.locale.providers=COMPAT,SPI to revert to legacy behavior rather than using Unicode CLDR, we get initial-cap.

Julho

Details

  • The formatting rules defined by cultural norms may vary by JVM implementation and version.
  • The JVM in IdeOne.com refuses to localize. Only US English. See proof.
  • You are using the wrong classes. Use java.time for all date-time handling.

Code.

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.

11 de julho de 2018 17:57:36 NZST

Cultural norms

Deciding an issue such as capitalization of a month name depends on a culture’s norms. Of course, those norms can vary, and reasonable people can disagree.

But at some point, decisions have to be made. A Java implementation must have a set of rules for making these localization rules.

Unicode CLDR

Perhaps you are using Java 8 or earlier. My code in this Answer was produced with Java 10.

One important difference is that as of Java 9, for the Oracle JDK and OpenJDK projects, the default source of localization rules changed to using the Unicode CLDR (see Wikipedia). In Java 8 and earlier, each JVM provided their own set of rules.

Also, note that the Unicode CLDR is updated from time-to-time, and some rules may change.

So, you may well see different results for localization depending on which version of Java is in use, and on which implementation of Java is in use.

Context of presentation

Perhaps the issue here is one of context. In some cultures, the formatting rules such as capitalization of a month name vary by whether the month is being presented alone or embedded within a date.

Let’s try translate.Google.com:

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

So Google seems to vary by context, but I am not certain what is going on with that first case being initial-cap.

This context can be indicated by the use of the TextStyle enum used in Month::getDisplayName method. That enum offers …STANDALONE variations.

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

julho

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

julho

So no, context seems to not be an issue here when using Java 10.

java.time

You are using the troublesome old classes now supplanted by the java.time classes.

Instead of java.util.Date, use java.time.Instant. Both represent a moment in UTC. The modern Instant class uses a finer resolution of nanoseconds instead of milliseconds.

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

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

Adjust from UTC to the time zone where the people of that region use a particular wall-clock time you desire. Apply a ZoneId to get a ZonedDateTime object. Time zone is completely orthogonal to locale. One relates to the content of the moment, the other affects only the localization used in generating a String to represent that content. You could, for example, use a Japan time zone with a Portuguese locale.

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

Now, we want to generate strings representing that moment. First, generate a String in standard ISO 8601 format extended by appending the name of the time zone in square brackets.

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

2018-07-11T17:57:36.446917+12:00[Pacific/Auckland]

You want to localize the generated String objects. Use DateTimeFormatter to control the formatting of the String to be generated. Specify a FormatStyle to control how long or abbreviated.

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, 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

For fun, let’s try FormatStyle.LONG instead of 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 refuses to localize

I enjoy using IdeOne.com to demo Java code. Unfortunately, its JVM refuses to use any Locale except US English. So, no go for the code above.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

  • Java SE 8, Java SE 9, Java SE 10, and later
    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.


A brief history of time

Responding to comments on this Answer…

Timeline:

  • Sun Microsystems launches Java 1.0 with some date-time classes (java.util.Date etc.) donated by IBM/Taligent. Unfortunately, they are poorly-designed and flawed, confusing and troublesome.
  • Sun adds more classes in Java 1.1 (java.util.Calendar etc.) in an attempt to improve the date-time handling. But these too turn out to be poorly-designed and confusing, not a real improvement.
  • Years later, a man Stephen Colebourne founds the Joda-Time project, to make a comprehensive sophisticated date-time handling library, a first in the IT industry afaik. Huge success. One of the most popular Java libraries, added routinely to many developers’ projects.
  • Sun, then Oracle, and the JCP community finally recognize that the old date-time classes shipped with the earliest versions of Java are not adequate. This recognition opens the door for that same Stephen Colebourne to launch JSR 310, a specification for an all-new date-time API for inclusion as part of the Java platform. This project is based on concepts from Joda-Time, but is an entirely new rewrite. The new rewrite builds on the experience of having created Joda-Time, asking “If we knew then what we know now, how would we have designed Joda-Time?”.
  • The implementation of JSR 310 is built as the java.time package to be included with the then-upcoming Java 8.
  • To ensure wider acceptance of the java.time API, Stephen Colebourne launches the open-source ThreeTen-Backport project, mimicking nearly the same API as in java.time but capable of running on Java 6 and Java 7. Not quite all of the java.time functionality, but most of it. The goal is to enable a developer on earlier Java to start using the API. Then when moving to later versions of Java they need do little more than change their import statements from org.threeten.bp.* to java.time.*.
  • With Java 8 successfully shipping, Stephen Colebourne et al. shift the Joda-Time project into maintenance-mode, still actively updated with tzdata changes and bug fixes, but no further feature work to be done. They advise migration away from Joda-Time and on to the java.time classes.
  • Stephen Colebourne also launches the open-source ThreeTen-Extra project to work on additional features that may or may not eventually be submitted to later versions of JSR 310 (to become additional java.time classes). Meanwhile, these extra features, extensions to the java.time functionality, are provided as a library to the public for those developers who find them useful.


来源:https://stackoverflow.com/questions/51275322/javas-messageformat-not-localizing-portuguese-months-in-dates-in-lowercase

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!