Here is an ObjectStringConverter implementation that will convert java.util.Date into the ISO 8601 format used in the XML Schema Definition types xsd:dateTime, xsd:date, and xsd:time, depending on the specified flavour.

import java.text.*;
import java.util.*;
import org.apache.commons.betwixt.expression.*;
import org.apache.commons.betwixt.strategy.*;

/**
 * @author Jesse Sweetland
 */
public class DateObjectStringConverter extends DefaultObjectStringConverter {
    private static final DateFormat PSUEDO_ISO8601_DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    private static final DateFormat PSUEDO_ISO8601_DATETIME_FORMAT_TZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
    private static final DateFormat PSUEDO_ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    private static final DateFormat PSUEDO_ISO8601_DATE_FORMAT_TZ = new SimpleDateFormat("yyyy-MM-ddZ");
    private static final DateFormat PSUEDO_ISO8601_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
    private static final DateFormat PSUEDO_ISO8601_TIME_FORMAT_TZ = new SimpleDateFormat("HH:mm:ssZ");
    
    protected String getFlavor(Context context) {
        return context.getOptions().getValue(FLAVOUR_OPTION_NAME);
    }
    
    public String objectToString(Object object, Class type, Context context) {
        String flavor = getFlavor(context);
        if("xsd:date".equals(flavor)){
            return toIso8601Date((Date)object);
        } else if("xsd:time".equals(flavor)){
            return toIso8601Time((Date)object);
        } else {
            // Assume xsd:dateTime if not specified
            return toIso8601DateTime((Date)object);
        }
    }
    
    public Object stringToObject(String value, Class type, Context context) {
        String flavor = getFlavor(context);
        if("xsd:date".equals(flavor)){
            return fromIso8601Date(value);
        } else if("xsd:time".equals(flavor)){
            return fromIso8601Time(value);
        } else {
            // Assume xsd:dateTime if not specified
            return fromIso8601DateTime(value);
        }
    }

    private String toIso8601DateTime(Date dateTime) {
        if(dateTime == null) return null;
        StringBuilder psuedoIso8601DateTime = new StringBuilder(PSUEDO_ISO8601_DATETIME_FORMAT_TZ.format(dateTime));
        String iso8601DateTime = psuedoIso8601DateTime.insert(psuedoIso8601DateTime.length() - 2, ':').toString();
        return iso8601DateTime;
    }
    
    private String toIso8601Date(Date date) {
        if(date == null) return null;
        StringBuilder psuedoIso8601Date = new StringBuilder(PSUEDO_ISO8601_DATE_FORMAT_TZ.format(date));
        String iso8601Date = psuedoIso8601Date.insert(psuedoIso8601Date.length() - 2, ':').toString();
        return iso8601Date;
    }
 
    private String toIso8601Time(Date time) {
        if(time == null) return null;
        StringBuilder psuedoIso8601Time = new StringBuilder(PSUEDO_ISO8601_TIME_FORMAT_TZ.format(time));
        String iso8601Time = psuedoIso8601Time.insert(psuedoIso8601Time.length() - 2, ':').toString();
        return iso8601Time;
    }
    
    private Date fromIso8601DateTime(String iso8601DateTime) {
        if(iso8601DateTime == null) return null;
        try {
            iso8601DateTime = iso8601DateTime.trim();
            boolean tzPresent = iso8601DateTime.length() > 19;
            if(tzPresent) {
                String psuedoIso8601DateTime;
                if(iso8601DateTime.charAt(19) == 'Z') {
                    psuedoIso8601DateTime = new StringBuilder(iso8601DateTime.substring(0, 19)).append("-0000").toString();
                } else {
                    psuedoIso8601DateTime = new StringBuilder(iso8601DateTime).deleteCharAt(22).toString();
                }
                return PSUEDO_ISO8601_DATETIME_FORMAT_TZ.parse(psuedoIso8601DateTime);
            } else {
                return PSUEDO_ISO8601_DATETIME_FORMAT.parse(iso8601DateTime);
            }
        } catch(Throwable t) {
            throw new IllegalArgumentException(t);
        }
    }
    
    private Date fromIso8601Date(String iso8601Date) {
        if(iso8601Date == null) return null;
        try {
            iso8601Date = iso8601Date.trim();
            boolean tzPresent = iso8601Date.length() > 10;
            if(tzPresent) {
                String psuedoIso8601Date;
                if(iso8601Date.charAt(10) == 'Z') {
                    psuedoIso8601Date = new StringBuilder(iso8601Date.substring(0, 10)).append("-0000").toString();
                } else {
                    psuedoIso8601Date = new StringBuilder(iso8601Date).deleteCharAt(13).toString();
                }
                return PSUEDO_ISO8601_DATE_FORMAT_TZ.parse(psuedoIso8601Date);
            } else {
                return PSUEDO_ISO8601_DATE_FORMAT.parse(iso8601Date);
            }
        } catch(Throwable t) {
            throw new IllegalArgumentException(t);
        }
    }
 
