Joda Time

Many developers prefer to use Joda Time instead of Java's date and time classes.

This page demonstrates the JodaTimeOutput component. With JodaTimeOutput you can specify a style or a formatter.
In this example we get a row from database table DatesExample into an object called datesExample and display a field called aDateMidnight several ways...
Example Output How Achieved
2024-03-20T00:00:00.000Z $ {datesExample.aDateMidnight}
Wednesday, March 20, 2024 <t:jodatimeoutput value="datesExample.aDateMidnight" style="F-"/>
March 20, 2024 <t:jodatimeoutput value="datesExample.aDateMidnight" style="L-"/>
Mar 20, 2024 <t:jodatimeoutput value="datesExample.aDateMidnight" style="M-"/>
3/20/24 <t:jodatimeoutput value="datesExample.aDateMidnight" style="S-"/>
mercredi 20 mars 2024 <t:jodatimeoutput value="datesExample.aDateMidnight" formatter="french"/>
2024-03-20 <t:jodatimeoutput value="datesExample.aDateMidnight" formatter="ISODate"/>
20 Mar 2024 <t:jodatimeoutput value="datesExample.aDateMidnight" pattern="dd MMM yyyy"/>
Notes: JodaTimeOutput can render any DateTime, DateMidnight, LocalDateTime, LocalDate, or LocalTime object. Here is a demonstration:
Class Example Field Using Expansion Using JodaTimeOutput Style Used
DateTime datesExample.aDateTime 2001-07-31T10:35:17.000Z July 31, 2001 10:35:17 AM UTC style="LL"
DateMidnight datesExample.aDateMidnight 2024-03-20T00:00:00.000Z March 20, 2024 style="L-"
LocalDateTime datesExample.aLocalDateTime 2001-07-31T10:35:17.000 July 31, 2001 10:35:17 AM style="LL"
LocalDate datesExample.aLocalDate 2001-07-24 July 24, 2001 style="L-"
LocalTime datesExample.aLocalTimeAsTime 10:35:17.000 10:35:17 AM style="-L"
We do more with Joda Time in the Type Coercer and Property Editors examples.

Home

