JSF convertDateTime with timezone in datatable
Trying to output a list of items in a datatable, like this:
<t:dataTable value="#{mybean.list}" var="item">
<h:column>
<h:outputText value="#{item.time}">
<f:convertDateTime pattern="yyyy-MM-dd HH:mm:ssZ" timeZone="#{item.timeZone}" />
</h:outputText>
</h:column>
</t:dataTable>
It always formats the time in GMT. It works as expected if I use a string constant or a bean which isn't the datatable variable (like '#{mybean.timeZone}').
Solution 1:
Unfortunately, that's the nature of <f:xxx>
tags. When the view is to be built, a single instance of the tag is been built where the converter is instantiated. All of its attribtues are been read and set only once. At the moment the view is been built, the #{item}
resolves to null
(it's only available during rendering of the view), so the timeZone
attribute will be null
and then default to UTC. When the view is to be rendered, the very same converter instance is been reused for each row of the table.
There are several ways to solve this. I can think of a custom converter or an EL function. I think a custom converter is after all the best as it can then also be reused in input components. The following kickoff example should work out for you (nullchecks and on omitted for brevity):
@FacesConverter("extendedDateTimeConverter")
public class ExtendedDateTimeConverter extends DateTimeConverter {
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
setPattern((String) component.getAttributes().get("pattern"));
setTimeZone(TimeZone.getTimeZone((String) component.getAttributes().get("timeZone")));
return super.getAsObject(context, component, value);
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
setPattern((String) component.getAttributes().get("pattern"));
setTimeZone(TimeZone.getTimeZone((String) component.getAttributes().get("timeZone")));
return super.getAsString(context, component, value);
}
}
which can be used as
<h:outputText value="#{item.time}">
<f:converter converterId="extendedDateTimeConverter" />
<f:attribute name="pattern" value="yyyy-MM-dd HH:mm:ssZ" />
<f:attribute name="timeZone" value="#{item.timeZone}" />
</h:outputText>
This way the timezone is resolved everytime the converter is invoked instead of during its construction.
Update: the OmniFaces <o:converter>
solves exactly this problem without the need for a custom converter.
<h:outputText value="#{item.time}">
<o:converter converterId="javax.faces.DateTime" pattern="yyyy-MM-dd HH:mm:ssZ" timeZone="#{item.timeZone}" />
</h:outputText>