This is an automated email from the ASF dual-hosted git repository.
yamer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git
The following commit(s) were added to refs/heads/main by this push:
new e744532642 [Incubator kie issues#2260] Fix tests in tck due to
failure in implicit conversion of Date Time (#6614)
e744532642 is described below
commit e74453264299cda7949864c07df8f1b0973d4b09
Author: AthiraHari77 <[email protected]>
AuthorDate: Mon Mar 9 21:39:10 2026 +0530
[Incubator kie issues#2260] Fix tests in tck due to failure in implicit
conversion of Date Time (#6614)
* [incubator-kie-issues#2260] fix date time issue
* [incubator-kie-issues#2260] fix date time issue
* [incubator-kie-issues#2260] fix date time issue
* [incubator-kie-issues#2260] fix date time issue
* [incubator-kie-issues#2260] fix review comments
* [incubator-kie-issues#2260] update class name from CustomZonedDateTime to
FormattedZonedDateTime
* [incubator-kie-issues#2260] update test cases
* [incubator-kie-issues#2260] update test cases
* [incubator-kie-issues#2260] update test cases
* [incubator-kie-issues#2260] update test cases
---------
Co-authored-by: athira <[email protected]>
---
.../ast/infixexecutors/InfixExecutorUtils.java | 5 +-
.../runtime/custom/FormattedZonedDateTime.java | 212 ++++++++++++++++++
.../runtime/functions/DateAndTimeFunction.java | 26 ++-
.../dmn/feel/runtime/functions/TimeFunction.java | 2 +-
.../org/kie/dmn/feel/util/BuiltInTypeUtils.java | 3 +-
.../java/org/kie/dmn/feel/util/CodegenUtils.java | 36 ++--
.../org/kie/dmn/feel/util/DateTimeEvalHelper.java | 15 +-
.../java/org/kie/dmn/feel/util/EvalHelper.java | 15 +-
.../main/java/org/kie/dmn/feel/util/TypeUtil.java | 3 +
.../runtime/custom/FormattedZonedDateTimeTest.java | 240 +++++++++++++++++++++
.../runtime/functions/DateAndTimeFunctionTest.java | 39 +++-
.../feel/runtime/functions/TimeFunctionTest.java | 8 +-
.../org/kie/dmn/feel/util/CodegenUtilsTest.java | 53 +++++
13 files changed, 604 insertions(+), 53 deletions(-)
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/InfixExecutorUtils.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/InfixExecutorUtils.java
index 8220a8c96c..aafe156ad6 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/InfixExecutorUtils.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/InfixExecutorUtils.java
@@ -28,6 +28,7 @@ import java.util.function.BinaryOperator;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.FEELDialect;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
import org.kie.dmn.feel.util.BooleanEvalHelper;
import org.kie.dmn.feel.util.Msg;
@@ -204,12 +205,12 @@ public class InfixExecutorUtils {
final EvaluationContext ctx) {
// Both datetimes have a timezone or both timezones don't have it.
Cannot combine timezoned datetime and
// datetime without a timezone.
- if ((leftTemporal instanceof ZonedDateTime || leftTemporal instanceof
OffsetDateTime)
+ if ((leftTemporal instanceof ZonedDateTime || leftTemporal instanceof
FormattedZonedDateTime || leftTemporal instanceof OffsetDateTime)
&& (rightTemporal instanceof LocalDateTime)) {
ctx.notifyEvt(() -> new
InvalidParametersEvent(FEELEvent.Severity.ERROR, Msg
.createMessage(Msg.DATE_AND_TIME_TIMEZONE_NEEDED, "first",
leftTemporal, "second", rightTemporal)));
return false;
- } else if ((rightTemporal instanceof ZonedDateTime || rightTemporal
instanceof OffsetDateTime)
+ } else if ((rightTemporal instanceof ZonedDateTime || rightTemporal
instanceof FormattedZonedDateTime || rightTemporal instanceof OffsetDateTime)
&& (leftTemporal instanceof LocalDateTime)) {
ctx.notifyEvt(() -> new
InvalidParametersEvent(FEELEvent.Severity.ERROR, Msg
.createMessage(Msg.DATE_AND_TIME_TIMEZONE_NEEDED,
"second", rightTemporal, "first", leftTemporal)));
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/custom/FormattedZonedDateTime.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/custom/FormattedZonedDateTime.java
new file mode 100644
index 0000000000..e4d1836551
--- /dev/null
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/custom/FormattedZonedDateTime.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.kie.dmn.feel.runtime.custom;
+
+import org.kie.dmn.feel.runtime.functions.DateAndTimeFunction;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.chrono.ChronoLocalDateTime;
+import java.time.chrono.ChronoZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalUnit;
+import java.util.Objects;
+
+/**
+ * This class is meant as sort-of <b>decorator</b> over
<code>ZonedDateTime</code>, that is a final class.
+ * <p>
+ * <ul>
+ * <li><b>String representation ({@link #toString()}):</b> Provides a custom
string format that:
+ * <ul>
+ * <li>Always preserves seconds in the output, even when they are zero
(e.g., "10:10:00" instead of "10:10")</li>
+ * <li>Uses ISO_OFFSET_DATE_TIME format for ZoneOffset zones (e.g.,
"2021-01-01T10:10:10+11:00")</li>
+ * <li>Uses REGION_DATETIME_FORMATTER for ZoneRegion zones, which
properly handles extended years</li>
+ * </ul>
+ * </li>
+ * <li><b>Equality semantics:</b> Can be compared with both
formattedZonedDateTime and ZonedDateTime instances,
+ * delegating to the underlying ZonedDateTime for actual comparison
logic</li>
+ * </ul>
+ * <p>
+ * All temporal operations delegate to the wrapped ZonedDateTime instance,
maintaining full compatibility
+ * with the Java Time API while providing the custom string representation
required by FEEL specifications.
+ */
+public final class FormattedZonedDateTime
+ implements Temporal, ChronoZonedDateTime<LocalDate>, Serializable {
+
+ private final ZonedDateTime zonedDateTime;
+ private final String stringRepresentation;
+
+ private FormattedZonedDateTime(ZonedDateTime zonedDateTime) {
+ this.zonedDateTime = zonedDateTime;
+ ZoneId zone = zonedDateTime.getZone();
+ if (zone instanceof ZoneOffset) {
+ // For ZoneOffset, use ISO format (e.g., 2021-01-01T10:10:10+11:00)
+ this.stringRepresentation =
zonedDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+ } else {
+ // For ZoneRegion, use REGION_DATETIME_FORMATTER which properly
handles extended years
+ this.stringRepresentation =
zonedDateTime.format(DateAndTimeFunction.REGION_DATETIME_FORMATTER);
+ }
+ }
+
+ public static FormattedZonedDateTime of(LocalDate date, LocalTime time,
ZoneId zone) {
+ return new FormattedZonedDateTime(ZonedDateTime.of(date, time, zone));
+ }
+
+ public static FormattedZonedDateTime from(TemporalAccessor temporal) {
+ return new FormattedZonedDateTime(ZonedDateTime.from(temporal));
+ }
+
+ public static FormattedZonedDateTime of(int coercedYear, int coercedMonth,
int coercedDay, int coercedHour, int coercedMinute, int coercedSecond, int
nanoOfSecond, ZoneId zoneId) {
+ return new FormattedZonedDateTime(ZonedDateTime.of(coercedYear,
coercedMonth, coercedDay, coercedHour, coercedMinute, coercedSecond,
nanoOfSecond, zoneId));
+ }
+
+ @Override
+ public ChronoLocalDateTime<LocalDate> toLocalDateTime() {
+ return zonedDateTime.toLocalDateTime();
+ }
+
+ @Override
+ public ZoneOffset getOffset() {
+ return zonedDateTime.getOffset();
+ }
+
+ @Override
+ public ZoneId getZone() {
+ return zonedDateTime.getZone();
+ }
+
+ @Override
+ public ChronoZonedDateTime<LocalDate> withEarlierOffsetAtOverlap() {
+ return new
FormattedZonedDateTime(zonedDateTime.withEarlierOffsetAtOverlap());
+ }
+
+ @Override
+ public ChronoZonedDateTime<LocalDate> withLaterOffsetAtOverlap() {
+ return new
FormattedZonedDateTime(zonedDateTime.withLaterOffsetAtOverlap());
+ }
+
+ @Override
+ public ChronoZonedDateTime<LocalDate> withZoneSameLocal(ZoneId zone) {
+ return new
FormattedZonedDateTime(zonedDateTime.withZoneSameLocal(zone));
+ }
+
+ @Override
+ public ChronoZonedDateTime<LocalDate> withZoneSameInstant(ZoneId zone) {
+ return new
FormattedZonedDateTime(zonedDateTime.withZoneSameInstant(zone));
+ }
+
+ @Override
+ public ChronoZonedDateTime<LocalDate> with(TemporalField field, long
newValue) {
+ return new FormattedZonedDateTime(zonedDateTime.with(field, newValue));
+ }
+
+ @Override
+ public ChronoZonedDateTime<LocalDate> plus(long amountToAdd, TemporalUnit
unit) {
+ return new FormattedZonedDateTime(zonedDateTime.plus(amountToAdd,
unit));
+ }
+
+ @Override
+ public ChronoZonedDateTime<LocalDate> plus(TemporalAmount amount) {
+ return new FormattedZonedDateTime(zonedDateTime.plus(amount));
+ }
+
+ @Override
+ public ChronoZonedDateTime<LocalDate> minus(TemporalAmount amount) {
+ return new FormattedZonedDateTime(zonedDateTime.minus(amount));
+ }
+
+ @Override
+ public long until(Temporal endExclusive, TemporalUnit unit) {
+ return zonedDateTime.until(endExclusive, unit);
+ }
+
+ @Override
+ public boolean isSupported(TemporalField field) {
+ return zonedDateTime.isSupported(field);
+ }
+
+ @Override
+ public long getLong(TemporalField field) {
+ return zonedDateTime.getLong(field);
+ }
+
+ @Override
+ public boolean isSupported(TemporalUnit unit) {
+ return zonedDateTime.isSupported(unit);
+ }
+
+ @Override
+ public ChronoZonedDateTime<LocalDate> minus(long amountToSubtract,
TemporalUnit unit) {
+ return new
FormattedZonedDateTime(zonedDateTime.minus(amountToSubtract, unit));
+ }
+
+ public static FormattedZonedDateTime parse(CharSequence text) {
+ return new FormattedZonedDateTime(ZonedDateTime.parse(text));
+ }
+
+ @Override
+ public int compareTo(ChronoZonedDateTime<?> other) {
+ if (other instanceof FormattedZonedDateTime) {
+ return zonedDateTime.compareTo(((FormattedZonedDateTime)
other).zonedDateTime);
+ }
+ return zonedDateTime.compareTo(other);
+ }
+
+ public FormattedZonedDateTime(ZonedDateTime zonedDateTime, String
stringRepresentation) {
+ this.zonedDateTime = zonedDateTime;
+ this.stringRepresentation = stringRepresentation;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o instanceof FormattedZonedDateTime that) {
+ return Objects.equals(zonedDateTime, that.zonedDateTime);
+ }
+ if (o instanceof ZonedDateTime other) {
+ return Objects.equals(zonedDateTime, other);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(zonedDateTime);
+ }
+
+ @Override
+ public String toString() {
+ return stringRepresentation;
+ }
+
+ public ZonedDateTime getZonedDateTime() {
+ return zonedDateTime;
+ }
+
+}
\ No newline at end of file
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/DateAndTimeFunction.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/DateAndTimeFunction.java
index f648035e89..66f217e781 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/DateAndTimeFunction.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/DateAndTimeFunction.java
@@ -25,7 +25,6 @@ import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.TemporalAccessor;
@@ -36,6 +35,7 @@ import java.util.TimeZone;
import org.kie.dmn.api.feel.runtime.events.FEELEvent.Severity;
import org.kie.dmn.feel.runtime.FEELDateTimeFunction;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
import static org.kie.dmn.feel.util.NumberEvalHelper.coerceIntegerNumber;
@@ -107,12 +107,12 @@ public class DateAndTimeFunction
TemporalAccessor validatedTime = getValidTime(time);
if (validatedDate instanceof LocalDate && validatedTime instanceof
LocalTime) {
if (zoneId != null) {
- return FEELFnResult.ofResult(ZonedDateTime.of((LocalDate)
validatedDate, (LocalTime) validatedTime, zoneId));
+ return
FEELFnResult.ofResult(FormattedZonedDateTime.of((LocalDate) validatedDate,
(LocalTime) validatedTime, zoneId));
} else {
return FEELFnResult.ofResult(LocalDateTime.of((LocalDate)
validatedDate, (LocalTime) validatedTime));
}
} else if (validatedDate instanceof LocalDate &&
time.query(TemporalQueries.localTime()) != null &&
time.query(TemporalQueries.zone()) != null) {
- return FEELFnResult.ofResult(ZonedDateTime.of((LocalDate)
validatedDate, LocalTime.from(validatedTime), zoneId != null ? zoneId :
ZoneId.from(validatedTime)));
+ return
FEELFnResult.ofResult(FormattedZonedDateTime.of((LocalDate) validatedDate,
LocalTime.from(validatedTime), zoneId != null ? zoneId :
ZoneId.from(validatedTime)));
}
return FEELFnResult.ofError(new
InvalidParametersEvent(Severity.ERROR, "cannot invoke function for the input
parameters"));
} catch (IllegalArgumentException e) {
@@ -136,10 +136,22 @@ public class DateAndTimeFunction
try {
if( val.contains( "T" ) ) {
- return FEELFnResult.ofResult(FEEL_DATE_TIME.parseBest(val,
ZonedDateTime::from, OffsetDateTime::from, LocalDateTime::from));
+ return FEELFnResult.ofResult(FEEL_DATE_TIME.parseBest(val,
FormattedZonedDateTime::from, OffsetDateTime::from, LocalDateTime::from));
} else {
- LocalDate value = DateTimeFormatter.ISO_DATE.parse(val,
LocalDate::from);
- return FEELFnResult.ofResult( LocalDateTime.of(value,
LocalTime.of(0, 0)));
+ TemporalAccessor parsed =
DateTimeFormatter.ISO_DATE.parse(val);
+ LocalDate value = LocalDate.from(parsed);
+ ZoneId zoneId = parsed.query(TemporalQueries.zone());
+ if (zoneId == null) {
+ ZoneOffset offset = parsed.query(TemporalQueries.offset());
+ if (offset != null) {
+ zoneId = ZoneId.ofOffset("UTC", offset);
+ }
+ }
+ if (zoneId != null) {
+ return
FEELFnResult.ofResult(FormattedZonedDateTime.of(value, LocalTime.of(0, 0),
zoneId));
+ } else {
+ return FEELFnResult.ofResult(LocalDateTime.of(value,
LocalTime.of(0, 0)));
+ }
}
} catch ( Exception e ) {
return FEELFnResult.ofError(new
InvalidParametersEvent(Severity.ERROR, "from", "date-parsing exception", e));
@@ -199,7 +211,7 @@ public class DateAndTimeFunction
int coercedHour = coerceIntegerNumber(hour).orElseThrow(() -> new
NoSuchElementException("hour"));
int coercedMinute = coerceIntegerNumber(minute).orElseThrow(() ->
new NoSuchElementException("minute"));
int coercedSecond = coerceIntegerNumber(second).orElseThrow(() ->
new NoSuchElementException("second"));
- return FEELFnResult.ofResult(ZonedDateTime.of(coercedYear,
coercedMonth, coercedDay,
+ return
FEELFnResult.ofResult(FormattedZonedDateTime.of(coercedYear, coercedMonth,
coercedDay,
coercedHour, coercedMinute, coercedSecond, 0,
TimeZone.getTimeZone(timezone).toZoneId()));
} catch (NoSuchElementException e) { // thrown by
Optional.orElseThrow()
return FEELFnResult.ofError(new
InvalidParametersEvent(Severity.ERROR, e.getMessage(), "could not be coerced to
Integer: either null or not a valid Number."));
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/TimeFunction.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/TimeFunction.java
index 776d704f3a..db75e743b9 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/TimeFunction.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/TimeFunction.java
@@ -168,7 +168,7 @@ public class TimeFunction
ZoneId zone = date.query(TemporalQueries.zoneId());
if (!(zone instanceof ZoneOffset)) {
// TZ is a ZoneRegion, so do NOT normalize (although the
result will be unreversible, but will keep what was supplied originally).
- // Unfortunately java.time.Parsed is a package-private
class, hence will need to re-parse in order to have it instantiated.
+ // Unfortunately java.time.Parsed is a package-private
class, hence will need to re-parse in order to have it instantiated.
return
invoke(getFormattedStringFromTemporalAccessorAndZone(date, zone));
} else {
return FEELFnResult.ofResult(OffsetTime.from(date));
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/BuiltInTypeUtils.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/BuiltInTypeUtils.java
index 7be6f7f95a..dba81654ed 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/BuiltInTypeUtils.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/BuiltInTypeUtils.java
@@ -40,6 +40,7 @@ import org.kie.dmn.feel.lang.types.BuiltInType;
import org.kie.dmn.feel.runtime.FEELFunction;
import org.kie.dmn.feel.runtime.Range;
import org.kie.dmn.feel.runtime.UnaryTest;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
import org.kie.dmn.feel.runtime.custom.ZoneTime;
public class BuiltInTypeUtils {
@@ -73,7 +74,7 @@ public class BuiltInTypeUtils {
return BuiltInType.DATE;
} else if (o instanceof LocalTime || o instanceof OffsetTime || o
instanceof ZoneTime) {
return BuiltInType.TIME;
- } else if (o instanceof ZonedDateTime || o instanceof OffsetDateTime
|| o instanceof LocalDateTime) {
+ } else if (o instanceof ZonedDateTime || o instanceof
FormattedZonedDateTime || o instanceof OffsetDateTime || o instanceof
LocalDateTime) {
return BuiltInType.DATE_TIME;
} else if (o instanceof Duration || o instanceof ChronoPeriod) {
return BuiltInType.DURATION;
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CodegenUtils.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CodegenUtils.java
index 5c291f7ddb..94f8361180 100644
--- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CodegenUtils.java
+++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CodegenUtils.java
@@ -18,6 +18,8 @@
*/
package org.kie.dmn.feel.util;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
+
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -151,22 +153,10 @@ public class CodegenUtils {
} else if (object instanceof String string) {
return
getVariableDeclaratorWithInitializerExpression(variableName, STRING_CT,
new
StringLiteralExpr(escapeJava(string)));
+ } else if (object instanceof FormattedZonedDateTime
formattedZonedDateTime) {
+ return getVariableDeclaratorWithZonedDateTime(variableName,
formattedZonedDateTime.getZonedDateTime());
} else if (object instanceof ZonedDateTime zonedDateTime) {
- Expression zoneIdExpression = new MethodCallExpr(ZONE_ID_N, OF_S,
-
NodeList.nodeList(new StringLiteralExpr(zonedDateTime.getZone().getId())));
- NodeList arguments = NodeList.nodeList(new
IntegerLiteralExpr(zonedDateTime.getYear()),
- new
IntegerLiteralExpr(zonedDateTime.getMonthValue()),
- new
IntegerLiteralExpr(zonedDateTime.getDayOfMonth()),
- new
IntegerLiteralExpr(zonedDateTime.getHour()),
- new
IntegerLiteralExpr(zonedDateTime.getMinute()),
- new
IntegerLiteralExpr(zonedDateTime.getSecond()),
- new
IntegerLiteralExpr(zonedDateTime.getNano()),
- zoneIdExpression);
- return getVariableDeclaratorWithMethodCall(variableName,
- ZONED_DATE_TIME_CT,
- OF_S,
- ZONED_DATE_TIME_N,
- arguments);
+ return getVariableDeclaratorWithZonedDateTime(variableName,
zonedDateTime);
} else if (object instanceof TemporalAccessor temporalAccessor) {
// FallBack in case of Parse or other unmanaged classes - keep at
the end
String parsedString =
DateTimeEvalHelper.toParsableString(temporalAccessor);
@@ -181,6 +171,22 @@ public class CodegenUtils {
}
}
+ static VariableDeclarationExpr
getVariableDeclaratorWithZonedDateTime(String variableName, ZonedDateTime
zonedDateTime) {
+ Expression zoneIdExpression = new MethodCallExpr(ZONE_ID_N, OF_S,
+ NodeList.nodeList(new
StringLiteralExpr(zonedDateTime.getZone().getId())));
+ NodeList arguments = NodeList.nodeList(new
IntegerLiteralExpr(zonedDateTime.getYear()),
+ new IntegerLiteralExpr(zonedDateTime.getMonthValue()),
+ new IntegerLiteralExpr(zonedDateTime.getDayOfMonth()),
+ new IntegerLiteralExpr(zonedDateTime.getHour()),
+ new IntegerLiteralExpr(zonedDateTime.getMinute()),
+ new IntegerLiteralExpr(zonedDateTime.getSecond()),
+ new IntegerLiteralExpr(zonedDateTime.getNano()),
+ zoneIdExpression);
+ return getVariableDeclaratorWithInitializerExpression(variableName,
+ ZONED_DATE_TIME_CT,
+ new MethodCallExpr(ZONED_DATE_TIME_N, OF_S, arguments));
+ }
+
public static StringLiteralExpr getStringLiteralExpr(String text) {
if (text.startsWith("\"") && text.endsWith("\"")) {
String actualStringContent = text.substring(1, text.length() - 1);
// remove start/end " from the FEEL text expression.
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/DateTimeEvalHelper.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/DateTimeEvalHelper.java
index 9e3f895964..ca5cd7f6fe 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/DateTimeEvalHelper.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/DateTimeEvalHelper.java
@@ -26,6 +26,7 @@ import java.util.Optional;
import java.util.function.BiPredicate;
import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,12 +51,14 @@ public class DateTimeEvalHelper {
*/
public static long valuedt(TemporalAccessor datetime, ZoneId
otherTimezoneOffset) {
ZoneId alternativeTZ =
Optional.ofNullable(otherTimezoneOffset).orElse(ZoneOffset.UTC);
- if (datetime instanceof LocalDateTime) {
- return ((LocalDateTime)
datetime).atZone(alternativeTZ).toEpochSecond();
- } else if (datetime instanceof ZonedDateTime) {
- return ((ZonedDateTime) datetime).toEpochSecond();
- } else if (datetime instanceof OffsetDateTime) {
- return ((OffsetDateTime) datetime).toEpochSecond();
+ if (datetime instanceof LocalDateTime localDateTime) {
+ return localDateTime.atZone(alternativeTZ).toEpochSecond();
+ } else if (datetime instanceof ZonedDateTime zonedDateTime) {
+ return zonedDateTime.toEpochSecond();
+ } else if (datetime instanceof OffsetDateTime offsetDateTime) {
+ return offsetDateTime.toEpochSecond();
+ } else if (datetime instanceof FormattedZonedDateTime
formattedZonedDateTime) {
+ return formattedZonedDateTime.getZonedDateTime().toEpochSecond();
} else {
throw new RuntimeException("valuedt() for " + datetime + " but is
not a FEEL date and time " + datetime.getClass());
}
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java
index 331b183bc3..9b9bb02e64 100644
--- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java
+++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java
@@ -42,6 +42,7 @@ import java.util.stream.Stream;
import org.kie.dmn.api.core.FEELPropertyAccessible;
import org.kie.dmn.feel.lang.FEELProperty;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
import org.kie.dmn.feel.runtime.Range;
import org.kie.dmn.feel.runtime.Range.RangeBoundary;
import org.kie.dmn.feel.runtime.impl.UndefinedValueComparable;
@@ -181,15 +182,17 @@ public class EvalHelper {
break;
case "value":
result = null;
- if (current instanceof LocalTime) {
- result = BigDecimal.valueOf(((LocalTime)
current).toSecondOfDay());
- } else if (current instanceof OffsetTime) {
- result = BigDecimal.valueOf(((OffsetTime)
current).toLocalTime().toSecondOfDay());
+ if (current instanceof LocalTime localTime) {
+ result = BigDecimal.valueOf(localTime.toSecondOfDay());
+ } else if (current instanceof OffsetTime offsetTime) {
+ result =
BigDecimal.valueOf(offsetTime.toLocalTime().toSecondOfDay());
} else if (current instanceof LocalDate date) {
ZonedDateTime dtAtMidnightUTC =
date.atStartOfDay(ZoneOffset.UTC);
result =
BigDecimal.valueOf(dtAtMidnightUTC.toEpochSecond());
- } else if (current instanceof ZonedDateTime) {
- result = BigDecimal.valueOf(((ZonedDateTime)
current).toEpochSecond());
+ } else if (current instanceof ZonedDateTime zonedDateTime)
{
+ result =
BigDecimal.valueOf(zonedDateTime.toEpochSecond());
+ } else if (current instanceof FormattedZonedDateTime
formattedZonedDateTime) {
+ result =
BigDecimal.valueOf(formattedZonedDateTime.getZonedDateTime().toEpochSecond());
}
break;
default:
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/TypeUtil.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/TypeUtil.java
index 8c9eb1d649..46cf9323bc 100644
--- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/TypeUtil.java
+++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/TypeUtil.java
@@ -37,6 +37,7 @@ import java.util.Set;
import org.kie.dmn.feel.lang.types.impl.ComparablePeriod;
import org.kie.dmn.feel.runtime.Range;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
import org.kie.dmn.feel.runtime.custom.ZoneTime;
import org.kie.dmn.feel.runtime.functions.DateAndTimeFunction;
import org.kie.dmn.feel.runtime.functions.DateFunction;
@@ -86,6 +87,8 @@ public final class TypeUtil {
} else if (val instanceof LocalDateTime || val instanceof
OffsetDateTime) {
return
formatDateTimeString(DateAndTimeFunction.FEEL_DATE_TIME.format((TemporalAccessor)
val),
wrapForCodeUsage);
+ } else if (val instanceof FormattedZonedDateTime) {
+ return formatDateTimeString(val.toString(), wrapForCodeUsage);
} else if (val instanceof ZonedDateTime) {
TemporalAccessor ta = (TemporalAccessor) val;
ZoneId zone = ta.query(TemporalQueries.zone());
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/custom/FormattedZonedDateTimeTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/custom/FormattedZonedDateTimeTest.java
new file mode 100644
index 0000000000..4ce7de0530
--- /dev/null
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/custom/FormattedZonedDateTimeTest.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.kie.dmn.feel.runtime.custom;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalQueries;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class FormattedZonedDateTimeTest {
+
+ private static final String REFERENCED_DATE = "2024-08-10";
+ private static final String REFERENCED_TIME = "10:15:00";
+ private static final String REFERENCED_ZONE = "Europe/Paris";
+ private static LocalDate localDate;
+ private static LocalTime localTime;
+ private static ZoneId zoneId;
+ private static ZonedDateTime zonedDateTime;
+ private static FormattedZonedDateTime formattedZonedDateTime;
+
+ @BeforeAll
+ static void setUpClass() {
+ localDate = DateTimeFormatter.ISO_DATE.parse(REFERENCED_DATE,
LocalDate::from);
+ localTime = DateTimeFormatter.ISO_TIME.parse(REFERENCED_TIME,
LocalTime::from);
+ zoneId = ZoneId.of(REFERENCED_ZONE);
+ zonedDateTime = ZonedDateTime.of(localDate, localTime, zoneId);
+ formattedZonedDateTime = FormattedZonedDateTime.of(localDate,
localTime, zoneId);
+ }
+
+ @Test
+ void ofLocalDateLocalTimeZoneId() {
+ FormattedZonedDateTime retrieved =
FormattedZonedDateTime.of(localDate, localTime, zoneId);
+ assertThat(retrieved).isNotNull();
+ assertThat(retrieved.getZonedDateTime()).isEqualTo(zonedDateTime);
+ assertThat(retrieved.getZone()).isEqualTo(zoneId);
+ }
+
+ @Test
+ void ofIntegers() {
+ FormattedZonedDateTime retrieved = FormattedZonedDateTime.of(2024, 8,
10, 10, 15, 0, 0, zoneId);
+ assertThat(retrieved).isNotNull();
+ assertThat(retrieved.getZonedDateTime()).isEqualTo(zonedDateTime);
+ assertThat(retrieved.getZone()).isEqualTo(zoneId);
+ }
+
+ @Test
+ void from() {
+ FormattedZonedDateTime retrieved =
FormattedZonedDateTime.from(zonedDateTime);
+ assertThat(retrieved).isNotNull();
+ assertThat(retrieved.getZonedDateTime()).isEqualTo(zonedDateTime);
+ }
+
+ @Test
+ void getZone() {
+ assertThat(formattedZonedDateTime.getZone()).isEqualTo(zoneId);
+ }
+
+ @Test
+ void getOffset() {
+
assertThat(formattedZonedDateTime.getOffset()).isEqualTo(zonedDateTime.getOffset());
+ }
+
+ @Test
+ void toLocalDateTime() {
+
assertThat(formattedZonedDateTime.toLocalDateTime()).isEqualTo(zonedDateTime.toLocalDateTime());
+ }
+
+ @Test
+ void compareTo() {
+ FormattedZonedDateTime toCompare = FormattedZonedDateTime.of(2024, 8,
10, 9, 30, 0, 0, zoneId);
+
assertThat(formattedZonedDateTime.compareTo(toCompare)).isEqualTo(zonedDateTime.compareTo(toCompare.getZonedDateTime()));
+ }
+
+ @Test
+ void withTemporalField() {
+ FormattedZonedDateTime expected =
FormattedZonedDateTime.from(zonedDateTime.with(ChronoField.HOUR_OF_DAY, 3));
+ assertThat(formattedZonedDateTime.with(ChronoField.HOUR_OF_DAY,
3)).isEqualTo(expected);
+ }
+
+ @Test
+ void plusLong() {
+ FormattedZonedDateTime expected =
FormattedZonedDateTime.from(zonedDateTime.plus(3, ChronoUnit.HOURS));
+ assertThat(formattedZonedDateTime.plus(3,
ChronoUnit.HOURS)).isEqualTo(expected);
+ }
+
+ @Test
+ void plusTemporalAmount() {
+ TemporalAmount amount = Duration.of(23, ChronoUnit.MINUTES);
+ FormattedZonedDateTime expected =
FormattedZonedDateTime.from(zonedDateTime.plus(amount));
+ assertThat(formattedZonedDateTime.plus(amount)).isEqualTo(expected);
+ }
+
+ @Test
+ void minusLong() {
+ FormattedZonedDateTime expected =
FormattedZonedDateTime.from(zonedDateTime.minus(3, ChronoUnit.HOURS));
+ assertThat(formattedZonedDateTime.minus(3,
ChronoUnit.HOURS)).isEqualTo(expected);
+ }
+
+ @Test
+ void minusTemporalAmount() {
+ TemporalAmount amount = Duration.of(23, ChronoUnit.MINUTES);
+ FormattedZonedDateTime expected =
FormattedZonedDateTime.from(zonedDateTime.minus(amount));
+ assertThat(formattedZonedDateTime.minus(amount)).isEqualTo(expected);
+ }
+
+ @Test
+ void until() {
+ FormattedZonedDateTime endExclusive = FormattedZonedDateTime.of(2024,
8, 10, 9, 30, 0, 0, zoneId);
+ long expected = zonedDateTime.until(endExclusive.getZonedDateTime(),
ChronoUnit.SECONDS);
+ long retrieved = formattedZonedDateTime.until(endExclusive,
ChronoUnit.SECONDS);
+ assertThat(retrieved).isEqualTo(expected);
+ }
+
+ @Test
+ void isSupportedTemporalUnit() {
+ assertThat(ChronoUnit.values()).allMatch(unit ->
formattedZonedDateTime.isSupported(unit) == zonedDateTime.isSupported(unit));
+ }
+
+ @Test
+ void isSupportedTemporalField() {
+ assertThat(ChronoField.values()).allMatch(field ->
formattedZonedDateTime.isSupported(field) == zonedDateTime.isSupported(field));
+ }
+
+ @Test
+ void getLong() {
+ assertThat(ChronoField.values()).filteredOn(zonedDateTime::isSupported)
+ .allMatch(field -> zonedDateTime.getLong(field) ==
formattedZonedDateTime.getLong(field));
+ }
+
+ @Test
+ void query() {
+
assertThat(formattedZonedDateTime.query(TemporalQueries.zoneId())).isEqualTo(zoneId);
+
assertThat(formattedZonedDateTime.query(TemporalQueries.zone())).isEqualTo(zoneId);
+
assertThat(formattedZonedDateTime.query(TemporalQueries.localDate())).isEqualTo(zonedDateTime.query(TemporalQueries.localDate()));
+
assertThat(formattedZonedDateTime.query(TemporalQueries.localTime())).isEqualTo(zonedDateTime.query(TemporalQueries.localTime()));
+
assertThat(formattedZonedDateTime.query(TemporalQueries.offset())).isEqualTo(zonedDateTime.query(TemporalQueries.offset()));
+ }
+
+ @Test
+ void testEquals() {
+ FormattedZonedDateTime toCompare = FormattedZonedDateTime.of(2024, 8,
10, 9, 30, 0, 0, zoneId);
+ assertThat(toCompare).isNotEqualTo(formattedZonedDateTime);
+ toCompare = FormattedZonedDateTime.of(localDate, localTime, zoneId);
+ assertThat(toCompare).isEqualTo(formattedZonedDateTime);
+ }
+
+ @Test
+ void testToStringWithZeroHoursMinutesAndSeconds() {
+ // Test that toString() preserves hours, minutes and seconds when they
are 0
+ ZonedDateTime zdt = ZonedDateTime.of(2000, 12, 1, 0, 0, 0, 0,
ZoneOffset.UTC);
+ FormattedZonedDateTime formatted = FormattedZonedDateTime.from(zdt);
+ String expected = "2000-12-01T00:00:00Z";
+ assertThat(formatted.toString()).isEqualTo(expected);
+ }
+
+ @Test
+ void testToStringWithZeroSeconds() {
+ // Test that toString() preserves seconds even when they are 0 with
ZoneRegion
+ ZonedDateTime zdt = ZonedDateTime.of(2000, 12, 1, 14, 30, 0, 0,
ZoneOffset.UTC);
+ FormattedZonedDateTime formatted = FormattedZonedDateTime.from(zdt);
+ String expected = "2000-12-01T14:30:00Z";
+ assertThat(formatted.toString()).isEqualTo(expected);
+ }
+
+ @Test
+ void testToStringWithZeroMinutesAndZeroSeconds() {
+ // Test that toString() preserves both minutes and seconds when they
are 0
+ ZonedDateTime zdt = ZonedDateTime.of(2000, 12, 1, 10, 0, 0, 0,
ZoneOffset.UTC);
+ FormattedZonedDateTime formatted = FormattedZonedDateTime.from(zdt);
+ String expected = "2000-12-01T10:00:00Z";
+ assertThat(formatted.toString()).isEqualTo(expected);
+ }
+
+ @Test
+ void testToStringWithNonZeroSeconds() {
+ // Test with non-zero seconds to ensure normal behavior
+ ZonedDateTime zdt = ZonedDateTime.of(2000, 12, 1, 18, 45, 30, 0,
ZoneOffset.UTC);
+ FormattedZonedDateTime formatted = FormattedZonedDateTime.from(zdt);
+ String expected = "2000-12-01T18:45:30Z";
+ assertThat(formatted.toString()).isEqualTo(expected);
+ }
+
+ @Test
+ void testToStringWithDifferentTimezones() {
+ // Test various timezone formats to ensure seconds are always preserved
+
+ // UTC - uses ZoneRegion format with @
+ ZonedDateTime utc = ZonedDateTime.of(2000, 12, 1, 9, 15, 0, 0,
ZoneId.of("UTC"));
+ FormattedZonedDateTime formattedUtc = FormattedZonedDateTime.from(utc);
+
assertThat(formattedUtc.toString()).isEqualTo("2000-12-01T09:15:00@UTC");
+ // ZoneOffset.UTC - uses ISO format with Z
+ ZonedDateTime zdtWithZoneOffsetUTC = ZonedDateTime.of(2000, 12, 1, 0,
0, 0, 0, ZoneOffset.UTC);
+ FormattedZonedDateTime formatted =
FormattedZonedDateTime.from(zdtWithZoneOffsetUTC);
+ assertThat(formatted.toString()).isEqualTo("2000-12-01T00:00:00Z");
+
+ // Positive offset - uses ISO format
+ ZonedDateTime plusOffset = ZonedDateTime.of(2000, 12, 1, 9, 15, 0, 0,
ZoneId.of("+10:00"));
+ FormattedZonedDateTime formattedPlus =
FormattedZonedDateTime.from(plusOffset);
+
assertThat(formattedPlus.toString()).isEqualTo("2000-12-01T09:15:00+10:00");
+
+ // Negative offset - uses ISO format
+ ZonedDateTime minusOffset = ZonedDateTime.of(2000, 12, 1, 9, 15, 0, 0,
ZoneId.of("-05:00"));
+ FormattedZonedDateTime formattedMinus =
FormattedZonedDateTime.from(minusOffset);
+
assertThat(formattedMinus.toString()).isEqualTo("2000-12-01T09:15:00-05:00");
+
+ // Named timezone - uses @ format
+ ZonedDateTime named = ZonedDateTime.of(2000, 12, 1, 9, 15, 0, 0,
ZoneId.of("Asia/Tokyo"));
+ FormattedZonedDateTime formattedNamed =
FormattedZonedDateTime.from(named);
+
assertThat(formattedNamed.toString()).isEqualTo("2000-12-01T09:15:00@Asia/Tokyo");
+ }
+
+}
\ No newline at end of file
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/DateAndTimeFunctionTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/DateAndTimeFunctionTest.java
index 081394b15b..084038717f 100644
---
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/DateAndTimeFunctionTest.java
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/DateAndTimeFunctionTest.java
@@ -33,8 +33,8 @@ import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
-import java.util.NoSuchElementException;
import org.junit.jupiter.api.Test;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
class DateAndTimeFunctionTest {
@@ -47,14 +47,14 @@ class DateAndTimeFunctionTest {
assertThat(retrievedResult).isNotNull();
assertThat(retrievedResult.isRight()).isTrue();
TemporalAccessor retrieved = retrievedResult.getOrElse(null);
- assertThat(retrieved).isNotNull().isInstanceOf(ZonedDateTime.class);
- ZonedDateTime retrievedZonedDateTime = (ZonedDateTime) retrieved;
- assertThat(retrievedZonedDateTime.getYear()).isEqualTo(2017);
- assertThat(retrievedZonedDateTime.getMonthValue()).isEqualTo(8);
- assertThat(retrievedZonedDateTime.getDayOfMonth()).isEqualTo(10);
- assertThat(retrievedZonedDateTime.getHour()).isEqualTo(10);
- assertThat(retrievedZonedDateTime.getMinute()).isEqualTo(20);
- assertThat(retrievedZonedDateTime.getSecond()).isZero();
+
assertThat(retrieved).isNotNull().isInstanceOf(FormattedZonedDateTime.class);
+ FormattedZonedDateTime retrievedZonedDateTime =
(FormattedZonedDateTime) retrieved;
+
assertThat((retrievedZonedDateTime).getZonedDateTime().getYear()).isEqualTo(2017);
+
assertThat((retrievedZonedDateTime).getZonedDateTime().getMonthValue()).isEqualTo(8);
+
assertThat((retrievedZonedDateTime).getZonedDateTime().getDayOfMonth()).isEqualTo(10);
+
assertThat((retrievedZonedDateTime).getZonedDateTime().getHour()).isEqualTo(10);
+
assertThat((retrievedZonedDateTime).getZonedDateTime().getMinute()).isEqualTo(20);
+
assertThat((retrievedZonedDateTime).getZonedDateTime().getSecond()).isZero();
assertThat(retrievedZonedDateTime.getZone()).isEqualTo(ZoneId.of("Europe/Paris"));
}
@@ -164,8 +164,8 @@ class DateAndTimeFunctionTest {
FEELFnResult<TemporalAccessor> result =
dateTimeFunction.invoke(LocalDate.of(2024, 12, 24), LocalTime.of(23, 59, 0),
"Z");
assertThat(result.isRight()).isTrue();
assertThat(result.getOrElse(null)).isNotNull();
- ZonedDateTime actualDateTime = (ZonedDateTime) result.getOrElse(null);
- ZonedDateTime expectedDateTime = ZonedDateTime.of(2024, 12, 24, 23,
59, 0, 0, ZoneOffset.UTC);
+ FormattedZonedDateTime actualDateTime = (FormattedZonedDateTime)
result.getOrElse(null);
+ FormattedZonedDateTime expectedDateTime =
FormattedZonedDateTime.of(2024, 12, 24, 23, 59, 0, 0, ZoneOffset.UTC);
assertThat(expectedDateTime).isEqualTo(actualDateTime);
FEELFnResult<TemporalAccessor> retrievedResult =
dateTimeFunction.invoke("2024-12-24T23:59:00Z");
assertThat(retrievedResult.isRight()).isTrue();
@@ -268,4 +268,21 @@ class DateAndTimeFunctionTest {
assertResultError(DateAndTimeFunction.generateDateTimeAndTimezone(null,null,
ZoneId.of("America/Costa_Rica")), InvalidParametersEvent.class);
}
+ @Test
+ void invokeParamStringDateWithOffset() {
+ // Test case to verify date string with offset returns
formattedZonedDateTime
+ FEELFnResult<TemporalAccessor> result =
dateTimeFunction.invoke("2017-09-07+02:00");
+ assertThat(result).isNotNull();
+ assertThat(result.isRight()).isTrue();
+ TemporalAccessor retrieved = result.getOrElse(null);
+ assertThat(retrieved).isNotNull();
+
+ // Verify it returns formattedZonedDateTime with timezone preserved
+ assertThat(retrieved).isInstanceOf(FormattedZonedDateTime.class);
+ FormattedZonedDateTime formattedZonedDateTime =
(FormattedZonedDateTime) retrieved;
+
assertThat(formattedZonedDateTime.getZonedDateTime().toLocalDate()).isEqualTo(LocalDate.of(2017,
9, 7));
+
assertThat(formattedZonedDateTime.getZonedDateTime().toLocalTime()).isEqualTo(LocalTime.of(0,
0, 0));
+
assertThat(formattedZonedDateTime.getZone()).isEqualTo(ZoneOffset.of("+02:00"));
+ }
+
}
\ No newline at end of file
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/TimeFunctionTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/TimeFunctionTest.java
index c4985c8ccc..85d9831332 100644
---
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/TimeFunctionTest.java
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/TimeFunctionTest.java
@@ -27,11 +27,11 @@ import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import org.junit.jupiter.api.Test;
import org.kie.dmn.feel.runtime.custom.ZoneTime;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
import static org.assertj.core.api.Assertions.assertThat;
@@ -188,7 +188,7 @@ class TimeFunctionTest {
@Test
void invokeWithZonedDateTime() {
- ZonedDateTime from = (ZonedDateTime)
DateAndTimeFunction.INSTANCE.invoke("2017-08-10T10:20:00@Europe/Paris")
+ FormattedZonedDateTime from = (FormattedZonedDateTime)
DateAndTimeFunction.INSTANCE.invoke("2017-08-10T10:20:00@Europe/Paris")
.getOrElse(null);
assertThat(from).isNotNull();
FEELFnResult<TemporalAccessor> retrievedResult =
timeFunction.invoke(from);
@@ -212,13 +212,13 @@ class TimeFunctionTest {
@Test
void getFormattedStringFromTemporalAccessorAndZone() {
- ZonedDateTime date = (ZonedDateTime)
DateAndTimeFunction.INSTANCE.invoke("2017-08-10T10:20:10@Europe/Paris")
+ FormattedZonedDateTime date = (FormattedZonedDateTime)
DateAndTimeFunction.INSTANCE.invoke("2017-08-10T10:20:10@Europe/Paris")
.getOrElse(null);
assertThat(date).isNotNull();
ZoneId zone = date.query(TemporalQueries.zoneId());
assertThat(TimeFunction.getFormattedStringFromTemporalAccessorAndZone(date,
zone))
.isEqualTo("10:20:10@Europe/Paris");
- date = (ZonedDateTime)
DateAndTimeFunction.INSTANCE.invoke("2017-08-10T10:20:00@Europe/Paris").getOrElse(null);
+ date = (FormattedZonedDateTime)
DateAndTimeFunction.INSTANCE.invoke("2017-08-10T10:20:00@Europe/Paris").getOrElse(null);
assertThat(date).isNotNull();
zone = date.query(TemporalQueries.zoneId());
assertThat(TimeFunction.getFormattedStringFromTemporalAccessorAndZone(date,
zone))
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CodegenUtilsTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CodegenUtilsTest.java
new file mode 100644
index 0000000000..830299b2e1
--- /dev/null
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CodegenUtilsTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.kie.dmn.feel.util;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import com.github.javaparser.ast.expr.VariableDeclarationExpr;
+import org.junit.jupiter.api.Test;
+import org.kie.dmn.feel.runtime.custom.FormattedZonedDateTime;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CodegenUtilsTest {
+
+ @Test
+ void testGetVariableDeclaratorWithZonedDateTime() {
+ ZonedDateTime zonedDateTime = ZonedDateTime.of(2024, 3, 15, 10, 30,
45, 123456789, ZoneId.of("America/New_York"));
+ String variableName = "testZdt";
+
+ VariableDeclarationExpr result =
CodegenUtils.getVariableDeclaratorWithZonedDateTime(variableName,
zonedDateTime);
+
+ assertThat(result).isNotNull();
+ assertThat(result.toString()).contains(variableName);
+ assertThat(result.toString()).contains("ZonedDateTime.of");
+ assertThat(result.toString()).contains("2024");
+ assertThat(result.toString()).contains("3");
+ assertThat(result.toString()).contains("15");
+ assertThat(result.toString()).contains("10");
+ assertThat(result.toString()).contains("30");
+ assertThat(result.toString()).contains("45");
+ assertThat(result.toString()).contains("123456789");
+ assertThat(result.toString()).contains("America/New_York");
+ }
+
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]