This is an automated email from the ASF dual-hosted git repository.
gitgabrio 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 af0f91ae0c [incubator-kie-issues#69] Time with a timezone is parsed
into type Parsed (#5996)
af0f91ae0c is described below
commit af0f91ae0cbf53108d912e03b2ba11a00173701a
Author: Gabriele Cardosi <[email protected]>
AuthorDate: Fri Jun 21 10:36:55 2024 +0200
[incubator-kie-issues#69] Time with a timezone is parsed into type Parsed
(#5996)
* [incubator-kie-issues#69] Creating ZonedOffsetTime decorator and related
tests
* [incubator-kie-issues#69] Fixing test to consider different offsets based
on current date
* [incubator-kie-issues#69] Fix as per pr suggestion
* [incubator-kie-issues#69] Fix as per pr suggestion
* [incubator-kie-issues#69] Fixing formatting on ZoneTime
---------
Co-authored-by: Gabriele-Cardosi <[email protected]>
---
.../org/kie/dmn/feel/lang/types/BuiltInType.java | 3 +-
.../org/kie/dmn/feel/runtime/custom/ZoneTime.java | 240 ++++++++++++++++++++
.../dmn/feel/runtime/functions/TimeFunction.java | 16 ++
.../runtime/functions/extended/TimeFunction.java | 9 +
.../main/java/org/kie/dmn/feel/util/TypeUtil.java | 3 +
.../dmn/feel/runtime/FEELDateTimeDurationTest.java | 34 +++
.../kie/dmn/feel/runtime/custom/ZoneTimeTest.java | 243 +++++++++++++++++++++
.../functions/ComposingDifferentFunctionsTest.java | 2 +-
.../feel/runtime/functions/TimeFunctionTest.java | 12 +
9 files changed, 560 insertions(+), 2 deletions(-)
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/BuiltInType.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/BuiltInType.java
index cec0f953b7..fc28acc001 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/BuiltInType.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/BuiltInType.java
@@ -44,6 +44,7 @@ import org.kie.dmn.feel.marshaller.FEELStringMarshaller;
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.ZoneTime;
public enum BuiltInType implements SimpleType {
@@ -124,7 +125,7 @@ public enum BuiltInType implements SimpleType {
return STRING;
} else if( o instanceof LocalDate ) {
return DATE;
- } else if( o instanceof LocalTime || o instanceof OffsetTime ) {
+ } else if( o instanceof LocalTime || o instanceof OffsetTime || o
instanceof ZoneTime) {
return TIME;
} else if( o instanceof ZonedDateTime || o instanceof OffsetDateTime
|| o instanceof LocalDateTime ) {
return DATE_TIME;
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/custom/ZoneTime.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/custom/ZoneTime.java
new file mode 100644
index 0000000000..8cdbb64d33
--- /dev/null
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/custom/ZoneTime.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.io.Serializable;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.ResolverStyle;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQueries;
+import java.time.temporal.TemporalQuery;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.ValueRange;
+import java.util.Objects;
+
+import static java.time.temporal.ChronoField.HOUR_OF_DAY;
+import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
+import static java.time.temporal.ChronoField.NANO_OF_SECOND;
+import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
+
+/**
+ * This class is meant as sort-of <b>decorator</b> over
<code>OffsetTime</code>, that is a final class.
+ * It is used to provide both time and zoneid information, replacing the
<code>Parsed</code> instance that would be
+ * returned otherwise by
+ * {@link org.kie.dmn.feel.runtime.functions.TimeFunction#invoke(String)}
+ */
+public final class ZoneTime
+ implements Temporal,
+ TemporalAdjuster,
+ Comparable<ZoneTime>,
+ Serializable {
+
+ private final OffsetTime offsetTime;
+ private final ZoneId zoneId;
+ private final String stringRepresentation;
+ private final boolean hasSeconds;
+
+ public static final DateTimeFormatter ZONED_OFFSET_WITH_SECONDS;
+ public static final DateTimeFormatter ZONED_OFFSET_WITHOUT_SECONDS;
+
+ static {
+ ZONED_OFFSET_WITHOUT_SECONDS = new DateTimeFormatterBuilder()
+ .parseCaseInsensitive()
+ .appendValue(HOUR_OF_DAY, 2)
+ .appendLiteral(':')
+ .appendValue(MINUTE_OF_HOUR, 2)
+ .appendLiteral("@")
+ .appendZoneRegionId()
+ .toFormatter()
+ .withResolverStyle(ResolverStyle.STRICT);
+
+ ZONED_OFFSET_WITH_SECONDS = new DateTimeFormatterBuilder()
+ .parseCaseInsensitive()
+ .appendValue(HOUR_OF_DAY, 2)
+ .appendLiteral(':')
+ .appendValue(MINUTE_OF_HOUR, 2)
+ .appendLiteral(':')
+ .appendValue(SECOND_OF_MINUTE, 2)
+ .optionalStart()
+ .appendFraction(NANO_OF_SECOND, 0, 9, true)
+ .optionalStart()
+ .appendLiteral("@")
+ .appendZoneRegionId()
+ .optionalEnd()
+ .toFormatter()
+ .withResolverStyle(ResolverStyle.STRICT);
+ }
+
+ public static ZoneTime of(LocalTime localTime, ZoneId zoneId, boolean
hasSeconds) {
+ return new ZoneTime(localTime, zoneId, hasSeconds);
+ }
+
+ public ZoneId getZoneId() {
+ return zoneId;
+ }
+
+ public String getTimezone() {
+ return zoneId.getId();
+ }
+
+ private ZoneTime(LocalTime localTime, ZoneId zoneId, boolean hasSeconds) {
+ ZoneOffset offset = zoneId.getRules().getOffset(LocalDateTime.now());
+ this.offsetTime = OffsetTime.of(localTime, offset);
+ this.zoneId = zoneId;
+ this.hasSeconds = hasSeconds;
+ this.stringRepresentation = String.format("%s@%s", localTime, zoneId);
+ }
+
+ // package default for testing purpose
+ ZoneTime(OffsetTime offsetTime, ZoneId zoneId, boolean hasSeconds) {
+ this.offsetTime = offsetTime;
+ this.zoneId = zoneId;
+ this.hasSeconds = hasSeconds;
+ String offsetString =
offsetTime.toString().replace(offsetTime.getOffset().toString(), "");
+ this.stringRepresentation = String.format("%s@%s", offsetString,
zoneId);
+ }
+
+ @Override
+ public int compareTo(ZoneTime o) {
+ return offsetTime.compareTo(o.offsetTime);
+ }
+
+ @Override
+ public Temporal with(TemporalField field, long newValue) {
+ return getNewZoneOffset(offsetTime.with(field, newValue));
+ }
+
+ @Override
+ public Temporal with(TemporalAdjuster adjuster) {
+ return getNewZoneOffset(offsetTime.with(adjuster));
+ }
+
+ @Override
+ public Temporal plus(long amountToAdd, TemporalUnit unit) {
+ return getNewZoneOffset(offsetTime.plus(amountToAdd, unit));
+ }
+
+ @Override
+ public Temporal plus(TemporalAmount amount) {
+ return getNewZoneOffset(offsetTime.plus(amount));
+ }
+
+ @Override
+ public Temporal minus(long amountToSubtract, TemporalUnit unit) {
+ return
+ getNewZoneOffset(offsetTime.minus(amountToSubtract, unit));
+ }
+
+ @Override
+ public Temporal minus(TemporalAmount amount) {
+ return
+ getNewZoneOffset(offsetTime.minus(amount));
+ }
+
+ @Override
+ public long until(Temporal endExclusive, TemporalUnit unit) {
+ return offsetTime.until(endExclusive, unit);
+ }
+
+ @Override
+ public boolean isSupported(TemporalUnit unit) {
+ return offsetTime.isSupported(unit);
+ }
+
+ @Override
+ public boolean isSupported(TemporalField field) {
+ return offsetTime.isSupported(field);
+ }
+
+ @Override
+ public long getLong(TemporalField field) {
+ return offsetTime.getLong(field);
+ }
+
+ @Override
+ public Temporal adjustInto(Temporal temporal) {
+ return offsetTime.adjustInto(temporal);
+ }
+
+ @Override
+ public <R> R query(TemporalQuery<R> query) {
+ if (query == TemporalQueries.zoneId() || query ==
TemporalQueries.zone()) {
+ return (R) zoneId;
+ } else if
(query.toString().contains(DateTimeFormatterBuilder.class.getCanonicalName())) {
+ return (R) zoneId;
+ } else {
+ return offsetTime.query(query);
+ }
+ }
+
+ @Override
+ public ValueRange range(TemporalField field) {
+ return offsetTime.range(field);
+ }
+
+ @Override
+ public int get(TemporalField field) {
+ return offsetTime.get(field);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ZoneTime that)) {
+ return false;
+ }
+ return Objects.equals(offsetTime, that.offsetTime) &&
Objects.equals(zoneId, that.zoneId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(offsetTime, zoneId);
+ }
+
+ @Override
+ public String toString() {
+ return stringRepresentation;
+ }
+
+ public String format() {
+ return hasSeconds ? ZONED_OFFSET_WITH_SECONDS.format(this) :
ZONED_OFFSET_WITHOUT_SECONDS.format(this);
+ }
+
+ // Package access for testing purpose
+ OffsetTime getOffsetTime() {
+ return offsetTime;
+ }
+
+ // Package access for testing purpose
+ ZoneTime getNewZoneOffset(OffsetTime offset) {
+ return new ZoneTime(offset, zoneId, hasSeconds);
+ }
+
+}
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 93564f637d..7bcd6e76d5 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
@@ -32,9 +32,11 @@ import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
+import java.util.regex.Pattern;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
import org.kie.dmn.api.feel.runtime.events.FEELEvent.Severity;
+import org.kie.dmn.feel.runtime.custom.ZoneTime;
import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
public class TimeFunction
@@ -43,6 +45,10 @@ public class TimeFunction
public static final TimeFunction INSTANCE = new TimeFunction();
public static final DateTimeFormatter FEEL_TIME;
+
+ private static final String timePatternString =
"[0-9]{2}[:]{1}[0-9]{2}[:]{1}[0-9]{2}";
+ private static final Pattern timePattern =
Pattern.compile(timePatternString);
+
static {
FEEL_TIME = new DateTimeFormatterBuilder().parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_TIME)
@@ -77,6 +83,12 @@ public class TimeFunction
// if it does not contain any zone information at all, then I
know for certain is a local time.
LocalTime asLocalTime = parsed.query(LocalTime::from);
return FEELFnResult.ofResult(asLocalTime);
+ } else if (parsed.query(TemporalQueries.zone()) != null) {
+ boolean hasSeconds = timeStringWithSeconds(val);
+ LocalTime asLocalTime = parsed.query(LocalTime::from);
+ ZoneId zoneId = parsed.query(TemporalQueries.zone());
+ ZoneTime zoneTime = ZoneTime.of(asLocalTime, zoneId,
hasSeconds);
+ return FEELFnResult.ofResult(zoneTime);
}
return FEELFnResult.ofResult(parsed);
@@ -85,6 +97,10 @@ public class TimeFunction
}
}
+ public static boolean timeStringWithSeconds(String val) {
+ return timePattern.matcher(val).find();
+ }
+
private static final BigDecimal NANO_MULT = BigDecimal.valueOf( 1000000000
);
public FEELFnResult<TemporalAccessor> invoke(
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/extended/TimeFunction.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/extended/TimeFunction.java
index 6433dcdb83..744474dd61 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/extended/TimeFunction.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/extended/TimeFunction.java
@@ -34,6 +34,7 @@ import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
+import org.kie.dmn.feel.runtime.custom.ZoneTime;
import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
import org.kie.dmn.feel.runtime.functions.BaseFEELFunction;
import org.kie.dmn.feel.runtime.functions.BuiltInFunctions;
@@ -42,6 +43,8 @@ import
org.kie.dmn.feel.runtime.functions.FEELConversionFunctionNames;
import org.kie.dmn.feel.runtime.functions.FEELFnResult;
import org.kie.dmn.feel.runtime.functions.ParameterName;
+import static
org.kie.dmn.feel.runtime.functions.TimeFunction.timeStringWithSeconds;
+
public class TimeFunction extends BaseFEELFunction {
public static final TimeFunction INSTANCE = new TimeFunction();
@@ -81,6 +84,12 @@ public class TimeFunction extends BaseFEELFunction {
// if it does not contain any zone information at all, then I
know for certain is a local time.
LocalTime asLocalTime = parsed.query(LocalTime::from);
return FEELFnResult.ofResult(asLocalTime);
+ } else if (parsed.query(TemporalQueries.zone()) != null) {
+ boolean hasZeroSeconds = timeStringWithSeconds(val);
+ LocalTime asLocalTime = parsed.query(LocalTime::from);
+ ZoneId zoneId = parsed.query(TemporalQueries.zone());
+ ZoneTime zoneTime = ZoneTime.of(asLocalTime, zoneId,
hasZeroSeconds);
+ return FEELFnResult.ofResult(zoneTime);
}
return FEELFnResult.ofResult(parsed);
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 e53a492457..7af8ad5d7a 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.ZoneTime;
import org.kie.dmn.feel.runtime.functions.DateAndTimeFunction;
import org.kie.dmn.feel.runtime.functions.DateFunction;
import org.kie.dmn.feel.runtime.functions.TimeFunction;
@@ -74,6 +75,8 @@ public final class TypeUtil {
return formatDate((LocalDate) val, wrapForCodeUsage);
} else if (val instanceof LocalTime || val instanceof OffsetTime) {
return
formatTimeString(TimeFunction.FEEL_TIME.format((TemporalAccessor) val),
wrapForCodeUsage);
+ } else if (val instanceof ZoneTime zoneTime) {
+ return formatTimeString(zoneTime.format(), wrapForCodeUsage);
} else if (val instanceof LocalDateTime || val instanceof
OffsetDateTime) {
return
formatDateTimeString(DateAndTimeFunction.FEEL_DATE_TIME.format((TemporalAccessor)
val), wrapForCodeUsage);
} else if (val instanceof ZonedDateTime) {
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELDateTimeDurationTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELDateTimeDurationTest.java
index 157e96149c..c22d69e572 100644
---
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELDateTimeDurationTest.java
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELDateTimeDurationTest.java
@@ -36,6 +36,9 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
import org.kie.dmn.feel.lang.FEELDialect;
import org.kie.dmn.feel.lang.types.impl.ComparablePeriod;
+import org.kie.dmn.feel.runtime.custom.ZoneTime;
+
+import static
org.kie.dmn.feel.runtime.functions.TimeFunction.timeStringWithSeconds;
public class FEELDateTimeDurationTest extends BaseFEELTest {
@@ -88,6 +91,9 @@ public class FEELDateTimeDurationTest extends BaseFEELTest {
{ "(@\"13:20:00@Etc/UTC\").timezone", "Etc/UTC" , null},
{ "(@\"13:20:00@Etc/GMT\").timezone", "Etc/GMT" , null},
{ "-duration( \"P2Y2M\" )", ComparablePeriod.parse( "-P2Y2M" )
, null},
+ {"@\"2023-10-10T10:31:00@Australia/Melbourne\"",
DateTimeFormatter.ISO_DATE_TIME.parse("2023-10-10T10" +
+
":31+11:00[Australia/Melbourne]",
ZonedDateTime::from), null},
+ {"@\"10:15:00@Australia/Melbourne\"",
ZoneTime.of(LocalTime.of(10, 15), ZoneId.of("Australia/Melbourne"), true),
null},
// comparison operators
{ "duration( \"P1Y6M\" ) = duration( \"P1Y6M\" )",
Boolean.TRUE , null},
@@ -184,6 +190,27 @@ public class FEELDateTimeDurationTest extends BaseFEELTest
{
{ "time( 22, 57, 00, duration(\"PT5H\")) + duration(
\"PT1H1M\" )", OffsetTime.of( 23, 58, 0, 0, ZoneOffset.ofHours( 5 ) ) , null},
{ "duration( \"PT1H1M\" ) + time( 22, 57, 00,
duration(\"PT5H\"))", OffsetTime.of( 23, 58, 0, 0, ZoneOffset.ofHours( 5 ) ) ,
null},
+ { "@\"P1D\" + @\"2023-10-10T10:31:00@Australia/Melbourne\"",
DateTimeFormatter.ISO_DATE_TIME.parse("2023-10-11T10" +
+
":31+11:00[Australia/Melbourne]",
ZonedDateTime::from), null},
+ { "@\"-P1D\" + @\"2023-10-10T10:31:00@Australia/Melbourne\"",
DateTimeFormatter.ISO_DATE_TIME.parse("2023-10-09T10" +
+
":31+11:00[Australia/Melbourne]",
ZonedDateTime::from), null},
+ { "@\"P1D\" + @\"10:15:00@Australia/Melbourne\"",
getCorrectZoneTime("10:15", "Australia/Melbourne"), null},
+ { "@\"-P1D\" + @\"10:15:00@Australia/Melbourne\"",
getCorrectZoneTime("10:15", "Australia/Melbourne"), null},
+ { "@\"PT1H\" + @\"10:15:00@Australia/Melbourne\"",
getCorrectZoneTime("11:15", "Australia/Melbourne"), null},
+ { "@\"-PT1H\" + @\"10:15:00@Australia/Melbourne\"",
getCorrectZoneTime("09:15", "Australia/Melbourne"), null},
+
+
+ { "@\"10:15:00@Australia/Melbourne\" + @\"P1D\"",
getCorrectZoneTime("10:15", "Australia/Melbourne"), null},
+ { "@\"10:15:00@Australia/Melbourne\" - @\"P1D\"",
getCorrectZoneTime("10:15", "Australia/Melbourne"), null},
+ { "@\"10:15:00@Australia/Melbourne\" + @\"-P1D\"",
getCorrectZoneTime("10:15", "Australia/Melbourne"), null},
+ { "@\"10:15:00@Australia/Melbourne\" + @\"PT1H\"",
getCorrectZoneTime("11:15", "Australia/Melbourne"), null},
+ { "@\"10:15:00@Australia/Melbourne\" - @\"PT1H\"",
getCorrectZoneTime("09:15", "Australia/Melbourne"), null},
+ { "@\"10:15:00@Australia/Melbourne\" + @\"-PT1H\"",
getCorrectZoneTime("09:15", "Australia/Melbourne"), null},
+
+ {"string(@\"10:10@Australia/Melbourne\" + @\"PT1H\")",
"11:10@Australia/Melbourne", null},
+ {"string(@\"10:10:00@Australia/Melbourne\" + @\"PT1H\")",
"11:10:00@Australia/Melbourne", null},
+
+
// TODO support for zones - fix when timezones solved out
(currently returns ZonedDateTime)
// { "date and time(\"2016-07-29T05:48:23.765-05:00\") +
duration( \"P1Y1M\" ) ", OffsetDateTime.of(2017, 8, 29, 5, 48, 23, 765000000,
ZoneOffset.ofHours( -5 )), null},
// { "date and time(\"2016-07-29T05:48:23.765-05:00\") +
duration( \"P1DT1H1M\" ) ", OffsetDateTime.of(2016, 7, 30, 6, 49, 23,
765000000, ZoneOffset.ofHours( -5 )), null},
@@ -305,4 +332,11 @@ public class FEELDateTimeDurationTest extends BaseFEELTest
{
};
return addAdditionalParameters(cases, false);
}
+
+ private static ZoneTime getCorrectZoneTime(String baseTime, String zone) {
+ LocalTime localTime = DateTimeFormatter.ISO_TIME.parse(baseTime,
LocalTime::from );
+ ZoneId zoneId = ZoneId.of(zone);
+ return ZoneTime.of(localTime, zoneId, timeStringWithSeconds(baseTime));
+ }
+
}
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/custom/ZoneTimeTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/custom/ZoneTimeTest.java
new file mode 100644
index 0000000000..81245b662e
--- /dev/null
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/custom/ZoneTimeTest.java
@@ -0,0 +1,243 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalQueries;
+import java.util.Arrays;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static
org.kie.dmn.feel.runtime.custom.ZoneTime.ZONED_OFFSET_WITH_SECONDS;
+import static
org.kie.dmn.feel.runtime.custom.ZoneTime.ZONED_OFFSET_WITHOUT_SECONDS;
+
+class ZoneTimeTest {
+
+ private static final String REFERENCED_TIME = "10:15:00";
+ private static final String REFERENCED_ZONE = "Australia/Melbourne";
+ private static LocalTime localTime ;
+ private static ZoneId zoneId;
+ private static OffsetTime offsetTime;
+ private static ZoneTime zoneTime;
+
+ @BeforeAll
+ static void setUpClass() {
+ localTime = DateTimeFormatter.ISO_TIME.parse(REFERENCED_TIME,
LocalTime::from );
+ zoneId = ZoneId.of(REFERENCED_ZONE);
+ offsetTime = getCorrectOffsetTime();
+ zoneTime = ZoneTime.of(localTime, zoneId, true);
+ }
+
+ @Test
+ void of() {
+ ZoneTime retrieved = ZoneTime.of(localTime, zoneId, true);
+ assertNotNull(retrieved);
+ assertEquals(offsetTime, retrieved.getOffsetTime());
+ assertEquals(zoneId, retrieved.getZoneId());
+ }
+
+
+ @Test
+ void getTimezone() {
+ assertEquals(REFERENCED_ZONE, zoneTime.getTimezone());
+ }
+
+ @Test
+ void compareTo() {
+ ZoneTime toCompare =
ZoneTime.of(DateTimeFormatter.ISO_TIME.parse("09:34:31", LocalTime::from),
zoneId, false);
+ OffsetTime comparison = toCompare.getOffsetTime();
+ assertEquals(offsetTime.compareTo(comparison),
zoneTime.compareTo(toCompare));
+
+ }
+
+ @Test
+ void withTemporalField() {
+ ZoneTime expected = new
ZoneTime(offsetTime.with(ChronoField.HOUR_OF_DAY, 3), zoneId, false);
+ assertEquals(expected, zoneTime.with(ChronoField.HOUR_OF_DAY, 3));
+ }
+
+ @Test
+ void withTemporalAdjuster() {
+ TemporalAdjuster adjuster =
ZoneTime.of(DateTimeFormatter.ISO_TIME.parse("09:34:31", LocalTime::from),
zoneId
+ , false);
+ ZoneTime expected = new ZoneTime(offsetTime.with(adjuster), zoneId,
false);
+ assertEquals(expected, zoneTime.with(adjuster));
+ adjuster = DateTimeFormatter.ISO_TIME.parse("09:34:31",
LocalTime::from );
+ expected = new ZoneTime(offsetTime.with(adjuster), zoneId, false);
+ assertEquals(expected, zoneTime.with(adjuster));
+ }
+
+
+ @Test
+ void plusLong() {
+ ZoneTime expected = new ZoneTime(offsetTime.plus(3, ChronoUnit.HOURS),
zoneId, false);
+ assertEquals(expected, zoneTime.plus(3, ChronoUnit.HOURS));
+ }
+
+ @Test
+ void plusTemporalAmount() {
+ TemporalAmount amount = Duration.of(23, ChronoUnit.MINUTES);
+ ZoneTime expected = new ZoneTime(offsetTime.plus(amount), zoneId,
false);
+ assertEquals(expected, zoneTime.plus(amount));
+ }
+
+ @Test
+ void minusLong() {
+ ZoneTime expected = new ZoneTime(offsetTime.minus(3,
ChronoUnit.HOURS), zoneId, false);
+ assertEquals(expected, zoneTime.minus(3, ChronoUnit.HOURS));
+ }
+
+ @Test
+ void minusTemporalAmount() {
+ TemporalAmount amount = Duration.of(23, ChronoUnit.MINUTES);
+ ZoneTime expected = new ZoneTime(offsetTime.minus(amount), zoneId,
false);
+ assertEquals(expected, zoneTime.minus(amount));
+ }
+
+ @Test
+ void until() {
+ ZoneTime endExclusive =
ZoneTime.of(DateTimeFormatter.ISO_TIME.parse("09:34:31", LocalTime::from),
zoneId,
+ false);
+ long expected = offsetTime.until(endExclusive, ChronoUnit.SECONDS);
+ long retrieved = zoneTime.until(endExclusive, ChronoUnit.SECONDS);
+ assertEquals(expected, retrieved);
+ }
+
+ @Test
+ void isSupportedTemporalUnit() {
+ for (ChronoUnit unit : ChronoUnit.values()) {
+ assertEquals(offsetTime.isSupported(unit),
zoneTime.isSupported(unit));
+ }
+ }
+
+ @Test
+ void isSupportedTemporalField() {
+ for (ChronoField field : ChronoField.values()) {
+ assertEquals(offsetTime.isSupported(field),
zoneTime.isSupported(field));
+ }
+ }
+
+ @Test
+ void getLong() {
+ Arrays.stream(ChronoField.values()).filter(offsetTime::isSupported)
+ .forEach(field -> assertEquals(offsetTime.getLong(field),
zoneTime.getLong(field)));
+ }
+
+ @Test
+ void adjustInto() {
+ ZoneTime temporal =
ZoneTime.of(DateTimeFormatter.ISO_TIME.parse("09:34:31", LocalTime::from),
zoneId, false);
+ assertEquals(offsetTime.adjustInto(temporal),
zoneTime.adjustInto(temporal));
+ }
+
+ @Test
+ void query() {
+ assertEquals(zoneId, zoneTime.query(TemporalQueries.zoneId()));
+ assertEquals(zoneId, zoneTime.query(TemporalQueries.zone()));
+ assertEquals(offsetTime.query(TemporalQueries.localTime()),
zoneTime.query(TemporalQueries.localTime()));
+ assertEquals(offsetTime.query(TemporalQueries.offset()),
zoneTime.query(TemporalQueries.offset()));
+ }
+
+ @Test
+ void range() {
+ Arrays.stream(ChronoField.values()).filter(offsetTime::isSupported)
+ .forEach(field -> assertEquals(offsetTime.range(field),
zoneTime.range(field)));
+ }
+
+ @Test
+ void get() {
+ Arrays.stream(ChronoField.values())
+ .filter(offsetTime::isSupported)
+ .filter(field -> field != ChronoField.NANO_OF_DAY && field !=
ChronoField.MICRO_OF_DAY) // Unsupported by offsettime.get()
+ .forEach(field -> assertEquals(offsetTime.get(field),
zoneTime.get(field)));
+ }
+
+ @Test
+ void testEquals() {
+ ZoneTime toCompare =
ZoneTime.of(DateTimeFormatter.ISO_TIME.parse("09:34:31", LocalTime::from),
zoneId, false);
+ assertFalse(zoneTime.equals(toCompare));
+ toCompare = ZoneTime.of(localTime, zoneId, false);
+ assertTrue(zoneTime.equals(toCompare));
+ }
+
+ @Test
+ void testZONED_OFFSET_WITHOUT_SECONDS() {
+ String timeString = "09:34";
+ ZoneTime toFormat =
ZoneTime.of(DateTimeFormatter.ISO_TIME.parse(timeString, LocalTime::from),
zoneId, false);
+ String expected = String.format("%s@%s", timeString, REFERENCED_ZONE);
+ assertEquals(expected, ZONED_OFFSET_WITHOUT_SECONDS.format(toFormat));
+ }
+
+ @Test
+ void testZONED_OFFSET_WITH_SECONDS() {
+ String timeString = "09:34:34";
+ ZoneTime toFormat =
ZoneTime.of(DateTimeFormatter.ISO_TIME.parse(timeString, LocalTime::from),
zoneId, true);
+ String expected = String.format("%s@%s", timeString, REFERENCED_ZONE);
+ assertEquals(expected, ZONED_OFFSET_WITH_SECONDS.format(toFormat));
+
+ timeString = "09:34:00";
+ toFormat = ZoneTime.of(DateTimeFormatter.ISO_TIME.parse(timeString,
LocalTime::from), zoneId, true);
+ expected = String.format("%s@%s", timeString, REFERENCED_ZONE);
+ assertEquals(expected, ZONED_OFFSET_WITH_SECONDS.format(toFormat));
+ }
+
+ @Test
+ void testFormatWithoutSeconds() {
+ String timeString = "09:34";
+ ZoneTime toFormat =
ZoneTime.of(DateTimeFormatter.ISO_TIME.parse(timeString, LocalTime::from),
zoneId, false);
+ String expected = String.format("%s@%s", timeString, REFERENCED_ZONE);
+ assertEquals(expected, toFormat.format());
+ }
+
+ @Test
+ void testFormatWithSeconds() {
+ String timeString = "09:34:00";
+ ZoneTime toFormat =
ZoneTime.of(DateTimeFormatter.ISO_TIME.parse(timeString, LocalTime::from),
zoneId, true);
+ String expected = String.format("%s@%s", timeString, REFERENCED_ZONE);
+ assertEquals(expected, toFormat.format());
+
+ timeString = "09:34:34";
+ toFormat = ZoneTime.of(DateTimeFormatter.ISO_TIME.parse(timeString,
LocalTime::from), zoneId, true);
+ expected = String.format("%s@%s", timeString, REFERENCED_ZONE);
+ assertEquals(expected, toFormat.format());
+ }
+
+ private static OffsetTime getCorrectOffsetTime() {
+ String correctOffsetTimeString =
getCorrectOffsetTimeString(REFERENCED_TIME, REFERENCED_ZONE);
+ return DateTimeFormatter.ISO_TIME.parse( correctOffsetTimeString,
OffsetTime::from );
+ }
+
+ private static String getCorrectOffsetTimeString(String baseTime, String
zone) {
+ ZoneId zoneId = ZoneId.of(zone);
+ ZoneOffset offset = zoneId.getRules().getOffset(LocalDateTime.now());
+ String diffOffset = offset.getId();
+ return String.format("%s%s",baseTime, diffOffset);
+ }
+}
\ No newline at end of file
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ComposingDifferentFunctionsTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ComposingDifferentFunctionsTest.java
index 21821635e9..9da075adf9 100644
---
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ComposingDifferentFunctionsTest.java
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ComposingDifferentFunctionsTest.java
@@ -101,6 +101,6 @@ class ComposingDifferentFunctionsTest {
assertThat(timeOnDateTime.query(TemporalQueries.localTime())).isEqualTo(LocalTime.of(10,
20, 0));
assertThat(timeOnDateTime.query(TemporalQueries.zone())).isEqualTo(ZoneId.of("Europe/Paris"));
- FunctionTestUtil.assertResult(stringFunction.invoke(timeOnDateTime),
"10:20:00@Europe/Paris");
+ FunctionTestUtil.assertResult(stringFunction.invoke(timeOnDateTime),
"10:20@Europe/Paris");
}
}
\ 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 7f0ba0729b..6e12e09917 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
@@ -35,6 +35,8 @@ import org.junit.jupiter.api.Test;
import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
class TimeFunctionTest {
@@ -173,4 +175,14 @@ class TimeFunctionTest {
timeFunction.invoke(10, 43, BigDecimal.valueOf(15.154),
Duration.ofHours(-1)),
OffsetTime.of(10, 43, 15, 154000000, ZoneOffset.ofHours(-1)));
}
+
+ @Test
+ void timeStringWithSeconds() {
+
assertTrue(TimeFunction.timeStringWithSeconds("10:10:00@Australia/Melbourne"));
+ assertTrue(TimeFunction.timeStringWithSeconds("10:10:00+10:00"));
+ assertTrue(TimeFunction.timeStringWithSeconds("10:10:00:123"));
+
+
assertFalse(TimeFunction.timeStringWithSeconds("10:10@Australia/Melbourne"));
+ assertFalse(TimeFunction.timeStringWithSeconds("10:10+10:00"));
+ }
}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]