Tuesday, January 29, 2013

Global DateTimeConverter without separators

We are developing an application where data processing speed is important and users have to enter a lot of dates into the system. This is replacing a legacy Oracle Forms system where people were used to entering dates without any separators. For example they want to enter 120613 for June 12th 2013 (or December 6th 2013 if you're from the US).

ADF Faces (and MyFaces Trinidad) has its own DateTime converter that is already pretty advanced and lenient. You can give it a pattern and it will try to parse any input in several variations of the supplied pattern. For example, both "Jan/4/2004" and "01.4.2004" will be parsed with a pattern of "MMM/d/yyyy". For months it will try MMM, MM and M and it will try any of the separators - or . or /

But we wanted the converter to also try a pattern without any separators. You could specify a secondary pattern for a converter and it will also try to parse any input with this secondary pattern while still using the primary pattern for displaying the date/time. But I don't like the idea of manually setting a secondary pattern on each and every date field in the application.

So we replace the default DateTime converter with our own. This custom converter extends from the default ADF Faces/Trinidad converter:

public class LenientDateTimeConverter extends
       org.apache.myfaces.trinidadinternal.convert.DateTimeConverter

It can then override the getSecondaryPattern method and return a pattern without any separators:
@Override
    public String getSecondaryPattern() {
        final String secPattern = super.getSecondaryPattern();
        if (secPattern != null) {
            // use secondary pattern when explicitly set
            return secPattern;
        }
        // get primary pattern (or default if none defined)
        final DateFormat dateFormat =
            getDateFormat(FacesContext.getCurrentInstance(), getPattern(),
                          true, null);
        if (dateFormat instanceof SimpleDateFormat) {
            return getPatternWithoutSeparators(((SimpleDateFormat)dateFormat).toPattern());
        }
        return null;
    }

The final trick is to register this custom converter as the default one for the entire application. The challenge is that there are a number of ways the ADF/JSF framework looks for a datetime converter and we have to replace all of them. This requires the registration of a number of converters in the faces-config.xml file:

  <!-- for inputDates without explicit converter so they will use default JSF converter -->
  <converter>
    <display-name>Date Time Converter</display-name>
    <converter-id>javax.faces.DateTime</converter-id>
    <converter-class>com.redheap.datenosep.LenientDateTimeConverter</converter-class>
  </converter>

  <!-- for input- and output-components that use explicit af:convertDateTime -->
  <converter>
    <display-name>Date Time Converter</display-name>
    <converter-id>org.apache.myfaces.trinidad.DateTime</converter-id>
    <converter-class>com.redheap.datenosep.LenientDateTimeConverter</converter-class>
  </converter>

  <!-- for outputText without explicit converter who determine converter based on java class -->
  <converter>
    <display-name>Date Time Converter</display-name>
    <converter-for-class>java.util.Date</converter-for-class>
    <converter-class>com.redheap.datenosep.LenientDateTimeConverter</converter-class>
  </converter>
  <converter>
    <display-name>Date Time Converter</display-name>
    <converter-for-class>java.sql.Date</converter-for-class>
    <converter-class>com.redheap.datenosep.LenientDateTimeConverter</converter-class>
  </converter>

The sample application comes with a demo page to test the different locale and input types (date, time or both). Below are some screenshots that show the computed secondary pattern as well as the accepted and parsed input in both en_US and nl locale:
entering date without separators in en_US locale

finished entering date in en_US locale

entering date without separators in nl locale

finished entering date in nl locale
Sample application: DateWithoutSeparator
Source code repository: DateWithoutSeparator