Java 10 Unicode Locale Extensions: Currency, Week, Region

Introduction A java.util.Locale such as en-US tells Java's formatting classes which language and country conventions to follow - how to format numbers, currencies and dates, and which day starts the week. But users do not always want all the conventions of their country. An American working in Europe may want amounts shown in euros, or weeks starting on Monday. Such preferences can be added to a locale as Unicode extensions. Java 7 supported only a few of them, and Java 10 (JEP 314) added support for more. This tutorial explains the Unicode locale extensions in Java 10 with examples. We will first see how an extension is written in a language tag. We will then use the four extensions added in Java 10 - cu for the currency, fw for the first day of the week, rg for the region override and tz for the time zone - and see which classes use them. Unicode extensions in a language tag Locales are written as language tags in the BCP 47 standard, such as en-US for English as used in the United States. A tag can be followed by -u- and then one or more key-value pairs, which are the Unicode locale extensions -
en-US-u-cu-eur-fw-mon
This means "English, United States, but with the euro as the currency and Monday as the first day of the week". A locale with extensions is created with Locale.forLanguageTag(), or with Locale.Builder's setUnicodeLocaleKeyword() method. Java 7 already supported the ca (calendar) and nu (numbering system) keys. Java 10 added -
  • cu - the currency, as an ISO 4217 code in lower case, such as eur or inr.
  • fw - the first day of the week, as sun, mon, ... sat.
  • rg - a region override, such as gbzzzz for the United Kingdom. The region's conventions are used instead of the country's, while the language stays the same.
  • tz - the time zone, as a short Unicode time zone ID, such as jptyo for Asia/Tokyo.
cu - overriding the currency
Choosing the currency with the cu extension
//CurrencyExtensionDemo.java
import java.text.NumberFormat;
import java.util.Locale;

public class CurrencyExtensionDemo {
  public static void main(String[] args) {
    for (String tag : new String[] {"en-US", "en-US-u-cu-eur", "en-US-u-cu-inr"}) {
      Locale locale = Locale.forLanguageTag(tag);
      NumberFormat format = NumberFormat.getCurrencyInstance(locale);
      System.out.printf("%-16s %-11s %s%n", tag, format.format(1234.5), format.getCurrency());
    }
    Locale euro = Locale.forLanguageTag("en-US-u-cu-eur");
    System.out.println("Extension keys: " + euro.getUnicodeLocaleKeys()
        + ", cu = " + euro.getUnicodeLocaleType("cu"));
  }
}
 OUTPUT of the above code
en-US            $1,234.50   USD
en-US-u-cu-eur   €1,234.50   EUR
en-US-u-cu-inr   ₹1,234.50   INR
Extension keys: [cu], cu = eur
Explanation of the code
  • Plain en-US formats amounts in US dollars. With -u-cu-eur the same locale uses the euro, and with -u-cu-inr the Indian rupee - while the number format itself (the comma as the grouping separator and the period as the decimal point) still follows en-US.
  • getUnicodeLocaleKeys() and getUnicodeLocaleType() read the extensions of a locale, which is useful for checking what preferences a locale carries.
fw - overriding the first day of the week In the United States weeks start on Sunday, while in most of Europe they start on Monday. The fw extension changes this for both the old Calendar class and the date-time API's WeekFields -
Choosing the first day of the week with the fw extension
//FirstDayExtensionDemo.java
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.Calendar;
import java.util.Locale;

public class FirstDayExtensionDemo {
  public static void main(String[] args) {
    LocalDate date = LocalDate.of(2018, 3, 20);   // a Tuesday
    for (String tag : new String[] {"en-US", "en-US-u-fw-mon"}) {
      Locale locale = Locale.forLanguageTag(tag);
      DayOfWeek firstDay = WeekFields.of(locale).getFirstDayOfWeek();
      LocalDate weekStart = date.with(TemporalAdjusters.previousOrSame(firstDay));
      System.out.printf("%-15s first day=%-7s week of %s starts on %s (Calendar: %d)%n",
          tag, firstDay, date, weekStart, Calendar.getInstance(locale).getFirstDayOfWeek());
    }
  }
}
 OUTPUT of the above code
