BEANUTILS-515: Java 8 Time converters. (#13)

* BEANUTILS-515: Java 8 Time converters

* BEANUTILS-515: Java 8 Time converters

* BEANUTILS-515: Java 8 Time converters

* Update maven.yml

Display better error message for failing build.
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index fe2b2b7..5cfe7db 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -69,6 +69,9 @@
       <action issue="BEANUTILS-528" dev="ggregory" type="update" due-to="Melloware, Matt Sicker, Gary Gregory">
         New converters for UUID, URI, and Path #10.
       </action>
+      <action issue="BEANUTILS-515" dev="ggregory" type="update" due-to="Melloware, Matt Sicker, Gary Gregory">
+        New converters for Java 8 Time classes LocalDate, LocaleDateTime, ZonedDateTime, OffsetDateTime #13.
+      </action>
     </release>
 
     <release version="1.9.4" date="2019-08-13" description="The primary reason for this release is a bugfix for
diff --git a/src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java b/src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java
index 0e16ce8..a3e362c 100644
--- a/src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java
+++ b/src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java
@@ -19,6 +19,12 @@
 import java.text.DateFormat;
 import java.text.ParsePosition;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
@@ -35,6 +41,10 @@
  * <ul>
  *     <li><code>java.util.Date</code></li>
  *     <li><code>java.util.Calendar</code></li>
+ *     <li><code>java.time.LocalDate</code></li>
+ *     <li><code>java.time.LocalDateTime</code></li>
+ *     <li><code>java.time.OffsetDateTime</code></li>
+ *     <li><code>java.time.ZonedDateTime</code></li>
  *     <li><code>java.sql.Date</code></li>
  *     <li><code>java.sql.Time</code></li>
  *     <li><code>java.sql.Timestamp</code></li>
@@ -233,6 +243,14 @@
             date = ((Calendar)value).getTime();
         } else if (value instanceof Long) {
             date = new Date(((Long)value).longValue());
+        } else if (value instanceof LocalDateTime) {
+            date =  java.sql.Timestamp.valueOf(((LocalDateTime)value));
+        } else if (value instanceof LocalDate) {
+            date =  java.sql.Date.valueOf(((LocalDate)value));
+        } else if (value instanceof ZonedDateTime) {
+            date =  Date.from(((ZonedDateTime)value).toInstant());
+        } else if (value instanceof OffsetDateTime) {
+            date =  Date.from(((OffsetDateTime)value).toInstant());
         }
 
         String result = null;
@@ -266,6 +284,10 @@
      * <ul>
      *     <li><code>java.util.Date</code></li>
      *     <li><code>java.util.Calendar</code></li>
+     *     <li><code>java.time.LocalDate</code></li>
+     *     <li><code>java.time.LocalDateTime</code></li>
+     *     <li><code>java.time.OffsetDateTime</code></li>
+     *     <li><code>java.time.ZonedDateTime</code></li>
      *     <li><code>java.sql.Date</code></li>
      *     <li><code>java.sql.Time</code></li>
      *     <li><code>java.sql.Timestamp</code></li>
@@ -324,6 +346,30 @@
             return toDate(targetType, longObj.longValue());
         }
 