    private Date fromIso8601Time(String iso8601Time) {
        if(iso8601Time == null) return null;
        try {
            iso8601Time = iso8601Time.trim();
            boolean tzPresent = iso8601Time.length() > 8;
            if(tzPresent) {
                String psuedoIso8601Time;
                if(iso8601Time.charAt(8) == 'Z') {
                    psuedoIso8601Time = new StringBuilder(iso8601Time.substring(0, 8)).append("-0000").toString();
                } else {
                    psuedoIso8601Time = new StringBuilder(iso8601Time).deleteCharAt(11).toString();
                }
                return PSUEDO_ISO8601_TIME_FORMAT_TZ.parse(psuedoIso8601Time);
            } else {
                return PSUEDO_ISO8601_TIME_FORMAT.parse(iso8601Time);
            }
        } catch(Throwable t) {
            throw new IllegalArgumentException(t);
        }
    }
    
    public static void main(String[] args) {
        DateObjectStringConverter dosc = new DateObjectStringConverter();
        
        Date now = new Date();
        
        System.out.println(now + " => " + dosc.toIso8601DateTime(now));
        System.out.println(now + " => " + dosc.toIso8601Date(now));
        System.out.println(now + " => " + dosc.toIso8601Time(now));
        
        String dateTime = "2006-08-09T13:26:50";
        System.out.println(dateTime + " => " + dosc.fromIso8601DateTime(dateTime));
        
        String dateTimeTz = "2006-08-09T13:26:50-05:00";
        System.out.println(dateTimeTz + " => " + dosc.fromIso8601DateTime(dateTimeTz));
        
        String dateTimeUtc = "2006-08-09T13:26:50Z";
        System.out.println(dateTimeUtc + " => " + dosc.fromIso8601DateTime(dateTimeUtc));
        
        String date = "2006-08-09";
        System.out.println(date + " => " + dosc.fromIso8601Date(date));
        
        String dateTz = "2006-08-09-05:00";
        System.out.println(date + " => " + dosc.fromIso8601Date(dateTz));
        
        String dateUtc = "2006-08-09Z";
        System.out.println(date + " => " + dosc.fromIso8601Date(dateUtc));
        
        String time = "13:26:50";
        System.out.println(time + " => " + dosc.fromIso8601Time(time));
        
        String timeTz = "13:26:50-05:00";
        System.out.println(timeTz + " => " + dosc.fromIso8601Time(timeTz));
        
        String timeUtc = "13:26:50Z";
        System.out.println(timeUtc + " => " + dosc.fromIso8601Time(timeUtc));
    }
}

The .betwixt mapping documents will look something like this:

<?xml version="1.0"?>
<info>
    <element name="myElement">
        <element name="dateTime" property="dateTime">
            <option>
                <name>org.apache.commons.betwixt.flavour</name>
                <value>xsd:dateTime</value>
            </option>
        </element>
        <element name="date" property="date">
            <option>
                <name>org.apache.commons.betwixt.flavour</name>
                <value>xsd:date</value>
            </option>
        </element>
        <element name="time" property="time">
            <option>
                <name>org.apache.commons.betwixt.flavour</name>
                <value>xsd:time</value>
            </option>
        </element>
    </element>
</info>

Note that if no flavor is specified, it defaults to dateTime format. Also, when writing dates, the timezone is always specified (which is highly recommended).

Betwixt allows options to be specified on attributes, and those options are actually parsed and stored on the attribute, but the context that is passed to ObjectStringConverters is the element context, and so the options on that context apply to the element and not the attribute. If all of your attributes only use one flavor of date/time, then you can move the option up to the element level. If, however, you need to support multiple flavors across many attributes, here is a hacked version of the ObjectStringConverter above that "guesses" the correct XSD format. The algorithms used to guess formats are as follows:

objectToString

stringToObject

import java.text.*;
import java.util.*;
import org.apache.commons.betwixt.expression.*;
import org.apache.commons.betwixt.strategy.*;

/**
 *
 * @author jessesw
 */
public class DateObjectStringConverter extends DefaultObjectStringConverter {
    private static final DateFormat PSUEDO_ISO8601_DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    private static final DateFormat PSUEDO_ISO8601_DATETIME_FORMAT_TZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
    private static final DateFormat PSUEDO_ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    private static final DateFormat PSUEDO_ISO8601_DATE_FORMAT_TZ = new SimpleDateFormat("yyyy-MM-ddZ");
    private static final DateFormat PSUEDO_ISO8601_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
    private static final DateFormat PSUEDO_ISO8601_TIME_FORMAT_TZ = new SimpleDateFormat("HH:mm:ssZ");
    
