rcorreia
rcorreia

Reputation: 559

Joda-Time ignoring locale on en-US server

I'm using Joda-Time to convert my string dates to timestamp. In my pt-BR machine everything works fine. However, on an en-US server Joda ignores the custom locale.

On my laptop (Windows 8 pt-BR):

Locale.setDefault(new Locale("pt", "BR"));
DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("dd-MMM-yy");
dateFormatter.parseLocalDate("13-Out-14").toDateTimeAtStartOfDay().getMillis();

This code runs flawlessly. The word for October in portuguese is Outubro.

Locale.setDefault(new Locale("en", "US"));
DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("dd-MMM-yy");
dateFormatter.parseLocalDate("13-Oct-14").toDateTimeAtStartOfDay().getMillis();

The code above works fine as well. The JVM recognizes the new locale and converts the date. However, when I try to run the first piece of code on a Windows Server 2012 en-US I get an exception.

java.lang.IllegalArgumentException: Invalid format: "13-Out-14" is malformed at "Out-14"
  at org.joda.time.format.DateTimeFormatter.parseLocalDateTime(DateTimeFormatter.java:854)
  at org.joda.time.format.DateTimeFormatter.parseLocalDate(DateTimeFormatter.java:798)
  at br.com.luminiti.pro.service.DataUtil.dataStringToLong(DataUtil.java:155)

And if I change Out to Oct it converts with no problem. So it seems the JVM is using the language of the machine itself.

How to resolve this issue?

Thanks!

Upvotes: 3

Views: 2520

Answers (1)

Basil Bourque
Basil Bourque

Reputation: 339303

Uppercase O Is Problem

The expected abbreviated name for October in Portuguese is out, not Out.

To demonstrate, this code in Joda-Time 2.5:

System.out.println( "October in Portuguese: " + DateTimeFormat.forPattern( "dd-MMM-yy" ).withLocale( locale_ptBR ).print( new DateTime( 2014 , 10 , 13 , 0 , 0 , 0 ) ) );

…outputs a lowercase o:

October in Portuguese: 13-out-14

➥ So, change your input string from 13-Out-14 to 13-out-14 and your code works.

Month Names Abbreviated

See what abbreviated month names are expected by Joda-Time:

Locale locale_ptBR = new Locale( "pt" , "BR" );  // Portuguese in Brazil.
System.out.println( "Short months for locale : " + locale_ptBR + Arrays.toString( org.joda.time.DateTimeUtils.getDateFormatSymbols( locale_ptBR ).getShortMonths() ) );

Locale locale_frCA = Locale.CANADA_FRENCH;  // Québec.
System.out.println( "Short months for locale : " + locale_frCA + Arrays.toString( org.joda.time.DateTimeUtils.getDateFormatSymbols( locale_frCA ).getShortMonths() ) );

Locale locale_enUS = Locale.US;  // United States.
System.out.println( "Short months for locale : " + locale_enUS + Arrays.toString( org.joda.time.DateTimeUtils.getDateFormatSymbols( locale_enUS ).getShortMonths() ) );

When run with Joda-Time 2.5 in Java 8 Update 25 on a Mac (Mountain Lion) configured for United States (US).

Short months for locale : pt_BR [jan, fev, mar, abr, mai, jun, jul, ago, set, out, nov, dez, ]
Short months for locale : fr_CA [janv., févr., mars, avr., mai, juin, juil., août, sept., oct., nov., déc., ]
Short months for locale : en_US [Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, ]

You can see how they vary in uppercase/lowercase, number of characters, and punctuation (.).

The rest of this answer addresses other issues with your Question’s code.

Avoid Setting Default Locale

Calling Locale::setDefault is a risky bad practice as it affects all the Java code running in all the threads of all the apps running in this entire Java Virtual Machine (JVM). Not only are you subverting the possible intention of other code to use the original default Locale, you are doing so at runtime, on-the-fly. You are changing it while that other code executes. Not good.

Specify Locale

Rather than override the default Locale, specify your desired Locale. Call DateTimeFormatter::withLocale to tell the formatter to apply a specific Locale when parsing or generating strings.

Specify Time Zone

The code in the Question fails to account for the issue of time zone. Time zone is crucial for parsing and determining a date. In example code below, try swapping out the time zone applied to the formatter to see very different results.

If omitted, you get the JVM’s current default time zone applied. Such implicit use of the current default means your code's behavior may vary at runtime on various machines.

To specify a time zone, call withZone.

Use proper time zone names. Never use the 3 or 4 letter codes that are neither standardized nor unique.

No Need For Local Date

No need for a LocalDate in this particular case. Doing so should work, but keeping it in DateTime objects might be simpler.

Example Code

Example code in Joda-Time 2.5.

DateTimeZone zoneSaoPaulo = DateTimeZone.forID( "America/Sao_Paulo" );
DateTimeZone zoneNewYork = DateTimeZone.forID( "America/New_York" );
DateTimeZone zoneLosAngeles = DateTimeZone.forID( "America/Los_Angeles" );

Locale locale_ptBR = new Locale( "pt" , "BR" );
Locale locale_enUS = new Locale( "en" , "US" );

DateTimeFormatter formatter_ptBR = DateTimeFormat.forPattern( "dd-MMM-yy" ).withLocale( locale_ptBR ).withZone( zoneSaoPaulo );
DateTimeFormatter formatter_enUS = DateTimeFormat.forPattern( "dd-MMM-yy" ).withLocale( locale_enUS ).withZone( zoneSaoPaulo );  // Try swapping out this time zone to see very different results.

DateTime dateTime_ptBR = formatter_ptBR.parseDateTime( "13-out-14" ).withTimeAtStartOfDay();  // Month must be lowercase for Portuguese, "out" not "Out".
DateTime dateTime_enUS = formatter_enUS.parseDateTime( "13-Oct-14" ).withTimeAtStartOfDay();  // The call to "withTimeAtStartOfDay" is not necessary as it is the default when parsing date-only. I would include it to be self-documenting of our intention.

long millis_ptBR = dateTime_ptBR.getMillis();
long millis_enUS = dateTime_enUS.getMillis();

Dump to console.

System.out.println( "dateTime_ptBR : " + dateTime_ptBR );
System.out.println( "dateTime_ptBR : " + formatter_ptBR.print( dateTime_ptBR ) );
System.out.println( "dateTime_ptBR : " + DateTimeFormat.forStyle( "FF" ).withLocale( locale_ptBR ).print( dateTime_ptBR ) );
System.out.println( "millis_ptBR : " + millis_ptBR );

System.out.println( "dateTime_enUS : " + dateTime_enUS );
System.out.println( "dateTime_enUS : " + formatter_enUS.print( dateTime_enUS ) );
System.out.println( "dateTime_enUS : " + DateTimeFormat.forStyle( "FF" ).withLocale( locale_enUS ).print( dateTime_enUS ) );
System.out.println( "millis_enUS : " + millis_enUS );

System.out.println( "UTC : " + dateTime_ptBR.withZone( DateTimeZone.UTC ) );

When run.

dateTime_ptBR : 2014-10-13T00:00:00.000-03:00
dateTime_ptBR : 13-out-14
dateTime_ptBR : Segunda-feira, 13 de Outubro de 2014 00h00min00s BRT
millis_ptBR : 1413169200000

dateTime_enUS : 2014-10-13T00:00:00.000-03:00
dateTime_enUS : 13-Oct-14
dateTime_enUS : Monday, October 13, 2014 12:00:00 AM BRT
millis_enUS : 1413169200000

UTC : 2014-10-13T03:00:00.000Z

Upvotes: 3

Related Questions