Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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}').

like image 550
Per Johansson Avatar asked Aug 19 '11 13:08

Per Johansson


1 Answers

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>
like image 197
BalusC Avatar answered Oct 14 '22 18:10

BalusC