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]


Reply via email to