Formatting
This lesson explains how to format numbers, currencies, dates, times, and text messages. Because end users can see these data elements, their format must conform to various cultural conventions. Following the examples in this lesson will teach you how to:
- Format data elements in a locale-sensitive manner
- Keep your code locale-independent
- Avoid the need to write formatting routines for specific locales
Numbers and Currencies
Programs store and operate on numbers in a locale-independent way. Before displaying or printing a number, a program must convert it to a String
that is in a locale-sensitive format. For example, in France the number 123456.78 should be formatted as 123 456,78, and in Germany it should appear as 123.456,78. In this section, you will learn how to make your programs independent of the locale conventions for decimal points, thousands-separators, and other formatting properties.
Using Predefined Formats
By invoking the methods provided by the NumberFormat
class, you can format numbers, currencies, and percentages according toLocale
. The material that follows demonstrates formatting techniques with a sample program called NumberFormatDemo.java
.
Numbers
You can use the NumberFormat
methods to format primitive-type numbers, such as double
, and their corresponding wrapper objects, such as Double
.
The following code example formats a Double
according to Locale
. Invoking the getNumberInstance
method returns a locale-specific instance of NumberFormat
. The format
method accepts the Double
as an argument and returns the formatted number in aString
.
static public void displayNumber(Locale currentLocale) {
Integer quantity = new Integer(123456);
Double amount = new Double(345987.246);
NumberFormat numberFormatter;
String quantityOut;
String amountOut;
numberFormatter = NumberFormat.getNumberInstance(currentLocale);
quantityOut = numberFormatter.format(quantity);
amountOut = numberFormatter.format(amount);
System.out.println(quantityOut + " " + currentLocale.toString());
System.out.println(amountOut + " " + currentLocale.toString());
}
This example prints the following; it shows how the format of the same number varies with Locale
:
123 456 fr_FR
345 987,246 fr_FR
123.456 de_DE
345.987,246 de_DE
123,456 en_US
345,987.246 en_US
Using Digit Shapes Other Than Arabic Numerals
By default, when text contains numeric values, those values are displayed using Arabic digits. When other Unicode digit shapes are preferred, use the java.awt.font.NumericShaper
class. The NumericShaper
API enables you to display a numeric value represented internally as an ASCII value in any Unicode digit shape. See Converting Latin Digits to Other Unicode Digits for more information.
In addition, some locales have variant codes that specify that Unicode digit shapes be used in place of Arabic digits, such as the locale for the Thai language. See the section Variant Code in Creating a Locale for more information.
Currencies
If you are writing business applications, you will probably need to format and display currencies. You format currencies in the same manner as numbers, except that you call getCurrencyInstance
to create a formatter. When you invoke the format
method, it returns a String
that includes the formatted number and the appropriate currency sign.
This code example shows how to format currency in a locale-specific manner:
static public void displayCurrency( Locale currentLocale) {
Double currencyAmount = new Double(9876543.21);
Currency currentCurrency = Currency.getInstance(currentLocale);
NumberFormat currencyFormatter =
NumberFormat.getCurrencyInstance(currentLocale);
System.out.println(
currentLocale.getDisplayName() + ", " +
currentCurrency.getDisplayName() + ": " +
currencyFormatter.format(currencyAmount));
}
The output generated by the preceding lines of code is as follows:
French (France), Euro: 9 876 543,21 €
German (Germany), Euro: 9.876.543,21 €
English (United States), US Dollar: $9,876,543.21
At first glance, this output may look wrong to you because the numeric values are all the same. Of course, 9 876 543,21 € is not equivalent to $9,876,543.21. However, bear in mind that the NumberFormat
class is unaware of exchange rates. The methods belonging to the NumberFormat
class format currencies but do not convert them.
Note that the Currency
class is designed so that there is never more than one Currency
instance for any given currency. Therefore, there is no public constructor. As demonstrated in the previous code example, you obtain a Currency
instance using thegetInstance
methods.
The sample InternationalizedMortgageCalculator.java
also demonstrates how to use the Currency
class. (Note that this sample does not convert currency values.)
The sample InternationalizedMortgageCalculator.java
requires the following resource files:
The Currency
class contains other methods to retrieve currency related information:
-
getAvailableCurrencies
: Returns all available currencies in the JDK -
getCurrencyCode
: Returns the ISO 4217 numeric code for aCurrency
instance -
getSymbol
: Returns the symbol for aCurrency
instance. You can optionally specify as an argument aLocale
object. Consider the following excerpt:Locale enGBLocale = new Locale.Builder().setLanguage("en").setRegion("GB").build(); Locale enUSLocale = new Locale.Builder().setLanguage("en").setRegion("US").build(); Currency currencyInstance = Currency.getInstance(enUSLocale); System.out.println( "Symbol for US Dollar, en-US locale: " + currencyInstance.getSymbol(enUSLocale)); System.out.println( "Symbol for US Dollar, en-UK locale: " + currencyInstance.getSymbol(enGBLocale));
The excerpt prints the following:
Symbol for US Dollar, en-US locale: $ Symbol for US Dollar, en-UK locale: USD
This excerpt demonstrates that the symbol of a currency can vary depending on the locale.
-
getDisplayName
: Returns the display name for aCurrency
instance. Like thegetSymbol
method, you can optionally specify aLocale
object.
Extensible Support for ISO 4217 Currency Codes
ISO 4217 is a standard published by the International Standards Organization. It specifies three-letter codes (and equivalent three-digit numeric codes) to represent currencies and funds. This standard is maintained by an external agency and is released independent of the Java SE platform.
Suppose that a country adopts a different currency and the ISO 4217 maintenance agency releases a currency update. To implement this update and thereby supercede the default currency at runtime, create a properties file named <JAVA_HOME>/lib/currency.properties
. This file contains the key/value pairs of the ISO 3166 country code, and the ISO 4217 currency data. The value part consists of three comma-separated ISO 4217 currency values: an alphabetic code, a numeric code, and a minor unit. Any lines beginning with the hash character (#
), are treated as comment lines. For example:
# Sample currency property for Canada
CA=CAD,124,2
CAD
stands for the Canadian dollar; 124
is the numeric code for the Canadian dollar; and 2
is the minor unit, which is the number of decimal places the currency requires to represent fractional currencies. For example, the following properties file will supercede the default Canadian currency to a Canadian dollar that does not have any units smaller than the dollar:
CA=CAD,124,0
Percentages
You can also use the methods of the NumberFormat
class to format percentages. To get the locale-specific formatter, invoke thegetPercentInstance
method. With this formatter, a decimal fraction such as 0.75 is displayed as 75%.
The following code sample shows how to format a percentage.
static public void displayPercent(Locale currentLocale) {
Double percent = new Double(0.75);
NumberFormat percentFormatter;
String percentOut;
percentFormatter = NumberFormat.getPercentInstance(currentLocale);
percentOut = percentFormatter.format(percent);
System.out.println(percentOut + " " + currentLocale.toString());
}
This sample prints the following:
75 % fr_FR
75% de_DE
75% en_US
Dates and Times
Version note: This Date and Time section uses the date and time APIs in the java.util package. The java.time APIs, available in the JDK 8 release, provides a comprehensive date and time model that offers significant improvements over the java.util classes. The java.time APIs are described in the Date Time trail. The Legacy Date-Time Code page might be of particular interest.
Date
objects represent dates and times. You cannot display or print a Date
object without first converting it to a String
that is in the proper format. Just what is the "proper" format? First, the format should conform to the conventions of the end user's Locale
. For example, Germans recognize 20.4.09
as a valid date, but Americans expect that same date to appear as 4/20/09
. Second, the format should include the necessary information. For instance, a program that measures network performance may report on elapsed milliseconds. An online appointment calendar probably won't display milliseconds, but it will show the days of the week.
This section explains how to format dates and times in various ways and in a locale-sensitive manner. If you follow these techniques your programs will display dates and times in the appropriate Locale
, but your source code will remain independent of any specific Locale
.
Using Predefined Formats
The DateFormat
class allows you to format dates and times with predefined styles in a locale-sensitive manner. The sections that follow demonstrate how to use the DateFormat
class with a program called DateFormatDemo.java
.
Dates
Formatting dates with the DateFormat
class is a two-step process. First, you create a formatter with the getDateInstance
method. Second, you invoke the format
method, which returns a String
containing the formatted date. The following example formats today's date by calling these two methods:
Date today;
String dateOut;
DateFormat dateFormatter;
dateFormatter = DateFormat.getDateInstance(DateFormat.DEFAULT, currentLocale);
today = new Date();
dateOut = dateFormatter.format(today);
System.out.println(dateOut + " " + currentLocale.toString());
The output generated by this code follows. Notice that the formats of the dates vary with Locale
. Since DateFormat
is locale-sensitive, it takes care of the formatting details for each Locale
.
30 juin 2009 fr_FR
30.06.2009 de_DE
Jun 30, 2009 en_US
The preceding code example specified the DEFAULT
formatting style. The DEFAULT
style is just one of the predefined formatting styles that the DateFormat
class provides, as follows:
- DEFAULT
- SHORT
- MEDIUM
- LONG
- FULL
The following table shows how dates are formatted for each style with the U.S. and French locales:
Style | U.S. Locale | French Locale |
---|---|---|
DEFAULT |
Jun 30, 2009 | 30 juin 2009 |
SHORT |
6/30/09 | 30/06/09 |
MEDIUM |
Jun 30, 2009 | 30 juin 2009 |
LONG |
June 30, 2009 | 30 juin 2009 |
FULL |
Tuesday, June 30, 2009 | mardi 30 juin 2009 |
Times
Date
objects represent both dates and times. Formatting times with the DateFormat
class is similar to formatting dates, except that you create the formatter with the getTimeInstance
method, as follows:
DateFormat timeFormatter =
DateFormat.getTimeInstance(DateFormat.DEFAULT, currentLocale);
The table that follows shows the various predefined format styles for the U.S. and German locales:
Style | U.S. Locale | German Locale |
---|---|---|
DEFAULT |
7:03:47 AM | 7:03:47 |
SHORT |
7:03 AM | 07:03 |
MEDIUM |
7:03:47 AM | 07:03:07 |
LONG |
7:03:47 AM PDT | 07:03:45 PDT |
FULL |
7:03:47 AM PDT | 7.03 Uhr PDT |
Both Dates and Times
To display a date and time in the same String
, create the formatter with the getDateTimeInstance
method. The first parameter is the date style, and the second is the time style. The third parameter is the Locale
. Here's a quick example:
DateFormat formatter = DateFormat.getDateTimeInstance(
DateFormat.LONG,
DateFormat.LONG,
currentLocale);
The following table shows the date and time formatting styles for the U.S. and French locales:
Style | U.S. Locale | French Locale |
---|---|---|
DEFAULT |
Jun 30, 2009 7:03:47 AM | 30 juin 2009 07:03:47 |
SHORT |
6/30/09 7:03 AM | 30/06/09 07:03 |
MEDIUM |
Jun 30, 2009 7:03:47 AM | 30 juin 2009 07:03:47 |
LONG |
June 30, 2009 7:03:47 AM PDT | 30 juin 2009 07:03:47 PDT |
FULL |
Tuesday, June 30, 2009 7:03:47 AM PDT |
mardi 30 juin 2009 07 h 03 PDT |
Customizing Formats
The previous section, Using Predefined Formats, described the formatting styles provided by the DateFormat
class. In most cases these predefined formats are adequate. However, if you want to create your own customized formats, you can use the SimpleDateFormat
class.
The code examples that follow demonstrate the methods of the SimpleDateFormat
class. You can find the full source code for the examples in the file named SimpleDateFormatDemo
.
About Patterns
When you create a SimpleDateFormat
object, you specify a pattern String
. The contents of the pattern String
determine the format of the date and time. For a full description of the pattern's syntax, see the tables in Date Format Pattern Syntax.
The following code formats a date and time according to the pattern String
passed to the SimpleDateFormat
constructor. The String
returned by the format
method contains the formatted date and time that are to be displayed.
Date today;
String output;
SimpleDateFormat formatter;
formatter = new SimpleDateFormat(pattern, currentLocale);
today = new Date();
output = formatter.format(today);
System.out.println(pattern + " " + output);
The following table shows the output generated by the previous code example when the U.S. Locale
is specified:
Pattern | Output |
---|---|
dd.MM.yy | 30.06.09 |
yyyy.MM.dd G 'at' hh:mm:ss z | 2009.06.30 AD at 08:29:36 PDT |
EEE, MMM d, ''yy | Tue, Jun 30, '09 |
h:mm a | 8:29 PM |
H:mm | 8:29 |
H:mm:ss:SSS | 8:28:36:249 |
K:mm a,z | 8:29 AM,PDT |
yyyy.MMMMM.dd GGG hh:mm aaa | 2009.June.30 AD 08:29 AM |
Patterns and Locale
The SimpleDateFormat
class is locale-sensitive. If you instantiate SimpleDateFormat
without a Locale
parameter, it will format the date and time according to the default Locale
. Both the pattern and the Locale
determine the format. For the same pattern, SimpleDateFormat
may format a date and time differently if the Locale
varies.
In the example code that follows, the pattern is hardcoded in the statement that creates the SimpleDateFormat
object:
Date today;
String result;
SimpleDateFormat formatter;
formatter = new SimpleDateFormat("EEE d MMM yy", currentLocale);
today = new Date();
result = formatter.format(today);
System.out.println("Locale: " + currentLocale.toString());
System.out.println("Result: " + result);
When the currentLocale
is set to different values, the preceding code example generates this output:
Locale: fr_FR
Result: mar. 30 juin 09
Locale: de_DE
Result: Di 30 Jun 09
Locale: en_US
Result: Tue 30 Jun 09
Date Format Pattern Syntax
You can design your own format patterns for dates and times from the list of symbols in the following table:
Symbol | Meaning | Presentation | Example |
---|---|---|---|
G | era designator | Text | AD |
y | year | Number | 2009 |
M | month in year | Text & Number | July & 07 |
d | day in month | Number | 10 |
h | hour in am/pm (1-12) | Number | 12 |
H | hour in day (0-23) | Number | 0 |
m | minute in hour | Number | 30 |
s | second in minute | Number | 55 |
S | millisecond | Number | 978 |
E | day in week | Text | Tuesday |
D | day in year | Number | 189 |
F | day of week in month | Number | 2 (2nd Wed in July) |
w | week in year | Number | 27 |
W | week in month | Number | 2 |
a | am/pm marker | Text | PM |
k | hour in day (1-24) | Number | 24 |
K | hour in am/pm (0-11) | Number | 0 |
z | time zone | Text | Pacific Standard Time |
' | escape for text | Delimiter | (none) |
' | single quote | Literal | ' |
Characters that are not letters are treated as quoted text. That is, they will appear in the formatted text even if they are not enclosed within single quotes.
The number of symbol letters you specify also determines the format. For example, if the "zz" pattern results in "PDT," then the "zzzz" pattern generates "Pacific Daylight Time." The following table summarizes these rules:
Presentation | Number of Symbols | Result |
---|---|---|
Text | 1 - 3 | abbreviated form, if one exists |
Text | >= 4 | full form |
Number | minimum number of digits is required | shorter numbers are padded with zeros (for a year, if the count of 'y' is 2, then the year is truncated to 2 digits) |
Text & Number | 1 - 2 | number form |
Text & Number | 3 |
text form |
Changing Date Format Symbols
The format
method of the SimpleDateFormat
class returns a String
composed of digits and symbols. For example, in the String
"Friday, April 10, 2009," the symbols are "Friday" and "April." If the symbols encapsulated in SimpleDateFormat
don't meet your needs, you can change them with the DateFormatSymbols
. You can change symbols that represent names for months, days of the week, and time zones, among others. The following table lists the DateFormatSymbols
methods that allow you to modify the symbols:
Setter Method | Example of a Symbol the Method Modifies |
---|---|
setAmPmStrings |
PM |
setEras |
AD |
setMonths |
December |
setShortMonths |
Dec |
setShortWeekdays |
Tue |
setWeekdays |
Tuesday |
setZoneStrings |
PST |
The following example invokes setShortWeekdays
to change the short names of the days of the week from lowercase to uppercase characters. The full source code for this example is in DateFormatSymbolsDemo
. The first element in the array argument of setShortWeekdays
is a null String
. Therefore the array is one-based rather than zero-based. The SimpleDateFormat
constructor accepts the modified DateFormatSymbols
object as an argument. Here is the source code:
Date today;
String result;
SimpleDateFormat formatter;
DateFormatSymbols symbols;
String[] defaultDays;
String[] modifiedDays;
symbols = new DateFormatSymbols( new Locale("en", "US"));
defaultDays = symbols.getShortWeekdays();
for (int i = 0; i < defaultDays.length; i++) {
System.out.print(defaultDays[i] + " ");
}
System.out.println();
String[] capitalDays = {
"", "SUN", "MON",
"TUE", "WED", "THU",
"FRI", "SAT"
};
symbols.setShortWeekdays(capitalDays);
modifiedDays = symbols.getShortWeekdays();
for (int i = 0; i < modifiedDays.length; i++) {
System.out.print(modifiedDays[i] + " ");
}
System.out.println();
System.out.println();
formatter = new SimpleDateFormat("E", symbols);
today = new Date();
result = formatter.format(today);
System.out.println("Today's day of the week: " + result);
The preceding code generates this output:
Sun Mon Tue Wed Thu Fri Sat
SUN MON TUE WED THU FRI SAT
Today's day of the week: MON
Messages
We all like to use programs that let us know what's going on. Programs that keep us informed often do so by displaying status and error messages. Of course, these messages need to be translated so they can be understood by end users around the world. The sectionIsolating Locale-Specific Data discusses translatable text messages. Usually, you're done after you move a message String
into a ResourceBundle
. However, if you've embedded variable data in a message, you'll have to take some extra steps to prepare it for translation.
A compound message contains variable data. In the following list of compound messages, the variable data is underlined:
The disk named MyDisk contains 300 files.
The current balance of account #34-09-222 is $2,745.72.
405,390 people have visited your website since January 1, 2009.
Delete all files older than 120 days.
You might be tempted to construct the last message in the preceding list by concatenating phrases and variables as follows:
double numDays;
ResourceBundle msgBundle;
// ...
String message = msgBundle.getString(
"deleteolder" +
numDays.toString() +
msgBundle.getString("days"));
This approach works fine in English, but it won't work for languages in which the verb appears at the end of the sentence. Because the word order of this message is hardcoded, your localizers won't be able to create grammatically correct translations for all languages.
How can you make your program localizable if you need to use compound messages? You can do so by using the MessageFormat
class, which is the topic of this section.
Caution:
Compound messages are difficult to translate because the message text is fragmented. If you use compound messages, localization will take longer and cost more. Therefore you should use compound messages only when necessary.