From 4f1fe884dc1321f3d38b230d67a37acfef067e9a Mon Sep 17 00:00:00 2001 From: Oswaldo Baptista Vicente Junior <45291656+oswaldobapvicjr@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:18:03 -0300 Subject: [PATCH 1/6] Add functions DT_DATE_NOW and DT_DATE_TODAY --- .../config/ExpressionConfiguration.java | 2 + .../datetime/DateTimeNowFunction.java | 41 +++++++++++++++++ .../datetime/DateTimeTodayFunction.java | 44 +++++++++++++++++++ .../datetime/DateTimeFunctionsTest.java | 28 +++++++++++- 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java create mode 100644 src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java diff --git a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java index b7a86b61..78404125 100644 --- a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java +++ b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java @@ -188,6 +188,8 @@ public class ExpressionConfiguration { Map.entry("DT_DATE_NEW", new DateTimeNewFunction()), Map.entry("DT_DATE_PARSE", new DateTimeParseFunction()), Map.entry("DT_DATE_FORMAT", new DateTimeFormatFunction()), + Map.entry("DT_DATE_NOW", new DateTimeNowFunction()), + Map.entry("DT_DATE_TODAY", new DateTimeTodayFunction()), Map.entry("DT_DATE_TO_EPOCH", new DateTimeToEpochFunction()), Map.entry("DT_DURATION_NEW", new DurationNewFunction()), Map.entry("DT_DURATION_FROM_MILLIS", new DurationFromMillisFunction()), diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java new file mode 100644 index 00000000..bca7443b --- /dev/null +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java @@ -0,0 +1,41 @@ +/* + Copyright 2012-2024 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 java.time.Instant; + +import com.ezylang.evalex.Expression; +import com.ezylang.evalex.data.EvaluationValue; +import com.ezylang.evalex.functions.AbstractFunction; +import com.ezylang.evalex.parser.Token; + +/** + * Produces a new DATE_TIME that represents the current date and time. + *

+ * It is useful to calculate a value based on the current date and time. For example, if + * you know the start DATE_TIME of a running process, you might use the following + * expression to find the DURATION that represents the process age: + *

{@code DT_DATE_NOW() - startDateTime}
+ * + * @author oswaldobapvicjr + */ +public class DateTimeNowFunction extends AbstractFunction { + @Override + public EvaluationValue evaluate( + Expression expression, Token functionToken, EvaluationValue... parameterValues) { + return expression.convertValue(Instant.now()); + } +} diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java new file mode 100644 index 00000000..c6fb5472 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java @@ -0,0 +1,44 @@ +/* + Copyright 2012-2024 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 java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; + +import com.ezylang.evalex.Expression; +import com.ezylang.evalex.data.EvaluationValue; +import com.ezylang.evalex.functions.AbstractFunction; +import com.ezylang.evalex.parser.Token; + +/** + * Produces a new DATE_TIME that represents the current date, at midnight (00:00), in the + * system default time-zone. + *

+ * It is useful for DATE_TIME comparison, when the current time must not be considered. + * For example, in the expression: + *

{@code IF(expiryDate > DT_DATE_TODAY(), "expired", "valid")}
+ * + * @author oswaldobapvicjr + */ +public class DateTimeTodayFunction extends AbstractFunction { + @Override + public EvaluationValue evaluate( + Expression expression, Token functionToken, EvaluationValue... parameterValues) { + Instant today = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant(); + return expression.convertValue(today); + } +} diff --git a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java index cc465f58..caa45341 100644 --- a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java +++ b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java @@ -23,7 +23,12 @@ import com.ezylang.evalex.config.ExpressionConfiguration; import com.ezylang.evalex.config.TestConfigurationProvider; import com.ezylang.evalex.parser.ParseException; + +import java.time.Instant; +import java.time.LocalDate; import java.time.ZoneId; +import java.util.TimeZone; + import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -31,9 +36,17 @@ class DateTimeFunctionsTest extends BaseEvaluationTest { + private static final ZoneId DEFAULT_ZONE_ID = ZoneId.of("Europe/Berlin"); + + static { + // Let the default time-zone be set programmatically to standardize + // tests dependent on the default time-zone + TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_ZONE_ID)); + } + private static final ExpressionConfiguration DateTimeTestConfiguration = TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators.toBuilder() - .zoneId(ZoneId.of("Europe/Berlin")) + .zoneId(DEFAULT_ZONE_ID) .build(); @ParameterizedTest @@ -231,6 +244,19 @@ void testDateTimeToEpoch(String expression, String expectedResult) assertExpressionHasExpectedResult(expression, expectedResult); } + @Test + void testDateTimeNow() throws EvaluationException, ParseException { + assertExpressionHasExpectedResult( + "DT_DATE_NOW()", Instant.now().toString(), DateTimeTestConfiguration); + } + + @Test + void testDateTimeToday() throws EvaluationException, ParseException { + String expected = LocalDate.now().atStartOfDay(DEFAULT_ZONE_ID).toInstant().toString(); + assertExpressionHasExpectedResult( + "DT_DATE_TODAY()", expected, DateTimeTestConfiguration); + } + @ParameterizedTest @CsvSource( delimiter = '|', From cba74b124e073c84bda80b6fa16c52473f2bbcef Mon Sep 17 00:00:00 2001 From: Oswaldo Baptista Vicente Junior <45291656+oswaldobapvicjr@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:23:41 -0300 Subject: [PATCH 2/6] Apply spotless --- .../datetime/DateTimeNowFunction.java | 18 ++++++++------ .../datetime/DateTimeTodayFunction.java | 24 +++++++++++-------- .../datetime/DateTimeFunctionsTest.java | 11 ++++----- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java index bca7443b..22392e0e 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java @@ -15,20 +15,24 @@ */ package com.ezylang.evalex.functions.datetime; -import java.time.Instant; - 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; /** * Produces a new DATE_TIME that represents the current date and time. - *

- * It is useful to calculate a value based on the current date and time. For example, if - * you know the start DATE_TIME of a running process, you might use the following - * expression to find the DURATION that represents the process age: - *

{@code DT_DATE_NOW() - startDateTime}
+ * + *

It is useful to calculate a value based on the current date and time. For example, if you know + * the start DATE_TIME of a running process, you might use the following expression to find the + * DURATION that represents the process age: + * + *

+ * + * {@code DT_DATE_NOW() - startDateTime} + * + *
* * @author oswaldobapvicjr */ diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java index c6fb5472..52c0d7a5 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java @@ -15,22 +15,26 @@ */ package com.ezylang.evalex.functions.datetime; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; - 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.LocalDate; +import java.time.ZoneId; /** - * Produces a new DATE_TIME that represents the current date, at midnight (00:00), in the - * system default time-zone. - *

- * It is useful for DATE_TIME comparison, when the current time must not be considered. - * For example, in the expression: - *

{@code IF(expiryDate > DT_DATE_TODAY(), "expired", "valid")}
+ * Produces a new DATE_TIME that represents the current date, at midnight (00:00), in the system + * default time-zone. + * + *

It is useful for DATE_TIME comparison, when the current time must not be considered. For + * example, in the expression: + * + *

+ * + * {@code IF(expiryDate > DT_DATE_TODAY(), "expired", "valid")} + * + *
* * @author oswaldobapvicjr */ diff --git a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java index caa45341..e79b0c87 100644 --- a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java +++ b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java @@ -23,12 +23,10 @@ import com.ezylang.evalex.config.ExpressionConfiguration; import com.ezylang.evalex.config.TestConfigurationProvider; import com.ezylang.evalex.parser.ParseException; - import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.util.TimeZone; - import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -39,9 +37,9 @@ class DateTimeFunctionsTest extends BaseEvaluationTest { private static final ZoneId DEFAULT_ZONE_ID = ZoneId.of("Europe/Berlin"); static { - // Let the default time-zone be set programmatically to standardize - // tests dependent on the default time-zone - TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_ZONE_ID)); + // Let the default time-zone be set programmatically to standardize + // tests dependent on the default time-zone + TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_ZONE_ID)); } private static final ExpressionConfiguration DateTimeTestConfiguration = @@ -253,8 +251,7 @@ void testDateTimeNow() throws EvaluationException, ParseException { @Test void testDateTimeToday() throws EvaluationException, ParseException { String expected = LocalDate.now().atStartOfDay(DEFAULT_ZONE_ID).toInstant().toString(); - assertExpressionHasExpectedResult( - "DT_DATE_TODAY()", expected, DateTimeTestConfiguration); + assertExpressionHasExpectedResult("DT_DATE_TODAY()", expected, DateTimeTestConfiguration); } @ParameterizedTest From 725544ad6bf2d6362ab704a3113d31f7e88736da Mon Sep 17 00:00:00 2001 From: Oswaldo Baptista Vicente Junior <45291656+oswaldobapvicjr@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:31:11 -0300 Subject: [PATCH 3/6] Update functions.md --- docs/references/functions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/references/functions.md b/docs/references/functions.md index 406cd47b..bd41ecc9 100644 --- a/docs/references/functions.md +++ b/docs/references/functions.md @@ -86,6 +86,8 @@ Available through the _ExpressionConfiguration.StandardFunctionsDictionary_ cons | DT_DATE_NEW(millis) | Returns a new DATE_TIME from the epoch of 1970-01-01T00:00:00Z in milliseconds. | | DT_DATE_PARSE(value [,zoneId] [,format, ...]) | Converts the given string value to a date time value by using the optional time zone and formats. All formats are used until the first matching format. Without a format, the configured formats are used. Time zone can be NULL, the the configured time zone is used. | | DT_DATE_FORMAT(value, [,format] [,zoneId]) | Formats the given date-time to a string using the given optional format and time zone. Without a format, the first configured format is used. The zone id defaults to the configured zone id. | +| DT_DATE_NOW() | Produces a new DATE_TIME that represents the current date and time, in the system default time zo | +| DT_DATE_TODAY() | Produces a new DATE_TIME that represents the current date, at midnight (00:00), in the system default time zone. | | DT_DATE_TO_EPOCH(value) | Converts the given value to epoch timestamp in millisecond. | | DT_DURATION_NEW(days [,hours, minutes, seconds, nanos]) | Returns a new DURATION value with the given parameters. | | DT_DURATION_PARSE(value) | Converts the given ISO-8601 duration string representation to a duration value. E.g. "P2DT3H4M" parses 2 days, 3 hours and 4 minutes. | From aa39583a203101b69d6192e0e149ed0a63c11e26 Mon Sep 17 00:00:00 2001 From: Oswaldo Baptista Vicente Junior <45291656+oswaldobapvicjr@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:57:46 -0300 Subject: [PATCH 4/6] Fix JUnit for DT_DATE_NOW --- .../functions/datetime/DateTimeFunctionsTest.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java index e79b0c87..93a3468b 100644 --- a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java +++ b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java @@ -23,7 +23,6 @@ import com.ezylang.evalex.config.ExpressionConfiguration; import com.ezylang.evalex.config.TestConfigurationProvider; import com.ezylang.evalex.parser.ParseException; -import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.util.TimeZone; @@ -242,10 +241,16 @@ void testDateTimeToEpoch(String expression, String expectedResult) assertExpressionHasExpectedResult(expression, expectedResult); } - @Test - void testDateTimeNow() throws EvaluationException, ParseException { - assertExpressionHasExpectedResult( - "DT_DATE_NOW()", Instant.now().toString(), DateTimeTestConfiguration); + @ParameterizedTest + @CsvSource( + delimiter = '|', + value = { + "DT_DATE_NOW() > DT_DATE_TODAY() | true", + "DT_DATE_NOW() > (DT_DATE_TODAY() + DT_DURATION_PARSE(\"P1D\")) | false" + }) + void testDateTimeNow(String expression, String expectedResult) + throws EvaluationException, ParseException { + assertExpressionHasExpectedResult(expression, expectedResult); } @Test From d14e8cab5aac8f2532e799d00969cc097deb67b7 Mon Sep 17 00:00:00 2001 From: Oswaldo Baptista Vicente Junior <45291656+oswaldobapvicjr@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:47:34 -0300 Subject: [PATCH 5/6] Fix doc --- docs/references/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/references/functions.md b/docs/references/functions.md index bd41ecc9..c0d2a053 100644 --- a/docs/references/functions.md +++ b/docs/references/functions.md @@ -86,7 +86,7 @@ Available through the _ExpressionConfiguration.StandardFunctionsDictionary_ cons | DT_DATE_NEW(millis) | Returns a new DATE_TIME from the epoch of 1970-01-01T00:00:00Z in milliseconds. | | DT_DATE_PARSE(value [,zoneId] [,format, ...]) | Converts the given string value to a date time value by using the optional time zone and formats. All formats are used until the first matching format. Without a format, the configured formats are used. Time zone can be NULL, the the configured time zone is used. | | DT_DATE_FORMAT(value, [,format] [,zoneId]) | Formats the given date-time to a string using the given optional format and time zone. Without a format, the first configured format is used. The zone id defaults to the configured zone id. | -| DT_DATE_NOW() | Produces a new DATE_TIME that represents the current date and time, in the system default time zo | +| DT_DATE_NOW() | Produces a new DATE_TIME that represents the current date and time, in the system default time zone. | | DT_DATE_TODAY() | Produces a new DATE_TIME that represents the current date, at midnight (00:00), in the system default time zone. | | DT_DATE_TO_EPOCH(value) | Converts the given value to epoch timestamp in millisecond. | | DT_DURATION_NEW(days [,hours, minutes, seconds, nanos]) | Returns a new DURATION value with the given parameters. | From bc6c19096162bc7f5726fdf1c9d92334cd276d45 Mon Sep 17 00:00:00 2001 From: Oswaldo Baptista Vicente Junior <45291656+oswaldobapvicjr@users.noreply.github.com> Date: Mon, 1 Apr 2024 00:20:42 -0300 Subject: [PATCH 6/6] Fixing comments from 5th commit --- docs/references/functions.md | 6 +-- .../config/ExpressionConfiguration.java | 6 +-- .../ezylang/evalex/functions/FunctionIfc.java | 11 +++++ .../datetime/DateTimeFormatFunction.java | 1 + .../datetime/DateTimeParseFunction.java | 1 + .../datetime/DateTimeTodayFunction.java | 25 ++++++++++-- .../datetime/DurationNewFunction.java | 1 + .../evalex/parser/ShuntingYardConverter.java | 2 +- .../ezylang/evalex/BaseEvaluationTest.java | 5 +++ .../datetime/DateTimeFunctionsTest.java | 40 +++++++++---------- .../parser/ShuntingYardExceptionsTest.java | 3 +- 11 files changed, 69 insertions(+), 32 deletions(-) diff --git a/docs/references/functions.md b/docs/references/functions.md index c0d2a053..fade7160 100644 --- a/docs/references/functions.md +++ b/docs/references/functions.md @@ -82,14 +82,14 @@ Available through the _ExpressionConfiguration.StandardFunctionsDictionary_ cons | Name | Description | |--------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| DT_DATE_NEW(year, month, day [,hour, minute, second, millis, nanos] [,zoneId]) | Returns a new DATE_TIME value with the given parameters. Any optional time zone (string) can be specified, e.g. "Europe/Berlin", or "GMT+02:00. If no zone id is specified, the configured zone id is used. | +| DT_DATE_NEW(year, month, day [,hour, minute, second, millis, nanos] [,zoneId]) | Returns a new DATE_TIME value with the given parameters. Any optional time zone (string) can be specified, e.g. "Europe/Berlin", or "GMT+02:00". If no zone id is specified, the configured zone id is used. | | DT_DATE_NEW(millis) | Returns a new DATE_TIME from the epoch of 1970-01-01T00:00:00Z in milliseconds. | | DT_DATE_PARSE(value [,zoneId] [,format, ...]) | Converts the given string value to a date time value by using the optional time zone and formats. All formats are used until the first matching format. Without a format, the configured formats are used. Time zone can be NULL, the the configured time zone is used. | | DT_DATE_FORMAT(value, [,format] [,zoneId]) | Formats the given date-time to a string using the given optional format and time zone. Without a format, the first configured format is used. The zone id defaults to the configured zone id. | -| DT_DATE_NOW() | Produces a new DATE_TIME that represents the current date and time, in the system default time zone. | -| DT_DATE_TODAY() | Produces a new DATE_TIME that represents the current date, at midnight (00:00), in the system default time zone. | | DT_DATE_TO_EPOCH(value) | Converts the given value to epoch timestamp in millisecond. | | DT_DURATION_NEW(days [,hours, minutes, seconds, nanos]) | Returns a new DURATION value with the given parameters. | | DT_DURATION_PARSE(value) | Converts the given ISO-8601 duration string representation to a duration value. E.g. "P2DT3H4M" parses 2 days, 3 hours and 4 minutes. | | DT_DURATION_FROM_MILLIS(millis) | Returns a new DURATION value with the given milliseconds. | | DT_DURATION_TO_MILLIS(value) | Converts the given duration to a milliseconds value. | +| DT_DATE_NOW() | Produces a new DATE_TIME that represents the current date and time. | +| DT_DATE_TODAY([zoneId]) | Produces a new DATE_TIME that represents the current date, at midnight (00:00). An optional time zone (string) can be specified, e.g. "America/Sao_Paulo", or "GMT-03:00". If no zone id is specified, the configured zone id is used. | diff --git a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java index 78404125..923f77da 100644 --- a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java +++ b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java @@ -188,13 +188,13 @@ public class ExpressionConfiguration { Map.entry("DT_DATE_NEW", new DateTimeNewFunction()), Map.entry("DT_DATE_PARSE", new DateTimeParseFunction()), Map.entry("DT_DATE_FORMAT", new DateTimeFormatFunction()), - Map.entry("DT_DATE_NOW", new DateTimeNowFunction()), - Map.entry("DT_DATE_TODAY", new DateTimeTodayFunction()), Map.entry("DT_DATE_TO_EPOCH", new DateTimeToEpochFunction()), Map.entry("DT_DURATION_NEW", new DurationNewFunction()), Map.entry("DT_DURATION_FROM_MILLIS", new DurationFromMillisFunction()), Map.entry("DT_DURATION_TO_MILLIS", new DurationToMillisFunction()), - Map.entry("DT_DURATION_PARSE", new DurationParseFunction())); + Map.entry("DT_DURATION_PARSE", new DurationParseFunction()), + Map.entry("DT_NOW", new DateTimeNowFunction()), + Map.entry("DT_TODAY", new DateTimeTodayFunction())); /** The math context to use. */ @Builder.Default @Getter private final MathContext mathContext = DEFAULT_MATH_CONTEXT; diff --git a/src/main/java/com/ezylang/evalex/functions/FunctionIfc.java b/src/main/java/com/ezylang/evalex/functions/FunctionIfc.java index beb63975..ee31efc9 100644 --- a/src/main/java/com/ezylang/evalex/functions/FunctionIfc.java +++ b/src/main/java/com/ezylang/evalex/functions/FunctionIfc.java @@ -79,4 +79,15 @@ default boolean isParameterLazy(int parameterIndex) { } return getFunctionParameterDefinitions().get(parameterIndex).isLazy(); } + + /** + * Returns the count of non-var-arg parameters defined by this function. If the function has + * var-args, the the result is the count of parameter definitions - 1. + * + * @return the count of non-var-arg parameters defined by this function. + */ + default int getCountOfNonVarArgParameters() { + int numOfParameters = getFunctionParameterDefinitions().size(); + return hasVarArgs() ? numOfParameters - 1 : numOfParameters; + } } diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java index 6cb7dfdb..908886e8 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java @@ -30,6 +30,7 @@ * format is given, the first format defined in the configured formats is used. Second optional * parameter is the zone-id to use wit formatting. Default is the configured zone-id. */ +@FunctionParameter(name = "value") @FunctionParameter(name = "parameters", isVarArg = true) public class DateTimeFormatFunction extends AbstractFunction { @Override diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeParseFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeParseFunction.java index fba7392a..d1a08de2 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeParseFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeParseFunction.java @@ -36,6 +36,7 @@ * pattern will be used. If NULL is specified for the time zone, the currently * configured zone is used. If no formatters a */ +@FunctionParameter(name = "value") @FunctionParameter(name = "parameters", isVarArg = true) public class DateTimeParseFunction extends AbstractFunction { diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java index 52c0d7a5..97c4fb02 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java @@ -15,17 +15,19 @@ */ package com.ezylang.evalex.functions.datetime; +import com.ezylang.evalex.EvaluationException; import com.ezylang.evalex.Expression; +import com.ezylang.evalex.config.ExpressionConfiguration; 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.Instant; import java.time.LocalDate; import java.time.ZoneId; /** - * Produces a new DATE_TIME that represents the current date, at midnight (00:00), in the system - * default time-zone. + * Produces a new DATE_TIME that represents the current date, at midnight (00:00). * *

It is useful for DATE_TIME comparison, when the current time must not be considered. For * example, in the expression: @@ -36,13 +38,28 @@ * * * + *

This function may accept an optional time zone to be applied. If no zone ID is specified, the + * default zone ID defined at the {@link ExpressionConfiguration} will be used. + * * @author oswaldobapvicjr */ +@FunctionParameter(name = "parameters", isVarArg = true) public class DateTimeTodayFunction extends AbstractFunction { @Override public EvaluationValue evaluate( - Expression expression, Token functionToken, EvaluationValue... parameterValues) { - Instant today = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant(); + Expression expression, Token functionToken, EvaluationValue... parameterValues) + throws EvaluationException { + ZoneId zoneId = parseZoneId(expression, functionToken, parameterValues); + Instant today = LocalDate.now().atStartOfDay(zoneId).toInstant(); return expression.convertValue(today); } + + private ZoneId parseZoneId( + Expression expression, Token functionToken, EvaluationValue... parameterValues) + throws EvaluationException { + if (parameterValues.length > 0 && !parameterValues[0].isNullValue()) { + return ZoneIdConverter.convert(functionToken, parameterValues[0].getStringValue()); + } + return expression.getConfiguration().getZoneId(); + } } diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DurationNewFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DurationNewFunction.java index 6cf184b0..62afd703 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DurationNewFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DurationNewFunction.java @@ -28,6 +28,7 @@ * All other parameters are optional and specify hours, minutes, seconds, milliseconds and * nanoseconds. */ +@FunctionParameter(name = "days") @FunctionParameter(name = "parameters", isVarArg = true) public class DurationNewFunction extends AbstractFunction { @Override diff --git a/src/main/java/com/ezylang/evalex/parser/ShuntingYardConverter.java b/src/main/java/com/ezylang/evalex/parser/ShuntingYardConverter.java index e4e53587..96e2544f 100644 --- a/src/main/java/com/ezylang/evalex/parser/ShuntingYardConverter.java +++ b/src/main/java/com/ezylang/evalex/parser/ShuntingYardConverter.java @@ -164,7 +164,7 @@ private void processBraceClose() throws ParseException { private void validateFunctionParameters(Token functionToken, ArrayList parameters) throws ParseException { FunctionIfc function = functionToken.getFunctionDefinition(); - if (parameters.size() < function.getFunctionParameterDefinitions().size()) { + if (parameters.size() < function.getCountOfNonVarArgParameters()) { throw new ParseException(functionToken, "Not enough parameters for function"); } if (!function.hasVarArgs() diff --git a/src/test/java/com/ezylang/evalex/BaseEvaluationTest.java b/src/test/java/com/ezylang/evalex/BaseEvaluationTest.java index 69a29c1a..26b09d07 100644 --- a/src/test/java/com/ezylang/evalex/BaseEvaluationTest.java +++ b/src/test/java/com/ezylang/evalex/BaseEvaluationTest.java @@ -41,6 +41,11 @@ protected void assertExpressionHasExpectedResult( .isEqualTo(expectedResult); } + protected EvaluationValue evaluate(String expression) throws EvaluationException, ParseException { + return evaluate( + expression, TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators); + } + 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/functions/datetime/DateTimeFunctionsTest.java b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java index 93a3468b..5e26a3ed 100644 --- a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java +++ b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java @@ -15,7 +15,9 @@ */ package com.ezylang.evalex.functions.datetime; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.within; import com.ezylang.evalex.BaseEvaluationTest; import com.ezylang.evalex.EvaluationException; @@ -23,9 +25,10 @@ import com.ezylang.evalex.config.ExpressionConfiguration; import com.ezylang.evalex.config.TestConfigurationProvider; import com.ezylang.evalex.parser.ParseException; +import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; -import java.util.TimeZone; +import java.time.temporal.ChronoUnit; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -35,12 +38,6 @@ class DateTimeFunctionsTest extends BaseEvaluationTest { private static final ZoneId DEFAULT_ZONE_ID = ZoneId.of("Europe/Berlin"); - static { - // Let the default time-zone be set programmatically to standardize - // tests dependent on the default time-zone - TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_ZONE_ID)); - } - private static final ExpressionConfiguration DateTimeTestConfiguration = TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators.toBuilder() .zoneId(DEFAULT_ZONE_ID) @@ -241,22 +238,25 @@ void testDateTimeToEpoch(String expression, String expectedResult) assertExpressionHasExpectedResult(expression, expectedResult); } - @ParameterizedTest - @CsvSource( - delimiter = '|', - value = { - "DT_DATE_NOW() > DT_DATE_TODAY() | true", - "DT_DATE_NOW() > (DT_DATE_TODAY() + DT_DURATION_PARSE(\"P1D\")) | false" - }) - void testDateTimeNow(String expression, String expectedResult) - throws EvaluationException, ParseException { - assertExpressionHasExpectedResult(expression, expectedResult); + @Test + void testDateTimeNow() throws EvaluationException, ParseException { + Instant expectedTime = Instant.now(); + Instant actualTime = evaluate("DT_NOW()").getDateTimeValue(); + assertThat(actualTime).isCloseTo(expectedTime, within(1, ChronoUnit.SECONDS)); + } + + @Test + void testDateTimeTodayDefaultTimeZone() throws EvaluationException, ParseException { + Instant expectedTime = LocalDate.now().atStartOfDay(DEFAULT_ZONE_ID).toInstant(); + Instant actualTime = evaluate("DT_TODAY()").getDateTimeValue(); + assertThat(actualTime).isEqualTo(expectedTime); } @Test - void testDateTimeToday() throws EvaluationException, ParseException { - String expected = LocalDate.now().atStartOfDay(DEFAULT_ZONE_ID).toInstant().toString(); - assertExpressionHasExpectedResult("DT_DATE_TODAY()", expected, DateTimeTestConfiguration); + void testDateTimeTodayDifferentTimeZone() throws EvaluationException, ParseException { + Instant expectedTime = LocalDate.now().atStartOfDay(ZoneId.of("America/Sao_Paulo")).toInstant(); + Instant actualTime = evaluate("DT_TODAY(\"America/Sao_Paulo\")").getDateTimeValue(); + assertThat(actualTime).isEqualTo(expectedTime); } @ParameterizedTest diff --git a/src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java b/src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java index 9a261b12..4bac2fd8 100644 --- a/src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java +++ b/src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java @@ -125,7 +125,8 @@ void testFunctionNotEnoughParameters() { @Test void testFunctionNotEnoughParametersForVarArgs() { - Expression expression = new Expression("MIN()"); + // The DT_DATE_PARSE() function has a required parameter and var-args + Expression expression = new Expression("DT_DATE_PARSE()"); assertThatThrownBy(expression::evaluate) .isInstanceOf(ParseException.class)