JSF-Facelets custom date converter

13 June 2007

Peter Hilton

by Peter Hilton

This article describes how to implement a custom converter in JavaServer Faces (JSF) and Facelets that formats dates using relative dates for yesterday, today and tomorrow. The functionality is as follows.

  1. Format dates in dd-MMM-yyyy format.

  2. Use yesterday, today or tomorrow instead of dd-MMM-yyyy for relative dates.

  3. Optionally, do not include times when displaying dates.

This requires very little code in JSF, and even less when you use Facelets instead of JavaServer Pages (JSP). You need to:

  • write a date formatter

  • write a class that implements javax.faces.convert.Converter

  • declare the converter in faces-config.xml

  • register the converter for java.util.Date in faces-config.xml

  • declare the custom converter tag in a Facelets tag library descriptor.

Note that you do not need a separate tag handler class, as you would if you were using JSF with JSP.

Date formatter class

The following code is the self-explanatory helper class that actually formats the date and time.

public class DateUtil {
   final public static String DATE_FORMAT = "dd-MMM-yyyy";
   final public static String TIME_FORMAT = "HH:mm";
   /**
    * Display a calendar as a string and it can be 'today', 'yesterday' or 'tomorrow'.
    * Note that this implentation doesn't handle today/tomorrow across the new year.
    *
    * @param showTime toggles whether the time is displayed as well
    */
   public static String formatRelativeDate(final Date date, final boolean showTime) {
      Calendar calendar = Calendar.getInstance();
      calendar.setTime(date);

      Calendar now = Calendar.getInstance();
      final int today = now.get(Calendar.DAY_OF_YEAR);
      final SimpleDateFormat timeFormat = new SimpleDateFormat(" " + TIME_FORMAT);
      final String time = showTime ? timeFormat.format(calendar.getTime()) : "";

      if (now.get(Calendar.YEAR) == calendar.get(Calendar.YEAR)) {
         if (today == calendar.get(Calendar.DAY_OF_YEAR)) {
            return "today" + time;
         }
         else if (today - 1 == calendar.get(Calendar.DAY_OF_YEAR)) {
            return "yesterday" + time;
         }
         else if (today + 1 == calendar.get(Calendar.DAY_OF_YEAR)) {
            return "tomorrow" + time;
         }
      }
      final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
      return dateFormat.format(date) + time;
   }
}

Converter implementation class

The following class implements javax.faces.convert.Converter, excluding the unimplemented getAsObject(FacesContext,UIComponent,String) method.

The CONVERTER_ID constant defines a logical name for the converter which you use to refer to this converter in configuration files and the view templates.

/**
 * Converter for formatting dates as relative dates (yesterday, today, tomorrow)
 * with a time zone indicator.
 */
public class RelativeDateTimeConverter implements Converter {
   public static final String CONVERTER_ID = "RelativeDateTime";
   private String showTime;
   public RelativeDateTimeConverter() {
      setShowTime("true");
   }
   public String getAsString(FacesContext context, UIComponent c, Object object)
   throws ConverterException {
      final Date date = (Date) object;
      return date == null ? "" : DateUtil.display(date, "true".equals(getShowTime()));
   }
   public String getShowTime() {
      return showTime;
   }
   public void setShowTime(String showTime) {
      this.showTime = showTime;
   }
}

Note that there are two unresolved issues in this version. First, it should be possible to specify the showTime property’s default value in faces-config.xml instead of here, in the constructor. Second, this property is currently a String instead of a Boolean.

Declare the converter

The code above is all of the Java code you need. Now you need to declare the converter, by adding the following to faces-config.xml

<converter>
   <description>Converter for formatting dates as relative dates
   (yesterday, today, tomorrow) with a time zone indicator.</description>
   <converter-id>RelativeDateTime</converter-id>
   <converter-class>myproject.converters.RelativeDateTimeConverter</converter-class>
   <property>
      <property-name>showTime</property-name>
      <property-class>java.lang.String</property-class>
   </property>
</converter>

Once you have done this, you can now use the converter to format a date object of type java.util.Date in your view in two ways:

<h:outputText value="#{date}" converter="RelativeDateTime"/>

<h:outputText value="#{date}">
   <f:converter converterId="RelativeDateTime"/>
</h:outputText>

Register the converter for java.util.Date

If you register the converter, you do not have to explicitly specify it in the view templates, as in the h:outputText examples above. Add the following to faces-config.xml

<converter>
   <converter-for-class>java.util.Date</converter-for-class>
   <converter-class>myproject.converters.RelativeDateTimeConverter</converter-class>
</converter>

The converter will now be used automatically when you do:

<h:outputText value="#{date}"/>

Define the Facelets tag

This converter has a showTime so we will need to use a custom tag if we want to set this to a value other than its default. Unlike JSP, Facelets does not require a tag handler class to define a tag. Instead, simply use a tag library description as follows, which Facelets will use to create a tag and auto-wire the converter’s properties.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE facelet-taglib PUBLIC
   "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
   "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib>
   <namespace>http://lunatech.com/</namespace>
   <tag>
      <tag-name>relativeDateTimeConverter</tag-name>
      <converter>
         <converter-id>RelativeDateTime</converter-id>
      </converter>
   </tag>
</facelet-taglib>

Use a file name ending in .taglib.xml and put it in the /META-INF directory of one of your application’s JARs.

Now you can display the date without the time:

<ui:composition
   xmlns="http://www.w3.org/1999/xhtml"
   xmlns:luna="http://lunatech.com/">
<h:outputText value="#{date}">
   <luna:relativeDateTimeConverter showTime="false"/>
</h:outputText>

For the tag to work, the file’s namespace must match the namespace declared in the tag library descriptor.

Seam @Converter annotation

Note that if you are using Seam, you can do this more simply using the @Converter annotation.