The source for IPersonFinderServiceLocal and @EJB is shown in the Session Beans and @EJB examples.


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like &nbsp; 
     We use a "strict" DTD to make IE follow the alignment rules. -->
     
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<body class="container">
    <h3>Joda Time</h3>
    Many developers prefer to use <a href="http://joda-time.sourceforge.net/">Joda Time</a> instead of Java's date and time classes.<br/><br/>
    
    This page demonstrates the <strong>JodaTimeOutput</strong> component. With JodaTimeOutput you can specify a style or a formatter.<br/> 
    In this example we get a row from database table DatesExample into an object called datesExample and display a field called 
    aDateMidnight several ways...<br/>
    
    <div class="eg">
        <table>
            <tr>
                <th>Example Output</th>
                <th>How Achieved</th>
            </tr>
            <tr>
                <td>${datesExample.aDateMidnight}</td>
                <td>$ {datesExample.aDateMidnight}</td>
            </tr>
            <tr>
                <td><t:jodatimeoutput value="datesExample.aDateMidnight" style="F-"/></td>
                <td>&lt;t:jodatimeoutput value="datesExample.aDateMidnight" style="F-"/></td>
            </tr>
            <tr>
                <td><t:jodatimeoutput value="datesExample.aDateMidnight" style="L-"/></td>
                <td>&lt;t:jodatimeoutput value="datesExample.aDateMidnight" style="L-"/></td>
            </tr>
            <tr>
                <td><t:jodatimeoutput value="datesExample.aDateMidnight" style="M-"/></td>
                <td>&lt;t:jodatimeoutput value="datesExample.aDateMidnight" style="M-"/></td>
            </tr>
            <tr>
                <td><t:jodatimeoutput value="datesExample.aDateMidnight" style="S-"/></td>
                <td>&lt;t:jodatimeoutput value="datesExample.aDateMidnight" style="S-"/></td>
            </tr>
            <tr>
                <td><t:jodatimeoutput value="datesExample.aDateMidnight" formatter="french"/></td>
                <td>&lt;t:jodatimeoutput value="datesExample.aDateMidnight" formatter="french"/></td>
            </tr>
            <tr>
                <td><t:jodatimeoutput value="datesExample.aDateMidnight" formatter="ISODate"/></td>
                <td>&lt;t:jodatimeoutput value="datesExample.aDateMidnight" formatter="ISODate"/></td>
            </tr>
            <tr>
                <td><t:jodatimeoutput value="datesExample.aDateMidnight" pattern="dd MMM yyyy"/></td>
                <td>&lt;t:jodatimeoutput value="datesExample.aDateMidnight" pattern="dd MMM yyyy"/></td>
            </tr>
        </table>
    </div>

    Notes:
    <ul>
        <li>aDateMidnight is actually stored in the DatesExample table as an SQL DATE but the DatesExample object exposes it as a 
        <strong>DateMidnight</strong> field.</li>
        <li><strong>style</strong> is any joda-time
        <a href="http://joda-time.sourceforge.net/api-release/org/joda/time/format/DateTimeFormat.html#forStyle(java.lang.String)">
        Style</a> pattern.</li>
        <li><strong>formatter</strong> is any joda-time
        <a href="http://joda-time.sourceforge.net/api-release/org/joda/time/format/DateTimeFormatter.html">DateTimeFormatter</a>.</li>
        <li><strong>pattern</strong> is any joda-time
        <a href="http://joda-time.sourceforge.net/api-release/org/joda/time/format/DateTimeFormat.html#forPattern(java.lang.String)">
        pattern</a> string.</li>
    </ul>

    JodaTimeOutput can render any DateTime, DateMidnight, LocalDateTime, LocalDate, or LocalTime object. Here is a demonstration:<br/>
    
    <div class="eg">
        <table>
            <tr>
                <th>Class</th>
                <th>Example Field</th>
                <th>Using Expansion</th>
                <th>Using JodaTimeOutput</th>
                <th>Style Used</th>
            </tr>
            <tr>
                <td>DateTime</td>
                <td>datesExample.aDateTime</td>
                <td>${datesExample.aDateTime}</td>
                <td><t:jodatimeoutput value="datesExample.aDateTime" style="LL"/></td>
                <td>style="LL"</td>
            </tr>
            <tr>
                <td>DateMidnight</td>
                <td>datesExample.aDateMidnight</td>
                <td>${datesExample.aDateMidnight}</td>
                <td><t:jodatimeoutput value="datesExample.aDateMidnight" style="L-"/></td>
                <td>style="L-"</td>
            </tr>
            <tr>
                <td>LocalDateTime</td>
                <td>datesExample.aLocalDateTime</td>
                <td>${datesExample.aLocalDateTime}</td>
                <td><t:jodatimeoutput value="datesExample.aLocalDateTime" style="LL"/></td>
                <td>style="LL"</td>
            </tr>
            <tr>
                <td>LocalDate</td>
                <td>datesExample.aLocalDate</td>
                <td>${datesExample.aLocalDate}</td>
                <td><t:jodatimeoutput value="datesExample.aLocalDate" style="L-"/></td>
                <td>style="L-"</td>
            </tr>
            <tr>
                <td>LocalTime</td>
                <td>datesExample.aLocalTimeAsTime</td>
                <td>${datesExample.aLocalTimeAsTime}</td>
                <td><t:jodatimeoutput value="datesExample.aLocalTimeAsTime" style="-L"/></td>
                <td>style="-L"</td>
            </tr>
        </table>
    </div>

    We do more with Joda Time in the <em>Type Coercer</em> and <em>Property Editors</em> examples.<br/><br/>
    
    <t:pagelink page="Index">Home</t:pagelink><br/><br/>
    
    The source for IPersonFinderServiceLocal and @EJB is shown in the Session Beans and @EJB examples.<br/><br/>

    <t:tabgroup>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/jodatime/JodaTime.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/jodatime/JodaTime.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/jodatime.css"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/JodaTimeOutput.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/datestuff/DateStuffService.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/datestuff/DatesExample.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/util/JodaTimeUtil.java"/>
    </t:tabgroup>
</body>
</html>


package jumpstart.web.pages.examples.jodatime;

import java.util.Locale;

import javax.ejb.EJB;

import jumpstart.business.domain.datestuff.DatesExample;
import jumpstart.business.domain.datestuff.iface.IDateStuffServiceLocal;

import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Property;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;

@Import(stylesheet = "css/examples/jodatime.css")
public class JodaTime {

    // Screen fields

    @Property
    private DatesExample datesExample;

    @Property
    private DateTimeFormatter french;

    @Property
    private DateTimeFormatter ISODate;

    // Generally useful bits and pieces

    @EJB
    private IDateStuffServiceLocal dateStuffService;

    // The code

    void setupRender() {

        // Ask business service to find DatesExample with id = 1.

        datesExample = dateStuffService.findDatesExample(1L);

        if (datesExample == null) {
            throw new IllegalStateException("Database data has not been set up!");
        }

        french = DateTimeFormat.fullDate().withLocale(Locale.FRENCH);
        ISODate = ISODateTimeFormat.date();
    }
}


