Wednesday, May 8, 2013

JDK 8's Calendar.Builder

One of the defining characteristics of the brave new world of Java is the increasing prevalence of the builder pattern in the Java space. Groovy, which appears to be the most popular alternative language (to Java) on the JVM, is well-known for its heavy use of the Builder in both the core libraries and in Groovy-supported libraries and frameworks. Josh Bloch brought the pattern to the forefront of Java developer community mindset with coverage of the pattern in Item #2 of the second edition of his highly influential Effective Java. There have been several builders added to the JDK including the addition of Locale.Builder in J2SE 1.7. In this post, I briefly introduce Calendar.Builder coming to JDK 8.

Today, a Java developer typically populates an instance of the Calendar class by either calling one of the "set" methods that accepts a lengthy list of content for the instance or by calling individual "set" methods on the instance one after another. These two typical approaches for populating a Calendar instance are demonstrated in the next two code listings.

Populating Calendar with Single 'set' Method
/**
 * Demonstrate pre-JDK 8 method of instantiating Calendar instance using
 * "set" method for main fields.
 */
public static void demoCalendarWithSingleSet()
{
   final Calendar calendar =
      Calendar.getInstance(TimeZone.getTimeZone(timeZoneId),
         ENGLISH);
   calendar.set(2013, APRIL, 6, 15, 45, 22);
   out.println("Calendar via Constructor: " + stringifyCalendar(calendar));
}
Populating Calendar with Multiple Individual 'set' Methods
/**
 * Demonstrate pre-JDK 8 method of instantiating Calendar instance using
 * individual "set" calls for each pair of field names and values.
 */
public static void demoCalendarWithIndividualSets()
{
   final Calendar calendar =
      Calendar.getInstance(
         TimeZone.getTimeZone(timeZoneId),
         ENGLISH);
   calendar.set(YEAR, 2013);
   calendar.set(MONTH, APRIL);
   calendar.set(DATE, 6);
   calendar.set(HOUR, 3);
   calendar.set(MINUTE, 45);
   calendar.set(SECOND, 22);
   calendar.set(AM_PM, PM);
   out.println("Calendar via set methods: " + stringifyCalendar(calendar));
}

SIDE NOTE: In both of the examples above, I used another increasingly popular feature of modern Java: the static import. The constants such as ENGLISH, YEAR, and SECOND are actually statically imported from classes such as Locale and Calendar. As I have previously written, static imports seem to be increasingly popular with Java developers, especially in light of the trend toward fluent interfaces.

The two "traditional" approaches shown above show different ways to populate the Calendar instance. One extreme is to set each individual field separately while the other is to set all the significant fields with a single "set" method. There are advantages to each approach. The single "set" method has fewer states of an "unfinished" object than the multiple-set approach, but the multiple-set approach is more readable because the name of the value being set is clear based on the first parameter to each "set" method. The single-set approach is a little unwieldy because it takes six integers that can be easily mixed up in order passed because there is no obvious way to differentiate which integer is which other than the implicit order.

Calendar.Builder leverages on the advertised benefits of the Builder as described by Bloch: removes the existence of "inconsistent states partway through [an object's] construction." This is demonstrated in the next code listing.

Calendar.Builder Allows Single-Statement Instantiation with Readable Settings
   /**
    * Demonstrate using JDK 8's Calendar.Builder to instantiate an instance of
    * Calendar using the set methods to set each field individually based on
    * field name and value.
    */
   public static void demoCalendarWithCalendarBuilderSetFields()
   {
      final Calendar calendar =
         new Calendar.Builder()
            .set(YEAR, 2013)
            .set(MONTH, APRIL)
            .set(DATE, 6)
            .set(HOUR, 15)
            .set(MINUTE, 45)
            .set(SECOND, 22)
            .setTimeZone(TimeZone.getTimeZone(timeZoneId))
            .setLocale(ENGLISH)
            .build();
      out.println(
           "Calendar via Calendar.Builder 'set' Fields: "
         + stringifyCalendar(calendar));
   }

In the above code listing, the Calendar instance is created AND populated in one statement, removing the need to risk an object being in an inconsistent state across multiple statements. This example retains the readability of the traditional individual "set" methods approach [set(int, int)] with the added safety of having the object populated fully immediately at instantiation.

For developers who want to provide fewer individual "set" methods, another opportunity with Calendar.Builder is to use the setDate(int, int, int) and setTimeOfDay(int, int, int) methods as demonstrated in the next code listing.

