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.