.eg {
                margin: 20px 0;
                padding: 14px;
                border: 1px solid #ddd;
                border-radius: 6px;
                -webkit-border-radius: 6px;
                -mox-border-radius: 6px;
                background-color: #fafafa;
                color: #666;
}

.eg th {
                padding-left: 12px;
                text-align: left;
}

.eg td {
                padding-left: 12px;
                padding-right: 12px;
}


// This is based on Tapestry's Output component.

package jumpstart.web.components;

import java.text.Format;
import java.util.Locale;

import org.apache.tapestry5.Binding;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.SupportsInformalParameters;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.services.ComponentDefaultProvider;
import org.joda.time.base.AbstractInstant;
import org.joda.time.base.AbstractPartial;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

/**
 * A component for formatting for output of JodaTime objects. It supports subclasses of AbstractInstant and
 * AbstractPartial. If the component is represented in the template using an element, then the element (plus any
 * informal parameters) will be output around the formatted value.
 */
@SupportsInformalParameters
public class JodaTimeOutput {
    
    // Parameters
    
    /**
     * The value to be output (before formatting). If the formatted value is blank, no output is produced.
     */
    @Parameter(required = true)
    private Object value;

    /** The format to be applied to the object. */
    @Parameter(required = false)
    private DateTimeFormatter formatter;

    /** The format to be applied to the object. */
    @Parameter(required = false, defaultPrefix = BindingConstants.LITERAL)
    private String style;

    /** The format to be applied to the object. */
    @Parameter(required = false, defaultPrefix = BindingConstants.LITERAL)
    private String pattern;

    /** This is declared so we catch slip-ups - an error will point the developer to formatter instead. */
    @Parameter(required = false, defaultPrefix = BindingConstants.LITERAL)
    private Format format;

    /**
     * The element name, derived from the component template. This can even be overridden manually if desired (for
     * example, to sometimes render a surrounding element and other times not).
     */
    @Parameter("componentResources.elementName")
    private String elementName;
    
    // Useful bits and pieces

    @Inject
    private ComponentDefaultProvider defaultProvider;

    @Inject
    private ComponentResources componentResources;
    
    @Inject
    private Locale locale;
    
    // The code

    Binding defaultValue() {
        return defaultProvider.defaultBinding("value", componentResources);
    }

    void setupRender() {

        if (format != null) {
            throw new IllegalArgumentException(
                    "JodaTimeOutput does not allow \"format\" parameter.  Valid parameters are \"style\", \"formatter\", and \"pattern\".  Formatter type is DateTimeFormatter.");
        }

        int formatParams = 0;

        if (style != null) {
            formatParams += 1;
        }
        if (formatter != null) {
            formatParams += 1;
        }
        if (pattern != null) {
            formatParams += 1;
        }

        if (formatParams > 1) {
            throw new IllegalArgumentException(
                    "JodaTimeOutput can optionally receive \"style\" parameter, \"formatter\" parameter, or \"pattern\" parameter, but no more than one of them.  Received  "
                            + formatParams + " of them.");
        }

    }

    boolean beginRender(MarkupWriter writer) {

        String formatted = (value == null ? "" : format(value));

        if (InternalUtils.isNonBlank(formatted)) {
            if (elementName != null) {
                writer.element(elementName);

                componentResources.renderInformalParameters(writer);
            }

            writer.write(formatted);

            if (elementName != null)
                writer.end();
        }

        return false;
    }

    private String format(Object value) {
        String formatted = "";

        if (value != null) {

            // If value is an AbstractInstant - includes DateTime and DateMidnight

            if (value instanceof AbstractInstant) {
                AbstractInstant ai = ((AbstractInstant) value);
                if (style != null) {
                    formatted = DateTimeFormat.forStyle(style).withLocale(locale).print(ai);
                }
                else if (formatter != null) {
                    formatted = ai.toString(formatter);
                }
                else if (pattern != null) {
                    formatted = DateTimeFormat.forPattern(pattern).print(ai);
                }
                else {
                    formatted = value.toString();
                }
            }

            // Else if value is an AbstractPartial - includes LocalDate, LocalTime,
            // LocalDateTime, YearMonthDay, and TimeOfDay

            else if (value instanceof AbstractPartial) {
                AbstractPartial ap = ((AbstractPartial) value);
                if (style != null) {
                    formatted = DateTimeFormat.forStyle(style).withLocale(locale).print(ap);
                }
                else if (formatter != null) {
                    formatted = ap.toString(formatter);
                }
                else if (pattern != null) {
                    formatted = DateTimeFormat.forPattern(pattern).print(ap);
                }
                else {
                    formatted = value.toString();
                }
            }

            // Else value is an unsupported type

            else {
                throw new IllegalArgumentException(
                        "JodaTimeOutput received a value of the wrong type.  Supported types are subclasses of AbstractInstant and AbstractPartial.  Type found is "
                                + value.getClass().getName() + ".");
            }
        }

        return formatted;
    }

