diff --git a/src/main/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImpl.java b/src/main/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImpl.java index b238c1be..d6de6ce1 100644 --- a/src/main/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImpl.java +++ b/src/main/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImpl.java @@ -7,6 +7,7 @@ import ch.jalu.configme.properties.types.NumberType; import ch.jalu.configme.properties.types.RegexType; import ch.jalu.configme.properties.types.StringType; +import ch.jalu.configme.properties.types.TemporalType; import ch.jalu.typeresolver.TypeInfo; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -74,7 +75,10 @@ public LeafValueHandlerImpl(@NotNull MapperLeafType @NotNull ... leafTypes) { NumberType.SHORT, NumberType.BIG_INTEGER, NumberType.BIG_DECIMAL, - RegexType.REGEX) + RegexType.REGEX, + TemporalType.LOCAL_DATE, + TemporalType.LOCAL_TIME, + TemporalType.LOCAL_DATE_TIME) .collect(Collectors.toCollection(ArrayList::new)); } diff --git a/src/main/java/ch/jalu/configme/properties/LocalDateProperty.java b/src/main/java/ch/jalu/configme/properties/LocalDateProperty.java new file mode 100644 index 00000000..880c4c7b --- /dev/null +++ b/src/main/java/ch/jalu/configme/properties/LocalDateProperty.java @@ -0,0 +1,22 @@ +package ch.jalu.configme.properties; + +import ch.jalu.configme.properties.types.TemporalType; +import org.jetbrains.annotations.NotNull; + +import java.time.LocalDate; + +/** + * {@link LocalDate} property. + */ +public class LocalDateProperty extends TypeBasedProperty { + + /** + * Constructor. + * + * @param path the path of the property + * @param defaultValue the default value of the property + */ + public LocalDateProperty(@NotNull String path, @NotNull LocalDate defaultValue) { + super(path, TemporalType.LOCAL_DATE, defaultValue); + } +} diff --git a/src/main/java/ch/jalu/configme/properties/LocalDateTimeProperty.java b/src/main/java/ch/jalu/configme/properties/LocalDateTimeProperty.java new file mode 100644 index 00000000..612664ec --- /dev/null +++ b/src/main/java/ch/jalu/configme/properties/LocalDateTimeProperty.java @@ -0,0 +1,22 @@ +package ch.jalu.configme.properties; + +import ch.jalu.configme.properties.types.TemporalType; +import org.jetbrains.annotations.NotNull; + +import java.time.LocalDateTime; + +/** + * {@link LocalDateTime} property. + */ +public class LocalDateTimeProperty extends TypeBasedProperty { + + /** + * Constructor. + * + * @param path the path of the property + * @param defaultValue the default value of the property + */ + public LocalDateTimeProperty(@NotNull String path, @NotNull LocalDateTime defaultValue) { + super(path, TemporalType.LOCAL_DATE_TIME, defaultValue); + } +} diff --git a/src/main/java/ch/jalu/configme/properties/LocalTimeProperty.java b/src/main/java/ch/jalu/configme/properties/LocalTimeProperty.java new file mode 100644 index 00000000..5f6349a8 --- /dev/null +++ b/src/main/java/ch/jalu/configme/properties/LocalTimeProperty.java @@ -0,0 +1,22 @@ +package ch.jalu.configme.properties; + +import ch.jalu.configme.properties.types.TemporalType; +import org.jetbrains.annotations.NotNull; + +import java.time.LocalTime; + +/** + * {@link LocalTime} property. + */ +public class LocalTimeProperty extends TypeBasedProperty { + + /** + * Constructor. + * + * @param path the path of the property + * @param defaultValue the default value of the property + */ + public LocalTimeProperty(@NotNull String path, @NotNull LocalTime defaultValue) { + super(path, TemporalType.LOCAL_TIME, defaultValue); + } +} diff --git a/src/main/java/ch/jalu/configme/properties/types/TemporalType.java b/src/main/java/ch/jalu/configme/properties/types/TemporalType.java new file mode 100644 index 00000000..494007c7 --- /dev/null +++ b/src/main/java/ch/jalu/configme/properties/types/TemporalType.java @@ -0,0 +1,82 @@ +package ch.jalu.configme.properties.types; + +import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.Temporal; +import java.util.Arrays; +import java.util.List; +import java.util.function.BiFunction; + +/** + * Property type and mapper leaf type for temporal values. + * + * @param the expected temporal type + */ +public class TemporalType extends PropertyAndLeafType { + + /** Local Date temporal type. */ + public static final TemporalType LOCAL_DATE = new TemporalType<>( + LocalDate.class, Arrays.asList("yyyy-MM-dd", "dd.MM.yyyy", "MM/dd/yyyy"), LocalDate::parse); + /** Local Time temporal type. */ + public static final TemporalType LOCAL_TIME = new TemporalType<>( + LocalTime.class, Arrays.asList("HH:mm:ss", "HH.mm", "HH:mm"), LocalTime::parse); + /** Local Date Time temporal type. */ + public static final TemporalType LOCAL_DATE_TIME = new TemporalType<>( + LocalDateTime.class, Arrays.asList("yyyy-MM-dd HH:mm:ss", "dd.MM.yyyy HH:mm:ss", "MM/dd/yyyy HH:mm:ss"), + LocalDateTime::parse); + + private final List supportedFormats; + private final BiFunction temporalParser; + private String defaultExportFormat; + + /** + * Constructor. + * + * @param clazz the temporal type this type should convert to + * @param supportedFormats list of conversion formats supported for this type + * @param defaultParser function which can parse a value to this type in one of the given supportedFormats + */ + public TemporalType(@NotNull Class clazz, @NotNull List supportedFormats, + @NotNull BiFunction defaultParser) { + super(clazz); + if (supportedFormats.isEmpty()) { + throw new IllegalArgumentException("At least one supported format must be provided."); + } + this.supportedFormats = supportedFormats; + this.temporalParser = defaultParser; + this.defaultExportFormat = supportedFormats.get(0); + } + + @Override + public @Nullable T convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) { + if (!(object instanceof String)) { + return null; + } + return convertToTemporalType((String) object); + } + + @Override + public @Nullable Object toExportValue(@NotNull T value) { + return DateTimeFormatter.ofPattern(this.defaultExportFormat).format(value); + } + + private T convertToTemporalType(String temporalText) { + for (String format: this.supportedFormats) { + try { + T parsedValue = this.temporalParser.apply(temporalText, DateTimeFormatter.ofPattern(format)); + this.defaultExportFormat = format; + return parsedValue; + } catch (DateTimeParseException e) { + // try next format + } + } + return null; + } +} diff --git a/src/test/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImplTest.java b/src/test/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImplTest.java index 39d99516..3bb5c49b 100644 --- a/src/test/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImplTest.java +++ b/src/test/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImplTest.java @@ -9,12 +9,12 @@ import ch.jalu.configme.properties.types.NumberType; import ch.jalu.configme.properties.types.RegexType; import ch.jalu.configme.properties.types.StringType; +import ch.jalu.configme.properties.types.TemporalType; import ch.jalu.typeresolver.typeimpl.WildcardTypeImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -59,7 +59,7 @@ void shouldReturnDefaultLeafTypes() { List leafTypes = LeafValueHandlerImpl.createDefaultLeafTypes(); // then - assertThat(leafTypes, hasSize(12)); + assertThat(leafTypes, hasSize(15)); assertThat(leafTypes.get(0), sameInstance(BooleanType.BOOLEAN)); assertThat(leafTypes.get(1), sameInstance(StringType.STRING)); assertThat(leafTypes.get(2), sameInstance(NumberType.INTEGER)); @@ -72,6 +72,9 @@ void shouldReturnDefaultLeafTypes() { assertThat(leafTypes.get(9), sameInstance(NumberType.BIG_INTEGER)); assertThat(leafTypes.get(10), sameInstance(NumberType.BIG_DECIMAL)); assertThat(leafTypes.get(11), sameInstance(RegexType.REGEX)); + assertThat(leafTypes.get(12), sameInstance(TemporalType.LOCAL_DATE)); + assertThat(leafTypes.get(13), sameInstance(TemporalType.LOCAL_TIME)); + assertThat(leafTypes.get(14), sameInstance(TemporalType.LOCAL_DATE_TIME)); } @Test @@ -96,7 +99,7 @@ void shouldCreateValueHandlerWithBuilder() { .addType(leafType1) .addDefaults() .addType(leafType2) - .removeMatchingTypes(type -> type instanceof NumberType) + .removeMatchingTypes(type -> type instanceof NumberType || type instanceof TemporalType) .build(); // then @@ -166,13 +169,11 @@ void shouldNotConvertForUnsupportedTargetTypes() { Object object = "2020-02-13"; ConvertErrorRecorder errorRecorder = mock(ConvertErrorRecorder.class); - MappingContext dateContext = MappingContextImpl.createRoot(of(LocalDate.class), errorRecorder); MappingContext wildcardContext = MappingContextImpl.createRoot(of(WildcardTypeImpl.newUnboundedWildcard()), errorRecorder); LeafValueHandlerImpl leafValueHandler = new LeafValueHandlerImpl(LeafValueHandlerImpl.createDefaultLeafTypes()); // when / then - assertThat(leafValueHandler.convert(object, dateContext), nullValue()); assertThat(leafValueHandler.convert(object, wildcardContext), nullValue()); } @@ -194,7 +195,6 @@ void shouldNotConvertUnsupportedValuesToExportValues() { ExportContext exportContext = ExportContextImpl.createRoot(); // when / then - assertThat(leafValueHandler.toExportValue(LocalDate.now(), exportContext), nullValue()); assertThat(leafValueHandler.toExportValue(new Object(), exportContext), nullValue()); } diff --git a/src/test/java/ch/jalu/configme/properties/LocalDatePropertyTest.java b/src/test/java/ch/jalu/configme/properties/LocalDatePropertyTest.java new file mode 100644 index 00000000..fbdfe029 --- /dev/null +++ b/src/test/java/ch/jalu/configme/properties/LocalDatePropertyTest.java @@ -0,0 +1,70 @@ +package ch.jalu.configme.properties; + +import ch.jalu.configme.properties.convertresult.PropertyValue; +import ch.jalu.configme.resource.PropertyReader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; + +import static ch.jalu.configme.TestUtils.isErrorValueOf; +import static ch.jalu.configme.TestUtils.isValidValueOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for {@link LocalDateProperty}. + */ +@ExtendWith(MockitoExtension.class) +class LocalDatePropertyTest { + + private static PropertyReader reader; + + @BeforeAll + static void setUpConfiguration() { + reader = mock(PropertyReader.class); + when(reader.getObject("local-date.path.test")).thenReturn("1999-01-31"); + when(reader.getObject("local-date.path.wrong")).thenReturn("31-01-1999"); + } + + @Test + void shouldGetLocalDateValue() { + // given + Property property = new LocalDateProperty("local-date.path.test", LocalDate.of(1970, 1, 31)); + + // when + PropertyValue result = property.determineValue(reader); + + //then + assertThat(result, isValidValueOf(LocalDate.of(1999, 1, 31))); + } + + @Test + void shouldGetLocalDateDefault() { + // given + LocalDate defaultValue = LocalDate.of(1970, 1, 31); + Property property = new LocalDateProperty("local-date.path.wrong", defaultValue); + + // when + PropertyValue result = property.determineValue(reader); + + // then + assertThat(result, isErrorValueOf(defaultValue)); + } + + @Test + void shouldReturnValueForExport() { + // given + Property property = new LocalDateProperty("export.path.local-date", LocalDate.of(1970, 1, 31)); + + // when + Object exportValue = property.toExportValue(LocalDate.of(2000, 12, 31)); + + // then + assertThat(exportValue, equalTo("2000-12-31")); + } +} diff --git a/src/test/java/ch/jalu/configme/properties/LocalDateTimePropertyTest.java b/src/test/java/ch/jalu/configme/properties/LocalDateTimePropertyTest.java new file mode 100644 index 00000000..868bede4 --- /dev/null +++ b/src/test/java/ch/jalu/configme/properties/LocalDateTimePropertyTest.java @@ -0,0 +1,70 @@ +package ch.jalu.configme.properties; + +import ch.jalu.configme.properties.convertresult.PropertyValue; +import ch.jalu.configme.resource.PropertyReader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; + +import static ch.jalu.configme.TestUtils.isErrorValueOf; +import static ch.jalu.configme.TestUtils.isValidValueOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for {@link LocalDateTimeProperty}. + */ +@ExtendWith(MockitoExtension.class) +class LocalDateTimePropertyTest { + + private static PropertyReader reader; + + @BeforeAll + static void setUpConfiguration() { + reader = mock(PropertyReader.class); + when(reader.getObject("local-date-time.path.test")).thenReturn("2001-10-20 19:55:30"); + when(reader.getObject("local-date-time.path.wrong")).thenReturn("20-2001-10 55:19:30"); + } + + @Test + void shouldGetLocalDateTimeValue() { + // given + Property property = new LocalDateTimeProperty("local-date-time.path.test", LocalDateTime.of(1970, 1, 31, 12, 0)); + + // when + PropertyValue result = property.determineValue(reader); + + // then + assertThat(result, isValidValueOf(LocalDateTime.of(2001, 10, 20, 19, 55, 30))); + } + + @Test + void shouldGetLocalDateTimeDefault() { + // given + LocalDateTime defaultDateTime = LocalDateTime.of(1970, 1, 31, 12, 0); + Property property = new LocalDateTimeProperty("local-date-time.path.wrong", defaultDateTime); + + // when + PropertyValue result = property.determineValue(reader); + + // then + assertThat(result, isErrorValueOf(defaultDateTime)); + } + + @Test + void shouldReturnValueForExport() { + // given + Property property = new LocalDateTimeProperty("export.path.local-date-time", LocalDateTime.of(1970, 1, 31, 12, 0)); + + // when + Object exportedValue = property.toExportValue(LocalDateTime.of(2001, 10, 22, 11, 39, 42)); + + // then + assertThat(exportedValue, equalTo("2001-10-22 11:39:42")); + } +} diff --git a/src/test/java/ch/jalu/configme/properties/LocalTimePropertyTest.java b/src/test/java/ch/jalu/configme/properties/LocalTimePropertyTest.java new file mode 100644 index 00000000..567fa925 --- /dev/null +++ b/src/test/java/ch/jalu/configme/properties/LocalTimePropertyTest.java @@ -0,0 +1,70 @@ +package ch.jalu.configme.properties; + +import ch.jalu.configme.properties.convertresult.PropertyValue; +import ch.jalu.configme.resource.PropertyReader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalTime; + +import static ch.jalu.configme.TestUtils.isErrorValueOf; +import static ch.jalu.configme.TestUtils.isValidValueOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for {@link LocalTimeProperty}. + */ +@ExtendWith(MockitoExtension.class) +class LocalTimePropertyTest { + + private static PropertyReader reader; + + @BeforeAll + static void setUpConfiguration() { + reader = mock(PropertyReader.class); + when(reader.getObject("local-time.path.test")).thenReturn("19:55:30"); + when(reader.getObject("local-time.path.wrong")).thenReturn("55:19:30"); + } + + @Test + void shouldGetLocalTimeValue() { + // given + Property property = new LocalTimeProperty("local-time.path.test", LocalTime.of(12, 0)); + + // when + PropertyValue result = property.determineValue(reader); + + // then + assertThat(result, isValidValueOf(LocalTime.of(19, 55, 30))); + } + + @Test + void shouldGetLocalTimeDefault() { + // given + LocalTime defaultTime = LocalTime.of(12, 0); + Property property = new LocalTimeProperty("local-time.path.wrong", defaultTime); + + // when + PropertyValue result = property.determineValue(reader); + + // then + assertThat(result, isErrorValueOf(defaultTime)); + } + + @Test + void shouldReturnValueForExport() { + // given + Property property = new LocalTimeProperty("export.path.local-time", LocalTime.of(23, 59)); + + // when + Object exportedValue = property.toExportValue(LocalTime.of(19, 55)); + + // then + assertThat(exportedValue, equalTo("19:55:00")); + } +} diff --git a/src/test/java/ch/jalu/configme/properties/types/TemporalTypeTest.java b/src/test/java/ch/jalu/configme/properties/types/TemporalTypeTest.java new file mode 100644 index 00000000..f1a862f1 --- /dev/null +++ b/src/test/java/ch/jalu/configme/properties/types/TemporalTypeTest.java @@ -0,0 +1,131 @@ +package ch.jalu.configme.properties.types; + +import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Collections; + +import static ch.jalu.typeresolver.TypeInfo.of; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test for {@link TemporalType}. + */ +@ExtendWith(MockitoExtension.class) +public class TemporalTypeTest { + + @AfterAll + static void reset() { + ConvertErrorRecorder errorRecorder = new ConvertErrorRecorder(); + TemporalType.LOCAL_DATE.convert("1970-01-01", errorRecorder); + TemporalType.LOCAL_TIME.convert("12:00:00", errorRecorder); + TemporalType.LOCAL_DATE_TIME.convert("1970-01-01 12:00:00", errorRecorder); + } + + @Test + void shouldNotAllowToInstantiateTemporalTypeWithInvalidArguments() { + // when + IllegalArgumentException noSupportedFormats = assertThrows(IllegalArgumentException.class, + () -> new TemporalType<>(LocalDate.class, Collections.emptyList(), LocalDate::parse)); + + // then + assertThat(noSupportedFormats.getMessage(), matchesPattern("At least one supported format must be provided.")); + } + + @Test + void shouldConvertToGivenTemporalTypeForSupportedFormat() { + // given + ConvertErrorRecorder errorRecorder = new ConvertErrorRecorder(); + LocalDate localDate = LocalDate.of(1970, 1, 31); + LocalTime localTimeFull = LocalTime.of(15, 35, 40); + LocalTime localTimeShort = LocalTime.of(15, 35); + LocalDateTime localDateTime = LocalDateTime.of(1970, 1, 31, 15, 35, 40); + + // when / then + assertThat(TemporalType.LOCAL_DATE.convert("1970-01-31", of(LocalDate.class), errorRecorder), equalTo(localDate)); + assertThat(TemporalType.LOCAL_DATE.convert("31.01.1970", of(LocalDate.class), errorRecorder), equalTo(localDate)); + assertThat(TemporalType.LOCAL_DATE.convert("01/31/1970", of(LocalDate.class), errorRecorder), equalTo(localDate)); + assertThat(TemporalType.LOCAL_TIME.convert("15:35:40", of(LocalTime.class), errorRecorder), equalTo(localTimeFull)); + assertThat(TemporalType.LOCAL_TIME.convert("15.35", of(LocalTime.class), errorRecorder), equalTo(localTimeShort)); + assertThat(TemporalType.LOCAL_TIME.convert("15:35", of(LocalTime.class), errorRecorder), equalTo(localTimeShort)); + assertThat(TemporalType.LOCAL_DATE_TIME.convert("1970-01-31 15:35:40", of(LocalDateTime.class), errorRecorder), equalTo(localDateTime)); + assertThat(TemporalType.LOCAL_DATE_TIME.convert("31.01.1970 15:35:40", of(LocalDateTime.class), errorRecorder), equalTo(localDateTime)); + assertThat(TemporalType.LOCAL_DATE_TIME.convert("01/31/1970 15:35:40", of(LocalDateTime.class), errorRecorder), equalTo(localDateTime)); + } + + @Test + void shouldReturnNullForUnsupportedFormat() { + // given + ConvertErrorRecorder errorRecorder = new ConvertErrorRecorder(); + + // when / then + assertThat(TemporalType.LOCAL_DATE.convert("31-01-1970", of(LocalDate.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_DATE.convert("1970.01.31", of(LocalDate.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_DATE.convert("1970/01/31", of(LocalDate.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_TIME.convert("25:35:40", of(LocalTime.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_TIME.convert("15.35.40", of(LocalTime.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_TIME.convert("15:35.40", of(LocalTime.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_TIME.convert("15.35:40", of(LocalTime.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_DATE_TIME.convert("31-01-1970 25:35:40", of(LocalDateTime.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_DATE_TIME.convert("1970.01.31 15.35.40", of(LocalDateTime.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_DATE_TIME.convert("1970/01/31 15.35:40", of(LocalDateTime.class), errorRecorder), nullValue()); + } + + @Test + void shouldReturnNullForInvalidValue() { + // given + ConvertErrorRecorder errorRecorder = new ConvertErrorRecorder(); + + // when / then + assertThat(TemporalType.LOCAL_DATE.convert("test", of(LocalDate.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_TIME.convert("test", of(LocalTime.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_DATE_TIME.convert("test", of(LocalDateTime.class), errorRecorder), nullValue()); + } + + @Test + void shouldReturnNullForNull() { + // given + ConvertErrorRecorder errorRecorder = new ConvertErrorRecorder(); + + // when / then + assertThat(TemporalType.LOCAL_DATE.convert(null, of(LocalDate.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_TIME.convert(null, of(LocalTime.class), errorRecorder), nullValue()); + assertThat(TemporalType.LOCAL_DATE_TIME.convert(null, of(LocalDateTime.class), errorRecorder), nullValue()); + } + + @Test + void shouldExportValueAsString() { + // given / when / then + assertThat(TemporalType.LOCAL_DATE.toExportValue(LocalDate.of(1970, 1, 31)), equalTo("1970-01-31")); + assertThat(TemporalType.LOCAL_TIME.toExportValue(LocalTime.of(13, 55, 13)), equalTo("13:55:13")); + assertThat(TemporalType.LOCAL_DATE_TIME.toExportValue(LocalDateTime.of(1970, 1, 31, 12, 30, 47)), equalTo("1970-01-31 12:30:47")); + } + + @Test + void shouldExportValueInMatchingFormatAsString() { + // given + ConvertErrorRecorder errorRecorder = new ConvertErrorRecorder(); + LocalDate localDate = TemporalType.LOCAL_DATE.convert("01/31/1970", errorRecorder); + LocalTime localTime = TemporalType.LOCAL_TIME.convert("11:11", errorRecorder); + LocalDateTime localDateTime = TemporalType.LOCAL_DATE_TIME.convert("31.01.1970 13:55:13", errorRecorder); + + // when / then + assertThat(localDate, notNullValue()); + assertThat(localTime, notNullValue()); + assertThat(localDateTime, notNullValue()); + assertThat(TemporalType.LOCAL_DATE.toExportValue(localDate), equalTo("01/31/1970")); + assertThat(TemporalType.LOCAL_TIME.toExportValue(localTime), equalTo("11:11")); + assertThat(TemporalType.LOCAL_DATE_TIME.toExportValue(localDateTime), equalTo("31.01.1970 13:55:13")); + } +}