    public String objectToString(Object object, Class type, Context context) {
        if(object == null) return null;
        
        if(object instanceof java.sql.Time) {
            return toIso8601Time((Date)object);
        } else if(object instanceof java.sql.Date) {
            return toIso8601Date((Date)object);
        } else if(object instanceof java.util.Date) {
            return toIso8601DateTime((Date)object);
        } else {
            return super.objectToString(object, type, context);
        }
    }
    
    public Object stringToObject(String value, Class type, Context context) {
        if(value == null) return null;

        if(value.trim().indexOf(':') == 2) {
            return fromIso8601Time(value);
        } else if((value.trim().indexOf('-') == 4) && (value.trim().indexOf('T') < 0)) {
            return fromIso8601Date(value);
        } else if(value.trim().indexOf('T') == 10) {
            return fromIso8601DateTime(value);
        } else {
            return super.stringToObject(value, type, context);
        }
    }

    private String toIso8601DateTime(Date dateTime) {
        if(dateTime == null) return null;
        StringBuilder psuedoIso8601DateTime = new StringBuilder(PSUEDO_ISO8601_DATETIME_FORMAT_TZ.format(dateTime));
        String iso8601DateTime = psuedoIso8601DateTime.insert(psuedoIso8601DateTime.length() - 2, ':').toString();
        return iso8601DateTime;
    }
    
    private String toIso8601Date(Date date) {
        if(date == null) return null;
        StringBuilder psuedoIso8601Date = new StringBuilder(PSUEDO_ISO8601_DATE_FORMAT_TZ.format(date));
        String iso8601Date = psuedoIso8601Date.insert(psuedoIso8601Date.length() - 2, ':').toString();
        return iso8601Date;
    }
 
    private String toIso8601Time(Date time) {
        if(time == null) return null;
        StringBuilder psuedoIso8601Time = new StringBuilder(PSUEDO_ISO8601_TIME_FORMAT_TZ.format(time));
        String iso8601Time = psuedoIso8601Time.insert(psuedoIso8601Time.length() - 2, ':').toString();
        return iso8601Time;
    }
    
    private Date fromIso8601DateTime(String iso8601DateTime) {
        if(iso8601DateTime == null) return null;
        try {
            iso8601DateTime = iso8601DateTime.trim();
            boolean tzPresent = iso8601DateTime.length() > 19;
            if(tzPresent) {
                String psuedoIso8601DateTime;
                if(iso8601DateTime.charAt(19) == 'Z') {
                    psuedoIso8601DateTime = new StringBuilder(iso8601DateTime.substring(0, 19)).append("-0000").toString();
                } else {
                    psuedoIso8601DateTime = new StringBuilder(iso8601DateTime).deleteCharAt(22).toString();
                }
                return PSUEDO_ISO8601_DATETIME_FORMAT_TZ.parse(psuedoIso8601DateTime);
            } else {
                return PSUEDO_ISO8601_DATETIME_FORMAT.parse(iso8601DateTime);
            }
        } catch(Throwable t) {
            throw new IllegalArgumentException(t);
        }
    }
    
    private Date fromIso8601Date(String iso8601Date) {
        if(iso8601Date == null) return null;
        try {
            iso8601Date = iso8601Date.trim();
            boolean tzPresent = iso8601Date.length() > 10;
            if(tzPresent) {
                String psuedoIso8601Date;
                if(iso8601Date.charAt(10) == 'Z') {
                    psuedoIso8601Date = new StringBuilder(iso8601Date.substring(0, 10)).append("-0000").toString();
                } else {
                    psuedoIso8601Date = new StringBuilder(iso8601Date).deleteCharAt(13).toString();
                }
                return PSUEDO_ISO8601_DATE_FORMAT_TZ.parse(psuedoIso8601Date);
            } else {
                return PSUEDO_ISO8601_DATE_FORMAT.parse(iso8601Date);
            }
        } catch(Throwable t) {
            throw new IllegalArgumentException(t);
        }
    }
 
    private Date fromIso8601Time(String iso8601Time) {
        if(iso8601Time == null) return null;
        try {
            iso8601Time = iso8601Time.trim();
            boolean tzPresent = iso8601Time.length() > 8;
            if(tzPresent) {
                String psuedoIso8601Time;
                if(iso8601Time.charAt(8) == 'Z') {
                    psuedoIso8601Time = new StringBuilder(iso8601Time.substring(0, 8)).append("-0000").toString();
                } else {
                    psuedoIso8601Time = new StringBuilder(iso8601Time).deleteCharAt(11).toString();
                }
                return PSUEDO_ISO8601_TIME_FORMAT_TZ.parse(psuedoIso8601Time);
            } else {
                return PSUEDO_ISO8601_TIME_FORMAT.parse(iso8601Time);
            }
        } catch(Throwable t) {
            throw new IllegalArgumentException(t);
        }
    }
}

Betwixt/TipsAndHints/XsdDateTime (last edited 2009-09-20 23:48:24 by localhost)