    // For testing.

    void setup(Object value, String style, DateTimeFormatter formatter, String pattern, String elementName,
            ComponentResources componentResources) {
        this.value = value;
        this.style = style;
        this.formatter = formatter;
        this.pattern = pattern;
        this.elementName = elementName;
        this.componentResources = componentResources;
    }
}


package jumpstart.business.domain.datestuff;

import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import jumpstart.business.domain.datestuff.iface.IDateStuffServiceLocal;
import jumpstart.business.domain.datestuff.iface.IDateStuffServiceRemote;

@Stateless
@Local(IDateStuffServiceLocal.class)
@Remote(IDateStuffServiceRemote.class)
public class DateStuffService implements IDateStuffServiceLocal, IDateStuffServiceRemote {

    @PersistenceContext(unitName = "jumpstart")
    private EntityManager em;

    public DatesExample findDatesExample(Long id) {
        return em.find(DatesExample.class, id);
    }

    public DatesExample changeDatesExample(DatesExample datesExample) {
        return em.merge(datesExample);
    }

}


package jumpstart.business.domain.datestuff;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;

import jumpstart.util.JodaTimeUtil;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;

/**
 * The DatesExample entity.
 */
@Entity
@SuppressWarnings("serial")
public class DatesExample implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false)
    private Long id;

    @Version
    @Column(nullable = false)
    private Integer version;

    // Traditional java-style Date fields

    @Temporal(TemporalType.TIMESTAMP)
    private java.util.Date aTimestamp;

    @Temporal(TemporalType.DATE)
    private java.util.Date aDate;

    @Temporal(TemporalType.TIME)
    private java.util.Date aTime;

    // These fields are exposed as Joda Time types but persisted as standard Java types (Date, Timestamp, String,
    // Integer).

    private java.sql.Timestamp aDateTime;

    private java.sql.Timestamp aDateTimeWithTZ;

    private String aDateTimeTZ;

    // This JSR-303 validation will be picked up by Form. For BeanEditForm, we also annotate the getter.
    @NotNull
    private java.sql.Date aDateMidnight;

    private java.sql.Date aDateMidnightWithTZ;

    private String aDateMidnightTZ;

    private java.sql.Timestamp aLocalDateTime;

    // This JSR-303 validation will be picked up by Form. For BeanEditForm, we also annotate the getter.
    @NotNull
    private java.sql.Date aLocalDate;

    private java.sql.Time aLocalTimeAsTime;

    private Integer aLocalTimeAsMillis;

    private String aLocalTimeAsString;

    public String toString() {
        final String DIVIDER = ", ";

        StringBuilder buf = new StringBuilder();
        buf.append(this.getClass().getSimpleName() + ": ");
        buf.append("[");
        buf.append("id=" + id + DIVIDER);
        buf.append("aTimestamp=" + aTimestamp + DIVIDER);
        buf.append("aDate=" + aDate + DIVIDER);
        buf.append("aTime=" + aTime + DIVIDER);
        buf.append("aDateTime=" + aDateTime + DIVIDER);
        buf.append("aDateTimeWithTZ=" + aDateTimeWithTZ + DIVIDER);
        buf.append("aDateTimeTZ=" + aDateTimeTZ + DIVIDER);
        buf.append("aDateMidnight=" + aDateMidnight + DIVIDER);
        buf.append("aLocalDateTime=" + aLocalDateTime + DIVIDER);
        buf.append("aLocalDate=" + aLocalDate + DIVIDER);
        buf.append("aLocalTimeAsTime=" + aLocalTimeAsTime + DIVIDER);
        buf.append("aLocalTimeAsMillis=" + aLocalTimeAsMillis + DIVIDER);
        buf.append("aLocalTimeAsString=" + aLocalTimeAsString + DIVIDER);
        buf.append("version=" + version);
        buf.append("]");
        return buf.toString();
    }

    // The need for an equals() method is discussed at http://www.hibernate.org/109.html

    @Override
    public boolean equals(Object obj) {
        return (obj == this) || (obj instanceof DatesExample) && id != null && id.equals(((DatesExample) obj).getId());
    }

    // The need for a hashCode() method is discussed at http://www.hibernate.org/109.html

    @Override
    public int hashCode() {
        return id == null ? super.hashCode() : id.hashCode();
    }

    public Long getId() {
        return id;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public Date getADate() {
        return aDate;
    }

    public void setADate(Date aDate) {
        this.aDate = aDate;
    }

    public Date getATime() {
        return aTime;
    }

    public void setATime(Date aTime) {
        this.aTime = aTime;
    }

    public Date getATimestamp() {
        return aTimestamp;
    }

    public void setATimestamp(Date aTimestamp) {
        this.aTimestamp = aTimestamp;
    }

    public DateTime getADateTime() {
        return JodaTimeUtil.toDateTime(aDateTime);
    }

    public void setADateTime(DateTime dt) {
        this.aDateTime = JodaTimeUtil.toSQLTimestamp(dt);
    }

    public DateTime getADateTimeWithTZ() {
        return JodaTimeUtil.toDateTime(aDateTimeWithTZ, aDateTimeTZ);
    }

    public void setADateTimeWithTZ(DateTime dt) {
        this.aDateTimeWithTZ = JodaTimeUtil.toSQLTimestamp(dt);
        this.aDateTimeTZ = JodaTimeUtil.toTimeZoneID(dt);
    }

    // This JSR-303 validation will be picked up by BeanEditForm. For Form, we also annotate the field.
    @NotNull
    public DateMidnight getADateMidnight() {
        return JodaTimeUtil.toDateMidnight(aDateMidnight);
    }

    public void setADateMidnight(DateMidnight dm) {
        this.aDateMidnight = JodaTimeUtil.toSQLDate(dm);
    }

    public DateMidnight getADateMidnightWithTZ() {
        return JodaTimeUtil.toDateMidnight(aDateMidnightWithTZ, aDateMidnightTZ);
    }

    public void setADateMidnightWithTZ(DateMidnight dm) {
        this.aDateMidnightWithTZ = JodaTimeUtil.toSQLDate(dm);
        this.aDateMidnightTZ = JodaTimeUtil.toTimeZoneID(dm);
    }

    public LocalDateTime getALocalDateTime() {
        return JodaTimeUtil.toLocalDateTime(aLocalDateTime);
    }

    public void setALocalDateTime(LocalDateTime ldt) {
        this.aLocalDateTime = JodaTimeUtil.toSQLTimestamp(ldt);
    }

    // This JSR-303 validation will be picked up by BeanEditForm. For Form, we also annotate the field.
    @NotNull
    public LocalDate getALocalDate() {
        return JodaTimeUtil.toLocalDate(aLocalDate);
    }

    public void setALocalDate(LocalDate ld) {
        this.aLocalDate = JodaTimeUtil.toSQLDate(ld);
    }

    public LocalTime getALocalTimeAsTime() {
        return JodaTimeUtil.toLocalTime(aLocalTimeAsTime);
    }

    public void setALocalTimeAsTime(LocalTime lt) {
        this.aLocalTimeAsTime = JodaTimeUtil.toSQLTime(lt);
    }

    public LocalTime getALocalTimeAsMillis() {
        return JodaTimeUtil.toLocalTime(aLocalTimeAsMillis);
    }

    public void setALocalTimeAsMillis(LocalTime lt) {
        this.aLocalTimeAsMillis = JodaTimeUtil.toIntegerMillis(lt);
    }

    public LocalTime getALocalTimeAsString() {
        return JodaTimeUtil.toLocalTime(aLocalTimeAsString);
    }

    public void setALocalTimeAsString(LocalTime lt) {
        this.aLocalTimeAsString = JodaTimeUtil.toString(lt);
    }

}


