Skip to content

Commit 6ba84b6

Browse files
authored
Java 8 date and time support (#685)
PR: #172 Java 8 datetime types support
1 parent 38be522 commit 6ba84b6

21 files changed

Lines changed: 1287 additions & 40 deletions
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2005-2018 Dozer Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.dozermapper.core.converters;
17+
18+
import java.lang.reflect.InvocationTargetException;
19+
import java.lang.reflect.Method;
20+
import java.time.format.DateTimeFormatter;
21+
import java.time.temporal.TemporalAccessor;
22+
23+
import org.apache.commons.beanutils.Converter;
24+
25+
/**
26+
* Base converter to Java 8 date and time types.
27+
* <pre>
28+
* Supported basic conversions:
29+
* Any child of TemporalAccessor &#45;&gt; LocalDateTime
30+
* String &#45;&gt; LocalDateTime
31+
* </pre>
32+
*/
33+
public abstract class AbstractJava8DateTimeConverter implements Converter {
34+
35+
private final DateTimeFormatter formatter;
36+
37+
AbstractJava8DateTimeConverter(DateTimeFormatter formatter) {
38+
this.formatter = formatter;
39+
}
40+
41+
@Override
42+
@SuppressWarnings("unchecked")
43+
public Object convert(Class destClass, Object srcObject) {
44+
Class<?> srcObjectClass = srcObject.getClass();
45+
try {
46+
if (TemporalAccessor.class.isAssignableFrom(srcObjectClass)) {
47+
Method method = destClass.getDeclaredMethod("from", TemporalAccessor.class);
48+
return method.invoke(null, (TemporalAccessor)srcObject);
49+
} else if (String.class.isAssignableFrom(srcObjectClass) && formatter != null) {
50+
Method method = destClass.getDeclaredMethod("parse", CharSequence.class, DateTimeFormatter.class);
51+
return method.invoke(null, srcObject, formatter);
52+
} else {
53+
throw new ConversionException(String.format("Unsupported source object type: %s", srcObjectClass), null);
54+
}
55+
} catch (NoSuchMethodException | IllegalAccessException e) {
56+
throw new ConversionException(
57+
String.format("Failed to create %s from %s", destClass.getSimpleName(), srcObject.getClass().getSimpleName()), e);
58+
} catch (InvocationTargetException e) {
59+
throw new ConversionException(
60+
String.format("Failed to create %s from %s", destClass.getSimpleName(), srcObject.getClass().getSimpleName()), e.getTargetException());
61+
}
62+
}
63+
}

core/src/main/java/com/github/dozermapper/core/converters/DateFormatContainer.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@
1717

1818
import java.text.DateFormat;
1919
import java.text.SimpleDateFormat;
20+
import java.time.format.DateTimeFormatter;
2021
import java.util.Locale;
2122

2223
/**
2324
* Internal class used as a container to determine the date format to use for a particular field mapping. Only intended
2425
* for internal use.
2526
*/
2627
public class DateFormatContainer {
28+
2729
private String dfStr;
2830
private DateFormat dateFormat;
31+
private DateTimeFormatter dateTimeFormatter;
2932

3033
public DateFormatContainer(String dfStr) {
3134
this.dfStr = dfStr;
@@ -38,6 +41,28 @@ public DateFormat getDateFormat() {
3841
return dateFormat;
3942
}
4043

44+
/**
45+
* Date and time formatter for Java 8 Date &amp; Time objects.
46+
*
47+
* @return formatter
48+
*/
49+
public DateTimeFormatter getDateTimeFormatter() {
50+
if (dateTimeFormatter == null) {
51+
if (dfStr != null) {
52+
dateTimeFormatter = DateTimeFormatter.ofPattern(dfStr);
53+
}
54+
}
55+
return dateTimeFormatter;
56+
}
57+
58+
/**
59+
* TODO replace method call with reflection in tests.
60+
*
61+
* @param dateFormat dateFormat
62+
* @deprecated This method will break internal state of the instance by setting formatter which is not using
63+
* format provided using constructor. It will be removed in future releases.
64+
*/
65+
@Deprecated
4166
public void setDateFormat(DateFormat dateFormat) {
4267
this.dateFormat = dateFormat;
4368
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2005-2018 Dozer Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.dozermapper.core.converters;
17+
18+
import java.time.Instant;
19+
import java.time.LocalDate;
20+
import java.time.LocalDateTime;
21+
import java.time.LocalTime;
22+
import java.time.ZoneId;
23+
import java.time.format.DateTimeFormatter;
24+
import java.util.Date;
25+
26+
/**
27+
* Internal converter to {@link LocalDateTime} type. Only intended for internal use.
28+
* <pre>
29+
* Supported conversions:
30+
* Any child of TemporalAccessor &#45;&gt; LocalDateTime ()
31+
* String &#45;&gt; LocalDateTime
32+
* Long &#45;&gt; LocalDateTime
33+
* Date &#45;&gt; LocalDateTime
34+
* </pre>
35+
*/
36+
public final class LocalDateTimeConverter extends AbstractJava8DateTimeConverter {
37+
38+
public LocalDateTimeConverter(DateTimeFormatter formatter) {
39+
super(formatter);
40+
}
41+
42+
@Override
43+
public Object convert(Class destClass, Object srcObject) {
44+
LocalDateTime localDateTime = convertToLocalDateTime(srcObject);
45+
if (localDateTime != null) {
46+
if (LocalTime.class.isAssignableFrom(destClass)) {
47+
return localDateTime.toLocalTime();
48+
} else if (LocalDate.class.isAssignableFrom(destClass)) {
49+
return localDateTime.toLocalDate();
50+
}
51+
return localDateTime;
52+
}
53+
return super.convert(destClass, srcObject);
54+
}
55+
56+
private LocalDateTime convertToLocalDateTime(Object srcObject) {
57+
Class<?> srcObjectClass = srcObject.getClass();
58+
59+
if (Date.class.isAssignableFrom(srcObjectClass)) {
60+
Instant instant = ((Date)srcObject).toInstant();
61+
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
62+
} else if (Instant.class.isAssignableFrom(srcObjectClass)) {
63+
return LocalDateTime.ofInstant((Instant)srcObject, ZoneId.systemDefault());
64+
} else if (Long.class.isAssignableFrom(srcObjectClass)) {
65+
Instant instant = Instant.ofEpochMilli((Long)srcObject);
66+
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
67+
} else {
68+
return null;
69+
}
70+
}
71+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2005-2018 Dozer Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.dozermapper.core.converters;
17+
18+
import java.time.Instant;
19+
import java.time.OffsetDateTime;
20+
import java.time.OffsetTime;
21+
import java.time.ZoneId;
22+
import java.time.format.DateTimeFormatter;
23+
import java.util.Date;
24+
25+
/**
26+
* Internal class for converting supported types to {@link java.time.OffsetDateTime}.
27+
* Only intended for internal use.
28+
*/
29+
public class OffsetDateTimeConverter
30+
extends AbstractJava8DateTimeConverter {
31+
32+
public OffsetDateTimeConverter(DateTimeFormatter formatter) {
33+
super(formatter);
34+
}
35+
36+
@Override
37+
public Object convert(Class destClass, Object srcObject) {
38+
OffsetDateTime offsetDateTime = convertToOffsetDateTime(srcObject);
39+
if (offsetDateTime != null) {
40+
if (OffsetTime.class.isAssignableFrom(destClass)) {
41+
return offsetDateTime.toOffsetTime();
42+
}
43+
return offsetDateTime;
44+
}
45+
return super.convert(destClass, srcObject);
46+
}
47+
48+
private OffsetDateTime convertToOffsetDateTime(Object srcObject) {
49+
Class<?> srcObjectClass = srcObject.getClass();
50+
if (Date.class.isAssignableFrom(srcObjectClass)) {
51+
Instant instant = ((Date)srcObject).toInstant();
52+
return OffsetDateTime.ofInstant(instant, ZoneId.systemDefault());
53+
} else if (Instant.class.isAssignableFrom(srcObjectClass)) {
54+
return OffsetDateTime.ofInstant((Instant)srcObject, ZoneId.systemDefault());
55+
} else if (Long.class.isAssignableFrom(srcObjectClass)) {
56+
Instant instant = Instant.ofEpochMilli((Long)srcObject);
57+
return OffsetDateTime.ofInstant(instant, ZoneId.systemDefault());
58+
} else {
59+
return null;
60+
}
61+
}
62+
}

core/src/main/java/com/github/dozermapper/core/converters/PrimitiveOrWrapperConverter.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717

1818
import java.math.BigDecimal;
1919
import java.math.BigInteger;
20+
import java.time.LocalDate;
21+
import java.time.LocalDateTime;
22+
import java.time.LocalTime;
23+
import java.time.OffsetDateTime;
24+
import java.time.OffsetTime;
25+
import java.time.ZonedDateTime;
2026
import java.util.Calendar;
2127
import java.util.HashMap;
2228
import java.util.Map;
@@ -105,6 +111,12 @@ private Converter getPrimitiveOrWrapperConverter(Class destClass, DateFormatCont
105111
result = new EnumConverter();
106112
} else if (JAXBElement.class.isAssignableFrom(destClass) && destFieldName != null) {
107113
result = new JAXBElementConverter(destObj.getClass().getCanonicalName(), destFieldName, dateFormatContainer.getDateFormat(), beanContainer);
114+
} else if (isLocalTime(destClass)) {
115+
result = new LocalDateTimeConverter(dateFormatContainer.getDateTimeFormatter());
116+
} else if (isOffsetTime(destClass)) {
117+
result = new OffsetDateTimeConverter(dateFormatContainer.getDateTimeFormatter());
118+
} else if (ZonedDateTime.class.isAssignableFrom(destClass)) {
119+
result = new ZonedDateTimeConverter(dateFormatContainer.getDateTimeFormatter());
108120
}
109121
}
110122
return result == null ? new StringConstructorConverter(dateFormatContainer) : result;
@@ -118,7 +130,23 @@ public boolean accepts(Class<?> aClass) {
118130
|| Boolean.class.equals(aClass)
119131
|| java.util.Date.class.isAssignableFrom(aClass)
120132
|| java.util.Calendar.class.isAssignableFrom(aClass)
121-
|| aClass.isEnum();
133+
|| aClass.isEnum()
134+
|| LocalDateTime.class.isAssignableFrom(aClass)
135+
|| LocalDate.class.isAssignableFrom(aClass)
136+
|| LocalTime.class.isAssignableFrom(aClass)
137+
|| OffsetDateTime.class.isAssignableFrom(aClass)
138+
|| OffsetTime.class.isAssignableFrom(aClass)
139+
|| ZonedDateTime.class.isAssignableFrom(aClass);
122140
}
123141

142+
private static boolean isLocalTime(Class clazz) {
143+
return LocalDateTime.class.isAssignableFrom(clazz) ||
144+
LocalDate.class.isAssignableFrom(clazz) ||
145+
LocalTime.class.isAssignableFrom(clazz);
146+
}
147+
148+
private static boolean isOffsetTime(Class clazz) {
149+
return OffsetDateTime.class.isAssignableFrom(clazz) ||
150+
OffsetTime.class.isAssignableFrom(clazz);
151+
}
124152
}

core/src/main/java/com/github/dozermapper/core/converters/StringConverter.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,48 @@
1515
*/
1616
package com.github.dozermapper.core.converters;
1717

18+
import java.time.temporal.TemporalAccessor;
19+
import java.util.Calendar;
20+
import java.util.Date;
21+
1822
import org.apache.commons.beanutils.Converter;
1923

2024
/**
2125
* Internal class for converting Supported Data Types to String. Uses date formatter for Date and Calendar source
2226
* objects. Calls toString() on the source object for all other types. Only intended for internal use.
2327
*/
2428
public class StringConverter implements Converter {
29+
2530
private DateFormatContainer dateFormatContainer;
2631

2732
public StringConverter(DateFormatContainer dateFormatContainer) {
2833
this.dateFormatContainer = dateFormatContainer;
2934
}
3035

3136
public Object convert(Class destClass, Object srcObj) {
32-
String result;
3337
Class srcClass = srcObj.getClass();
34-
if (dateFormatContainer != null && java.util.Date.class.isAssignableFrom(srcClass)
35-
&& dateFormatContainer.getDateFormat() != null) {
36-
result = dateFormatContainer.getDateFormat().format((java.util.Date)srcObj);
37-
} else if (dateFormatContainer != null && java.util.Calendar.class.isAssignableFrom(srcClass)
38-
&& dateFormatContainer.getDateFormat() != null) {
39-
result = dateFormatContainer.getDateFormat().format(((java.util.Calendar)srcObj).getTime());
38+
if (hasDateFormat()) {
39+
if (Date.class.isAssignableFrom(srcClass)) {
40+
return dateFormatContainer.getDateFormat().format((Date)srcObj);
41+
}
42+
if (Calendar.class.isAssignableFrom(srcClass)) {
43+
return dateFormatContainer.getDateFormat().format(((Calendar)srcObj).getTime());
44+
} else if (TemporalAccessor.class.isAssignableFrom(srcClass)) {
45+
return dateFormatContainer.getDateTimeFormatter().format((TemporalAccessor)srcObj);
46+
} else {
47+
return srcObj.toString();
48+
}
4049
} else {
41-
result = srcObj.toString();
50+
return srcObj.toString();
4251
}
43-
44-
return result;
45-
}
46-
47-
public DateFormatContainer getDateFormatContainer() {
48-
return dateFormatContainer;
4952
}
5053

51-
public void setDateFormatContainer(DateFormatContainer dateFormat) {
52-
this.dateFormatContainer = dateFormat;
54+
/**
55+
* Whether date format is provided or not
56+
*
57+
* @return true - date format provided. Otherwise false.
58+
*/
59+
private boolean hasDateFormat() {
60+
return dateFormatContainer != null && dateFormatContainer.getDateFormat() != null;
5361
}
5462
}

0 commit comments

Comments
 (0)