JSF-Facelets custom date converter
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.
-
Format dates in dd-MMM-yyyy format.
-
Use yesterday, today or tomorrow instead of dd-MMM-yyyy for relative dates.
-
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.