+        // Handle LocalDate
+        if (value instanceof LocalDate) {
+            final LocalDate date = (LocalDate)value;
+            return toDate(targetType, date.atStartOfDay(getZoneId()).toInstant().toEpochMilli());
+        }
+
+        // Handle LocalDateTime
+        if (value instanceof LocalDateTime) {
+            final LocalDateTime date = (LocalDateTime)value;
+            return toDate(targetType, date.atZone(getZoneId()).toInstant().toEpochMilli());
+        }
+
+        // Handle ZonedDateTime
+        if (value instanceof ZonedDateTime) {
+            final ZonedDateTime date = (ZonedDateTime)value;
+            return toDate(targetType, date.toInstant().toEpochMilli());
+        }
+
+        // Handle OffsetDateTime
+        if (value instanceof OffsetDateTime) {
+            final OffsetDateTime date = (OffsetDateTime)value;
+            return toDate(targetType, date.toInstant().toEpochMilli());
+        }
+
         // Convert all other types to String & handle
         final String stringValue = value.toString().trim();
         if (stringValue.length() == 0) {
@@ -359,6 +405,9 @@
      * <ul>
      *     <li><code>java.util.Date</code></li>
      *     <li><code>java.util.Calendar</code></li>
+     *     <li><code>java.time.LocalDate</code></li>
+     *     <li><code>java.time.LocalDateTime</code></li>
+     *     <li><code>java.time.ZonedDateTime</code></li>
      *     <li><code>java.sql.Date</code></li>
      *     <li><code>java.sql.Time</code></li>
      *     <li><code>java.sql.Timestamp</code></li>
@@ -391,6 +440,30 @@
             return type.cast(new java.sql.Timestamp(value));
         }
 
+        // java.time.LocalDateTime
+        if (type.equals(LocalDate.class)) {
+        	LocalDate localDate =  Instant.ofEpochMilli(value).atZone(getZoneId()).toLocalDate();
+            return type.cast(localDate);
+        }
+
+        // java.time.LocalDateTime
+        if (type.equals(LocalDateTime.class)) {
+        	LocalDateTime localDateTime =  Instant.ofEpochMilli(value).atZone(getZoneId()).toLocalDateTime();
+            return type.cast(localDateTime);
+        }
+
+        // java.time.ZonedDateTime
+        if (type.equals(ZonedDateTime.class)) {
+        	ZonedDateTime zonedDateTime =  ZonedDateTime.ofInstant(Instant.ofEpochMilli(value), getZoneId());
+            return type.cast(zonedDateTime);
+        }
+
+        // java.time.OffsetDateTime
+        if (type.equals(OffsetDateTime.class)) {
+        	OffsetDateTime offsetDateTime =  OffsetDateTime.ofInstant(Instant.ofEpochMilli(value), getZoneId());
+            return type.cast(offsetDateTime);
+        }
+
         // java.util.Calendar
         if (type.equals(Calendar.class)) {
             Calendar calendar = null;
@@ -633,4 +706,13 @@
             log().debug(buffer.toString());
         }
     }
