{
* | Float, float | BigDecimal * |
* | CharSequence , String | String |
* | Boolean, boolean | Boolean |
+ * | Instant, instant | Instant |
+ * | ZonedDateTime, zonedDateTime | Instant |
+ * | LocalDate, localDate | Instant |
+ * | OffsetDateTime, offsetDateTime | Instant |
+ * | Duration, duration | Duration |
* | ASTNode | ASTNode |
* | List<?> | List<EvaluationValue> - each entry will be converted |
* | Map<?,?> | Map<String><EvaluationValue> - each entry will be converted. |
@@ -94,6 +111,9 @@ public class EvaluationValue implements Comparable {
if (number != null) {
this.dataType = DataType.NUMBER;
this.value = number;
+ } else if (value == null) {
+ this.dataType = DataType.NULL;
+ this.value = null;
} else if (value instanceof CharSequence) {
this.dataType = DataType.STRING;
this.value = ((CharSequence) value).toString();
@@ -103,6 +123,21 @@ public class EvaluationValue implements Comparable {
} else if (value instanceof Boolean) {
this.dataType = DataType.BOOLEAN;
this.value = value;
+ } else if (value instanceof Instant) {
+ this.dataType = DataType.DATE_TIME;
+ this.value = value;
+ } else if (value instanceof ZonedDateTime) {
+ this.dataType = DataType.DATE_TIME;
+ this.value = ((ZonedDateTime) value).toInstant();
+ } else if (value instanceof OffsetDateTime) {
+ this.dataType = DataType.DATE_TIME;
+ this.value = ((OffsetDateTime) value).toInstant();
+ } else if (value instanceof LocalDate) {
+ this.dataType = DataType.DATE_TIME;
+ this.value = ((LocalDate) value).atStartOfDay().atOffset(ZoneOffset.UTC).toInstant();
+ } else if (value instanceof Duration) {
+ this.dataType = DataType.DURATION;
+ this.value = value;
} else if (value instanceof ASTNode) {
this.dataType = DataType.EXPRESSION_NODE;
this.value = value;
@@ -126,6 +161,11 @@ public class EvaluationValue implements Comparable {
this.value = new BigDecimal(Double.toString(value), mathContext);
}
+ public EvaluationValue(LocalDateTime value, ZoneId zoneId) {
+ this.dataType = DataType.DATE_TIME;
+ this.value = value.atZone(zoneId).toInstant();
+ }
+
/**
* Converts a {@link Map} of objects to a {@link Map} of {@link EvaluationValue} values.
*
@@ -204,6 +244,23 @@ public class EvaluationValue implements Comparable {
return getDataType() == DataType.BOOLEAN;
}
+ /**
+ * Checks if the value is of type {@link DataType#DATE_TIME}.
+ *
+ * @return true or false.
+ */
+ public boolean isDateTimeValue() {
+ return getDataType() == DataType.DATE_TIME;
+ }
+
+ /**
+ * Checks if the value is of type {@link DataType#DURATION}.
+ *
+ * @return true or false.
+ */
+ public boolean isDurationValue() {
+ return getDataType() == DataType.DURATION;
+ }
/**
* Checks if the value is of type {@link DataType#ARRAY}.
*
@@ -231,6 +288,10 @@ public class EvaluationValue implements Comparable {
return getDataType() == DataType.EXPRESSION_NODE;
}
+ public boolean isNullValue() {
+ return getDataType() == DataType.NULL;
+ }
+
/**
* Creates a {@link DataType#NUMBER} value from a {@link String}.
*
@@ -266,6 +327,8 @@ public class EvaluationValue implements Comparable {
return (Boolean.TRUE.equals(value) ? BigDecimal.ONE : BigDecimal.ZERO);
case STRING:
return Boolean.parseBoolean((String) value) ? BigDecimal.ONE : BigDecimal.ZERO;
+ case NULL:
+ return null;
default:
return BigDecimal.ZERO;
}
@@ -283,10 +346,14 @@ public class EvaluationValue implements Comparable {
* @return The {@link String} representation of the value.
*/
public String getStringValue() {
- if (getDataType() == DataType.NUMBER) {
- return ((BigDecimal) value).toPlainString();
+ switch (getDataType()) {
+ case NUMBER:
+ return ((BigDecimal) value).toPlainString();
+ case NULL:
+ return null;
+ default:
+ return value.toString();
}
- return value.toString();
}
/**
@@ -308,11 +375,74 @@ public class EvaluationValue implements Comparable {
return (Boolean) value;
case STRING:
return Boolean.parseBoolean((String) value);
+ case NULL:
+ return NULL_BOOLEAN;
default:
return false;
}
}
+ /**
+ * Gets a {@link Instant} representation of the value. If possible and needed, a conversion will
+ * be made.
+ *
+ *
+ * - Any number value will return the instant from the epoc value.
+ *
- Any string with the string representation of a LocalDateTime (ex:
+ * "2018-11-30T18:35:24.00") (case ignored) will return the current LocalDateTime.
+ * - The date {@link Instant#EPOCH} will return if a conversion error occurs or in all other
+ * cases.
+ *
+ *
+ * @return The {@link Instant} representation of the value.
+ */
+ public Instant getDateTimeValue() {
+ try {
+ switch (getDataType()) {
+ case NUMBER:
+ return Instant.ofEpochMilli(((BigDecimal) value).longValue());
+ case DATE_TIME:
+ return (Instant) value;
+ case STRING:
+ return Instant.parse((String) value);
+ default:
+ return Instant.EPOCH;
+ }
+ } catch (DateTimeException ex) {
+ return Instant.EPOCH;
+ }
+ }
+
+ /**
+ * Gets a {@link Duration} representation of the value. If possible and needed, a conversion will
+ * be made.
+ *
+ *
+ * - Any non-zero number value will return the duration from the millisecond.
+ *
- Any string with the string representation of an {@link Duration} (ex:
+ * "PnDTnHnMn.nS") (case ignored) will return the current instant.
+ * - The {@link Duration#ZERO} will return if a conversion error occurs or in all other cases.
+ *
+ *
+ * @return The {@link Duration} representation of the value.
+ */
+ public Duration getDurationValue() {
+ try {
+ switch (getDataType()) {
+ case NUMBER:
+ return Duration.ofMillis(((BigDecimal) value).longValue());
+ case DURATION:
+ return (Duration) value;
+ case STRING:
+ return Duration.parse((String) value);
+ default:
+ return Duration.ZERO;
+ }
+ } catch (DateTimeException ex) {
+ return Duration.ZERO;
+ }
+ }
+
/**
* Gets a {@link List} representation of the value.
*
@@ -323,6 +453,8 @@ public class EvaluationValue implements Comparable {
public List getArrayValue() {
if (isArrayValue()) {
return (List) value;
+ } else if (isNullValue()) {
+ return NULL_ARRAY;
} else {
return Collections.emptyList();
}
@@ -338,6 +470,8 @@ public class EvaluationValue implements Comparable {
public Map getStructureValue() {
if (isStructureValue()) {
return (Map) value;
+ } else if (isNullValue()) {
+ return NULL_STRUCTURE;
} else {
return Collections.emptyMap();
}
@@ -359,6 +493,12 @@ public class EvaluationValue implements Comparable {
return getNumberValue().compareTo(toCompare.getNumberValue());
case BOOLEAN:
return getBooleanValue().compareTo(toCompare.getBooleanValue());
+ case NULL:
+ throw new NullPointerException("Can not compare a null value");
+ case DATE_TIME:
+ return getDateTimeValue().compareTo(toCompare.getDateTimeValue());
+ case DURATION:
+ return getDurationValue().compareTo(toCompare.getDurationValue());
default:
return getStringValue().compareTo(toCompare.getStringValue());
}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/AbstractDateTimeParseFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/AbstractDateTimeParseFunction.java
new file mode 100644
index 0000000..3a396ef
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/AbstractDateTimeParseFunction.java
@@ -0,0 +1,42 @@
+/*
+ Copyright 2012-2023 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.Expression;
+import com.ezylang.evalex.data.EvaluationValue;
+import com.ezylang.evalex.functions.AbstractFunction;
+import com.ezylang.evalex.parser.Token;
+import java.time.Instant;
+import java.time.ZoneId;
+
+public abstract class AbstractDateTimeParseFunction extends AbstractFunction {
+ @Override
+ public EvaluationValue evaluate(
+ Expression expression, Token functionToken, EvaluationValue... parameterValues) {
+ ZoneId zoneId = expression.getConfiguration().getDefaultZoneId();
+ Instant instant;
+
+ if (parameterValues.length < 2) {
+ instant = parse(parameterValues[0].getStringValue(), null, zoneId);
+ } else {
+ instant =
+ parse(parameterValues[0].getStringValue(), parameterValues[1].getStringValue(), zoneId);
+ }
+ return new EvaluationValue(instant);
+ }
+
+ protected abstract Instant parse(String value, String format, ZoneId zoneId);
+}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java
new file mode 100644
index 0000000..3596514
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java
@@ -0,0 +1,43 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.Expression;
+import com.ezylang.evalex.data.EvaluationValue;
+import com.ezylang.evalex.functions.AbstractFunction;
+import com.ezylang.evalex.functions.FunctionParameter;
+import com.ezylang.evalex.parser.Token;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+@FunctionParameter(name = "value", isVarArg = true)
+public class DateTimeFormatFunction extends AbstractFunction {
+ @Override
+ public EvaluationValue evaluate(
+ Expression expression, Token functionToken, EvaluationValue... parameterValues) {
+ String formatted;
+ ZoneId zoneId = expression.getConfiguration().getDefaultZoneId();
+ if (parameterValues.length < 2) {
+ formatted = parameterValues[0].getDateTimeValue().atZone(zoneId).toLocalDateTime().toString();
+ } else {
+ DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern(parameterValues[1].getStringValue());
+ formatted =
+ parameterValues[0].getDateTimeValue().atZone(zoneId).toLocalDateTime().format(formatter);
+ }
+ return new EvaluationValue(formatted);
+ }
+}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFromEpochFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFromEpochFunction.java
new file mode 100644
index 0000000..6432156
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFromEpochFunction.java
@@ -0,0 +1,34 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.Expression;
+import com.ezylang.evalex.data.EvaluationValue;
+import com.ezylang.evalex.functions.AbstractFunction;
+import com.ezylang.evalex.functions.FunctionParameter;
+import com.ezylang.evalex.parser.Token;
+import java.math.BigDecimal;
+import java.time.Instant;
+
+@FunctionParameter(name = "value")
+public class DateTimeFromEpochFunction extends AbstractFunction {
+ @Override
+ public EvaluationValue evaluate(
+ Expression expression, Token functionToken, EvaluationValue... parameterValues) {
+ BigDecimal millis = parameterValues[0].getNumberValue();
+ return new EvaluationValue(Instant.ofEpochMilli(millis.longValue()));
+ }
+}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFunction.java
new file mode 100644
index 0000000..56e1e25
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFunction.java
@@ -0,0 +1,45 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.Expression;
+import com.ezylang.evalex.data.EvaluationValue;
+import com.ezylang.evalex.functions.AbstractFunction;
+import com.ezylang.evalex.functions.FunctionParameter;
+import com.ezylang.evalex.parser.Token;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+
+@FunctionParameter(name = "values", isVarArg = true, nonNegative = true)
+public class DateTimeFunction extends AbstractFunction {
+ @Override
+ public EvaluationValue evaluate(
+ Expression expression, Token functionToken, EvaluationValue... parameterValues) {
+ int year = parameterValues[0].getNumberValue().intValue();
+ int month = parameterValues[1].getNumberValue().intValue();
+ int day = parameterValues[2].getNumberValue().intValue();
+ int hour = parameterValues.length >= 4 ? parameterValues[3].getNumberValue().intValue() : 0;
+ int minute = parameterValues.length >= 5 ? parameterValues[4].getNumberValue().intValue() : 0;
+ int second = parameterValues.length >= 6 ? parameterValues[5].getNumberValue().intValue() : 0;
+ int nanoOfs = parameterValues.length >= 7 ? parameterValues[6].getNumberValue().intValue() : 0;
+
+ ZoneId zoneId = expression.getConfiguration().getDefaultZoneId();
+ return new EvaluationValue(
+ LocalDateTime.of(year, month, day, hour, minute, second, nanoOfs)
+ .atZone(zoneId)
+ .toInstant());
+ }
+}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeParseFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeParseFunction.java
new file mode 100644
index 0000000..65f3b19
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeParseFunction.java
@@ -0,0 +1,64 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.functions.FunctionParameter;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Optional;
+
+@FunctionParameter(name = "value", isVarArg = true)
+public class DateTimeParseFunction extends AbstractDateTimeParseFunction {
+
+ protected Instant parse(String value, String format, ZoneId zoneId) {
+ return parseInstant(value)
+ .or(() -> parseLocalDateTime(value, format, zoneId))
+ .or(() -> parseDate(value, format))
+ .orElseThrow(() -> new IllegalArgumentException("Unable to parse date/time: " + value));
+ }
+
+ private Optional parseLocalDateTime(String value, String format, ZoneId zoneId) {
+ try {
+ DateTimeFormatter formatter =
+ (format == null
+ ? DateTimeFormatter.ISO_LOCAL_DATE_TIME
+ : DateTimeFormatter.ofPattern(format));
+ return Optional.of(LocalDateTime.parse(value, formatter).atZone(zoneId).toInstant());
+ } catch (DateTimeParseException ex) {
+ return Optional.empty();
+ }
+ }
+
+ private Optional parseDate(String value, String format) {
+ try {
+ DateTimeFormatter formatter =
+ (format == null ? DateTimeFormatter.ISO_LOCAL_DATE : DateTimeFormatter.ofPattern(format));
+ LocalDate localDate = LocalDate.parse(value, formatter);
+ return Optional.of(localDate.atStartOfDay().atOffset(ZoneOffset.UTC).toInstant());
+ } catch (DateTimeParseException ex) {
+ return Optional.empty();
+ }
+ }
+
+ private Optional parseInstant(String value) {
+ try {
+ return Optional.of(Instant.parse(value));
+ } catch (DateTimeParseException ex) {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeToEpochFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeToEpochFunction.java
new file mode 100644
index 0000000..2ee4f2e
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeToEpochFunction.java
@@ -0,0 +1,31 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.Expression;
+import com.ezylang.evalex.data.EvaluationValue;
+import com.ezylang.evalex.functions.AbstractFunction;
+import com.ezylang.evalex.functions.FunctionParameter;
+import com.ezylang.evalex.parser.Token;
+
+@FunctionParameter(name = "value")
+public class DateTimeToEpochFunction extends AbstractFunction {
+ @Override
+ public EvaluationValue evaluate(
+ Expression expression, Token functionToken, EvaluationValue... parameterValues) {
+ return new EvaluationValue(parameterValues[0].getDateTimeValue().toEpochMilli());
+ }
+}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromDaysFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromDaysFunction.java
new file mode 100644
index 0000000..95fac80
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromDaysFunction.java
@@ -0,0 +1,34 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.Expression;
+import com.ezylang.evalex.data.EvaluationValue;
+import com.ezylang.evalex.functions.AbstractFunction;
+import com.ezylang.evalex.functions.FunctionParameter;
+import com.ezylang.evalex.parser.Token;
+import java.math.BigDecimal;
+import java.time.Duration;
+
+@FunctionParameter(name = "value")
+public class DurationFromDaysFunction extends AbstractFunction {
+ @Override
+ public EvaluationValue evaluate(
+ Expression expression, Token functionToken, EvaluationValue... parameterValues) {
+ BigDecimal days = parameterValues[0].getNumberValue();
+ return new EvaluationValue(Duration.ofDays(days.longValue()));
+ }
+}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromMillisFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromMillisFunction.java
new file mode 100644
index 0000000..2d544f7
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromMillisFunction.java
@@ -0,0 +1,34 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.Expression;
+import com.ezylang.evalex.data.EvaluationValue;
+import com.ezylang.evalex.functions.AbstractFunction;
+import com.ezylang.evalex.functions.FunctionParameter;
+import com.ezylang.evalex.parser.Token;
+import java.math.BigDecimal;
+import java.time.Duration;
+
+@FunctionParameter(name = "value")
+public class DurationFromMillisFunction extends AbstractFunction {
+ @Override
+ public EvaluationValue evaluate(
+ Expression expression, Token functionToken, EvaluationValue... parameterValues) {
+ BigDecimal millis = parameterValues[0].getNumberValue();
+ return new EvaluationValue(Duration.ofMillis(millis.longValue()));
+ }
+}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DurationParseFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DurationParseFunction.java
new file mode 100644
index 0000000..c7dc271
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/DurationParseFunction.java
@@ -0,0 +1,33 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.Expression;
+import com.ezylang.evalex.data.EvaluationValue;
+import com.ezylang.evalex.functions.AbstractFunction;
+import com.ezylang.evalex.functions.FunctionParameter;
+import com.ezylang.evalex.parser.Token;
+import java.time.Duration;
+
+@FunctionParameter(name = "value")
+public class DurationParseFunction extends AbstractFunction {
+ @Override
+ public EvaluationValue evaluate(
+ Expression expression, Token functionToken, EvaluationValue... parameterValues) {
+ String text = parameterValues[0].getStringValue();
+ return new EvaluationValue(Duration.parse(text));
+ }
+}
diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/ZonedDateTimeParseFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/ZonedDateTimeParseFunction.java
new file mode 100644
index 0000000..5a2b2e3
--- /dev/null
+++ b/src/main/java/com/ezylang/evalex/functions/datetime/ZonedDateTimeParseFunction.java
@@ -0,0 +1,47 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.functions.FunctionParameter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Optional;
+
+@FunctionParameter(name = "value", isVarArg = true)
+public class ZonedDateTimeParseFunction extends AbstractDateTimeParseFunction {
+ protected Instant parse(String value, String format, ZoneId zoneId) {
+ return parseZonedDateTime(value, format, zoneId)
+ .orElseThrow(
+ () -> new IllegalArgumentException("Unable to parse zoned date/time: " + value));
+ }
+
+ private Optional parseZonedDateTime(String value, String format, ZoneId zoneId) {
+ try {
+ DateTimeFormatter formatter =
+ (format == null
+ ? DateTimeFormatter.ISO_ZONED_DATE_TIME
+ : DateTimeFormatter.ofPattern(format))
+ .withZone(zoneId);
+ ZonedDateTime zonedDateTime = ZonedDateTime.parse(value, formatter);
+ return Optional.of(zonedDateTime.toInstant());
+ } catch (DateTimeParseException ex) {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java
index 248966b..918d362 100644
--- a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java
+++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java
@@ -23,6 +23,7 @@ import com.ezylang.evalex.data.EvaluationValue;
import com.ezylang.evalex.operators.AbstractOperator;
import com.ezylang.evalex.operators.InfixOperator;
import com.ezylang.evalex.parser.Token;
+import java.time.Duration;
/** Subtraction of two numbers. */
@InfixOperator(precedence = OPERATOR_PRECEDENCE_ADDITIVE)
@@ -41,6 +42,24 @@ public class InfixMinusOperator extends AbstractOperator {
.getNumberValue()
.subtract(
rightOperand.getNumberValue(), expression.getConfiguration().getMathContext()));
+
+ } else if (leftOperand.isDateTimeValue() && rightOperand.isDateTimeValue()) {
+ return new EvaluationValue(
+ Duration.ofMillis(
+ leftOperand.getDateTimeValue().toEpochMilli()
+ - rightOperand.getDateTimeValue().toEpochMilli()));
+
+ } else if (leftOperand.isDateTimeValue() && rightOperand.isDurationValue()) {
+ return new EvaluationValue(
+ leftOperand.getDateTimeValue().minus(rightOperand.getDurationValue()));
+ } else if (leftOperand.isDurationValue() && rightOperand.isDurationValue()) {
+ return new EvaluationValue(
+ leftOperand.getDurationValue().minus(rightOperand.getDurationValue()));
+ } else if (leftOperand.isDateTimeValue() && rightOperand.isNumberValue()) {
+ return new EvaluationValue(
+ leftOperand
+ .getDateTimeValue()
+ .minus(Duration.ofMillis(rightOperand.getNumberValue().longValue())));
} else {
throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken);
}
diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java
index fede27c..bcc795f 100644
--- a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java
+++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java
@@ -22,6 +22,7 @@ import com.ezylang.evalex.data.EvaluationValue;
import com.ezylang.evalex.operators.AbstractOperator;
import com.ezylang.evalex.operators.InfixOperator;
import com.ezylang.evalex.parser.Token;
+import java.time.Duration;
/**
* Addition of numbers and strings. If one operand is a string, a string concatenation is performed.
@@ -40,6 +41,17 @@ public class InfixPlusOperator extends AbstractOperator {
leftOperand
.getNumberValue()
.add(rightOperand.getNumberValue(), expression.getConfiguration().getMathContext()));
+ } else if (leftOperand.isDateTimeValue() && rightOperand.isDurationValue()) {
+ return new EvaluationValue(
+ leftOperand.getDateTimeValue().plus(rightOperand.getDurationValue()));
+ } else if (leftOperand.isDurationValue() && rightOperand.isDurationValue()) {
+ return new EvaluationValue(
+ leftOperand.getDurationValue().plus(rightOperand.getDurationValue()));
+ } else if (leftOperand.isDateTimeValue() && rightOperand.isNumberValue()) {
+ return new EvaluationValue(
+ leftOperand
+ .getDateTimeValue()
+ .plus(Duration.ofMillis(rightOperand.getNumberValue().longValue())));
} else {
return new EvaluationValue(leftOperand.getStringValue() + rightOperand.getStringValue());
}
diff --git a/src/test/java/com/ezylang/evalex/BaseEvaluationTest.java b/src/test/java/com/ezylang/evalex/BaseEvaluationTest.java
index 69a29c1..fa73196 100644
--- a/src/test/java/com/ezylang/evalex/BaseEvaluationTest.java
+++ b/src/test/java/com/ezylang/evalex/BaseEvaluationTest.java
@@ -16,6 +16,7 @@
package com.ezylang.evalex;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.ezylang.evalex.config.ExpressionConfiguration;
import com.ezylang.evalex.config.TestConfigurationProvider;
@@ -41,6 +42,11 @@ public abstract class BaseEvaluationTest {
.isEqualTo(expectedResult);
}
+ protected void assertExpressionThrowsException(
+ String expression, String message, ExpressionConfiguration expressionConfiguration) {
+ assertThatThrownBy(() -> evaluate(expression, expressionConfiguration)).hasMessage(message);
+ }
+
private EvaluationValue evaluate(String expressionString, ExpressionConfiguration configuration)
throws EvaluationException, ParseException {
Expression expression = new Expression(expressionString, configuration);
diff --git a/src/test/java/com/ezylang/evalex/ExpressionEvaluatorNullTest.java b/src/test/java/com/ezylang/evalex/ExpressionEvaluatorNullTest.java
new file mode 100644
index 0000000..c44e108
--- /dev/null
+++ b/src/test/java/com/ezylang/evalex/ExpressionEvaluatorNullTest.java
@@ -0,0 +1,82 @@
+/*
+ Copyright 2012-2023 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.ezylang.evalex.parser.ParseException;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+class ExpressionEvaluatorNullTest extends BaseExpressionEvaluatorTest {
+
+ @Test
+ void testNullEquals() throws ParseException, EvaluationException {
+ Expression expression = createExpression("a == null");
+ assertExpressionHasExpectedResult(expression.with("a", null), "true");
+ assertExpressionHasExpectedResult(expression.with("a", 99), "false");
+ }
+
+ @Test
+ void testNullNotEquals() throws ParseException, EvaluationException {
+ Expression expression = new Expression("a != null");
+ assertExpressionHasExpectedResult(expression.with("a", null), "false");
+ assertExpressionHasExpectedResult(expression.with("a", 99), "true");
+ }
+
+ @Test
+ void testHandleWithIf() throws EvaluationException, ParseException {
+ Expression expression1 = createExpression("IF(a != null, a * 5, 1)");
+ assertExpressionHasExpectedResult(expression1.with("a", null), "1");
+ assertExpressionHasExpectedResult(expression1.with("a", 3), "15");
+
+ Expression expression2 =
+ createExpression("IF(a == null, \"Unknown name\", \"The name is \" + a)");
+ assertExpressionHasExpectedResult(expression2.with("a", null), "Unknown name");
+ assertExpressionHasExpectedResult(expression2.with("a", "Max"), "The name is Max");
+ }
+
+ @Test
+ void testHandleWithMaps() throws EvaluationException, ParseException {
+ Expression expression = createExpression("a == null && b == null");
+ Map values = new HashMap<>();
+ values.put("a", null);
+ values.put("b", null);
+
+ assertExpressionHasExpectedResult(expression.withValues(values), "true");
+ }
+
+ @Test
+ void testFailWithNoHandling() {
+ Expression expression1 = createExpression("a * 5").with("a", null);
+ assertThatThrownBy(expression1::evaluate)
+ .isInstanceOf(EvaluationException.class)
+ .hasMessage("Unsupported data types in operation");
+
+ Expression expression2 = createExpression("FLOOR(a)").with("a", null);
+ assertThatThrownBy(expression2::evaluate).isInstanceOf(NullPointerException.class);
+
+ Expression expression3 = createExpression("a > 5").with("a", null);
+ assertThatThrownBy(expression3::evaluate).isInstanceOf(NullPointerException.class);
+ }
+
+ private void assertExpressionHasExpectedResult(Expression expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertThat(expression.evaluate().getStringValue()).isEqualTo(expectedResult);
+ }
+}
diff --git a/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java b/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java
index de8ffeb..bd0b06d 100644
--- a/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java
+++ b/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java
@@ -26,11 +26,8 @@ import com.ezylang.evalex.parser.Token;
import com.ezylang.evalex.parser.Token.TokenType;
import java.math.BigDecimal;
import java.math.MathContext;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
+import java.time.*;
+import java.util.*;
import org.junit.jupiter.api.Test;
class EvaluationValueTest {
@@ -52,7 +49,9 @@ class EvaluationValueTest {
assertThat(value.isStructureValue()).isFalse();
assertThat(value.isArrayValue()).isFalse();
assertThat(value.isExpressionNode()).isFalse();
- assertDataIsCorrect(value, "Hello World", BigDecimal.ZERO, false, String.class);
+ assertThat(value.isNullValue()).isFalse();
+ assertDataIsCorrect(
+ value, "Hello World", BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, String.class);
}
@Test
@@ -60,7 +59,14 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue(new StringBuilder("Hello StringBuilder World"));
assertThat(value.isStringValue()).isTrue();
- assertDataIsCorrect(value, "Hello StringBuilder World", BigDecimal.ZERO, false, String.class);
+ assertDataIsCorrect(
+ value,
+ "Hello StringBuilder World",
+ BigDecimal.ZERO,
+ false,
+ Instant.EPOCH,
+ Duration.ZERO,
+ String.class);
}
@Test
@@ -68,7 +74,8 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue('a');
assertThat(value.isStringValue()).isTrue();
- assertDataIsCorrect(value, "a", BigDecimal.ZERO, false, String.class);
+ assertDataIsCorrect(
+ value, "a", BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, String.class);
}
@Test
@@ -81,7 +88,9 @@ class EvaluationValueTest {
assertThat(value.isStructureValue()).isFalse();
assertThat(value.isArrayValue()).isFalse();
assertThat(value.isExpressionNode()).isFalse();
- assertDataIsCorrect(value, "true", BigDecimal.ONE, true, Boolean.class);
+ assertThat(value.isNullValue()).isFalse();
+ assertDataIsCorrect(
+ value, "true", BigDecimal.ONE, true, Instant.EPOCH, Duration.ZERO, Boolean.class);
}
@Test
@@ -89,7 +98,8 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue(false);
assertThat(value.isBooleanValue()).isTrue();
- assertDataIsCorrect(value, "false", BigDecimal.ZERO, false, Boolean.class);
+ assertDataIsCorrect(
+ value, "false", BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, Boolean.class);
}
@Test
@@ -97,7 +107,8 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue("true");
assertThat(value.isStringValue()).isTrue();
- assertDataIsCorrect(value, "true", BigDecimal.ONE, true, String.class);
+ assertDataIsCorrect(
+ value, "true", BigDecimal.ONE, true, Instant.EPOCH, Duration.ZERO, String.class);
}
@Test
@@ -105,7 +116,124 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue(BigDecimal.ZERO);
assertThat(value.isNumberValue()).isTrue();
- assertDataIsCorrect(value, "0", BigDecimal.ZERO, false, BigDecimal.class);
+ assertDataIsCorrect(
+ value, "0", BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, BigDecimal.class);
+ }
+
+ @Test
+ void testInstant() {
+ Instant instant = Instant.parse("2022-10-30T00:00:00Z");
+ EvaluationValue value = new EvaluationValue(instant);
+
+ assertThat(value.isDateTimeValue()).isTrue();
+ assertDataIsCorrect(
+ value, instant.toString(), BigDecimal.ZERO, false, instant, Duration.ZERO, Instant.class);
+ }
+
+ @Test
+ void testLocalDate() {
+ LocalDate localDate = LocalDate.parse("2022-10-30");
+ EvaluationValue value = new EvaluationValue(localDate);
+
+ assertThat(value.isDateTimeValue()).isTrue();
+ assertDataIsCorrect(
+ value,
+ "2022-10-30T00:00:00Z",
+ BigDecimal.ZERO,
+ false,
+ Instant.parse("2022-10-30T00:00:00Z"),
+ Duration.ZERO,
+ Instant.class);
+ }
+
+ @Test
+ void testLocalDateTime() {
+ ZoneId zoneId = ZoneId.of("UTC+2");
+ LocalDateTime localDateTime = LocalDateTime.parse("2022-10-30T11:20:30");
+ EvaluationValue value = new EvaluationValue(localDateTime, zoneId);
+
+ assertThat(value.isDateTimeValue()).isTrue();
+ assertDataIsCorrect(
+ value,
+ "2022-10-30T09:20:30Z",
+ BigDecimal.ZERO,
+ false,
+ localDateTime.atZone(zoneId).toInstant(),
+ Duration.ZERO,
+ Instant.class);
+ }
+
+ @Test
+ void testZonedDateTime() {
+ ZonedDateTime zonedDateTime =
+ ZonedDateTime.of(LocalDateTime.of(2022, 10, 30, 11, 20, 30), ZoneId.of("GMT+05:30"));
+ EvaluationValue value = new EvaluationValue(zonedDateTime);
+
+ assertThat(value.isDateTimeValue()).isTrue();
+ assertDataIsCorrect(
+ value,
+ zonedDateTime.toInstant().toString(),
+ BigDecimal.ZERO,
+ false,
+ zonedDateTime.toInstant(),
+ Duration.ZERO,
+ Instant.class);
+ }
+
+ @Test
+ void testOffsetDateTime() {
+ OffsetDateTime offsetDateTime =
+ OffsetDateTime.of(LocalDateTime.of(2022, 10, 30, 11, 20, 30), ZoneOffset.of("+05:30"));
+ EvaluationValue value = new EvaluationValue(offsetDateTime);
+
+ assertThat(value.isDateTimeValue()).isTrue();
+ assertDataIsCorrect(
+ value,
+ offsetDateTime.toInstant().toString(),
+ BigDecimal.ZERO,
+ false,
+ offsetDateTime.toInstant(),
+ Duration.ZERO,
+ Instant.class);
+ }
+
+ @Test
+ void testStringDateTime() {
+ EvaluationValue value = new EvaluationValue("2022-10-30T11:20:30Z");
+
+ assertThat(value.isDateTimeValue()).isFalse();
+ assertDataIsCorrect(
+ value,
+ "2022-10-30T11:20:30Z",
+ BigDecimal.ZERO,
+ false,
+ Instant.parse("2022-10-30T11:20:30Z"),
+ Duration.ZERO,
+ String.class);
+ }
+
+ @Test
+ void testDuration() {
+ EvaluationValue value = new EvaluationValue(Duration.ofMinutes(1));
+
+ assertThat(value.isDurationValue()).isTrue();
+ assertDataIsCorrect(
+ value,
+ "PT1M",
+ BigDecimal.ZERO,
+ false,
+ Instant.EPOCH,
+ Duration.ofMinutes(1),
+ Duration.class);
+ }
+
+ @Test
+ void testStringDuration() {
+ EvaluationValue value = new EvaluationValue("PT24H");
+
+ assertThat(value.isDurationValue()).isFalse();
+ assertDataIsCorrect(
+ value, "PT24H", BigDecimal.ZERO, false, Instant.EPOCH, Duration.ofHours(24), String.class);
}
@Test
@@ -113,7 +241,14 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue(new BigDecimal("123.5"));
assertThat(value.isNumberValue()).isTrue();
- assertDataIsCorrect(value, "123.5", new BigDecimal("123.5"), true, BigDecimal.class);
+ assertDataIsCorrect(
+ value,
+ "123.5",
+ new BigDecimal("123.5"),
+ true,
+ Instant.ofEpochMilli(123),
+ Duration.ofMillis(123),
+ BigDecimal.class);
}
@Test
@@ -121,7 +256,14 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue((float) 4.5);
assertThat(value.isNumberValue()).isTrue();
- assertDataIsCorrect(value, "4.5", BigDecimal.valueOf((float) 4.5), true, BigDecimal.class);
+ assertDataIsCorrect(
+ value,
+ "4.5",
+ BigDecimal.valueOf((float) 4.5),
+ true,
+ Instant.ofEpochMilli(4),
+ Duration.ofMillis(4),
+ BigDecimal.class);
}
@Test
@@ -129,7 +271,14 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue(8.5);
assertThat(value.isNumberValue()).isTrue();
- assertDataIsCorrect(value, "8.5", BigDecimal.valueOf(8.5), true, BigDecimal.class);
+ assertDataIsCorrect(
+ value,
+ "8.5",
+ BigDecimal.valueOf(8.5),
+ true,
+ Instant.ofEpochMilli(8),
+ Duration.ofMillis(8),
+ BigDecimal.class);
}
@Test
@@ -137,7 +286,14 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue(6L);
assertThat(value.isNumberValue()).isTrue();
- assertDataIsCorrect(value, "6", new BigDecimal(6), true, BigDecimal.class);
+ assertDataIsCorrect(
+ value,
+ "6",
+ new BigDecimal(6),
+ true,
+ Instant.ofEpochMilli(6),
+ Duration.ofMillis(6),
+ BigDecimal.class);
}
@Test
@@ -145,7 +301,14 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue(5);
assertThat(value.isNumberValue()).isTrue();
- assertDataIsCorrect(value, "5", new BigDecimal(5), true, BigDecimal.class);
+ assertDataIsCorrect(
+ value,
+ "5",
+ new BigDecimal(5),
+ true,
+ Instant.ofEpochMilli(5),
+ Duration.ofMillis(5),
+ BigDecimal.class);
}
@Test
@@ -153,7 +316,14 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue((short) 4);
assertThat(value.isNumberValue()).isTrue();
- assertDataIsCorrect(value, "4", new BigDecimal(4), true, BigDecimal.class);
+ assertDataIsCorrect(
+ value,
+ "4",
+ new BigDecimal(4),
+ true,
+ Instant.ofEpochMilli(4),
+ Duration.ofMillis(4),
+ BigDecimal.class);
}
@Test
@@ -161,7 +331,14 @@ class EvaluationValueTest {
EvaluationValue value = new EvaluationValue((byte) 3);
assertThat(value.isNumberValue()).isTrue();
- assertDataIsCorrect(value, "3", new BigDecimal(3), true, BigDecimal.class);
+ assertDataIsCorrect(
+ value,
+ "3",
+ new BigDecimal(3),
+ true,
+ Instant.ofEpochMilli(3),
+ Duration.ofMillis(3),
+ BigDecimal.class);
}
@Test
@@ -175,6 +352,7 @@ class EvaluationValueTest {
assertThat(value.isStructureValue()).isFalse();
assertThat(value.isStringValue()).isFalse();
assertThat(value.isExpressionNode()).isFalse();
+ assertThat(value.isNullValue()).isFalse();
assertThat(value.getArrayValue()).hasSize(2);
assertThat(value.getArrayValue().get(0).getStringValue()).isEqualTo("1");
@@ -190,6 +368,13 @@ class EvaluationValueTest {
assertThat(value.getArrayValue()).isEmpty();
}
+ @Test
+ void testArrayNull() {
+ EvaluationValue value = new EvaluationValue(null);
+
+ assertThat(value.getArrayValue()).isNull();
+ }
+
@Test
void testStructure() {
Map structure = new HashMap<>();
@@ -203,11 +388,11 @@ class EvaluationValueTest {
assertThat(value.isStringValue()).isFalse();
assertThat(value.isArrayValue()).isFalse();
assertThat(value.isExpressionNode()).isFalse();
+ assertThat(value.isNullValue()).isFalse();
assertThat(value.getStructureValue()).hasSize(2);
assertThat(value.getStructureValue().get("a").getStringValue()).isEqualTo("Hello");
assertThat(value.getStructureValue().get("b").getStringValue()).isEqualTo("99");
-
assertThat(value.getValue()).isInstanceOf(Map.class);
}
@@ -218,6 +403,13 @@ class EvaluationValueTest {
assertThat(value.getStructureValue()).isEmpty();
}
+ @Test
+ void testStructureNull() {
+ EvaluationValue value = new EvaluationValue(null);
+
+ assertThat(value.getStructureValue()).isNull();
+ }
+
@Test
void testExpressionNode() {
ASTNode node = new ASTNode(new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT));
@@ -229,12 +421,15 @@ class EvaluationValueTest {
assertThat(value.isStructureValue()).isFalse();
assertThat(value.isArrayValue()).isFalse();
assertThat(value.isStringValue()).isFalse();
+ assertThat(value.isNullValue()).isFalse();
assertDataIsCorrect(
value,
"ASTNode(parameters=[], token=Token(startPosition=1, value=a, type=VARIABLE_OR_CONSTANT))",
BigDecimal.ZERO,
false,
+ Instant.EPOCH,
+ Duration.ZERO,
ASTNode.class);
}
@@ -277,6 +472,20 @@ class EvaluationValueTest {
.isEqualByComparingTo("3.99");
}
+ @Test
+ void testNull() {
+ EvaluationValue value = new EvaluationValue(null);
+
+ assertThat(value.isStringValue()).isFalse();
+ assertThat(value.isNumberValue()).isFalse();
+ assertThat(value.isBooleanValue()).isFalse();
+ assertThat(value.isStructureValue()).isFalse();
+ assertThat(value.isArrayValue()).isFalse();
+ assertThat(value.isExpressionNode()).isFalse();
+ assertThat(value.isNullValue()).isTrue();
+ assertDataIsCorrect(value, null, null, null);
+ }
+
@Test
void nestedEvaluationValue() {
try {
@@ -298,15 +507,27 @@ class EvaluationValueTest {
}
}
+ private void assertDataIsCorrect(
+ EvaluationValue value, String stringValue, BigDecimal numberValue, Boolean booleanValue) {
+ assertThat(value.getStringValue()).isEqualTo(stringValue);
+ assertThat(value.getNumberValue()).isEqualTo(numberValue);
+ assertThat(value.getBooleanValue()).isEqualTo(booleanValue);
+ }
+
private void assertDataIsCorrect(
EvaluationValue value,
String stringValue,
BigDecimal numberValue,
Boolean booleanValue,
+ Instant dateTimeValue,
+ Duration durationValue,
Class> valueInstance) {
+ assertDataIsCorrect(value, stringValue, numberValue, booleanValue);
assertThat(value.getStringValue()).isEqualTo(stringValue);
assertThat(value.getNumberValue()).isEqualTo(numberValue);
assertThat(value.getBooleanValue()).isEqualTo(booleanValue);
+ assertThat(value.getDateTimeValue()).isEqualTo(dateTimeValue);
+ assertThat(value.getDurationValue()).isEqualTo(durationValue);
assertThat(value.getValue()).isInstanceOf(valueInstance);
}
}
diff --git a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java
new file mode 100644
index 0000000..0844f63
--- /dev/null
+++ b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java
@@ -0,0 +1,159 @@
+/*
+ Copyright 2012-2022 Udo Klimaschewski
+
+ Licensed 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 com.ezylang.evalex.functions.datetime;
+
+import com.ezylang.evalex.BaseEvaluationTest;
+import com.ezylang.evalex.EvaluationException;
+import com.ezylang.evalex.config.ExpressionConfiguration;
+import com.ezylang.evalex.config.TestConfigurationProvider;
+import com.ezylang.evalex.parser.ParseException;
+import java.time.ZoneId;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class DateTimeFunctionsTest extends BaseEvaluationTest {
+
+ private static final ExpressionConfiguration DateTimeTestConfiguration =
+ TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators.toBuilder()
+ .defaultZoneId(ZoneId.of("UTC+2"))
+ .build();
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_PARSE(\"2022-10-30T11:50:20Z\") | 2022-10-30T11:50:20Z",
+ "DT_PARSE(\"2022-10-30T11:50:20\") | 2022-10-30T09:50:20Z",
+ "DT_PARSE(\"2022-10-30T11:50:20.000000030\") | 2022-10-30T09:50:20.000000030Z",
+ "DT_PARSE(\"2022-10-30\") | 2022-10-30T00:00:00Z",
+ "DT_PARSE(\"30/10/2022 11:50:20\", \"dd/MM/yyyy HH:mm:ss\") | 2022-10-30T09:50:20Z",
+ "DT_PARSE(\"30/10/2022\",\"dd/MM/yyyy\") | 2022-10-30T00:00:00Z",
+ })
+ void testDateTimeParse(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(expression, expectedResult, DateTimeTestConfiguration);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_PARSE(\"NOT A DATE\") | Unable to parse date/time: NOT A DATE",
+ })
+ void testDateTimeParseFailure(String expression, String message) {
+ assertExpressionThrowsException(expression, message, DateTimeTestConfiguration);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_ZONED_PARSE(\"2022-10-30T11:50:20Z\") | 2022-10-30T11:50:20Z",
+ "DT_ZONED_PARSE(\"2011-12-03T10:15:30+01:00[Europe/Paris]\") | 2011-12-03T09:15:30Z",
+ "DT_ZONED_PARSE(\"2011-12-03T10:15:30+01:00\") | 2011-12-03T09:15:30Z",
+ "DT_ZONED_PARSE(\"03/12/2011 10:15:30 Europe/Paris\", \"dd/MM/yyyy HH:mm:ss v\") |"
+ + " 2011-12-03T09:15:30Z",
+ "DT_ZONED_PARSE(\"03/08/2019T16:20:17:717+05:30\",\"dd/MM/uuuu'T'HH:mm:ss:SSSXXXXX\") |"
+ + " 2019-08-03T10:50:17.717Z",
+ })
+ void testZonedDateTimeParse(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(expression, expectedResult, DateTimeTestConfiguration);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_ZONED_PARSE(\"NOT A DATE\") | Unable to parse zoned date/time: NOT A DATE",
+ })
+ void testZonedDateTimeParseFailure(String expression, String message) {
+ assertExpressionThrowsException(expression, message, DateTimeTestConfiguration);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_DATE_TIME(2022,10,30) | 2022-10-29T22:00:00Z",
+ "DT_DATE_TIME(2022,10,30,11) | 2022-10-30T09:00:00Z",
+ "DT_DATE_TIME(2022,10,30,11,50,20) | 2022-10-30T09:50:20Z",
+ "DT_DATE_TIME(2022,10,30,11,50,20,30) | 2022-10-30T09:50:20.000000030Z"
+ })
+ void testDateTime(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(expression, expectedResult, DateTimeTestConfiguration);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_FORMAT(DT_PARSE(\"2022-10-30T11:50:20\")) | 2022-10-30T11:50:20",
+ "DT_FORMAT(DT_PARSE(\"2022-10-30T11:50:20.000000030\"), \"dd/MM/yyyy\") | 30/10/2022",
+ "DT_FORMAT(DT_PARSE(\"2022-10-30T11:50:20.000000030\"), \"dd/MM/yyyy HH:mm:ss\") |"
+ + " 30/10/2022 11:50:20"
+ })
+ void testDateTimeFormat(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(expression, expectedResult, DateTimeTestConfiguration);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_EPOCH(DT_DATE_TIME_EPOCH(1667130620000)) | 1667130620000",
+ "DT_EPOCH(DT_DATE_TIME_EPOCH(0)) | 0"
+ })
+ void testDateTimeToEpoch(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(expression, expectedResult);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_DURATION_MILLIS(1667130620000) | PT463091H50M20S",
+ "DT_DURATION_MILLIS(0) | PT0S"
+ })
+ void testDurationFromMillis(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(expression, expectedResult);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {"DT_DURATION_DAYS(53216) | PT1277184H", "DT_DURATION_DAYS(1) | PT24H"})
+ void testDurationFromDays(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(expression, expectedResult);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_DURATION_PARSE(\"PT1277184H\") | PT1277184H",
+ "DT_DURATION_PARSE(\"P1D\") | PT24H"
+ })
+ void testDurationParse(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(expression, expectedResult);
+ }
+}
diff --git a/src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java b/src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java
index b57beff..ad1fe42 100644
--- a/src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java
+++ b/src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java
@@ -19,7 +19,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.ezylang.evalex.BaseEvaluationTest;
import com.ezylang.evalex.EvaluationException;
+import com.ezylang.evalex.config.TestConfigurationProvider;
import com.ezylang.evalex.parser.ParseException;
+import java.time.ZoneId;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
@@ -84,11 +86,30 @@ class ArithmeticOperatorsTest extends BaseEvaluationTest {
"1-89282 : -89281",
"1.54321-1.5432 : 0.00001"
})
- void testInfixMinus(String expression, String expectedResult)
+ void testInfixMinusNumber(String expression, String expectedResult)
throws EvaluationException, ParseException {
assertExpressionHasExpectedResult(expression, expectedResult);
}
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_DATE_TIME(2022,10,30,11,50,30)-2000 | 2022-10-30T09:50:28Z",
+ "DT_DATE_TIME(2022,10,30,11,50,30)-DT_DATE_TIME(2022,10,30,11,50,28) | PT2S",
+ "DT_DATE_TIME(2022,10,30,11,50,30)-DT_DURATION_PARSE(\"PT2S\") | 2022-10-30T09:50:28Z",
+ "DT_DURATION_PARSE(\"PT5S\")-DT_DURATION_PARSE(\"PT2S\") | PT3S"
+ })
+ void testInfixMinusDateTime(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(
+ expression,
+ expectedResult,
+ TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators.toBuilder()
+ .defaultZoneId(ZoneId.of("UTC+2"))
+ .build());
+ }
+
@ParameterizedTest
@CsvSource(
delimiter = ':',
@@ -165,6 +186,24 @@ class ArithmeticOperatorsTest extends BaseEvaluationTest {
assertExpressionHasExpectedResult(expression, expectedResult);
}
+ @ParameterizedTest
+ @CsvSource(
+ delimiter = '|',
+ value = {
+ "DT_DATE_TIME(2022,10,30,11,50,30)+2000 | 2022-10-30T09:50:32Z",
+ "DT_DATE_TIME(2022,10,30,11,50,30)+DT_DURATION_PARSE(\"PT2S\") | 2022-10-30T09:50:32Z",
+ "DT_DURATION_PARSE(\"PT5S\")+DT_DURATION_PARSE(\"PT2S\") | PT7S"
+ })
+ void testInfixPlusDateTime(String expression, String expectedResult)
+ throws EvaluationException, ParseException {
+ assertExpressionHasExpectedResult(
+ expression,
+ expectedResult,
+ TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators.toBuilder()
+ .defaultZoneId(ZoneId.of("UTC+2"))
+ .build());
+ }
+
@ParameterizedTest
@CsvSource(
delimiter = ':',
diff --git a/src/test/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperatorTest.java b/src/test/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperatorTest.java
index c3c56e9..05fc27b 100644
--- a/src/test/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperatorTest.java
+++ b/src/test/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperatorTest.java
@@ -44,7 +44,10 @@ class InfixEqualsOperatorTest extends BaseEvaluationTest {
"\"abc\"=\"abc\" : true",
"\"abc\"=\"xyz\" : false",
"1+2=4-1 : true",
- "-5.2=-5.2 :true"
+ "-5.2=-5.2 : true",
+ "DT_DATE_TIME(2022,10,30)=DT_DATE_TIME(2022,10,30) : true",
+ "DT_DATE_TIME(2022,10,30)=DT_DATE_TIME(2022,10,01) : false",
+ "DT_DURATION_PARSE(\"PT24H\")=DT_DURATION_PARSE(\"P1D\") : true",
})
void testInfixEqualsLiterals(String expression, String expectedResult)
throws EvaluationException, ParseException {
diff --git a/src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperatorTest.java b/src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperatorTest.java
index 2c708a9..b6ce858 100644
--- a/src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperatorTest.java
+++ b/src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperatorTest.java
@@ -40,7 +40,11 @@ class InfixGreaterEqualsOperatorTest extends BaseEvaluationTest {
"\"9\">=\"5\" : true",
"\"9\">=\"9\" : true",
"-4>=-4 :true",
- "-4>=-5 :true"
+ "-4>=-5 :true",
+ "DT_DATE_TIME(2022,10,30)>=DT_DATE_TIME(2022,10,30) : true",
+ "DT_DATE_TIME(2022,10,30)>=DT_DATE_TIME(2022,10,28) : true",
+ "DT_DATE_TIME(2022,10,30)>=DT_DATE_TIME(2022,10,31) : false",
+ "DT_DURATION_PARSE(\"P2D\")>=DT_DURATION_PARSE(\"PT24H\") : true"
})
void testInfixGreaterEqualsLiterals(String expression, String expectedResult)
throws EvaluationException, ParseException {
diff --git a/src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperatorTest.java b/src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperatorTest.java
index 5f40249..335b765 100644
--- a/src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperatorTest.java
+++ b/src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperatorTest.java
@@ -36,7 +36,11 @@ class InfixGreaterOperatorTest extends BaseEvaluationTest {
"\"abc\">\"xyz\" : false",
"\"ABC\">\"abc\" : false",
"\"9\">\"5\" : true",
- "-4>-5 :true"
+ "-4>-5 :true",
+ "DT_DATE_TIME(2022,10,30)>DT_DATE_TIME(2022,10,30) : false",
+ "DT_DATE_TIME(2022,10,30)>DT_DATE_TIME(2022,10,28) : true",
+ "DT_DATE_TIME(2022,10,30)>DT_DATE_TIME(2022,10,31) : false",
+ "DT_DURATION_PARSE(\"P2D\")>DT_DURATION_PARSE(\"PT24H\") : true"
})
void testInfixGreaterLiterals(String expression, String expectedResult)
throws EvaluationException, ParseException {
diff --git a/src/test/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperatorTest.java b/src/test/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperatorTest.java
index 9d4e200..6ba9ec9 100644
--- a/src/test/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperatorTest.java
+++ b/src/test/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperatorTest.java
@@ -40,7 +40,11 @@ class InfixLessEqualsOperatorTest extends BaseEvaluationTest {
"\"5\"<=\"9\" : true",
"\"9\"<=\"9\" : true",
"-4<=-4 :true",
- "-5<=-4 :true"
+ "-5<=-4 :true",
+ "DT_DATE_TIME(2022,10,30)<=DT_DATE_TIME(2022,10,30) : true",
+ "DT_DATE_TIME(2022,10,30)<=DT_DATE_TIME(2022,10,28) : false",
+ "DT_DATE_TIME(2022,10,30)<=DT_DATE_TIME(2022,10,31) : true",
+ "DT_DURATION_PARSE(\"P2D\")<=DT_DURATION_PARSE(\"PT24H\") : false"
})
void testInfixLessEqualsLiterals(String expression, String expectedResult)
throws EvaluationException, ParseException {
diff --git a/src/test/java/com/ezylang/evalex/operators/booleans/InfixLessOperatorTest.java b/src/test/java/com/ezylang/evalex/operators/booleans/InfixLessOperatorTest.java
index 4a1ca57..5402489 100644
--- a/src/test/java/com/ezylang/evalex/operators/booleans/InfixLessOperatorTest.java
+++ b/src/test/java/com/ezylang/evalex/operators/booleans/InfixLessOperatorTest.java
@@ -38,7 +38,11 @@ class InfixLessOperatorTest extends BaseEvaluationTest {
"\"abc\"<\"xyz\" : true",
"\"abc\"<\"ABC\" : false",
"\"5\"<\"9\" : true",
- "-5<-4 :true"
+ "-5<-4 :true",
+ "DT_DATE_TIME(2022,10,30)