Calendar.Builder Setting Date and Time as Two Calls
/**
 * Demonstrate using JDK 8's Calendar.Builder to instantiate an instance of
 * Calendar using the "setDate" and "setTimeOfDay" builder methods.
 */
public static void demoCalendarWithCalendarBuilderSetDaySetTime()
{
   final Calendar calendar =
      new Calendar.Builder()
         .setDate(2013, APRIL, 6)
         .setTimeOfDay(15, 45, 22)
         .setTimeZone(TimeZone.getTimeZone(timeZoneId))
         .setLocale(ENGLISH).build();
   out.println(
        "Calendar via Calendar.Builder setDate/setTimeOfDay: "
      + stringifyCalendar(calendar));
}

There are fewer characters and lines to type with this approach, but it partially reintroduces the disadvantage of being more likely to have an integer parameter inadvertently switched as each of the two methods takes three integers (or an overloaded version of setTimeOfDay() will take a fourth integer representing milliseconds).

For developers wanting the ultimate flexibility in specifying Calendar parameters during its instantiation, Calendar.Builder provides the method setFields(int ...) that takes an arbitrary length of pairs of integers with the first integer of the pair representing the field to be set and the second integer of the pair representing the value for that field. This method is used in the next code listing.

Specifying Calendar Fields via Calendar.Builder's setFields Method
   /**
    * Demonstrate using JDK 8's Calendar.Builder to instantiate an instance of
    * Calendar using the setFields method that allows providing of Calendar
    * fields as key/value pairs.
    */
   public static void demoCalendarWithCalendarBuilderSetPairs()
   {
      final Calendar calendar =
         new Calendar.Builder()
            .setFields(YEAR, 2013, MONTH, APRIL, DATE, 6, HOUR, 15, MINUTE, 45, SECOND, 22)
            .setTimeZone(TimeZone.getTimeZone("timeZoneId"))
            .setLocale(ENGLISH)
            .build();
      out.println(
           "Calendar via Calendar.Builder setPairs: "
         + stringifyCalendar(calendar));
   }

This setFields(int ...) method brings greater risk of mangling the order of integers used for instantiation of the new instance of Calendar, but using the statically imported Calendar constants does improve readability and reduces the likelihood of mixing the integers incorrectly. If an odd number of integers is provided (meaning that there is an incomplete pair), an IllegalArgumentException is thrown.

Although Calendar.Builder provides some convenience in instantiating and populating an instance of Calendar, anyone fortunate enough to adopt JDK 8 will have access to the new date/time API and so the question might be asked, "Why use Calendar.Builder?" Perhaps the best answer is that there are millions of lines of existing code, libraries, and frameworks out there using and expecting Calendar instances, so it will probably be a long time before the need for Calendar is completely gone (if ever). Fortunately, Calandar.Builder makes it easy to covert an instance of Instant (part of the new Java data/time API) into a Calendar via CalendarBulder.setInstant(long). This is demonstrated in the next code listing.

Converting Instant to Calendar with Calendar.Builder
   /**
    * Demonstrate using JDK 8's Calendar.Builder to instantiate an instance of
    * Calendar based on "now" Instant.
    */
   public static void demoCalendarWithCalendarBuilderInstant()
   {
      final Calendar calendar =
         new Calendar.Builder().setInstant(Instant.now().toEpochMilli())
            .setTimeZone(TimeZone.getTimeZone(timeZoneId))
            .setLocale(ENGLISH)
            .build();
      out.println("Calendar via Calendar.Builder and Instant: " + stringifyCalendar(calendar));
   }

Note that an overloaded version of the setInstant method accepts a Date for instantiating a Calendar. In both cases, whether instantiated with setInstant(long) or setInstant(Date), no other "set" method on Calender.Builder should be called to avoid an IllegalStateException.

It is easy to go the other direction (getting an Instant from a Calendar) using Calendar.toInstant(). Other methods introduced to Calendar with JDK 1.8 are related to providing the current instance's calendar type (as a String) or the set of available calendar types (Set of Strings). When I run Calendar.getAvailableCalendarTypes() on my system, I see these three Strings: "gregory", "japanese", and "buddhist" (the same three calendars documented in Supported Calendars)

Conclusion

Like many Java developers, I'm looking forward to an improved Java data/time API that is built into the standard Java Development Kit. However, I also realize that, especially in large code bases and when using libraries and frameworks expecting Calendar or Date, that I won't be free of Calendar and Date for some time. The introduction of Calendar.Builder in JDK 8 eases that burden a bit.

No comments: