diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/LocalDateTimeProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/LocalDateTimeProp.java index 566dfd7f..d527c915 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/property/LocalDateTimeProp.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/LocalDateTimeProp.java @@ -7,7 +7,7 @@ import org.yarnandtail.andhow.valuetype.LocalDateTimeType; /** - * A Property that refers to a Long value. + * A Property that refers to a LocalDateTime value. * * All the basic Java types use a three letter abv. to keep declaration lines * short, in the form of: [Type]Prop diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/ZonedDateTimeProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/ZonedDateTimeProp.java new file mode 100644 index 00000000..12be3c92 --- /dev/null +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/ZonedDateTimeProp.java @@ -0,0 +1,91 @@ +package org.yarnandtail.andhow.property; + +import org.yarnandtail.andhow.api.*; +import org.yarnandtail.andhow.valid.ZonedDateTimeValidator; +import org.yarnandtail.andhow.valuetype.ZonedDateTimeType; + +import java.time.ZonedDateTime; +import java.util.List; + +/** + * A Property that refers to a ZonedDateTime value. + * + * By default this uses the TrimToNullTrimmer, which removes all whitespace from + * the value and ultimately null if the value is all whitespace. The String + * constructor version is used when creating instances of BigDecimal. + * + * @author chace86 + */ +public class ZonedDateTimeProp extends PropertyBase { + + public ZonedDateTimeProp(ZonedDateTime defaultValue, boolean required, String shortDesc, List> validators, + List aliases, PropertyType paramType, ValueType valueType, Trimmer trimmer, String helpText) { + super(defaultValue, required, shortDesc, validators, aliases, paramType, valueType, trimmer, helpText); + } + + /** + * Return an instance of ZonedDateTimeBuilder + */ + public static ZonedDateTimeBuilder builder() { return new ZonedDateTimeBuilder(); } + + /** + * Build a ZonedDateTimeProp + */ + public static class ZonedDateTimeBuilder extends PropertyBuilderBase { + + /** + * Construct an instance of ZonedDateTimeBuilder + */ + public ZonedDateTimeBuilder() { + instance = this; + valueType(ZonedDateTimeType.instance()); + trimmer(TrimToNullTrimmer.instance()); + } + + @Override + public ZonedDateTimeProp build() { + return new ZonedDateTimeProp(_defaultValue, _nonNull, _desc, _validators, + _aliases, PropertyType.SINGLE_NAME_VALUE, _valueType, _trimmer, _helpText); + } + + /** + * The property must be greater than the reference + * @param reference value the property must be greater than + * @return the builder instance + */ + public ZonedDateTimeBuilder mustBeGreaterThan(ZonedDateTime reference) { + validation(new ZonedDateTimeValidator.GreaterThan(reference)); + return instance; + } + + /** + * The property must be greater than or equal to the reference + * @param reference value the property must be greater than or equal to + * @return the builder instance + */ + public ZonedDateTimeBuilder mustBeGreaterThanOrEqualTo(ZonedDateTime reference) { + validation(new ZonedDateTimeValidator.GreaterThanOrEqualTo(reference)); + return instance; + } + + /** + * The property must be less than the reference + * @param reference value the property must be less than + * @return the builder instance + */ + public ZonedDateTimeBuilder mustBeLessThan(ZonedDateTime reference) { + validation(new ZonedDateTimeValidator.LessThan(reference)); + return instance; + } + + /** + * The property must be less than or equal to the reference + * @param reference value the property must be less than or equal to + * @return the builder instance + */ + public ZonedDateTimeBuilder mustBeLessThanOrEqualTo(ZonedDateTime reference) { + validation(new ZonedDateTimeValidator.LessThanOrEqualTo(reference)); + return instance; + } + } +} diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/ZonedDateTimeValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/ZonedDateTimeValidator.java new file mode 100644 index 00000000..5a314055 --- /dev/null +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/ZonedDateTimeValidator.java @@ -0,0 +1,99 @@ +package org.yarnandtail.andhow.valid; + +import org.yarnandtail.andhow.api.Validator; + +import java.time.ZonedDateTime; + +/** + * Abstract class implementing the Validator interface for ZonedDateTime. + * Extended by nested static classes. The nested classes implement + * constraints that may be used when building the property. + */ +public abstract class ZonedDateTimeValidator implements Validator { + + final ZonedDateTime ref; + + /** + * Base constructor of ZonedDateTimeValidator constraints + * @param ref to be compared to property value + */ + ZonedDateTimeValidator(ZonedDateTime ref) { this.ref = ref; } + + @Override + public boolean isSpecificationValid() { return ref != null; } + + @Override + public String getInvalidSpecificationMessage() { return "The constraint may not be null"; } + + /** + * Validate that a ZonedDateTime is greater than specified reference. + */ + public static class GreaterThan extends ZonedDateTimeValidator { + + /** + * Construct a GreaterThan property constraint + * @param ref to be compared to property value + */ + public GreaterThan(ZonedDateTime ref) { super(ref); } + + @Override + public boolean isValid(ZonedDateTime value) { return value != null && value.isAfter(ref); } + + @Override + public String getTheValueMustDescription() { return "be greater than " + ref.toString(); } + } + + /** + * Validate that a ZonedDateTime is greater than or equal to specified reference. + */ + public static class GreaterThanOrEqualTo extends ZonedDateTimeValidator { + + /** + * Construct a GreaterThanOrEqualTo property constraint + * @param ref to be compared to property value + */ + public GreaterThanOrEqualTo(ZonedDateTime ref) { super(ref); } + + @Override + public boolean isValid(ZonedDateTime value) { return value != null && (value.isEqual(ref) || value.isAfter(ref)); } + + @Override + public String getTheValueMustDescription() { return "be greater than or equal to " + ref; } + } + + /** + * Validate that a ZonedDateTime is less than to specified reference. + */ + public static class LessThan extends ZonedDateTimeValidator { + + /** + * Construct a LessThan property constraint + * @param ref to be compared to property value + */ + public LessThan(ZonedDateTime ref) { super(ref); } + + @Override + public boolean isValid(ZonedDateTime value) { return value != null && value.isBefore(ref); } + + @Override + public String getTheValueMustDescription() { return "be less than " + ref; } + } + + /** + * Validate that a ZonedDateTime is less than or equal to specified reference. + */ + public static class LessThanOrEqualTo extends ZonedDateTimeValidator { + + /** + * Construct a LessThanOrEqualTo property constraint + * @param ref to be compared to property value + */ + public LessThanOrEqualTo(ZonedDateTime ref) { super(ref); } + + @Override + public boolean isValid(ZonedDateTime value) { return value != null && (value.isEqual(ref) || value.isBefore(ref)); } + + @Override + public String getTheValueMustDescription() { return "be less than or equal to " + ref; } + } +} diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java index 0bec4fe9..114f69f5 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java @@ -7,7 +7,7 @@ /** * Type representation of Java BigDecimal objects. * - * This class is threadsafe and uses a singleton pattern to prevent multiple + * This class is threadsafe and uses a singleton pattern to prevent multiple * instances, since all users can safely use the same instance. * * @author chace86 diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/ZonedDateTimeType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/ZonedDateTimeType.java new file mode 100644 index 00000000..0bf5375a --- /dev/null +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/ZonedDateTimeType.java @@ -0,0 +1,52 @@ +package org.yarnandtail.andhow.valuetype; + +import org.yarnandtail.andhow.api.ParsingException; + +import java.time.ZonedDateTime; + +/** + * Type representation of a Java ZonedDateTime objects. + * + * This class is threadsafe and uses a singleton pattern to prevent multiple + * instances, since all users can safely use the same instance. + * + * @author chace86 + */ +public class ZonedDateTimeType extends BaseValueType { + + private static final ZonedDateTimeType INSTANCE = new ZonedDateTimeType(); + + private ZonedDateTimeType() { super(ZonedDateTime.class); } + + /** + * @return An instance of the {@link #ZonedDateTimeType()} + */ + public static ZonedDateTimeType instance() { return INSTANCE; } + + /** + * The text format used is the default for ZonedDateTime objects, which uses the + * ISO format like this: 2019-10-31T03:16:15.149Z[Europe/Paris]. Full documentation + * is here: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_ZONED_DATE_TIME + * @param sourceValue The {@link String} value to be parsed into a @{@link ZonedDateTime} + * @return A valid @{@link ZonedDateTime} + * @throws ParsingException If the @{@link String} cannot be parsed into a @{@link ZonedDateTime} + */ + @Override + public ZonedDateTime parse(String sourceValue) throws ParsingException { + + if (sourceValue != null) { + try { + return ZonedDateTime.parse(sourceValue); + } catch (Exception e) { + throw new ParsingException("Unable to convert to a LocalDateTime", sourceValue, e); + } + } else { + return null; + } + } + + @Override + public ZonedDateTime cast(Object o) throws RuntimeException { + return (ZonedDateTime)o; + } +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/valid/ZonedDateTimeValidatorTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/valid/ZonedDateTimeValidatorTest.java new file mode 100644 index 00000000..99fdff60 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/valid/ZonedDateTimeValidatorTest.java @@ -0,0 +1,114 @@ +package org.yarnandtail.andhow.valid; + +import org.junit.Test; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +import static org.junit.Assert.*; + +public class ZonedDateTimeValidatorTest { + + private static String EXPECTED_DBL_VALIDATOR_INVALID_MESSAGE = "The constraint may not be null"; + private static final ZonedDateTime CURRENT_TIME = ZonedDateTime.now(); + + @Test + public void testGreaterThan_IsSpecificationValid() { + ZonedDateTimeValidator.GreaterThan instance = new ZonedDateTimeValidator.GreaterThan(CURRENT_TIME); + assertTrue(instance.isSpecificationValid()); + + instance = new ZonedDateTimeValidator.GreaterThan(null); + assertFalse(instance.isSpecificationValid()); + assertEquals(EXPECTED_DBL_VALIDATOR_INVALID_MESSAGE, instance.getInvalidSpecificationMessage()); + } + + @Test + public void testGreaterThan_GetTheValueMustDescription() { + ZonedDateTimeValidator.GreaterThan instance = new ZonedDateTimeValidator.GreaterThan(CURRENT_TIME); + assertEquals("be greater than " + CURRENT_TIME, instance.getTheValueMustDescription()); + } + + @Test + public void testGreaterThan_IsValid() { + ZonedDateTimeValidator.GreaterThan instance = new ZonedDateTimeValidator.GreaterThan(CURRENT_TIME); + assertFalse(instance.isValid(CURRENT_TIME)); + assertFalse(instance.isValid(CURRENT_TIME.minus(1, ChronoUnit.DAYS))); + assertTrue(instance.isValid(CURRENT_TIME.plus(1, ChronoUnit.DAYS))); + assertFalse(instance.isValid(null)); + } + + @Test + public void testGreaterThanOrEqualTo_IsSpecificationValid() { + ZonedDateTimeValidator.GreaterThanOrEqualTo instance = new ZonedDateTimeValidator.GreaterThanOrEqualTo(CURRENT_TIME); + assertTrue(instance.isSpecificationValid()); + + instance = new ZonedDateTimeValidator.GreaterThanOrEqualTo(null); + assertFalse(instance.isSpecificationValid()); + assertEquals(EXPECTED_DBL_VALIDATOR_INVALID_MESSAGE, instance.getInvalidSpecificationMessage()); + } + + @Test + public void testGreaterThanOrEqualTo_GetTheValueMustDescription() { + ZonedDateTimeValidator.GreaterThanOrEqualTo instance = new ZonedDateTimeValidator.GreaterThanOrEqualTo(CURRENT_TIME); + assertEquals("be greater than or equal to " + CURRENT_TIME, instance.getTheValueMustDescription()); + } + + @Test + public void testGreaterThanOrEqualTo_IsValid() { + ZonedDateTimeValidator.GreaterThanOrEqualTo instance = new ZonedDateTimeValidator.GreaterThanOrEqualTo(CURRENT_TIME); + assertTrue(instance.isValid(CURRENT_TIME)); + assertFalse(instance.isValid(CURRENT_TIME.minus(1, ChronoUnit.DAYS))); + assertTrue(instance.isValid(CURRENT_TIME.plus(1, ChronoUnit.DAYS))); + assertFalse(instance.isValid(null)); + } + + @Test + public void testLessThan_IsSpecificationValid() { + ZonedDateTimeValidator.LessThan instance = new ZonedDateTimeValidator.LessThan(CURRENT_TIME); + assertTrue(instance.isSpecificationValid()); + + instance = new ZonedDateTimeValidator.LessThan(null); + assertFalse(instance.isSpecificationValid()); + assertEquals(EXPECTED_DBL_VALIDATOR_INVALID_MESSAGE, instance.getInvalidSpecificationMessage()); + } + + @Test + public void testLessThan_GetTheValueMustDescription() { + ZonedDateTimeValidator.LessThan instance = new ZonedDateTimeValidator.LessThan(CURRENT_TIME); + assertEquals("be less than " + CURRENT_TIME, instance.getTheValueMustDescription()); + } + + @Test + public void testLessThan_IsValid() { + ZonedDateTimeValidator.LessThan instance = new ZonedDateTimeValidator.LessThan(CURRENT_TIME); + assertFalse(instance.isValid(CURRENT_TIME)); + assertTrue(instance.isValid(CURRENT_TIME.minus(1, ChronoUnit.DAYS))); + assertFalse(instance.isValid(CURRENT_TIME.plus(1, ChronoUnit.DAYS))); + assertFalse(instance.isValid(null)); + } + + @Test + public void testLessThanOrEqualTo_IsSpecificationValid() { + ZonedDateTimeValidator.LessThanOrEqualTo instance = new ZonedDateTimeValidator.LessThanOrEqualTo(CURRENT_TIME); + assertTrue(instance.isSpecificationValid()); + + instance = new ZonedDateTimeValidator.LessThanOrEqualTo(null); + assertFalse(instance.isSpecificationValid()); + assertEquals(EXPECTED_DBL_VALIDATOR_INVALID_MESSAGE, instance.getInvalidSpecificationMessage()); + } + + @Test + public void testLessThanOrEqualTo_GetTheValueMustDescription() { + ZonedDateTimeValidator.LessThanOrEqualTo instance = new ZonedDateTimeValidator.LessThanOrEqualTo(CURRENT_TIME); + assertEquals("be less than or equal to " + CURRENT_TIME, instance.getTheValueMustDescription()); + } + + @Test + public void testLessThanOrEqualTo_IsValid() { + ZonedDateTimeValidator.LessThanOrEqualTo instance = new ZonedDateTimeValidator.LessThanOrEqualTo(CURRENT_TIME); + assertTrue(instance.isValid(CURRENT_TIME)); + assertTrue(instance.isValid(CURRENT_TIME.minus(1, ChronoUnit.DAYS))); + assertFalse(instance.isValid(CURRENT_TIME.plus(1, ChronoUnit.DAYS))); + assertFalse(instance.isValid(null)); + } +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/ZonedDateTimeTypeTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/ZonedDateTimeTypeTest.java new file mode 100644 index 00000000..186b7853 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/ZonedDateTimeTypeTest.java @@ -0,0 +1,39 @@ +package org.yarnandtail.andhow.valuetype; + +import org.junit.Test; +import static org.junit.Assert.*; +import org.yarnandtail.andhow.api.ParsingException; + +import java.time.ZonedDateTime; + +public class ZonedDateTimeTypeTest { + + @Test + public void testParseHappyPath() throws ParsingException { + ZonedDateTimeType type = ZonedDateTimeType.instance(); + assertEquals(ZonedDateTime.parse("2019-10-31T03:52:46.267346Z[Etc/UTC]"), type.parse("2019-10-31T03:52:46.267346Z[Etc/UTC]")); + assertNull(type.parse(null)); + } + + @Test(expected = ParsingException.class) + public void testParseInvalidDateFormat() throws ParsingException { + ZonedDateTimeType type = ZonedDateTimeType.instance(); + assertFalse(type.isParsable("abc")); + type.parse("abc"); + } + + @Test(expected = ParsingException.class) + public void testParseEmpty() throws ParsingException { + ZonedDateTimeType type = ZonedDateTimeType.instance(); + assertFalse(type.isParsable("")); + type.parse(""); + } + + @Test + public void testCast() { + ZonedDateTimeType type = ZonedDateTimeType.instance(); + Object o = ZonedDateTime.parse("2019-10-31T03:52:46.267346Z[Etc/UTC]"); + assertEquals(ZonedDateTime.parse("2019-10-31T03:52:46.267346Z[Etc/UTC]"), type.cast(o)); + assertTrue(type.cast(o) instanceof ZonedDateTime); + } +}