+
+    /**
+     * Gets the <code>java.time.ZoneId</code> from the <code>java.util.Timezone</code>
+     * set or use the system default if no time zone is set.
+     * @return the <code>ZoneId</code>
+     */
+    private ZoneId getZoneId() {
+    	return timeZone == null ? ZoneId.systemDefault() : timeZone.toZoneId();
+    }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/converters/LocalDateConverter.java b/src/main/java/org/apache/commons/beanutils2/converters/LocalDateConverter.java
new file mode 100644
index 0000000..4386f61
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils2/converters/LocalDateConverter.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2.converters;
+
+import java.time.LocalDate;
+
+/**
+ * {@link DateTimeConverter} implementation that handles conversion to
+ * and from <b>java.time.LocalDate</b> objects.
+ * <p>
+ * This implementation can be configured to handle conversion either
+ * by using a Locale's default format or by specifying a set of format
+ * patterns (note, there is no default String conversion for Calendar).
+ * See the {@link DateTimeConverter} documentation for further details.
+ * <p>
+ * Can be configured to either return a <i>default value</i> or throw a
+ * <code>ConversionException</code> if a conversion error occurs.
+ *
+ * @since 2.0
+ */
+public final class LocalDateConverter extends DateTimeConverter {
+
+    /**
+     * Construct a <b>java.time.LocalDate</b> <i>Converter</i> that throws
+     * a <code>ConversionException</code> if an error occurs.
+     */
+    public LocalDateConverter() {
+        super();
+    }
+
+    /**
+     * Construct a <b>java.time.LocalDate</b> <i>Converter</i> that returns
+     * a default value if an error occurs.
+     *
+     * @param defaultValue The default value to be returned
+     * if the value to be converted is missing or an error
+     * occurs converting the value.
+     */
+    public LocalDateConverter(final Object defaultValue) {
+        super(defaultValue);
+    }
+
+    /**
+     * Return the default type this <code>Converter</code> handles.
+     *
+     * @return The default type this <code>Converter</code> handles.
+     */
+    @Override
+    protected Class<?> getDefaultType() {
+        return LocalDate.class;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/beanutils2/converters/LocalDateTimeConverter.java b/src/main/java/org/apache/commons/beanutils2/converters/LocalDateTimeConverter.java
new file mode 100644
index 0000000..e151684
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils2/converters/LocalDateTimeConverter.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2.converters;
+
+import java.time.LocalDateTime;
+
+/**
+ * {@link DateTimeConverter} implementation that handles conversion to
+ * and from <b>java.time.LocalDateTime</b> objects.
+ * <p>
+ * This implementation can be configured to handle conversion either
+ * by using a Locale's default format or by specifying a set of format
+ * patterns (note, there is no default String conversion for Calendar).
+ * See the {@link DateTimeConverter} documentation for further details.
+ * <p>
+ * Can be configured to either return a <i>default value</i> or throw a
+ * <code>ConversionException</code> if a conversion error occurs.
+ *
+ * @since 2.0
+ */
+public final class LocalDateTimeConverter extends DateTimeConverter {
+
+    /**
+     * Construct a <b>java.time.LocalDateTime</b> <i>Converter</i> that throws
+     * a <code>ConversionException</code> if an error occurs.
+     */
+    public LocalDateTimeConverter() {
+        super();
+    }
+
+    /**
+     * Construct a <b>java.time.LocalDateTime</b> <i>Converter</i> that returns
+     * a default value if an error occurs.
+     *
+     * @param defaultValue The default value to be returned
+     * if the value to be converted is missing or an error
+     * occurs converting the value.
+     */
+    public LocalDateTimeConverter(final Object defaultValue) {
+        super(defaultValue);
+    }
+
+    /**
+     * Return the default type this <code>Converter</code> handles.
+     *
+     * @return The default type this <code>Converter</code> handles.
+     */
+    @Override
+    protected Class<?> getDefaultType() {
+        return LocalDateTime.class;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/beanutils2/converters/OffsetDateTimeConverter.java b/src/main/java/org/apache/commons/beanutils2/converters/OffsetDateTimeConverter.java
new file mode 100644
index 0000000..9713962
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils2/converters/OffsetDateTimeConverter.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2.converters;
+
+import java.time.OffsetDateTime;
+
+/**
+ * {@link DateTimeConverter} implementation that handles conversion to
+ * and from <b>java.time.OffsetDateTime</b> objects.
+ * <p>
+ * This implementation can be configured to handle conversion either
+ * by using a Locale's default format or by specifying a set of format
+ * patterns (note, there is no default String conversion for Calendar).
+ * See the {@link DateTimeConverter} documentation for further details.
+ * <p>
+ * Can be configured to either return a <i>default value</i> or throw a
+ * <code>ConversionException</code> if a conversion error occurs.
+ *
+ * @since 2.0
+ */
+public final class OffsetDateTimeConverter extends DateTimeConverter {
+
+    /**
+     * Construct a <b>java.time.OffsetDateTime</b> <i>Converter</i> that throws
+     * a <code>ConversionException</code> if an error occurs.
+     */
+    public OffsetDateTimeConverter() {
+        super();
+    }
+
+    /**
+     * Construct a <b>java.time.OffsetDateTime</b> <i>Converter</i> that returns
+     * a default value if an error occurs.
+     *
+     * @param defaultValue The default value to be returned
+     * if the value to be converted is missing or an error
+     * occurs converting the value.
+     */
+    public OffsetDateTimeConverter(final Object defaultValue) {
+        super(defaultValue);
+    }
+
+    /**
+     * Return the default type this <code>Converter</code> handles.
+     *
+     * @return The default type this <code>Converter</code> handles.
+     */
+    @Override
+    protected Class<?> getDefaultType() {
+        return OffsetDateTime.class;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/beanutils2/converters/ZonedDateTimeConverter.java b/src/main/java/org/apache/commons/beanutils2/converters/ZonedDateTimeConverter.java
new file mode 100644
index 0000000..c43c56d
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils2/converters/ZonedDateTimeConverter.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2.converters;
+
+import java.time.ZonedDateTime;
+
+/**
+ * {@link DateTimeConverter} implementation that handles conversion to
+ * and from <b>java.time.ZonedDateTime</b> objects.
+ * <p>
+ * This implementation can be configured to handle conversion either
+ * by using a Locale's default format or by specifying a set of format
+ * patterns (note, there is no default String conversion for Calendar).
+ * See the {@link DateTimeConverter} documentation for further details.
+ * <p>
+ * Can be configured to either return a <i>default value</i> or throw a
+ * <code>ConversionException</code> if a conversion error occurs.
+ *
+ * @since 2.0
+ */
+public final class ZonedDateTimeConverter extends DateTimeConverter {
+
+    /**
+     * Construct a <b>java.time.ZonedDateTime</b> <i>Converter</i> that throws
+     * a <code>ConversionException</code> if an error occurs.
+     */
+    public ZonedDateTimeConverter() {
+        super();
+    }
+
+    /**
+     * Construct a <b>java.time.ZonedDateTime</b> <i>Converter</i> that returns
+     * a default value if an error occurs.
+     *
+     * @param defaultValue The default value to be returned
+     * if the value to be converted is missing or an error
+     * occurs converting the value.
+     */
+    public ZonedDateTimeConverter(final Object defaultValue) {
+        super(defaultValue);
+    }
+
+    /**
+     * Return the default type this <code>Converter</code> handles.
+     *
+     * @return The default type this <code>Converter</code> handles.
+     */
+    @Override
+    protected Class<?> getDefaultType() {
+        return ZonedDateTime.class;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/beanutils2/converters/DateConverterTestBase.java b/src/test/java/org/apache/commons/beanutils2/converters/DateConverterTestBase.java
index 8b764b1..1b75bea 100644
--- a/src/test/java/org/apache/commons/beanutils2/converters/DateConverterTestBase.java
+++ b/src/test/java/org/apache/commons/beanutils2/converters/DateConverterTestBase.java
@@ -19,6 +19,12 @@
 
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;
@@ -99,7 +105,11 @@
             "from Calendar",
             "from SQL Date",
             "from SQL Time",
-            "from SQL Timestamp"
+            "from SQL Timestamp",
+            "from LocalDate",
+            "from LocalDateTime",
+            "from ZonedDateTime",
+            "from OffsetDateTime"
         };
 
         final long now = System.currentTimeMillis();
@@ -109,7 +119,11 @@
             new java.util.GregorianCalendar(),
             new java.sql.Date(now),
             new java.sql.Time(now),
-            new java.sql.Timestamp(now)
+            new java.sql.Timestamp(now),
+            Instant.ofEpochMilli(now).atZone(ZoneId.systemDefault()).toLocalDate().atStartOfDay(ZoneId.systemDefault()).toLocalDate(),
+            Instant.ofEpochMilli(now).atZone(ZoneId.systemDefault()).toLocalDateTime(),
+            ZonedDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault()),
+            OffsetDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault())
         };
 
         // Initialize calendar also with same ms to avoid a failing test in a new time slice
@@ -120,8 +134,14 @@
             assertNotNull("Convert " + message[i] + " should not be null", val);
             assertTrue("Convert " + message[i] + " should return a " + getExpectedType().getName(),
                        getExpectedType().isInstance(val));
+
+            long test = now;
+            if (date[i] instanceof LocalDate || val instanceof LocalDate) {
+            	test = Instant.ofEpochMilli(now).atZone(ZoneId.systemDefault()).toLocalDate().atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
+			}
+
             assertEquals("Convert " + message[i] + " should return a " + date[0],
-                         now, getTimeInMillis(val));
+            		test, getTimeInMillis(val));
         }
     }
 
@@ -202,6 +222,9 @@
         // java.sql.Time --> String Conversion
         stringConversion(converter, expected, toSqlTime(calendar));
 
+        // java.time.LocalDateTime --> String Conversion
+        stringConversion(converter, expected, toLocalDateTime(calendar));
+
         stringConversion(converter, null, null);
         stringConversion(converter, "", "");
 
@@ -509,6 +532,15 @@
     java.sql.Timestamp toSqlTimestamp(final Calendar calendar) {
         return new java.sql.Timestamp(getTimeInMillis(calendar));
     }
+    
+    /**
+     * Convert a Calendar to a java.time.LocalDateTime
+     * @param calendar The calendar object to convert
+     * @return The converted java.time.LocalDate
+     */
+    LocalDateTime toLocalDateTime(final Calendar calendar) {
+        return Instant.ofEpochMilli(calendar.getTimeInMillis()).atZone(ZoneId.systemDefault()).toLocalDateTime();
+    }
 
     /**
      * Convert a Date or Calendar objects to the time in millisconds
@@ -528,6 +560,22 @@
             return timeInMillis;
         }
 
+        if (date instanceof LocalDate) {
+            return  ((LocalDate)date).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        }
+
+        if (date instanceof LocalDateTime) {
+            return  ((LocalDateTime)date).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        }
+
+        if (date instanceof ZonedDateTime) {
+            return  ((ZonedDateTime)date).toInstant().toEpochMilli();
+        }
+
+        if (date instanceof OffsetDateTime) {
+            return  ((OffsetDateTime)date).toInstant().toEpochMilli();
+        }
+
         if (date instanceof Calendar) {
             return ((Calendar)date).getTime().getTime();
         }
diff --git a/src/test/java/org/apache/commons/beanutils2/converters/LocalDateConverterTestCase.java b/src/test/java/org/apache/commons/beanutils2/converters/LocalDateConverterTestCase.java
new file mode 100644
index 0000000..277e82f
--- /dev/null
+++ b/src/test/java/org/apache/commons/beanutils2/converters/LocalDateConverterTestCase.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2.converters;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Calendar;
+
+import junit.framework.TestSuite;
+
+/**
+ * Test Case for the LocalDateConverter class.
+ *
+ */
+public class LocalDateConverterTestCase extends DateConverterTestBase {
+
+    /**
+     * Construct a new Date test case.
+     * @param name Test Name
+     */
+    public LocalDateConverterTestCase(final String name) {
+        super(name);
+    }
+
+    // ------------------------------------------------------------------------
+
+    /**
+     * Create Test Suite
+     * @return test suite
+     */
+    public static TestSuite suite() {
+        return new TestSuite(LocalDateConverterTestCase.class);
+    }
+
+    /** Set Up */
+    @Override
+    public void setUp() throws Exception {
+    }
+
+    /** Tear Down */
+    @Override
+    public void tearDown() throws Exception {
+    }
+
+    // ------------------------------------------------------------------------
+
+    /**
+     * Create the Converter with no default value.
+     * @return A new Converter
+     */
+    @Override
+    protected DateTimeConverter makeConverter() {
+        return new LocalDateConverter();
+    }
+
+    /**
+     * Create the Converter with a default value.
+     * @param defaultValue The default value
+     * @return A new Converter
+     */
+    @Override
+    protected DateTimeConverter makeConverter(final Object defaultValue) {
+        return new LocalDateConverter(defaultValue);
+    }
+
+    /**
+     * Return the expected type
+     * @return The expected type
+     */
+    @Override
+    protected Class<?> getExpectedType() {
+        return LocalDate.class;
+    }
+
+    /**
+     * Convert from a Calendar to the appropriate Date type
+     *
+     * @param value The Calendar value to convert
+     * @return The converted value
+     */
+    @Override
+    protected Object toType(final Calendar value) {
+        return Instant.ofEpochMilli(value.getTimeInMillis()).atZone(ZoneId.systemDefault()).toLocalDate();
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/beanutils2/converters/LocalDateTimeConverterTestCase.java b/src/test/java/org/apache/commons/beanutils2/converters/LocalDateTimeConverterTestCase.java
new file mode 100644
index 0000000..ad63e35
--- /dev/null
+++ b/src/test/java/org/apache/commons/beanutils2/converters/LocalDateTimeConverterTestCase.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2.converters;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Calendar;
+
+import junit.framework.TestSuite;
+
+/**
+ * Test Case for the LocalDateTimeConverter class.
+ *
+ */
+public class LocalDateTimeConverterTestCase extends DateConverterTestBase {
+
+    /**
+     * Construct a new Date test case.
+     * @param name Test Name
+     */
+    public LocalDateTimeConverterTestCase(final String name) {
+        super(name);
+    }
+
+    // ------------------------------------------------------------------------
+
+    /**
+     * Create Test Suite
+     * @return test suite
+     */
+    public static TestSuite suite() {
+        return new TestSuite(LocalDateTimeConverterTestCase.class);
+    }
+
+    /** Set Up */
+    @Override
+    public void setUp() throws Exception {
+    }
+
+    /** Tear Down */
+    @Override
+    public void tearDown() throws Exception {
+    }
+
+    // ------------------------------------------------------------------------
+
+    /**
+     * Create the Converter with no default value.
+     * @return A new Converter
+     */
+    @Override
+    protected DateTimeConverter makeConverter() {
+        return new LocalDateTimeConverter();
+    }
+
+    /**
+     * Create the Converter with a default value.
+     * @param defaultValue The default value
+     * @return A new Converter
+     */
+    @Override
+    protected DateTimeConverter makeConverter(final Object defaultValue) {
+        return new LocalDateTimeConverter(defaultValue);
+    }
+
+    /**
+     * Return the expected type
+     * @return The expected type
+     */
+    @Override
+    protected Class<?> getExpectedType() {
+        return LocalDateTime.class;
+    }
+
+    /**
+     * Convert from a Calendar to the appropriate Date type
+     *
+     * @param value The Calendar value to convert
+     * @return The converted value
+     */
+    @Override
+    protected Object toType(final Calendar value) {
+        return Instant.ofEpochMilli(value.getTimeInMillis()).atZone(ZoneId.systemDefault()).toLocalDateTime();
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/beanutils2/converters/OffsetDateTimeConverterTestCase.java b/src/test/java/org/apache/commons/beanutils2/converters/OffsetDateTimeConverterTestCase.java
new file mode 100644
index 0000000..c4d2f05
--- /dev/null
+++ b/src/test/java/org/apache/commons/beanutils2/converters/OffsetDateTimeConverterTestCase.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2.converters;
+
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.util.Calendar;
+
+import junit.framework.TestSuite;
+
+/**
+ * Test Case for the OffsetDateTimeConverter class.
+ *
+ */
+public class OffsetDateTimeConverterTestCase extends DateConverterTestBase {
+
+    /**
+     * Construct a new Date test case.
+     * @param name Test Name
+     */
+    public OffsetDateTimeConverterTestCase(final String name) {
+        super(name);
+    }
+
+    // ------------------------------------------------------------------------
+
+    /**
+     * Create Test Suite
+     * @return test suite
+     */
+    public static TestSuite suite() {
+        return new TestSuite(OffsetDateTimeConverterTestCase.class);
+    }
+
+    /** Set Up */
+    @Override
+    public void setUp() throws Exception {
+    }
+
+    /** Tear Down */
+    @Override
+    public void tearDown() throws Exception {
+    }
+
+    // ------------------------------------------------------------------------
+
+    /**
+     * Create the Converter with no default value.
+     * @return A new Converter
+     */
+    @Override
+    protected DateTimeConverter makeConverter() {
+        return new OffsetDateTimeConverter();
+    }
+
+    /**
+     * Create the Converter with a default value.
+     * @param defaultValue The default value
+     * @return A new Converter
+     */
+    @Override
+    protected DateTimeConverter makeConverter(final Object defaultValue) {
+        return new OffsetDateTimeConverter(defaultValue);
+    }
+
+    /**
+     * Return the expected type
+     * @return The expected type
+     */
+    @Override
+    protected Class<?> getExpectedType() {
+        return OffsetDateTime.class;
+    }
+
+    /**
+     * Convert from a Calendar to the appropriate Date type
+     *
+     * @param value The Calendar value to convert
+     * @return The converted value
+     */
+    @Override
+    protected Object toType(final Calendar value) {
+        return OffsetDateTime.ofInstant(Instant.ofEpochMilli(value.getTimeInMillis()), ZoneId.systemDefault());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/beanutils2/converters/ZonedDateTimeConverterTestCase.java b/src/test/java/org/apache/commons/beanutils2/converters/ZonedDateTimeConverterTestCase.java
new file mode 100644
index 0000000..25e8247
--- /dev/null
+++ b/src/test/java/org/apache/commons/beanutils2/converters/ZonedDateTimeConverterTestCase.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2.converters;
+
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.time.ZoneId;
+import java.util.Calendar;
+
+import junit.framework.TestSuite;
+
+/**
+ * Test Case for the ZonedDateTimeConverter class.
+ *
+ */
+public class ZonedDateTimeConverterTestCase extends DateConverterTestBase {
+
+    /**
+     * Construct a new Date test case.
+     * @param name Test Name
+     */
+    public ZonedDateTimeConverterTestCase(final String name) {
+        super(name);
+    }
+
+    // ------------------------------------------------------------------------
+
+    /**
+     * Create Test Suite
+     * @return test suite
+     */
+    public static TestSuite suite() {
+        return new TestSuite(ZonedDateTimeConverterTestCase.class);
+    }
+
+    /** Set Up */
+    @Override
+    public void setUp() throws Exception {
+    }
+
+    /** Tear Down */
+    @Override
+    public void tearDown() throws Exception {
+    }
+
+    // ------------------------------------------------------------------------
+
+    /**
+     * Create the Converter with no default value.
+     * @return A new Converter
+     */
+    @Override
+    protected DateTimeConverter makeConverter() {
+        return new ZonedDateTimeConverter();
+    }
+
+    /**
+     * Create the Converter with a default value.
+     * @param defaultValue The default value
+     * @return A new Converter
+     */
+    @Override
+    protected DateTimeConverter makeConverter(final Object defaultValue) {
+        return new ZonedDateTimeConverter(defaultValue);
+    }
+
+    /**
+     * Return the expected type
+     * @return The expected type
+     */
+    @Override
+    protected Class<?> getExpectedType() {
+        return ZonedDateTime.class;
+    }
+
+    /**
+     * Convert from a Calendar to the appropriate Date type
+     *
+     * @param value The Calendar value to convert
+     * @return The converted value
+     */
+    @Override
+    protected Object toType(final Calendar value) {
+        return ZonedDateTime.ofInstant(Instant.ofEpochMilli(value.getTimeInMillis()), ZoneId.systemDefault());
+    }
+}
\ No newline at end of file