[ Team LiB ] Previous Section Next Section

Using Locale-Based Text Formatters

Java relies on the java.text and the java.util packages for providing locale-based formatting. A locale represents both a language and a country, because two countries might share the same language but still format numbers, dates, and currencies differently. For example, the United States and the United Kingdom share the same language, but they format currencies differently because the United States uses a $ symbol and the UK uses the £ symbol.

In a Java application, you normally rely on the default locale, which the Java Virtual Machine detects by querying the operating system. Because you can assume that users have configured their systems with the locale they prefer, your application can safely rely on the default locale. In a Web application, however, the problem is more complicated. The application is running on a Web server, and the user is using a browser on another computer, possibly halfway around the world. You must create a locale object that conforms to the user's preference.

Creating a Locale Object

To create a locale object, you need at least a language for the locale, and preferably a country as well. There are also variant settings for a locale that are vendor- and browser-specific. If you don't know the country code for a locale, just use a blank string. You can still format dates and numbers without a country, but currencies will not have the correct currency symbol.

Online Standards

graphics/bytheway_icon.gif

You can find a list of the standard language codes, as defined by ISO-639, at http://lcweb.loc.gov/standards/iso639-2/englangn.html. You can find a list of valid country codes, as defined by ISO-3166, at http://www.oasis-open.org/cover/country3166.html.


The following code fragment creates a locale for French but does not specify a country code:


Locale french = new Locale("fr", "");

This code fragment creates a locale for German with a country code for Austria:


Locale germanAustria = new Locale("de", "AT");

Setting the Current Locale

graphics/watchout_icon.gif

Resist the temptation to use Locale.setDefault to set the current locale to be the browser's locale. You don't need to pass the locale to all the formatting routines that way, and for a Web application, you introduce an ugly threading problem. Two servlets might set the default locale at the same time. One servlet might set the locale to U.S. English and another might immediately change it to German for Germany. The servlet that wanted English would suddenly find it was using the German format.


Formatting Dates

You might have used the SimpleDateFormat class in the java.text package to format dates. Although it might provide an easy way to specify date formats, you lose some of the locale independence when you use it. When you create an instance of SimpleDateFormat, you must supply a basic pattern for the date. You might pick a format like MM/dd/yyyy, for example. Unfortunately, many countries write dates in the form dd/MM/yyyy.

The DateFormat class doesn't give you the leeway that SimpleDateFormat does. The DateFormat class has several factory methods that create a DateFormat object for you. You don't use the constructor. Instead, you call getDateInstance, getDateTimeInstance, or getTimeInstance, depending on whether you want to display dates only, dates and times, or times only.

When you create a DateFormat, you must specify one of four formats: SHORT, MEDIUM, LONG, or FULL. You can also give the locale for the format, and if you omit the locale, you'll get the default locale. For getDateInstance and getTimeInstance, you need to specify only one format. For getDateTimeInstance, you must specify SHORT, MEDIUM, LONG, or FULL for both the date and the time. You might choose to write out the date in long format but the time in full format.

Listing 22.2 shows a JSP that uses the four format options to display the date. The locale is not included in this example, however. You will see how to include it later in this hour.

Listing 22.2 Source Code for ShowDates.jsp
<%@ page language="java" import="java.text.*,java.util.*" %>
<%

    DateFormat dtShort = DateFormat.getDateTimeInstance(
        DateFormat.SHORT, DateFormat.SHORT);

    DateFormat dtMedium = DateFormat.getDateTimeInstance(
        DateFormat.MEDIUM, DateFormat.MEDIUM);

    DateFormat dtLong = DateFormat.getDateTimeInstance(
        DateFormat.LONG, DateFormat.LONG);

    DateFormat dtFull = DateFormat.getDateTimeInstance(
        DateFormat.FULL, DateFormat.FULL);
%>
<html>
<body>
A short date/time looks like: <%=dtShort.format(new Date())%><p>
A medium date/time looks like: <%=dtMedium.format(new Date())%><p>
A long date/time looks like: <%=dtLong.format(new Date())%><p>
A full date/time looks like: <%=dtFull.format(new Date())%><p>
</body>
</html>

Figure 22.3 shows the output of the ShowDates JSP.

Figure 22.3. You can choose between four basic styles of date and time.

graphics/22fig03.gif

Formatting Currency