en-US           first day=SUNDAY  week of 2018-03-20 starts on 2018-03-18 (Calendar: 1)
en-US-u-fw-mon  first day=MONDAY  week of 2018-03-20 starts on 2018-03-19 (Calendar: 2)
  • WeekFields.of(locale) gives the week definition for a locale. With -u-fw-mon, the first day becomes MONDAY, so the week containing Tuesday 20 March 2018 starts on Monday 19 March instead of Sunday 18 March.
  • Calendar numbers the days from Calendar.SUNDAY = 1, so its first day changes from 1 to 2 (Calendar.MONDAY).
rg - overriding the region Where cu and fw change one preference each, rg switches all the region-specific conventions to another region at once. The value is a region code followed by zzzz, so gbzzzz means the United Kingdom -
Overriding the region with the rg extension
//RegionExtensionDemo.java
import java.text.NumberFormat;
import java.time.temporal.WeekFields;
import java.util.Locale;

public class RegionExtensionDemo {
  public static void main(String[] args) {
    for (String tag : new String[] {"en-US", "en-US-u-rg-gbzzzz"}) {
      Locale locale = Locale.forLanguageTag(tag);
      System.out.printf("%-18s %s, weeks start on %s%n", tag,
          NumberFormat.getCurrencyInstance(locale).format(1234.5),
          WeekFields.of(locale).getFirstDayOfWeek());
    }
    // Explicit cu or fw keys take priority over the rg region
    Locale mixed = Locale.forLanguageTag("en-US-u-rg-gbzzzz-cu-eur");
    System.out.println("rg-gbzzzz with cu-eur: " + NumberFormat.getCurrencyInstance(mixed).format(1234.5));
  }
}
 OUTPUT of the above code
en-US              $1,234.50, weeks start on SUNDAY
en-US-u-rg-gbzzzz  £1,234.50, weeks start on MONDAY
rg-gbzzzz with cu-eur: €1,234.50
  • en-US-u-rg-gbzzzz keeps the English language, but uses British conventions - pounds sterling as the currency and Monday as the first day of the week.
  • When a locale has both rg and a specific key such as cu, the specific key wins, so the last line uses euros.
tz - the time zone, and DateTimeFormatter.localizedBy() Java 10 also added DateTimeFormatter.localizedBy(Locale), which, unlike withLocale(), also applies the locale's extensions to the formatter. With the tz extension, the formatter converts times to the given time zone -
Formatting in a time zone with the tz extension
//TimeZoneExtensionDemo.java
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class TimeZoneExtensionDemo {
  public static void main(String[] args) {
    ZonedDateTime release = ZonedDateTime.of(2018, 3, 20, 9, 0, 0, 0, ZoneOffset.UTC);
    DateTimeFormatter pattern = DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm VV");

    Locale tokyo = Locale.forLanguageTag("en-US-u-tz-jptyo");
    System.out.println("withLocale : " + pattern.withLocale(tokyo).format(release));
    System.out.println("localizedBy: " + pattern.localizedBy(tokyo).format(release));
    System.out.println("Zone set by localizedBy: " + pattern.localizedBy(tokyo).getZone());
  }
}
 OUTPUT of the above code
withLocale : 20 Mar 2018 09:00 Z
localizedBy: 20 Mar 2018 18:00 Asia/Tokyo
Zone set by localizedBy: Asia/Tokyo
  • withLocale() uses only the language and country, so the time stays in UTC.
  • localizedBy() reads -u-tz-jptyo and sets the formatter's zone to Asia/Tokyo, so 09:00 UTC is shown as 18:00 Tokyo time. It similarly applies the ca, nu and rg extensions.
Note - The extensions are honoured by the JDK's default locale data, which since Java 9 comes from the Unicode Common Locale Data Repository (CLDR). Output such as currency symbols can change slightly between JDK versions as the CLDR data is updated. Summary In this tutorial we saw how Unicode extensions add user preferences to a locale, written after -u- in a BCP 47 language tag. We used the extensions added in Java 10 - cu to change the currency, fw to change the first day of the week for both WeekFields and Calendar, rg to switch to another region's conventions, and tz with the new DateTimeFormatter.localizedBy() to format times in a time zone. To see all the changes in this release, go back to the overview of new features in Java 10Click to read tutorial: Overview of New Features in Java 10, or continue with the new features in Java 11Click to read: new features in Java 11.