package jumpstart.util;

import java.util.TimeZone;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class JodaTimeUtil {
    public static final DateTimeFormatter localDateFormatter = DateTimeFormat.forPattern("YYYYMMDD");

    // ///////////////////////////////////////////
    // From JodaTime types to standard Java types
    // ///////////////////////////////////////////

    public static java.sql.Date toSQLDate(DateMidnight dm) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Date d = (dm == null ? null : new java.sql.Date(dm.getMillis()));
        return d;
    }

    public static java.util.Date toJavaDate(DateMidnight dm) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.util.Date d = (dm == null ? null : new java.util.Date(dm.getMillis()));
        return d;
    }

    public static String toTimeZoneID(DateMidnight dm) {
        String s = (dm == null ? null : dm.getZone().getID());
        return s;
    }

    public static java.sql.Timestamp toSQLTimestamp(DateTime dt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Timestamp ts = (dt == null ? null : new java.sql.Timestamp(dt.getMillis()));
        return ts;
    }

    public static java.util.Date toJavaDate(DateTime dt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.util.Date ts = (dt == null ? null : new java.util.Date(dt.getMillis()));
        return ts;
    }

    public static String toTimeZoneID(DateTime dt) {
        String s = (dt == null ? null : dt.getZone().getID());
        return s;
    }

    public static java.sql.Date toSQLDate(LocalDate ld) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Date d = (ld == null ? null : new java.sql.Date(ld.toDateTimeAtStartOfDay().getMillis()));
        return d;
    }

    public static java.util.Date toJavaDate(LocalDate ld) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.util.Date d = (ld == null ? null : new java.util.Date(ld.toDateTimeAtStartOfDay().getMillis()));
        return d;
    }

    public static String toString(LocalDate ld) {
        // TODO - confirm this conversion always works, esp. across timezones
        String s = (ld == null ? null : localDateFormatter.withZone(DateTimeZone.UTC).print(ld));
        return s;
    }

    public static java.sql.Timestamp toSQLTimestamp(LocalDateTime ldt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Timestamp ts = (ldt == null ? null : new java.sql.Timestamp(ldt.toDateTime().getMillis()));
        return ts;
    }

    public static java.util.Date toJavaDate(LocalDateTime ldt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.util.Date d = (ldt == null ? null : new java.util.Date(ldt.toDateTime().getMillis()));
        return d;
    }

    public static java.sql.Time toSQLTime(LocalTime lt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Time t = (lt == null ? null : new java.sql.Time(lt.getMillisOfDay()));
        return t;
    }

    public static Integer toIntegerMillis(LocalTime lt) {
        Integer i = (lt == null ? null : new Integer(lt.getMillisOfDay()));
        return i;
    }

    public static String toString(LocalTime lt) {
        String s = (lt == null ? null : lt.toString());
        return s;
    }

    // ///////////////////////////////////////////
    // From standard Java types to JodaTime types
    // ///////////////////////////////////////////

    public static DateMidnight toDateMidnight(java.sql.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateMidnight dm = (d == null ? null : new DateMidnight(d));
        return dm;
    }

    public static DateMidnight toDateMidnight(java.util.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateMidnight dm = (d == null ? null : new DateMidnight(d));
        return dm;
    }

    public static DateMidnight toDateMidnight(java.sql.Date d, String timeZoneID) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateMidnight dm = (d == null ? null : new DateMidnight(d, DateTimeZone.forID(timeZoneID)));
        return dm;
    }

    public static DateMidnight toDateMidnight(java.util.Date d, String timeZoneID) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateMidnight dm = (d == null ? null : new DateMidnight(d, DateTimeZone.forID(timeZoneID)));
        return dm;
    }

    public static DateTime toDateTime(java.sql.Timestamp ts) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateTime dt = (ts == null ? null : new DateTime(ts));
        return dt;
    }

    public static DateTime toDateTime(java.util.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateTime dt = (d == null ? null : new DateTime(d));
        return dt;
    }

    public static DateTime toDateTime(java.sql.Timestamp ts, String timeZoneID) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateTime dt = (ts == null ? null : new DateTime(ts, DateTimeZone.forID(timeZoneID)));
        return dt;
    }

    public static DateTime toDateTime(java.util.Date d, String timeZoneID) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateTime dt = (d == null ? null : new DateTime(d, DateTimeZone.forID(timeZoneID)));
        return dt;
    }

    public static LocalDate toLocalDate(java.sql.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDate ld = (d == null ? null : LocalDate.fromDateFields(d));
        return ld;
    }

    public static LocalDate toLocalDate(java.util.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDate ld = (d == null ? null : LocalDate.fromDateFields(d));
        return ld;
    }

    public static LocalDate toLocalDate(String s) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDate ld = (s == null ? null : localDateFormatter.withZone(DateTimeZone.UTC).parseDateTime(s).toLocalDate());
        return ld;
    }

    public static LocalDateTime toLocalDateTime(java.sql.Timestamp ts) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDateTime ldt = (ts == null ? null : LocalDateTime.fromDateFields(ts));
        return ldt;
    }

    public static LocalDateTime toLocalDateTime(java.util.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDateTime ldt = (d == null ? null : LocalDateTime.fromDateFields(d));
        return ldt;
    }

    public static LocalTime toLocalTime(java.sql.Time t) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalTime lt = (t == null ? null : new LocalTime(t, DateTimeZone.UTC));
        return lt;
    }

    public static LocalTime toLocalTime(Integer i) {
        LocalTime lt = (i == null ? null : LocalTime.fromMillisOfDay(i));
        return lt;
    }

    public static LocalTime toLocalTime(String s) {
        LocalTime lt = (s == null ? null : new LocalTime(s));
        return lt;
    }

    // ///////////////////////////////////////////
    // Tests
    // ///////////////////////////////////////////

    public static void main(String[] args) {
        test_dateMidnight();
        System.out.println(" ");
        test_dateMidnight_tz();
        System.out.println(" ");
        test_dateTime();
        System.out.println(" ");
        test_dateTime_tz();
        System.out.println(" ");
        test_localDate();
        System.out.println(" ");
        test_localDate_tz();
        System.out.println(" ");
        test_localDateTime();
        System.out.println(" ");
        test_localDate_shift_java_tz();
        System.out.println(" ");
        test_localDate_shift_joda_tz();
        System.out.println(" ");
        test_localTime_as_integer();
        System.out.println(" ");
        test_localTime_as_string();
        System.out.println(" ");
    }

    public static DateTimeZone losAngeles = DateTimeZone.forID("America/Los_Angeles");
    public static DateTimeZone brisbane = DateTimeZone.forID("Australia/Brisbane");
    public static DateTimeZone perth = DateTimeZone.forID("Australia/Perth");

    public static void test_dateMidnight() {
        System.out.println("Test DateMidnight");
        DateMidnight dm1 = new DateMidnight();
        java.sql.Date d = toSQLDate(dm1);
        DateMidnight dm2 = toDateMidnight(d);
        System.out.println("DateMidnight 1 = " + dm1);
        System.out.println("Date           = " + d);
        System.out.println("DateMidnight 2 = " + dm2);
        if (!dm2.equals(dm1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_dateMidnight_tz() {
        System.out.println("Test DateMidnight with timezone");
        DateMidnight dm1 = new DateMidnight(losAngeles);
        java.sql.Date d = toSQLDate(dm1);
        String tzID = toTimeZoneID(dm1);
        DateMidnight dm2 = toDateMidnight(d, tzID);
        System.out.println("DateMidnight 1 = " + dm1);
        System.out.println("Date           = " + d);
        System.out.println("TimeZoneID     = " + tzID);
        System.out.println("DateMidnight 2 = " + dm2);
        if (!dm2.equals(dm1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_dateTime() {
        System.out.println("Test DateTime");
        DateTime dt1 = new DateTime();
        java.sql.Timestamp ts = toSQLTimestamp(dt1);
        DateTime dt2 = toDateTime(ts);
        System.out.println("DateTime 1 = " + dt1);
        System.out.println("Timestamp  = " + ts);
        System.out.println("DateTime 2 = " + dt2);
        if (!dt2.equals(dt1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_dateTime_tz() {
        System.out.println("Test DateTime with timezone");
        DateTime dt1 = new DateTime(losAngeles);
        java.sql.Timestamp ts = toSQLTimestamp(dt1);
        String tzID = toTimeZoneID(dt1);
        DateTime dt2 = toDateTime(ts, tzID);
        System.out.println("DateTime 1 = " + dt1);
        System.out.println("Timestamp  = " + ts);
        System.out.println("TimeZoneID = " + tzID);
        System.out.println("DateTime 2 = " + dt2);
        if (!dt2.equals(dt1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localDate() {
        System.out.println("Test LocalDate");
        LocalDate ld1 = new LocalDate();
        java.sql.Date d = toSQLDate(ld1);
        LocalDate ld2 = toLocalDate(d);
        System.out.println("LocalDate 1 = " + ld1);
        System.out.println("Date        = " + d);
        System.out.println("LocalDate 2 = " + ld2);
        if (!ld2.equals(ld1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localDate_tz() {
        System.out.println("Test LocalDate with timezone");
        DateMidnight dm1 = new DateMidnight(losAngeles);
        LocalDate ld1 = new LocalDate(dm1);
        java.sql.Date d = toSQLDate(ld1);
        LocalDate ld2 = toLocalDate(d);
        System.out.println("DateTime 1  = " + dm1);
        System.out.println("LocalDate 1 = " + ld1);
        System.out.println("Date        = " + d);
        System.out.println("LocalDate 2 = " + ld2);
        if (!ld2.equals(ld1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localDate_shift_java_tz() {
        System.out.println("Test LocalDate with shifted Java timezone");

        TimeZone originalTZ = TimeZone.getDefault();
        TimeZone losAngelesTZ = TimeZone.getTimeZone("America/Los_Angeles");

        TimeZone.setDefault(losAngelesTZ);
        LocalDate ld1 = new LocalDate();
        System.out.println("ld1 LocalDate()   = " + ld1 + " when default TZ = " + TimeZone.getDefault());

        java.sql.Date d = toSQLDate(ld1);
        System.out.println("d toSQLDate(ld1)  = " + d + " when default TZ = " + TimeZone.getDefault());
        TimeZone.setDefault(originalTZ);
        System.out.println("d toSQLDate(ld1)  = " + d + " when default TZ = " + TimeZone.getDefault());

        LocalDate ld2 = toLocalDate(d);
        System.out.println("ld2 toLocalDate(d) = " + ld2 + " when default TZ = " + TimeZone.getDefault());

        TimeZone.setDefault(originalTZ);
        if (!ld2.equals(ld1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localDate_shift_joda_tz() {
        System.out.println("Test LocalDate with shifted JodaTime timezone");
        DateTimeZone originalTZ = DateTimeZone.getDefault();
        DateTimeZone losAngelesTZ = DateTimeZone.forID("America/Los_Angeles");

        DateTimeZone.setDefault(losAngelesTZ);
        LocalDate ld0 = new LocalDate(losAngelesTZ);
        System.out.println("ld0 LocalDate(losAngelesTZ) = " + ld0 + " when default TZ = " + DateTimeZone.getDefault());

        DateTimeZone.setDefault(losAngelesTZ);
        LocalDate ld1 = new LocalDate();
        System.out.println("ld1 LocalDate()             = " + ld1 + " when default TZ = " + DateTimeZone.getDefault());

        java.sql.Date d0 = toSQLDate(ld1);
        System.out.println("d0 toSQLDate(ld0)           = " + d0 + " when default TZ = " + DateTimeZone.getDefault());
        java.sql.Date d1 = toSQLDate(ld1);
        System.out.println("d1 toSQLDate(ld1)           = " + d1 + " when default TZ = " + DateTimeZone.getDefault());
        DateTimeZone.setDefault(originalTZ);
        System.out.println("d1 toSQLDate(ld1)           = " + d1 + " when default TZ = " + DateTimeZone.getDefault());

        DateTimeZone.setDefault(originalTZ);
        LocalDate ld2 = toLocalDate(d1);
        System.out.println("ld2 toLocalDate(d1)         = " + ld2 + " when default TZ = " + DateTimeZone.getDefault());

        DateTimeZone.setDefault(originalTZ);
        if (!ld2.equals(ld1)) {
            throw new IllegalStateException();
        }
    }

    // public static void test_localDate_tz() {
    // System.out.println("Test LocalDate with different timezones");
    // LocalDate ld1 = new LocalDate(losAngeles);
    // java.sql.Date d1 = toSQLDate(ld1);
    // LocalDate ld2 = new LocalDate(perth);
    // java.sql.Date d2 = toSQLDate(ld2);
    // LocalDate ld3 = new LocalDate();
    // java.sql.Date d3 = toSQLDate(ld3);
    // System.out.println("LocalDate 1 = " + ld1);
    // System.out.println("Date        = " + d);
    // System.out.println("LocalDate 2 = " + ld2);
    // if (!ld2.equals(ld1)) {
    // throw new IllegalStateException();
    // }
    // }

    public static void test_localDateTime() {
        System.out.println("Test LocalDateTime");
        LocalDateTime ldt1 = new LocalDateTime();
        java.sql.Timestamp ts = toSQLTimestamp(ldt1);
        LocalDateTime ldt2 = toLocalDateTime(ts);
        System.out.println("LocalDateTime 1 = " + ldt1);
        System.out.println("Timestamp       = " + ts);
        System.out.println("LocalDateTime 2 = " + ldt2);
        if (!ldt2.equals(ldt1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localTime_as_integer() {
        System.out.println("Test LocalTime as Integer");
        LocalTime lt1 = new LocalTime();
        Integer i = toIntegerMillis(lt1);
        LocalTime lt2 = toLocalTime(i);
        System.out.println("LocalTime 1 = " + lt1);
        System.out.println("Integer     = " + i);
        System.out.println("LocalTime 2 = " + lt2);
        if (!lt2.equals(lt1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localTime_as_string() {
        System.out.println("Test LocalTime as String");
        LocalTime lt1 = new LocalTime();
        String t = toString(lt1);
        LocalTime lt2 = toLocalTime(t);
        System.out.println("LocalTime 1 = " + lt1);
        System.out.println("String      = " + t);
        System.out.println("LocalTime 2 = " + lt2);
        if (!lt2.equals(lt1)) {
            throw new IllegalStateException();
        }
    }

}