Formatting currency values is much more involved than formatting dates and times. It's not that there's anything difficult about formatting a currency value; the problem is that you can rarely just switch from one currency to another without performing some sort of conversion. The NumberFormat class formats a specific value such as 12.34 into dollars as $12.34 or into Deutschmarks as 12,34DM, but 12,34DM is not the same amount of money as $12.34. Java does not provide any way to convert from one currency to another.

When you think about it, it's almost impossible to make a standard API for converting currencies because currencies are traded at various rates. Imagine trying to make an API that lets you get the price of a stock or the price of a car. There would be many places to go for the information and many different formats for the data. You have the same problem trying to convert currencies. Perhaps one day all the currency traders will publish rates via a Web service using a standard XML format and you can perform reasonable conversions. Even so, because the rates fluctuate, you must still worry about how stale the information is. The conversion rate for an unstable currency might plummet over the course of a day or two.

By now you see that the capability to display currency values for different locales is not such a useful feature. If you do find that you need to display currency values, you can call NumberFormat.getCurrencyInstance and pass it the locale whose currency you want to display:


NumberFormat currencyFormat =
    NumberFormat.getCurrencyInstance(someLocale);

Getting a Locale for a Browser's Preferred Language

When you examine the ACCEPT-LANGUAGE header value, you will find a list of locale codes consisting of a two-letter language code, possibly a country code, and even variant options after the country code. Each locale code is separated by a comma. When you just want the preferred language (the browser sends the locales in order of preference), you need to grab the first one in the list.

Listing 22.3 shows a JavaServer Page that parses the ACCEPT-LANGUAGE header value and gets a locale value for the preferred locale.

Listing 22.3 Source Code for TestLocale.jsp
<%@ page language="java" import="java.text.*,java.util.*" %>
<%

// Get the default locale in case you can't determine the
// user's locale.
    Locale locale = Locale.getDefault();

// Get the browser's preferred language.
    String acceptLangString = request.getHeader("ACCEPT-LANGUAGE");

// If there is an ACCEPT-LANGUAGE header, parse it.
    if (acceptLangString != null)
    {

// The accepted languages should be separated by commas, but also
// add space as a separator to eliminate whitespace.
        StringTokenizer localeParser = new StringTokenizer(
            acceptLangString, " ,");

// See whether there is a language in the list (you need only the first one).
        if (localeParser.hasMoreTokens())
        {
// Get the locale.
            String localeStr = localeParser.nextToken();

// The locale should be in the format ll-CC where ll is the language
// and CC is the country, like en-US for English in the U.S. and
// de-DE for German in Germany. Allow the browser to use _ instead
// of -, too.
            StringTokenizer localeSplitter = new StringTokenizer(
                localeStr, "_-");

// Assume both values are blank.
            String language = "";
            String country = "";

// See whether a language is specified.
            if (localeSplitter.hasMoreTokens())
            {
                language = localeSplitter.nextToken();
            }

// See whether a country is specified (there won't always be one).
            if (localeSplitter.hasMoreTokens())
            {
                country = localeSplitter.nextToken();

            }

// Create a locale based on this language and country (if country is
// blank, you'll still get locale-based text, but currencies won't
// display correctly.
            locale = new Locale(language, country);
        }
    }
%>
<html>
<body>
Your locale language is <%=locale.getLanguage()%>.<p>
Your locale country is <%=locale.getCountry()%>.<p>
<%
// Get a formatter to display currency.
    NumberFormat currencyFormatter =
        NumberFormat.getCurrencyInstance(locale);

// Get a formatter to display dates and times.
    DateFormat dateFormatter =
        DateFormat.getDateTimeInstance(
            DateFormat.FULL, DateFormat.FULL, locale);
%>
A currency in your locale looks like this:
    <%= currencyFormatter.format(12.34) %><p>
A date in your locale looks like this:
    <%= dateFormatter.format(new Date()) %><p>
</body>
</html>

Figure 22.4 shows the TestLocale JavaServer Page running with a locale of en-US (English-U.S.).

Figure 22.4. The java.text package can format dates and currencies.

graphics/22fig04.gif

Figure 22.5 shows the TestLocale JSP running with a locale of de (German) and no country code. Notice the odd-looking character in the currency. If you don't specify a country, you'll see this odd symbol. Also notice that the currency formatter still uses the German convention of using a comma where English text uses a period. Although the currency formatter doesn't know the currency symbol, it uses the number formatter to format the currency value, and the number formatter doesn't need to know the country.

Figure 22.5. If you don't specify a country for a locale, the currency symbol isn't correct.

graphics/22fig05.gif

    [ Team LiB ] Previous Section Next Section