Last month, I presented the second part of a three-part series exploring the development of Java-based software for an international audience. Part 2 provided a complete list of Java's internationalization and localization classes -- as of JDK 1.1.6 -- and introduced the concept of an "umbrella" class. We explored character properties, string comparisons, and character-, line-, sentence-, and word-break detection. We saw how to set the default locale via the host computer's operating system (Windows 95 was used as an example) and continued to explore resource bundles -- specifically, we learned how to store image data in a list resource bundle.
Read the whole "Internationalize Your Software" series:
- Part 1. Learn how to develop software for the global marketplace
- Part 2. Explore concepts of internationalization and localization; characters and character definition standards; locales; and resource bundles
- Part 3. Explore dates, time zones, calendars, formatters, and international fonts
Part 3 closes the internationalization series with an expansion of the material presented in Part 2, including:
- Dates, time zones, and calendars
- International fonts and non-Unicode text
- Beyond JDK 1.1.6
In Part 1 of this series, I included calendars on a list of items requiring localization. In Part 3, we're going to examine calendars, along with dates and time zones, from Java's perspective.
How do we display numbers, dates, and messages, according to the conventions of different locales, without writing lots of code? We'll answer this question by examining Java's formatter classes. As we'll see, it's possible to use these same classes to parse user input in a locale-sensitive manner.
So far, we haven't seen an applet that displays Chinese, Arabic, Hebrew, or Japanese characters. Why? We'll find out when we explore international fonts and non-Unicode text.
And finally, although this series has been based on JDK 1.1.6, we'll move beyond JDK 1.1.6 and explore new internationalization features that have been introduced in JDK 1.1.7 and what's now known as the Java 2 platform (previously JDK 1.2).
In this article, Java applets are used to illustrate Java's internationalization and localization features. These applets were compiled with the JDK 1.1.6 compiler and tested with the JDK 1.1.6 appletviewer and Netscape Navigator 4.06 programs. Netscape was running version 1.1.5 of the Java runtime environment during testing.
Dates, time zones, and calendars
Many Java programs work with the concept of time. For example, one program might measure the interval between two events while another is designed to calculate a person's age. Different cultures tend to measure time in standardized units such as minutes and days. However, they don't all use the same calendar. For example, one culture might use the Gregorian calendar while another uses the 13-month lunar calendar. And we need to make sure that our international software takes this varying calendar usage into account, so that it exhibits consistent behavior for the particular locale in which it's used.
But before we look at calendars, we'll need to study dates and time zones. Why? Java's
Calendar class is intricately connected to the
TimeZone classes. Therefore, it would be a good idea to see how Java deals with dates and time zones before exploring the more complex concept of calendars.
A date is a concrete representation of a precise instant in time. Dates consist of several components -- day, month, year, hour, minute, second, and so on. Normally, we think of a date as consisting of a year, a month, and a day. We also think of a time as consisting of an hour, a minute, and a second.
Over the years, I've come across operating systems, programs, articles, and books that combine these two concepts into the single concept of a date. I've also seen other examples that treat these concepts as separate entities. Is one right and the other wrong? I think it's a case of "six of one and a half-dozen of the other." In other words, I think separating the entities is splitting hairs.
For the purposes of my article, I decided to combine these elements as a date. Think of the hour, minute, and second, as representing the fraction of a day when specifying a precise instant in time.
Date class is used to instantiate objects that represent dates. Internally, the
Date () constructor calls
System.currentTimeMillis () to obtain the host computer's current time -- expressed as the number of milliseconds that have elapsed since midnight GMT on January 1, 1970.
The following is a digital clock applet that uses the
Date class. Press the Start button to start this clock and the Stop button to stop it. The source code to this applet is located in example7.java.
The digital clock applet calls the
Date () constructor to instantiate a new
Date object. It also calls
toString () method to return a
String object that contains a human-readable date in the language and format -- weekday name, short month name, day of month, time (24-hour format), time zone, and year -- of the United States locale. Since
toString () always works with the United States locale, it's not a good idea to use this method to format the contents of
Date objects when developing international software. A better way to format
Date objects is to use the
DateFormat class, as we'll find out.
Date's methods --
parse (String), and so on -- have been deprecated because they are not amenable to internationalization. In other words, they are either based exclusively on the United States locale or they exclusively support the Gregorian calendar. There is no room for growth. These methods should not be used.
Their functionality has been replaced by the
Date's JDK documentation provides examples of replacement code. For example, a call to
setYear (int year) could be replaced by a call to
Calendar.set (Calendar.YEAR, year + 1900).
More detailed information about
Date is available in the following class reference, located at Sun's Java Web site: http://java.sun.com/products/jdk/1.1/docs/api/java.util.Date.html.
A time zone is a set of geographical regions that share a common time zone offset -- a specific number of hours relative to Greenwich Mean Time (GMT), the standard geographical location from where all time is measured. For example, the Central Standard Time (CST) and Eastern Standard Time (EST) time zones represent all the geographical regions located -6 and -5 hours, respectively, from GMT.
Why is Standard Time a part of the names given to these time zones? Standard Time is the default (normal) time used by a time zone. To capitalize on daylight hours as the seasons change, many regions within a time zone move their time setting forward in spring and backward in autumn by one or more hours. The period of time that lies between spring and autumn time changes is known as daylight savings time. Since standard time is the default time for a time zone, it makes sense to include Standard Time as part of a time zone's name.
TimeZone class is used to obtain objects that represent time zone offsets. Because
TimeZone is an abstract class, you must call one of
TimeZone's two static factory methods --
getDefault () and
getTimeZone (String) -- to return objects that have been instantiated from
TimeZone's concrete subclasses.
This table shows the results of running a Java application that calls some of
TimeZone's methods. These methods include
getTimeZone (), and
useDaylightTime (). The source code to this application is located in example8.java.
How does the preceding application work? The first task is to obtain
TimeZone subclass objects for the default and EST time zones. The following code fragment shows how this is done. It calls
getDefault () and
getTimeZone (String) static factory methods.
// Get the default TimeZone object for this computer.
TimeZone tz1 = TimeZone.getDefault ();
// Get the TimeZone object associated with Eastern Standard Time.
TimeZone tz2 = TimeZone.getTimeZone ("EST");
The next step is to display the time zone identifier (ID) associated with the default time zone, and obtain an array of IDs. (A time zone identifier is string of characters that uniquely identifies either a time zone -- such as CST or EST -- or a region within a time zone that differs, based on daylight savings time behavior, from the rest of the time zone.) For example, Phoenix, AZ, and Denver, CO, lie within the same time zone, Mountain Standard Time (MST), but differ in daylight savings time behavior. Denver takes daylight savings into account, but Phoenix does not. The three-letter ID for Denver is MST while the three-letter ID for Phoenix is PNT (I'm unsure what PNT stands for as I found it appearing in a comment for Phoenix in the TimeZone.java source file. I suspect PNT was chosen because the more natural PST is already used for Pacific Standard Time).
getAvailableIDs () method, as of JDK 1.1.6, returns these three-letter names. However, this will probably change. Because the three-letter IDs differ in some respect to current standards, they've been replaced by longer and more meaningful names. Denver's new ID is called America/Denver while Phoenix's ID is called America/Phoenix.
This name change has been partially reflected in JDK 1.1.6.
getDefault () method obtains the default ID from the system properties. If this ID follows the new format, it will be remapped to the older (and less accurate) three-letter ID -- for compatibility reasons. The following code fragment calls
getID () to obtain the default time zone's ID and
getAvailableIDs () to obtain an array of supported IDs, which are then displayed to the user.
// Get the ID associated with the default time zone and display it.
System.out.println ("Default time zone ID: " + tz1.getID () + "\n");
// Get an array of IDs.
String  IDs = tz1.getAvailableIDs ();
// Display all of these IDs.
System.out.println ("Available IDs"); System.out.println ("=============\n");
for (int i = 0; i < IDs.length; i++) System.out.println (IDs [i]);
The next step involves calling the
getAvailableIDs (int) method to obtain an array of IDs for all geographical regions located in a time zone that are -10 hours from GMT. We also display these IDs to the user, as shown in the following code fragment.
// Get an array of IDs associated with a time zone -10 hours from GMT.
IDs = tz1.getAvailableIDs (-10 * millisInHour);
// Display all of these IDs.
System.out.println ("IDs associated with time zone -10 hours from GMT"); System.out.println ("================================================\n");
for (int i = 0; i < IDs.length; i++) System.out.println (IDs [i]);
Now, let's find out what offsets need to be added to GMT to obtain local time for someone living in the CST or EST time zones. The following code fragment calls the
getRawOffset () method, divided by the number of milliseconds in one hour -- 60 * 60 * 1000 -- to obtain these offsets (expressed as hours). Daylight savings time isn't compensated for when obtaining these offsets.
System.out.println (tz1.getID () + ": " + tz1.getRawOffset () / millisInHour);
System.out.println (tz2.getID () + ": " + tz2.getRawOffset () / millisInHour + "\n");
Finally, we examine the Hawaiian Standard Time (HST) and MST time zones to find out if they use daylight savings time. As it turns out, HST does not. The following code fragment calls
useDaylightTime () to obtain this information.
TimeZone tz = TimeZone.getTimeZone ("HST");
System.out.println ("HST (Hawaiian Standard Time): " + tz.useDaylightTime ());
tz = TimeZone.getTimeZone ("MST");
System.out.println ("MST (Mountain Standard Time): " + tz.useDaylightTime ());
Detailed information about
TimeZone is available in the following class reference, located at Sun's Java Web site: http://java.sun.com/products/jdk/1.1/docs/api/java.util.TimeZone.html.
As of JDK 1.1.6,
TimeZone has only one concrete subclass --
SimpleTimeZone. Objects instantiated from this class represent time zone offsets for use with the Gregorian calendar and can take daylight savings time into account. Although your code should normally work with the
TimeZone umbrella class (for maximum portability), it may come across a locale that has no time zone support. In this case, it would need to create a time zone object for this locale -- via