From f174988498a41dd24ddda6f527bf211796b9b2ea Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 18 Sep 2025 11:27:34 -0700 Subject: [PATCH] Evaluate CEL's timestamp and duration types to their native equivalent values PiperOrigin-RevId: 808664776 --- common/internal/BUILD.bazel | 10 + .../src/main/java/dev/cel/common/BUILD.bazel | 1 + .../dev/cel/common/CelProtoJsonAdapter.java | 10 + .../java/dev/cel/common/internal/BUILD.bazel | 30 ++ .../cel/common/internal/DateTimeHelpers.java | 261 ++++++++++++++++++ .../dev/cel/common/internal/ProtoAdapter.java | 2 +- .../cel/common/internal/ProtoLiteAdapter.java | 47 +++- .../values/BaseProtoCelValueConverter.java | 17 -- .../values/ProtoCelValueConverterTest.java | 16 +- .../cel/extensions/CelOptionalLibrary.java | 5 + .../extensions/CelOptionalLibraryTest.java | 11 +- .../java/dev/cel/runtime/RuntimeHelpers.java | 11 +- .../java/dev/cel/runtime/TypeResolver.java | 14 +- .../dev/cel/runtime/standard/AddOperator.java | 47 +++- .../java/dev/cel/runtime/standard/BUILD.bazel | 70 ++--- .../cel/runtime/standard/DateTimeHelpers.java | 85 ------ .../runtime/standard/DurationFunction.java | 27 +- .../cel/runtime/standard/GetDateFunction.java | 34 ++- .../standard/GetDayOfMonthFunction.java | 34 ++- .../standard/GetDayOfWeekFunction.java | 42 ++- .../standard/GetDayOfYearFunction.java | 34 ++- .../runtime/standard/GetFullYearFunction.java | 34 ++- .../runtime/standard/GetHoursFunction.java | 45 ++- .../standard/GetMillisecondsFunction.java | 49 +++- .../runtime/standard/GetMinutesFunction.java | 46 ++- .../runtime/standard/GetMonthFunction.java | 34 ++- .../runtime/standard/GetSecondsFunction.java | 57 +++- .../standard/GreaterEqualsOperator.java | 31 ++- .../cel/runtime/standard/GreaterOperator.java | 31 ++- .../dev/cel/runtime/standard/IntFunction.java | 13 +- .../runtime/standard/LessEqualsOperator.java | 31 ++- .../cel/runtime/standard/LessOperator.java | 31 ++- .../cel/runtime/standard/StringFunction.java | 25 +- .../runtime/standard/SubtractOperator.java | 47 +++- .../runtime/standard/TimestampFunction.java | 44 ++- .../runtime/CelLiteRuntimeAndroidTest.java | 14 +- .../dev/cel/runtime/CelLiteRuntimeTest.java | 10 +- .../test/resources/arithmDuration.baseline | 16 +- .../test/resources/arithmTimestamp.baseline | 32 +-- runtime/src/test/resources/duration.baseline | 120 ++------ .../test/resources/durationFunctions.baseline | 50 +--- .../resources/dynamicMessage_adapted.baseline | 8 +- .../dynamicMessage_dynamicDescriptor.baseline | 3 +- .../test/resources/timeConversions.baseline | 23 +- runtime/src/test/resources/timestamp.baseline | 122 ++------ .../resources/timestampFunctions.baseline | 223 ++++----------- .../test/resources/typeComparisons.baseline | 11 + .../test/resources/unknownResultSet.baseline | 4 +- .../dev/cel/testing/BaseInterpreterTest.java | 62 +++-- .../java/dev/cel/testing/utils/BUILD.bazel | 1 + .../dev/cel/testing/utils/ExprValueUtils.java | 16 ++ 51 files changed, 1200 insertions(+), 841 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java delete mode 100644 runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java diff --git a/common/internal/BUILD.bazel b/common/internal/BUILD.bazel index a39bb9365..c69f26b3e 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -137,3 +137,13 @@ cel_android_library( name = "proto_time_utils_android", exports = ["//common/src/main/java/dev/cel/common/internal:proto_time_utils_android"], ) + +java_library( + name = "date_time_helpers", + exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers"], +) + +cel_android_library( + name = "date_time_helpers_android", + exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers_android"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 255ae5d8d..460a07f13 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -205,6 +205,7 @@ java_library( tags = [ ], deps = [ + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values", "//common/values:cel_byte_string", diff --git a/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java b/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java index e0afb8c4e..e74d52f7a 100644 --- a/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java +++ b/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java @@ -26,8 +26,10 @@ import com.google.protobuf.Struct; import com.google.protobuf.Timestamp; import com.google.protobuf.Value; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -118,6 +120,14 @@ public static Value adaptValueToJsonValue(Object value) { String duration = ProtoTimeUtils.toString((Duration) value); return json.setStringValue(duration).build(); } + if (value instanceof Instant) { + // Instant's toString follows RFC 3339 + return json.setStringValue(value.toString()).build(); + } + if (value instanceof java.time.Duration) { + String duration = DateTimeHelpers.toString((java.time.Duration) value); + return json.setStringValue(duration).build(); + } if (value instanceof FieldMask) { String fieldMaskStr = toJsonString((FieldMask) value); return json.setStringValue(fieldMaskStr).build(); diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index c508649a3..85b5df9a4 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -195,9 +195,11 @@ java_library( deps = [ ":well_known_proto", "//common:error_codes", + "//common:options", "//common:proto_json_adapter", "//common:runtime_exception", "//common/annotations", + "//common/internal:proto_time_utils", "//common/values", "//common/values:cel_byte_string", "@maven//:com_google_errorprone_error_prone_annotations", @@ -429,3 +431,31 @@ cel_android_library( "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) + +java_library( + name = "date_time_helpers", + srcs = ["DateTimeHelpers.java"], + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +cel_android_library( + name = "date_time_helpers_android", + srcs = ["DateTimeHelpers.java"], + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) diff --git a/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java new file mode 100644 index 000000000..9abe39b4b --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java @@ -0,0 +1,261 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 dev.cel.common.internal; + +import com.google.common.base.Strings; +import com.google.protobuf.Timestamp; +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.Locale; + +/** Collection of utility methods for CEL datetime handlings. */ +@Internal +@SuppressWarnings("JavaInstantGetSecondsGetNano") // Intended within CEL. +public final class DateTimeHelpers { + public static final String UTC = "UTC"; + + // Timestamp for "0001-01-01T00:00:00Z" + private static final long TIMESTAMP_SECONDS_MIN = -62135596800L; + // Timestamp for "9999-12-31T23:59:59Z" + private static final long TIMESTAMP_SECONDS_MAX = 253402300799L; + + private static final long DURATION_SECONDS_MIN = -315576000000L; + private static final long DURATION_SECONDS_MAX = 315576000000L; + private static final int NANOS_PER_SECOND = 1000000000; + + /** + * Constructs a new {@link LocalDateTime} instance + * + * @param ts Timestamp protobuf object + * @param tz Timezone based on the CEL specification. This is either the canonical name from tz + * database or a standard offset represented in (+/-)HH:MM. Few valid examples are: + * + * + * @return If an Invalid timezone is supplied. + */ + public static LocalDateTime newLocalDateTime(Timestamp ts, String tz) { + return Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()) + .atZone(timeZone(tz)) + .toLocalDateTime(); + } + + /** + * Constructs a new {@link LocalDateTime} instance from a Java Instant. + * + * @param instant Instant object + * @param tz Timezone based on the CEL specification. This is either the canonical name from tz + * database or a standard offset represented in (+/-)HH:MM. Few valid examples are: + * + * + * @return A new {@link LocalDateTime} instance. + */ + public static LocalDateTime newLocalDateTime(Instant instant, String tz) { + return instant.atZone(timeZone(tz)).toLocalDateTime(); + } + + /** + * Parse from RFC 3339 date string to {@link java.time.Instant}. + * + *

Example of accepted format: "1972-01-01T10:00:20.021-05:00" + */ + public static Instant parse(String text) { + OffsetDateTime offsetDateTime = OffsetDateTime.parse(text); + Instant instant = offsetDateTime.toInstant(); + checkValid(instant); + + return instant; + } + + /** Adds a duration to an instant. */ + public static Instant add(Instant ts, Duration dur) { + Instant newInstant = ts.plus(dur); + checkValid(newInstant); + + return newInstant; + } + + /** Adds two durations */ + public static Duration add(Duration d1, Duration d2) { + Duration newDuration = d1.plus(d2); + checkValid(newDuration); + + return newDuration; + } + + /** Subtracts a duration to an instant. */ + public static Instant subtract(Instant ts, Duration dur) { + Instant newInstant = ts.minus(dur); + checkValid(newInstant); + + return newInstant; + } + + /** Subtract a duration from another. */ + public static Duration subtract(Duration d1, Duration d2) { + Duration newDuration = d1.minus(d2); + checkValid(newDuration); + + return newDuration; + } + + /** + * Formats a {@link Duration} into a minimal seconds-based representation. + * + *

Note: follows {@code ProtoTimeUtils#toString(Duration)} implementation + */ + public static String toString(Duration duration) { + if (duration.isZero()) { + return "0s"; + } + + long totalNanos = duration.toNanos(); + StringBuilder sb = new StringBuilder(); + + if (totalNanos < 0) { + sb.append('-'); + totalNanos = -totalNanos; + } + + long seconds = totalNanos / 1_000_000_000; + int nanos = (int) (totalNanos % 1_000_000_000); + + sb.append(seconds); + + // Follows ProtoTimeUtils.toString(Duration) implementation + if (nanos > 0) { + sb.append('.'); + if (nanos % 1_000_000 == 0) { + // Millisecond precision (3 digits) + int millis = nanos / 1_000_000; + sb.append(String.format(Locale.US, "%03d", millis)); + } else if (nanos % 1_000 == 0) { + // Microsecond precision (6 digits) + int micros = nanos / 1_000; + sb.append(String.format(Locale.US, "%06d", micros)); + } else { + // Nanosecond precision (9 digits) + sb.append(String.format(Locale.US, "%09d", nanos)); + } + } + + sb.append('s'); + return sb.toString(); + } + + /** + * Get the DateTimeZone Instance. + * + * @param tz the ID of the datetime zone + * @return the ZoneId object + */ + private static ZoneId timeZone(String tz) { + try { + return ZoneId.of(tz); + } catch (DateTimeException e) { + // If timezone is not a string name (for example, 'US/Central'), it should be a numerical + // offset from UTC in the format [+/-]HH:MM. + try { + int ind = tz.indexOf(":"); + if (ind == -1) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } + + int hourOffset = Integer.parseInt(tz.substring(0, ind)); + int minOffset = Integer.parseInt(tz.substring(ind + 1)); + // Ensures that the offset are properly formatted in [+/-]HH:MM to conform with + // ZoneOffset's format requirements. + // Example: "-9:30" -> "-09:30" and "9:30" -> "+09:30" + String formattedOffset = + ((hourOffset < 0) ? "-" : "+") + + String.format(Locale.US, "%02d:%02d", Math.abs(hourOffset), minOffset); + + return ZoneId.of(formattedOffset); + + } catch (DateTimeException e2) { + throw new CelRuntimeException(e2, CelErrorCode.BAD_FORMAT); + } + } + } + + /** Throws an {@link IllegalArgumentException} if the given {@link Timestamp} is not valid. */ + private static void checkValid(Instant instant) { + long seconds = instant.getEpochSecond(); + + if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { + throw new IllegalArgumentException( + Strings.lenientFormat( + "Timestamp is not valid. " + + "Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]. " + + "Nanos (%s) must be in range [0, +999,999,999].", + seconds, instant.getNano())); + } + } + + /** Throws an {@link IllegalArgumentException} if the given {@link Duration} is not valid. */ + private static void checkValid(Duration duration) { + long seconds = duration.getSeconds(); + int nanos = duration.getNano(); + if (!isDurationValid(seconds, nanos)) { + throw new IllegalArgumentException( + Strings.lenientFormat( + "Duration is not valid. " + + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. " + + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. " + + "Nanos must have the same sign as seconds", + seconds, nanos)); + } + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@link Duration}. The {@code + * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos} + * value must be in the range [-999,999,999, +999,999,999]. + * + *

Note: Durations less than one second are represented with a 0 {@code seconds} field + * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero + * value for the {@code nanos} field must be of the same sign as the {@code seconds} field. + */ + private static boolean isDurationValid(long seconds, int nanos) { + if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) { + return false; + } + if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) { + return false; + } + if (seconds < 0 || nanos < 0) { + if (seconds > 0 || nanos > 0) { + return false; + } + } + return true; + } + + private DateTimeHelpers() {} +} diff --git a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java index 3461106ae..cd4be2f5e 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java @@ -125,7 +125,7 @@ public final class ProtoAdapter { public ProtoAdapter(DynamicProto dynamicProto, CelOptions celOptions) { this.dynamicProto = checkNotNull(dynamicProto); - this.protoLiteAdapter = new ProtoLiteAdapter(celOptions.enableUnsignedLongs()); + this.protoLiteAdapter = new ProtoLiteAdapter(celOptions); this.celOptions = celOptions; } diff --git a/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java index eb8c42862..e05d92f68 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java @@ -43,10 +43,12 @@ import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; import dev.cel.common.CelErrorCode; +import dev.cel.common.CelOptions; import dev.cel.common.CelProtoJsonAdapter; import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.Map; import java.util.Map.Entry; @@ -63,7 +65,7 @@ @Immutable public final class ProtoLiteAdapter { - private final boolean enableUnsignedLongs; + private final CelOptions celOptions; @SuppressWarnings("unchecked") public MessageLite adaptValueToWellKnownProto(Object value, WellKnownProto wellKnownProto) { @@ -94,9 +96,9 @@ public MessageLite adaptValueToWellKnownProto(Object value, WellKnownProto wellK case UINT64_VALUE: return adaptValueToUint64(value); case DURATION: - return (Duration) value; + return adaptValueToProtoDuration(value); case TIMESTAMP: - return (Timestamp) value; + return adaptValueToProtoTimestamp(value); case EMPTY: case FIELD_MASK: // These two WKTs are typically used in context of JSON conversions, in which they are @@ -114,7 +116,6 @@ public Any adaptValueToAny(Object value, String typeName) { return packAnyMessage((MessageLite) value, typeName); } - // if (value instanceof NullValue) { if (value instanceof dev.cel.common.values.NullValue) { return packAnyMessage( Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(), WellKnownProto.JSON_VALUE); @@ -140,6 +141,10 @@ public Any adaptValueToAny(Object value, String typeName) { wellKnownProto = WellKnownProto.JSON_LIST_VALUE; } else if (value instanceof Map) { wellKnownProto = WellKnownProto.JSON_STRUCT_VALUE; + } else if (value instanceof Instant) { + wellKnownProto = WellKnownProto.TIMESTAMP; + } else if (value instanceof java.time.Duration) { + wellKnownProto = WellKnownProto.DURATION; } else { throw new IllegalArgumentException("Unsupported value conversion to any: " + value); } @@ -175,16 +180,26 @@ public Object adaptWellKnownProtoToValue( case STRING_VALUE: return ((StringValue) proto).getValue(); case UINT32_VALUE: - if (enableUnsignedLongs) { + if (celOptions.enableUnsignedLongs()) { return UnsignedLong.fromLongBits( Integer.toUnsignedLong(((UInt32Value) proto).getValue())); } return (long) ((UInt32Value) proto).getValue(); case UINT64_VALUE: - if (enableUnsignedLongs) { + if (celOptions.enableUnsignedLongs()) { return UnsignedLong.fromLongBits(((UInt64Value) proto).getValue()); } return ((UInt64Value) proto).getValue(); + case TIMESTAMP: + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return ProtoTimeUtils.toJavaInstant((Timestamp) proto); + } + return proto; + case DURATION: + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return ProtoTimeUtils.toJavaDuration((Duration) proto); + } + return proto; default: return proto; } @@ -324,7 +339,23 @@ private static Any packAnyMessage(MessageLite msg, String typeUrl) { .build(); } - public ProtoLiteAdapter(boolean enableUnsignedLongs) { - this.enableUnsignedLongs = enableUnsignedLongs; + private Timestamp adaptValueToProtoTimestamp(Object value) { + if (!celOptions.evaluateCanonicalTypesToNativeValues()) { + return (Timestamp) value; + } + + return ProtoTimeUtils.toProtoTimestamp((Instant) value); + } + + private Duration adaptValueToProtoDuration(Object value) { + if (!celOptions.evaluateCanonicalTypesToNativeValues()) { + return (Duration) value; + } + + return ProtoTimeUtils.toProtoDuration((java.time.Duration) value); + } + + public ProtoLiteAdapter(CelOptions celOptions) { + this.celOptions = celOptions; } } diff --git a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java index 38741f555..450b273c2 100644 --- a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java @@ -53,23 +53,6 @@ public abstract class BaseProtoCelValueConverter extends CelValueConverter { public abstract CelValue fromProtoMessageToCelValue(MessageLite msg); - /** - * Adapts a {@link CelValue} to a native Java object. The CelValue is adapted into protobuf object - * when an equivalent exists. - */ - @Override - public Object fromCelValueToJavaObject(CelValue celValue) { - Preconditions.checkNotNull(celValue); - - if (celValue instanceof TimestampValue) { - return ProtoTimeUtils.toProtoTimestamp(((TimestampValue) celValue).value()); - } else if (celValue instanceof DurationValue) { - return ProtoTimeUtils.toProtoDuration(((DurationValue) celValue).value()); - } - - return super.fromCelValueToJavaObject(celValue); - } - /** {@inheritDoc} Protobuf semantics take precedence for conversion. */ @Override public CelValue fromJavaObjectToCelValue(Object value) { diff --git a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java index b7e72c6b8..234411ce7 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java @@ -16,12 +16,10 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.protobuf.Duration; -import com.google.protobuf.Timestamp; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; -import dev.cel.common.internal.ProtoTimeUtils; +import java.time.Duration; import java.time.Instant; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,13 +33,13 @@ public class ProtoCelValueConverterTest { DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE)); @Test - public void fromCelValueToJavaObject_returnsTimestampValue() { - Timestamp timestamp = - (Timestamp) + public void fromCelValueToJavaObject_returnsInstantValue() { + Instant timestamp = + (Instant) PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( TimestampValue.create(Instant.ofEpochSecond(50))); - assertThat(timestamp).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(50)); + assertThat(timestamp).isEqualTo(Instant.ofEpochSecond(50)); } @Test @@ -49,9 +47,9 @@ public void fromCelValueToJavaObject_returnsDurationValue() { Duration duration = (Duration) PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( - DurationValue.create(java.time.Duration.ofSeconds(10))); + DurationValue.create(Duration.ofSeconds(10))); - assertThat(duration).isEqualTo(ProtoTimeUtils.fromSecondsToDuration(10)); + assertThat(duration).isEqualTo(Duration.ofSeconds(10)); } @Test diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java index a384b09e2..6ed4d1491 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java +++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java @@ -50,6 +50,7 @@ import dev.cel.runtime.CelInternalRuntimeLibrary; import dev.cel.runtime.CelRuntimeBuilder; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Collection; import java.util.List; import java.util.Map; @@ -394,6 +395,10 @@ private static boolean isZeroValue(Object val) { } else if (val instanceof NullValue) { // A null value always represents an absent value return true; + } else if (val instanceof Instant) { + return val.equals(Instant.EPOCH); + } else if (val instanceof java.time.Duration) { + return val.equals(java.time.Duration.ZERO); } // Unknown. Assume that it is non-zero. diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java index a806037da..a51fbedbf 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java @@ -34,7 +34,6 @@ import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelValidationException; import dev.cel.common.CelVarDecl; -import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.CelType; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; @@ -52,6 +51,8 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; import dev.cel.runtime.InterpreterUtil; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Optional; @@ -70,16 +71,12 @@ private enum ConstantTestCases { UINT("5u", "0u", SimpleType.UINT, UnsignedLong.valueOf(5)), BOOL("true", "false", SimpleType.BOOL, true), BYTES("b'abc'", "b''", SimpleType.BYTES, CelByteString.copyFromUtf8("abc")), - DURATION( - "duration('180s')", - "duration('0s')", - SimpleType.DURATION, - ProtoTimeUtils.fromSecondsToDuration(180)), + DURATION("duration('180s')", "duration('0s')", SimpleType.DURATION, Duration.ofMinutes(3)), TIMESTAMP( "timestamp(1685552643)", "timestamp(0)", SimpleType.TIMESTAMP, - ProtoTimeUtils.fromSecondsToTimestamp(1685552643)); + Instant.ofEpochSecond(1685552643)); private final String sourceWithNonZeroValue; private final String sourceWithZeroValue; diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java index 37247f3d3..0e02af273 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java @@ -55,16 +55,23 @@ public static RuntimeHelpers create() { // Functions // ========= - /** Convert a string to a Duration. */ + /** Convert a string to a Protobuf Duration. */ @SuppressWarnings("AndroidJdkLibsChecker") // DateTimeParseException added in 26 public static Duration createDurationFromString(String d) { + java.time.Duration dv = createJavaDurationFromString(d); + return Duration.newBuilder().setSeconds(dv.getSeconds()).setNanos(dv.getNano()).build(); + } + + /** Convert a string to a native Java Duration. */ + @SuppressWarnings("AndroidJdkLibsChecker") // DateTimeParseException added in 26 + public static java.time.Duration createJavaDurationFromString(String d) { try { java.time.Duration dv = AmountFormats.parseUnitBasedDuration(d); // Ensure that the duration value can be adequately represented within a protobuf.Duration. checkArgument( dv.compareTo(DURATION_MAX) <= 0 && dv.compareTo(DURATION_MIN) >= 0, "invalid duration range"); - return Duration.newBuilder().setSeconds(dv.getSeconds()).setNanos(dv.getNano()).build(); + return dv; } catch (DateTimeParseException e) { throw new IllegalArgumentException("invalid duration format", e); } diff --git a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java index b9cc42cc1..905120988 100644 --- a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java @@ -35,6 +35,7 @@ import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -64,8 +65,17 @@ static TypeResolver create() { .put(UnsignedLong.class, TypeType.create(SimpleType.UINT)) .put(String.class, TypeType.create(SimpleType.STRING)) .put(NullValue.class, TypeType.create(SimpleType.NULL_TYPE)) - .put(Duration.class, TypeType.create(SimpleType.DURATION)) - .put(Timestamp.class, TypeType.create(SimpleType.TIMESTAMP)) + .put(java.time.Duration.class, TypeType.create(SimpleType.DURATION)) + .put(Instant.class, TypeType.create(SimpleType.TIMESTAMP)) + .put( + Duration.class, + TypeType.create( + SimpleType.DURATION)) // TODO: Remove once clients have been migrated + .put( + Timestamp.class, + TypeType.create( + SimpleType + .TIMESTAMP)) // TODO: Remove once clients have been migrated .put(ArrayList.class, TypeType.create(ListType.create(SimpleType.DYN))) .put(HashMap.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN))) .put(Optional.class, TypeType.create(OptionalType.create(SimpleType.DYN))) diff --git a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java index 9d75b15ce..42386a942 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java @@ -23,11 +23,13 @@ import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; import java.util.List; @@ -104,24 +106,51 @@ public enum AddOverload implements CelStandardOverload { (celOptions, runtimeEquality) -> CelFunctionBinding.from("add_double", Double.class, Double.class, Double::sum)), ADD_DURATION_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "add_duration_duration", Duration.class, Duration.class, ProtoTimeUtils::add)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "add_duration_duration", + java.time.Duration.class, + java.time.Duration.class, + DateTimeHelpers::add); + } else { + return CelFunctionBinding.from( + "add_duration_duration", Duration.class, Duration.class, ProtoTimeUtils::add); + } + }), ADD_TIMESTAMP_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "add_timestamp_duration", Timestamp.class, Duration.class, ProtoTimeUtils::add)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "add_timestamp_duration", + Instant.class, + java.time.Duration.class, + DateTimeHelpers::add); + } else { + return CelFunctionBinding.from( + "add_timestamp_duration", Timestamp.class, Duration.class, ProtoTimeUtils::add); + } + }), ADD_STRING( (celOptions, runtimeEquality) -> CelFunctionBinding.from( "add_string", String.class, String.class, (String x, String y) -> x + y)), ADD_DURATION_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "add_duration_timestamp", + java.time.Duration.class, + Instant.class, + (java.time.Duration d, Instant i) -> DateTimeHelpers.add(i, d)); + } else { + return CelFunctionBinding.from( "add_duration_timestamp", Duration.class, Timestamp.class, - (Duration x, Timestamp y) -> ProtoTimeUtils.add(y, x))), + (Duration d, Timestamp t) -> ProtoTimeUtils.add(t, d)); + } + }), @SuppressWarnings({"unchecked"}) ADD_LIST( (celOptions, runtimeEquality) -> diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index bac80475d..08177a474 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -50,6 +50,7 @@ java_library( ":standard_overload", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", "//runtime:function_binding", @@ -71,6 +72,7 @@ cel_android_library( ":standard_overload_android", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", "//runtime:function_binding_android", @@ -92,6 +94,7 @@ java_library( ":standard_overload", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -112,6 +115,7 @@ cel_android_library( ":standard_overload_android", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -389,9 +393,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -406,10 +410,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -423,9 +427,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -440,10 +444,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -457,9 +461,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -474,10 +478,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -491,9 +495,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -508,10 +512,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -525,9 +529,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -542,10 +546,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -559,9 +563,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -577,10 +581,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -595,9 +599,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -613,10 +617,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -631,9 +635,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -649,10 +653,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -667,9 +671,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -684,10 +688,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -701,9 +705,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -719,10 +723,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1307,6 +1311,7 @@ java_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", "//runtime:function_binding", @@ -1328,6 +1333,7 @@ cel_android_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", "//runtime:function_binding_android", @@ -1347,6 +1353,7 @@ java_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -1367,6 +1374,7 @@ cel_android_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1444,25 +1452,3 @@ java_library( "//common:error_codes", ], ) - -java_library( - name = "date_time_helpers", - srcs = ["DateTimeHelpers.java"], - visibility = ["//visibility:private"], - deps = [ - "//common:error_codes", - "//common:runtime_exception", - "@maven//:com_google_protobuf_protobuf_java", - ], -) - -cel_android_library( - name = "date_time_helpers_android", - srcs = ["DateTimeHelpers.java"], - visibility = ["//visibility:private"], - deps = [ - "//common:error_codes", - "//common:runtime_exception", - "@maven_android//:com_google_protobuf_protobuf_javalite", - ], -) diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java deleted file mode 100644 index d05125ed1..000000000 --- a/runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://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 dev.cel.runtime.standard; - -import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; -import java.time.DateTimeException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Locale; - -final class DateTimeHelpers { - static final String UTC = "UTC"; - - /** - * Constructs a new {@link LocalDateTime} instance - * - * @param ts Timestamp protobuf object - * @param tz Timezone based on the CEL specification. This is either the canonical name from tz - * database or a standard offset represented in (+/-)HH:MM. Few valid examples are: - *

- * - * @return If an Invalid timezone is supplied. - */ - static LocalDateTime newLocalDateTime(Timestamp ts, String tz) { - return Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()) - .atZone(timeZone(tz)) - .toLocalDateTime(); - } - - /** - * Get the DateTimeZone Instance. - * - * @param tz the ID of the datetime zone - * @return the ZoneId object - */ - private static ZoneId timeZone(String tz) { - try { - return ZoneId.of(tz); - } catch (DateTimeException e) { - // If timezone is not a string name (for example, 'US/Central'), it should be a numerical - // offset from UTC in the format [+/-]HH:MM. - try { - int ind = tz.indexOf(":"); - if (ind == -1) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); - } - - int hourOffset = Integer.parseInt(tz.substring(0, ind)); - int minOffset = Integer.parseInt(tz.substring(ind + 1)); - // Ensures that the offset are properly formatted in [+/-]HH:MM to conform with - // ZoneOffset's format requirements. - // Example: "-9:30" -> "-09:30" and "9:30" -> "+09:30" - String formattedOffset = - ((hourOffset < 0) ? "-" : "+") - + String.format(Locale.getDefault(), "%02d:%02d", Math.abs(hourOffset), minOffset); - - return ZoneId.of(formattedOffset); - - } catch (DateTimeException e2) { - throw new CelRuntimeException(e2, CelErrorCode.BAD_FORMAT); - } - } - } - - private DateTimeHelpers() {} -} diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java index d54e2999b..b17a9871e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java @@ -43,18 +43,33 @@ public static DurationFunction create(Iterable - CelFunctionBinding.from("duration_to_duration", Duration.class, (Duration x) -> x)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_duration", java.time.Duration.class, (java.time.Duration d) -> d); + } else { + return CelFunctionBinding.from( + "duration_to_duration", Duration.class, (Duration d) -> d); + } + }), STRING_TO_DURATION( (celOptions, runtimeEquality) -> CelFunctionBinding.from( "string_to_duration", String.class, (String d) -> { - try { - return RuntimeHelpers.createDurationFromString(d); - } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + try { + return RuntimeHelpers.createJavaDurationFromString(d); + } catch (IllegalArgumentException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } + } else { + try { + return RuntimeHelpers.createDurationFromString(d); + } catch (IllegalArgumentException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } } })), ; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java index bd48ec7da..1c1b7ffd4 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDate}. */ @@ -43,18 +44,35 @@ public static GetDateFunction create(Iterable o /** Overloads for the standard function. */ public enum GetDateOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_MONTH_1_BASED( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month_1_based", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth()); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month_1_based", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth()); + } + }), TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month_1_based_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth()); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month_1_based_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth()); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java index 171353c04..1efa43620 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDayOfMonth}. */ @@ -45,18 +46,35 @@ public static GetDayOfMonthFunction create( /** Overloads for the standard function. */ public enum GetDayOfMonthOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_MONTH( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth() - 1)), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth() - 1); + } + }), TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth() - 1)), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth() - 1); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java index 92ef90e79..4fa7000eb 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; @@ -23,6 +23,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import java.time.DayOfWeek; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDayOfWeek}. */ @@ -46,18 +47,41 @@ public static GetDayOfWeekFunction create( /** Overloads for the standard function. */ public enum GetDayOfWeekOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_WEEK( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_week", + Instant.class, + (Instant ts) -> { + // CEL treats Sunday as day 0, but Java.time treats it as day 7. + DayOfWeek dayOfWeek = newLocalDateTime(ts, UTC).getDayOfWeek(); + return (long) dayOfWeek.getValue() % 7; + }); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_week", Timestamp.class, (Timestamp ts) -> { // CEL treats Sunday as day 0, but Java.time treats it as day 7. DayOfWeek dayOfWeek = newLocalDateTime(ts, UTC).getDayOfWeek(); return (long) dayOfWeek.getValue() % 7; - })), + }); + } + }), TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_week_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> { + // CEL treats Sunday as day 0, but Java.time treats it as day 7. + DayOfWeek dayOfWeek = newLocalDateTime(ts, tz).getDayOfWeek(); + return (long) dayOfWeek.getValue() % 7; + }); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_week_with_tz", Timestamp.class, String.class, @@ -65,7 +89,9 @@ public enum GetDayOfWeekOverload implements CelStandardOverload { // CEL treats Sunday as day 0, but Java.time treats it as day 7. DayOfWeek dayOfWeek = newLocalDateTime(ts, tz).getDayOfWeek(); return (long) dayOfWeek.getValue() % 7; - })); + }); + } + }); private final FunctionBindingCreator bindingCreator; @Override diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java index 8c4d90e64..c74a85c1c 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDayOfYear}. */ @@ -45,18 +46,35 @@ public static GetDayOfYearFunction create( /** Overloads for the standard function. */ public enum GetDayOfYearOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_YEAR( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_year", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getDayOfYear() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_year", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfYear() - 1)), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfYear() - 1); + } + }), TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_year_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfYear() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_year_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfYear() - 1)), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfYear() - 1); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java index 61fe189cb..ca816eb80 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getFullYear}. */ @@ -44,18 +45,35 @@ public static GetFullYearFunction create( /** Overloads for the standard function. */ public enum GetFullYearOverload implements CelStandardOverload { TIMESTAMP_TO_YEAR( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_year", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getYear()); + } else { + return CelFunctionBinding.from( "timestamp_to_year", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getYear())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getYear()); + } + }), TIMESTAMP_TO_YEAR_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_year_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getYear()); + } else { + return CelFunctionBinding.from( "timestamp_to_year_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getYear())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getYear()); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java index ee36bfbb0..28d559248 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getHours}. */ @@ -45,21 +46,45 @@ public static GetHoursFunction create(Iterable - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_hours", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getHour()); + } else { + return CelFunctionBinding.from( "timestamp_to_hours", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getHour())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getHour()); + } + }), TIMESTAMP_TO_HOURS_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_hours_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getHour()); + } else { + return CelFunctionBinding.from( "timestamp_to_hours_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getHour())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getHour()); + } + }), DURATION_TO_HOURS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from("duration_to_hours", Duration.class, ProtoTimeUtils::toHours)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_hours", java.time.Duration.class, java.time.Duration::toHours); + } else { + return CelFunctionBinding.from( + "duration_to_hours", Duration.class, ProtoTimeUtils::toHours); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java index 369cd9ea2..53ec92a82 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getMilliseconds}. */ @@ -51,27 +52,51 @@ public enum GetMillisecondsOverload implements CelStandardOverload { // timestamp_to_milliseconds overload @SuppressWarnings("JavaLocalDateTimeGetNano") TIMESTAMP_TO_MILLISECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_milliseconds", + Instant.class, + (Instant ts) -> (long) (newLocalDateTime(ts, UTC).getNano() / 1e+6)); + } else { + return CelFunctionBinding.from( "timestamp_to_milliseconds", Timestamp.class, - (Timestamp ts) -> (long) (newLocalDateTime(ts, UTC).getNano() / 1e+6))), - + (Timestamp ts) -> (long) (newLocalDateTime(ts, UTC).getNano() / 1e+6)); + } + }), @SuppressWarnings("JavaLocalDateTimeGetNano") TIMESTAMP_TO_MILLISECONDS_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_milliseconds_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) (newLocalDateTime(ts, tz).getNano() / 1e+6)); + } else { + return CelFunctionBinding.from( "timestamp_to_milliseconds_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) (newLocalDateTime(ts, tz).getNano() / 1e+6))), + (Timestamp ts, String tz) -> (long) (newLocalDateTime(ts, tz).getNano() / 1e+6)); + } + }), DURATION_TO_MILLISECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_milliseconds", + java.time.Duration.class, + (java.time.Duration d) -> d.toMillis() % 1_000); + } else { + return CelFunctionBinding.from( "duration_to_milliseconds", Duration.class, (Duration arg) -> - ProtoTimeUtils.toMillis(arg) % java.time.Duration.ofSeconds(1).toMillis())); + ProtoTimeUtils.toMillis(arg) % java.time.Duration.ofSeconds(1).toMillis()); + } + }); private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java index 24c285c46..62ee130b6 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getMinutes}. */ @@ -46,22 +47,45 @@ public static GetMinutesFunction create( /** Overloads for the standard function. */ public enum GetMinutesOverload implements CelStandardOverload { TIMESTAMP_TO_MINUTES( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_minutes", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getMinute()); + } else { + return CelFunctionBinding.from( "timestamp_to_minutes", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMinute())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMinute()); + } + }), TIMESTAMP_TO_MINUTES_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_minutes_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getMinute()); + } else { + return CelFunctionBinding.from( "timestamp_to_minutes_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMinute())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMinute()); + } + }), DURATION_TO_MINUTES( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "duration_to_minutes", Duration.class, ProtoTimeUtils::toMinutes)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_minutes", java.time.Duration.class, java.time.Duration::toMinutes); + } else { + return CelFunctionBinding.from( + "duration_to_minutes", Duration.class, ProtoTimeUtils::toMinutes); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java index 34a33815d..99770e69f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function runtime definition for {@code getMonth}. */ @@ -43,18 +44,35 @@ public static GetMonthFunction create(Iterable - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_month", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getMonthValue() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_month", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMonthValue() - 1)), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMonthValue() - 1); + } + }), TIMESTAMP_TO_MONTH_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_month_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getMonthValue() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_month_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMonthValue() - 1)), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMonthValue() - 1); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java index e40fc62c5..f0357990b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getSeconds}. */ @@ -47,22 +48,56 @@ public static GetSecondsFunction create( /** Overloads for the standard function. */ public enum GetSecondsOverload implements CelStandardOverload { TIMESTAMP_TO_SECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_seconds", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getSecond()); + } else { + return CelFunctionBinding.from( "timestamp_to_seconds", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getSecond())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getSecond()); + } + }), TIMESTAMP_TO_SECONDS_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_seconds_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getSecond()); + } else { + return CelFunctionBinding.from( "timestamp_to_seconds_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getSecond())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getSecond()); + } + }), DURATION_TO_SECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "duration_to_seconds", Duration.class, ProtoTimeUtils::toSeconds)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_seconds", + java.time.Duration.class, + dur -> { + long truncatedSeconds = dur.getSeconds(); + // Preserve the existing behavior from protobuf where seconds is truncated towards + // 0 when negative. + if (dur.isNegative() && dur.getNano() > 0) { + truncatedSeconds++; + } + + return truncatedSeconds; + }); + } else { + return CelFunctionBinding.from( + "duration_to_seconds", Duration.class, ProtoTimeUtils::toSeconds); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java index 1eaced251..03be9c41b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the greater equals (>=) operator. */ @@ -81,12 +82,21 @@ public enum GreaterEqualsOverload implements CelStandardOverload { Double.class, (Double x, Double y) -> x >= y)), GREATER_EQUALS_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_equals_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) >= 0); + } else { + return CelFunctionBinding.from( "greater_equals_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) >= 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) >= 0); + } + }), GREATER_EQUALS_INT64( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -99,12 +109,21 @@ public enum GreaterEqualsOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) >= 0)), GREATER_EQUALS_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_equals_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.compareTo(i2) >= 0); + } else { + return CelFunctionBinding.from( "greater_equals_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) >= 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) >= 0); + } + }), GREATER_EQUALS_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java index 9eed5747b..f0db202a8 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the greater (>) operator. */ @@ -73,12 +74,21 @@ public enum GreaterOverload implements CelStandardOverload { CelFunctionBinding.from( "greater_double", Double.class, Double.class, (Double x, Double y) -> x > y)), GREATER_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) > 0); + } else { + return CelFunctionBinding.from( "greater_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) > 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) > 0); + } + }), GREATER_INT64( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -91,12 +101,21 @@ public enum GreaterOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) > 0)), GREATER_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.isAfter(i2)); + } else { + return CelFunctionBinding.from( "greater_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) > 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) > 0); + } + }), GREATER_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java index b29ca37a6..17ad835cb 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java @@ -24,6 +24,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code int} conversion function. */ @@ -104,9 +105,15 @@ public enum IntOverload implements CelStandardOverload { } })), TIMESTAMP_TO_INT64( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "timestamp_to_int64", Timestamp.class, ProtoTimeUtils::toSeconds)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_int64", Instant.class, Instant::getEpochSecond); + } else { + return CelFunctionBinding.from( + "timestamp_to_int64", Timestamp.class, ProtoTimeUtils::toSeconds); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java index 3da852f8a..8ff6f5248 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the less equals (<=) operator. */ @@ -77,12 +78,21 @@ public enum LessEqualsOverload implements CelStandardOverload { CelFunctionBinding.from( "less_equals_double", Double.class, Double.class, (Double x, Double y) -> x <= y)), LESS_EQUALS_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_equals_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) <= 0); + } else { + return CelFunctionBinding.from( "less_equals_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) <= 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) <= 0); + } + }), LESS_EQUALS_INT64( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -95,12 +105,21 @@ public enum LessEqualsOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) <= 0)), LESS_EQUALS_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_equals_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.compareTo(i2) <= 0); + } else { + return CelFunctionBinding.from( "less_equals_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) <= 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) <= 0); + } + }), LESS_EQUALS_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java index 8bae06a85..d348555b7 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the less (<) operator. */ @@ -135,12 +136,21 @@ public enum LessOverload implements CelStandardOverload { Double.class, (UnsignedLong x, Double y) -> ComparisonFunctions.compareUintDouble(x, y) == -1)), LESS_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) < 0); + } else { + return CelFunctionBinding.from( "less_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) < 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) < 0); + } + }), LESS_STRING( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -149,12 +159,21 @@ public enum LessOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) < 0)), LESS_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.isBefore(i2)); + } else { + return CelFunctionBinding.from( "less_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) < 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) < 0); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java index 3525ba5e0..aa1935876 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java @@ -23,10 +23,12 @@ import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code string} conversion function. */ @@ -90,13 +92,24 @@ public enum StringOverload implements CelStandardOverload { } }), TIMESTAMP_TO_STRING( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "timestamp_to_string", Timestamp.class, ProtoTimeUtils::toString)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from("timestamp_to_string", Instant.class, Instant::toString); + } else { + return CelFunctionBinding.from( + "timestamp_to_string", Timestamp.class, ProtoTimeUtils::toString); + } + }), DURATION_TO_STRING( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "duration_to_string", Duration.class, ProtoTimeUtils::toString)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_string", java.time.Duration.class, DateTimeHelpers::toString); + } else { + return CelFunctionBinding.from( + "duration_to_string", Duration.class, ProtoTimeUtils::toString); + } + }), UINT64_TO_STRING( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java index 695650390..a5d1359cc 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java @@ -22,10 +22,12 @@ import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the subtraction (-) operator. */ @@ -60,19 +62,37 @@ public enum SubtractOverload implements CelStandardOverload { } })), SUBTRACT_TIMESTAMP_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "subtract_timestamp_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> java.time.Duration.between(i2, i1)); + } else { + return CelFunctionBinding.from( "subtract_timestamp_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.between(y, x))), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.between(t2, t1)); + } + }), SUBTRACT_TIMESTAMP_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "subtract_timestamp_duration", + Instant.class, + java.time.Duration.class, + DateTimeHelpers::subtract); + } else { + return CelFunctionBinding.from( "subtract_timestamp_duration", Timestamp.class, Duration.class, - ProtoTimeUtils::subtract)), + ProtoTimeUtils::subtract); + } + }), SUBTRACT_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { @@ -106,12 +126,21 @@ public enum SubtractOverload implements CelStandardOverload { CelFunctionBinding.from( "subtract_double", Double.class, Double.class, (Double x, Double y) -> x - y)), SUBTRACT_DURATION_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "subtract_duration_duration", + java.time.Duration.class, + java.time.Duration.class, + DateTimeHelpers::subtract); + } else { + return CelFunctionBinding.from( "subtract_duration_duration", Duration.class, Duration.class, - ProtoTimeUtils::subtract)), + ProtoTimeUtils::subtract); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java index 9b64b78cb..8ed250143 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -19,10 +19,13 @@ import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import java.text.ParseException; +import java.time.Instant; +import java.time.format.DateTimeParseException; import java.util.Arrays; /** Standard function for {@code timestamp} conversion function. */ @@ -42,6 +45,7 @@ public static TimestampFunction create(Iterable @@ -49,19 +53,41 @@ public enum TimestampOverload implements CelStandardOverload { "string_to_timestamp", String.class, (String ts) -> { - try { - return ProtoTimeUtils.parse(ts); - } catch (ParseException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + try { + return DateTimeHelpers.parse(ts); + } catch (DateTimeParseException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } + + } else { + try { + return ProtoTimeUtils.parse(ts); + } catch (ParseException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } } })), TIMESTAMP_TO_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from("timestamp_to_timestamp", Timestamp.class, (Timestamp x) -> x)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_timestamp", Instant.class, (Instant x) -> x); + } else { + return CelFunctionBinding.from( + "timestamp_to_timestamp", Timestamp.class, (Timestamp x) -> x); + } + }), INT64_TO_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "int64_to_timestamp", Long.class, ProtoTimeUtils::fromSecondsToTimestamp)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "int64_to_timestamp", Long.class, Instant::ofEpochSecond); + } else { + return CelFunctionBinding.from( + "int64_to_timestamp", Long.class, ProtoTimeUtils::fromSecondsToTimestamp); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java index 2896e36f4..a960b6249 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java @@ -51,6 +51,8 @@ import dev.cel.runtime.standard.EqualsOperator; import dev.cel.runtime.standard.IntFunction; import dev.cel.runtime.standard.IntFunction.IntOverload; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import org.junit.Test; @@ -705,16 +707,8 @@ public void eval_protoMessage_mapFields(String checkedExpr) throws Exception { ImmutableMap.of(true, 9.1d, false, 10.2d), ImmutableMap.of(true, 11.3d, false, 12.4d), ImmutableMap.of(true, 1L, false, 2L), // Note: Enums are converted into integers - ImmutableMap.of( - true, - ProtoTimeUtils.fromSecondsToDuration(15), - false, - ProtoTimeUtils.fromSecondsToDuration(16)), - ImmutableMap.of( - true, - ProtoTimeUtils.fromSecondsToTimestamp(17), - false, - ProtoTimeUtils.fromSecondsToTimestamp(18))) + ImmutableMap.of(true, Duration.ofSeconds(15), false, Duration.ofSeconds(16)), + ImmutableMap.of(true, Instant.ofEpochSecond(17), false, Instant.ofEpochSecond(18))) .inOrder(); } diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java index 4083ae494..4ffe0941c 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java @@ -25,14 +25,12 @@ import com.google.protobuf.ByteString; import com.google.protobuf.BytesValue; import com.google.protobuf.DoubleValue; -import com.google.protobuf.Duration; import com.google.protobuf.FloatValue; import com.google.protobuf.Int32Value; import com.google.protobuf.Int64Value; import com.google.protobuf.ListValue; import com.google.protobuf.StringValue; import com.google.protobuf.Struct; -import com.google.protobuf.Timestamp; import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; import com.google.protobuf.util.Values; @@ -63,6 +61,8 @@ import dev.cel.testing.testdata.SimpleEnum; import dev.cel.testing.testdata.SingleFileCelDescriptor; import dev.cel.testing.testdata.SingleFileProto.SingleFile; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -301,7 +301,7 @@ public void fieldSelection_duration() throws Exception { Duration result = (Duration) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); - assertThat(result).isEqualTo(ProtoTimeUtils.fromSecondsToDuration(600)); + assertThat(result).isEqualTo(Duration.ofMinutes(10)); } @Test @@ -313,9 +313,9 @@ public void fieldSelection_timestamp() throws Exception { .setSingleTimestamp(ProtoTimeUtils.fromSecondsToTimestamp(50)) .build(); - Timestamp result = (Timestamp) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + Instant result = (Instant) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); - assertThat(result).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(50)); + assertThat(result).isEqualTo(Instant.ofEpochSecond(50)); } @Test diff --git a/runtime/src/test/resources/arithmDuration.baseline b/runtime/src/test/resources/arithmDuration.baseline index 3422aced4..9aa73405d 100644 --- a/runtime/src/test/resources/arithmDuration.baseline +++ b/runtime/src/test/resources/arithmDuration.baseline @@ -9,13 +9,7 @@ declare d3 { value google.protobuf.Duration } =====> -bindings: {d3=seconds: 25 -nanos: 45 -} +> {d2=seconds: 10 -nanos: 20 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {d3=PT25.000000045S} +> {d2=PT10.00000002S} +> {d1=PT15.000000025S} result: true Source: d3 - d1 == d2 @@ -29,11 +23,5 @@ declare d3 { value google.protobuf.Duration } =====> -bindings: {d3=seconds: 25 -nanos: 45 -} +> {d2=seconds: 10 -nanos: 20 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {d3=PT25.000000045S} +> {d2=PT10.00000002S} +> {d1=PT15.000000025S} result: true \ No newline at end of file diff --git a/runtime/src/test/resources/arithmTimestamp.baseline b/runtime/src/test/resources/arithmTimestamp.baseline index d7a1f318c..7ddf702ae 100644 --- a/runtime/src/test/resources/arithmTimestamp.baseline +++ b/runtime/src/test/resources/arithmTimestamp.baseline @@ -9,13 +9,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true Source: ts1 - d1 == ts2 @@ -29,13 +23,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true Source: ts2 + d1 == ts1 @@ -49,13 +37,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true Source: d1 + ts2 == ts1 @@ -69,11 +51,5 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true \ No newline at end of file diff --git a/runtime/src/test/resources/duration.baseline b/runtime/src/test/resources/duration.baseline index 9737a23c7..89d058086 100644 --- a/runtime/src/test/resources/duration.baseline +++ b/runtime/src/test/resources/duration.baseline @@ -6,11 +6,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: false Source: d1 < d2 @@ -21,11 +17,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 < d2 @@ -36,11 +28,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 < d2 @@ -51,11 +39,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: true Source: d1 < d2 @@ -66,11 +50,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: true Source: d1 <= d2 @@ -81,11 +61,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: false Source: d1 <= d2 @@ -96,11 +72,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 <= d2 @@ -111,11 +83,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 <= d2 @@ -126,11 +94,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: true Source: d1 <= d2 @@ -141,11 +105,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: true Source: d1 > d2 @@ -156,11 +116,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: true Source: d1 > d2 @@ -171,11 +127,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 > d2 @@ -186,11 +138,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 > d2 @@ -201,11 +149,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: false Source: d1 > d2 @@ -216,11 +160,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: false Source: d1 >= d2 @@ -231,11 +171,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: true Source: d1 >= d2 @@ -246,11 +182,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 >= d2 @@ -261,11 +193,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 >= d2 @@ -276,11 +204,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: false Source: d1 >= d2 @@ -291,9 +215,5 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: false \ No newline at end of file diff --git a/runtime/src/test/resources/durationFunctions.baseline b/runtime/src/test/resources/durationFunctions.baseline index 8e942e0d8..9bc61d4ae 100644 --- a/runtime/src/test/resources/durationFunctions.baseline +++ b/runtime/src/test/resources/durationFunctions.baseline @@ -3,9 +3,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 25 Source: d1.getHours() @@ -13,9 +11,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -25 Source: d1.getMinutes() @@ -23,9 +19,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 1559 Source: d1.getMinutes() @@ -33,9 +27,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -1559 Source: d1.getSeconds() @@ -43,9 +35,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 93541 Source: d1.getSeconds() @@ -53,9 +43,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -93541 Source: d1.getMilliseconds() @@ -63,9 +51,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 11 Source: d1.getMilliseconds() @@ -73,9 +59,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -11 Source: d1.getHours() < val @@ -86,9 +70,7 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {val=30} +> {d1=PT25H59M1.011S} result: true Source: d1.getMinutes() > val @@ -99,9 +81,7 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {val=30} +> {d1=PT25H59M1.011S} result: true Source: d1.getSeconds() > val @@ -112,9 +92,7 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {val=30} +> {d1=PT25H59M1.011S} result: true Source: d1.getMilliseconds() < val @@ -125,7 +103,5 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} -result: true \ No newline at end of file +bindings: {val=30} +> {d1=PT25H59M1.011S} +result: true diff --git a/runtime/src/test/resources/dynamicMessage_adapted.baseline b/runtime/src/test/resources/dynamicMessage_adapted.baseline index 789a39488..b012c3727 100644 --- a/runtime/src/test/resources/dynamicMessage_adapted.baseline +++ b/runtime/src/test/resources/dynamicMessage_adapted.baseline @@ -690,9 +690,7 @@ list_value { } } } -result: seconds: 10 -nanos: 20 - +result: PT10.00000002S Source: msg.single_timestamp declare msg { @@ -755,9 +753,7 @@ list_value { } } } -result: seconds: 100 -nanos: 200 - +result: 1970-01-01T00:01:40.000000200Z Source: msg.single_value declare msg { diff --git a/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline b/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline index 36dac04c3..8f91353b4 100644 --- a/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline +++ b/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline @@ -103,8 +103,7 @@ declare dur { bindings: {dur=type_url: "type.googleapis.com/google.protobuf.Duration" value: "\bd" } -result: seconds: 100 - +result: PT1M40S Source: TestAllTypes { single_any: any_packed_test_msg }.single_any declare any_packed_test_msg { diff --git a/runtime/src/test/resources/timeConversions.baseline b/runtime/src/test/resources/timeConversions.baseline index 68892ee2d..12a521441 100644 --- a/runtime/src/test/resources/timeConversions.baseline +++ b/runtime/src/test/resources/timeConversions.baseline @@ -4,9 +4,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 63126020 -nanos: 21000000 - +result: 1972-01-01T15:00:20.021Z Source: timestamp(123) declare t1 { @@ -14,8 +12,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 123 - +result: 1970-01-01T00:02:03Z Source: duration("15.11s") declare t1 { @@ -23,17 +20,14 @@ declare t1 { } =====> bindings: {} -result: seconds: 15 -nanos: 110000000 - +result: PT15.11S Source: int(t1) == 100 declare t1 { value google.protobuf.Timestamp } =====> -bindings: {t1=seconds: 100 -} +bindings: {t1=1970-01-01T00:01:40Z} result: true Source: duration("1h2m3.4s") @@ -42,9 +36,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 3723 -nanos: 400000000 - +result: PT1H2M3.4S Source: duration(duration('15.0s')) declare t1 { @@ -52,8 +44,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 15 - +result: PT15S Source: timestamp(timestamp(123)) declare t1 { @@ -61,4 +52,4 @@ declare t1 { } =====> bindings: {} -result: seconds: 123 \ No newline at end of file +result: 1970-01-01T00:02:03Z \ No newline at end of file diff --git a/runtime/src/test/resources/timestamp.baseline b/runtime/src/test/resources/timestamp.baseline index c215b581f..828e7f912 100644 --- a/runtime/src/test/resources/timestamp.baseline +++ b/runtime/src/test/resources/timestamp.baseline @@ -6,11 +6,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 < t2 @@ -21,11 +17,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 < t2 @@ -36,11 +28,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 < t2 @@ -51,11 +39,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: true Source: t1 < t2 @@ -66,11 +50,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} result: true Source: t1 <= t2 @@ -81,11 +61,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 <= t2 @@ -96,11 +72,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 <= t2 @@ -111,11 +83,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 <= t2 @@ -126,11 +94,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: true Source: t1 <= t2 @@ -141,11 +105,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} result: true Source: t1 > t2 @@ -156,11 +116,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 > t2 @@ -171,11 +127,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 > t2 @@ -186,11 +138,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 > t2 @@ -201,11 +149,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: false Source: t1 > t2 @@ -216,11 +160,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} result: false Source: t1 >= t2 @@ -231,11 +171,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 >= t2 @@ -246,11 +182,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 >= t2 @@ -261,11 +193,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 >= t2 @@ -276,11 +204,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: false Source: t1 >= t2 @@ -291,9 +215,5 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} -result: false +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} +result: false \ No newline at end of file diff --git a/runtime/src/test/resources/timestampFunctions.baseline b/runtime/src/test/resources/timestampFunctions.baseline index 322137892..e932867e8 100644 --- a/runtime/src/test/resources/timestampFunctions.baseline +++ b/runtime/src/test/resources/timestampFunctions.baseline @@ -3,9 +3,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1969 Source: ts1.getFullYear() @@ -13,9 +11,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1970 Source: ts1.getFullYear() @@ -23,8 +19,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 1969 Source: ts1.getFullYear("Indian/Cocos") @@ -32,9 +27,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1970 Source: ts1.getFullYear("2:00") @@ -42,9 +35,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1970 Source: ts1.getMonth("America/Los_Angeles") @@ -52,9 +43,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMonth() @@ -62,9 +51,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMonth() @@ -72,8 +59,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 11 Source: ts1.getMonth("Indian/Cocos") @@ -81,9 +67,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMonth("-8:15") @@ -91,9 +75,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getDayOfYear("America/Los_Angeles") @@ -101,9 +83,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 364 Source: ts1.getDayOfYear() @@ -111,9 +91,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfYear() @@ -121,8 +99,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 364 Source: ts1.getDayOfYear("Indian/Cocos") @@ -130,9 +107,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfYear("-9:00") @@ -140,9 +115,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 364 Source: ts1.getDayOfMonth("America/Los_Angeles") @@ -150,9 +123,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 30 Source: ts1.getDayOfMonth() @@ -160,9 +131,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfMonth() @@ -170,8 +139,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 30 Source: ts1.getDayOfMonth("Indian/Cocos") @@ -179,9 +147,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfMonth("8:00") @@ -189,9 +155,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDate("America/Los_Angeles") @@ -199,9 +163,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 31 Source: ts1.getDate() @@ -209,9 +171,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getDate() @@ -219,8 +179,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 31 Source: ts1.getDate("Indian/Cocos") @@ -228,9 +187,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getDate("9:30") @@ -238,9 +195,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getDayOfWeek("America/Los_Angeles") @@ -248,8 +203,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 6 Source: ts1.getDayOfWeek() @@ -257,8 +211,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 0 Source: ts1.getDayOfWeek() @@ -266,8 +219,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 3 Source: ts1.getDayOfWeek("Indian/Cocos") @@ -275,8 +227,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 0 Source: ts1.getDayOfWeek("-9:30") @@ -284,8 +235,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 6 Source: ts1.getHours("America/Los_Angeles") @@ -293,9 +243,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 16 Source: ts1.getHours() @@ -303,9 +251,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getHours() @@ -313,8 +259,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 23 Source: ts1.getHours("Indian/Cocos") @@ -322,9 +267,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 6 Source: ts1.getHours("6:30") @@ -332,9 +275,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 6 Source: ts1.getMinutes("America/Los_Angeles") @@ -342,9 +283,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMinutes() @@ -352,9 +291,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMinutes() @@ -362,8 +299,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 59 Source: ts1.getMinutes("Indian/Cocos") @@ -371,9 +307,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 30 Source: ts1.getMinutes("-8:00") @@ -381,9 +315,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getSeconds("America/Los_Angeles") @@ -391,9 +323,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getSeconds() @@ -401,9 +331,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getSeconds() @@ -411,8 +339,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 59 Source: ts1.getSeconds("Indian/Cocos") @@ -420,9 +347,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getSeconds("-8:00") @@ -430,9 +355,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getMilliseconds("America/Los_Angeles") @@ -440,9 +363,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMilliseconds() @@ -450,9 +371,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMilliseconds("Indian/Cocos") @@ -460,9 +379,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMilliseconds("-8:00") @@ -470,9 +387,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getFullYear() < val @@ -483,9 +398,7 @@ declare val { value int } =====> -bindings: {val=2013} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=2013} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getMonth() < val @@ -496,9 +409,7 @@ declare val { value int } =====> -bindings: {val=12} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=12} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDayOfYear() < val @@ -509,9 +420,7 @@ declare val { value int } =====> -bindings: {val=13} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=13} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDayOfMonth() < val @@ -522,9 +431,7 @@ declare val { value int } =====> -bindings: {val=10} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=10} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDate() < val @@ -535,9 +442,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDayOfWeek() < val @@ -548,9 +453,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getHours() < val @@ -561,9 +464,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getMinutes() < val @@ -574,9 +475,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getSeconds() < val @@ -587,9 +486,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getMilliseconds() < val @@ -600,7 +497,5 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true \ No newline at end of file diff --git a/runtime/src/test/resources/typeComparisons.baseline b/runtime/src/test/resources/typeComparisons.baseline index ae3e8aa01..e8fc3473f 100644 --- a/runtime/src/test/resources/typeComparisons.baseline +++ b/runtime/src/test/resources/typeComparisons.baseline @@ -42,3 +42,14 @@ Source: type(null) == null_type =====> bindings: {} result: true + +Source: type(duration) == google.protobuf.Duration && type(timestamp) == google.protobuf.Timestamp +declare duration { + value dyn +} +declare timestamp { + value dyn +} +=====> +bindings: {duration=PT0S, timestamp=1970-01-01T00:00:00Z} +result: true diff --git a/runtime/src/test/resources/unknownResultSet.baseline b/runtime/src/test/resources/unknownResultSet.baseline index 8c7183761..ad97004f3 100644 --- a/runtime/src/test/resources/unknownResultSet.baseline +++ b/runtime/src/test/resources/unknownResultSet.baseline @@ -60,7 +60,7 @@ declare x { } =====> bindings: {} -error: evaluation error at test_location:31: Failed to parse timestamp: invalid timestamp "bad timestamp string" +error: evaluation error at test_location:31: Text 'bad timestamp string' could not be parsed at index 0 error_code: BAD_FORMAT Source: x.single_int32 == 1 || x.single_string == "test" @@ -125,7 +125,7 @@ declare x { } =====> bindings: {} -error: evaluation error at test_location:31: Failed to parse timestamp: invalid timestamp "bad timestamp string" +error: evaluation error at test_location:31: Text 'bad timestamp string' could not be parsed at index 0 error_code: BAD_FORMAT Source: x.single_int32.f(1) diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index cd0da5b80..147c5baf9 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -37,7 +37,6 @@ import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.DoubleValue; -import com.google.protobuf.Duration; import com.google.protobuf.DynamicMessage; import com.google.protobuf.FloatValue; import com.google.protobuf.Int32Value; @@ -83,6 +82,8 @@ import dev.cel.runtime.CelVariableResolver; import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -344,9 +345,9 @@ public void arithmTimestamp() { declareVariable("ts1", SimpleType.TIMESTAMP); declareVariable("ts2", SimpleType.TIMESTAMP); declareVariable("d1", SimpleType.DURATION); - Duration d1 = Duration.newBuilder().setSeconds(15).setNanos(25).build(); - Timestamp ts1 = Timestamp.newBuilder().setSeconds(25).setNanos(35).build(); - Timestamp ts2 = Timestamp.newBuilder().setSeconds(10).setNanos(10).build(); + Duration d1 = Duration.ofSeconds(15, 25); + Instant ts1 = Instant.ofEpochSecond(25, 35); + Instant ts2 = Instant.ofEpochSecond(10, 10); CelVariableResolver resolver = extend( extend(ImmutableMap.of("d1", d1), ImmutableMap.of("ts1", ts1)), @@ -371,9 +372,9 @@ public void arithmDuration() { declareVariable("d1", SimpleType.DURATION); declareVariable("d2", SimpleType.DURATION); declareVariable("d3", SimpleType.DURATION); - Duration d1 = Duration.newBuilder().setSeconds(15).setNanos(25).build(); - Duration d2 = Duration.newBuilder().setSeconds(10).setNanos(20).build(); - Duration d3 = Duration.newBuilder().setSeconds(25).setNanos(45).build(); + java.time.Duration d1 = java.time.Duration.ofSeconds(15, 25); + java.time.Duration d2 = java.time.Duration.ofSeconds(10, 20); + java.time.Duration d3 = java.time.Duration.ofSeconds(25, 45); CelVariableResolver resolver = extend( @@ -606,9 +607,9 @@ public void has() throws Exception { public void duration() throws Exception { declareVariable("d1", SimpleType.DURATION); declareVariable("d2", SimpleType.DURATION); - Duration d1010 = Duration.newBuilder().setSeconds(10).setNanos(10).build(); - Duration d1009 = Duration.newBuilder().setSeconds(10).setNanos(9).build(); - Duration d0910 = Duration.newBuilder().setSeconds(9).setNanos(10).build(); + java.time.Duration d1010 = java.time.Duration.ofSeconds(10, 10); + java.time.Duration d1009 = java.time.Duration.ofSeconds(10, 9); + java.time.Duration d0910 = java.time.Duration.ofSeconds(9, 10); container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); source = "d1 < d2"; @@ -644,9 +645,9 @@ public void duration() throws Exception { public void timestamp() throws Exception { declareVariable("t1", SimpleType.TIMESTAMP); declareVariable("t2", SimpleType.TIMESTAMP); - Timestamp ts1010 = Timestamp.newBuilder().setSeconds(10).setNanos(10).build(); - Timestamp ts1009 = Timestamp.newBuilder().setSeconds(10).setNanos(9).build(); - Timestamp ts0910 = Timestamp.newBuilder().setSeconds(9).setNanos(10).build(); + Instant ts1010 = Instant.ofEpochSecond(10, 10); + Instant ts1009 = Instant.ofEpochSecond(10, 9); + Instant ts0910 = Instant.ofEpochSecond(9, 10); container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); source = "t1 < t2"; @@ -691,7 +692,7 @@ public void packUnpackAny() { declareVariable( "message", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); declareVariable("list", ListType.create(SimpleType.DYN)); - Duration duration = ProtoTimeUtils.fromSecondsToDuration(100); + com.google.protobuf.Duration duration = ProtoTimeUtils.fromSecondsToDuration(100); Any any = Any.pack(duration); TestAllTypes message = TestAllTypes.newBuilder().setSingleAny(any).build(); @@ -960,10 +961,11 @@ public void namespacedVariables() { @Test public void durationFunctions() { declareVariable("d1", SimpleType.DURATION); - Duration d1 = - Duration.newBuilder().setSeconds(25 * 3600 + 59 * 60 + 1).setNanos(11000000).build(); - Duration d2 = - Duration.newBuilder().setSeconds(-(25 * 3600 + 59 * 60 + 1)).setNanos(-11000000).build(); + long totalSeconds = 25 * 3600 + 59 * 60 + 1; + long nanos = 11000000; + Duration d1 = Duration.ofSeconds(totalSeconds, nanos); + Duration d2 = Duration.ofSeconds(-totalSeconds, -nanos); + container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); source = "d1.getHours()"; @@ -997,8 +999,8 @@ public void durationFunctions() { public void timestampFunctions() { declareVariable("ts1", SimpleType.TIMESTAMP); container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); - Timestamp ts1 = Timestamp.newBuilder().setSeconds(1).setNanos(11000000).build(); - Timestamp ts2 = ProtoTimeUtils.fromSecondsToTimestamp(-1); + Instant ts1 = Instant.ofEpochSecond(1, 11000000); + Instant ts2 = Instant.ofEpochSecond(-1, 0); source = "ts1.getFullYear(\"America/Los_Angeles\")"; runTest(ImmutableMap.of("ts1", ts1)); @@ -1050,7 +1052,7 @@ public void timestampFunctions() { source = "ts1.getDate(\"9:30\")"; runTest(ImmutableMap.of("ts1", ts1)); - Timestamp tsSunday = ProtoTimeUtils.fromSecondsToTimestamp(3 * 24 * 3600); + Instant tsSunday = Instant.ofEpochSecond(3 * 24 * 3600); source = "ts1.getDayOfWeek(\"America/Los_Angeles\")"; runTest(ImmutableMap.of("ts1", tsSunday)); source = "ts1.getDayOfWeek()"; @@ -1372,7 +1374,7 @@ public void timeConversions() { runTest(); source = "int(t1) == 100"; - runTest(ImmutableMap.of("t1", ProtoTimeUtils.fromSecondsToTimestamp(100))); + runTest(ImmutableMap.of("t1", Instant.ofEpochSecond(100))); source = "duration(\"1h2m3.4s\")"; runTest(); @@ -1927,6 +1929,15 @@ public void typeComparisons() { // Test whether null resolves to null_type. source = "type(null) == null_type"; runTest(); + + // Test runtime resolution of types + source = + "type(duration) == google.protobuf.Duration && " + + "type(timestamp) == google.protobuf.Timestamp"; + // Intentionally declare as dyns + declareVariable("duration", SimpleType.DYN); + declareVariable("timestamp", SimpleType.DYN); + runTest(ImmutableMap.of("duration", java.time.Duration.ZERO, "timestamp", Instant.EPOCH)); } @Test @@ -2075,7 +2086,8 @@ public void dynamicMessage_adapted() throws Exception { .setSingleStringWrapper(StringValue.of("hello")) .setSingleUint32Wrapper(UInt32Value.of(12)) .setSingleUint64Wrapper(UInt64Value.of(34)) - .setSingleDuration(Duration.newBuilder().setSeconds(10).setNanos(20)) + .setSingleDuration( + com.google.protobuf.Duration.newBuilder().setSeconds(10).setNanos(20)) .setSingleTimestamp(Timestamp.newBuilder().setSeconds(100).setNanos(200)) .setSingleValue(Value.newBuilder().setStringValue("a")) .setSingleStruct( @@ -2127,10 +2139,10 @@ public void dynamicMessage_adapted() throws Exception { .isInstanceOf(BASE_CEL_OPTIONS.enableUnsignedLongs() ? UnsignedLong.class : Long.class); source = "msg.single_duration"; - assertThat(runTest(input)).isInstanceOf(Duration.class); + assertThat(runTest(input)).isInstanceOf(java.time.Duration.class); source = "msg.single_timestamp"; - assertThat(runTest(input)).isInstanceOf(Timestamp.class); + assertThat(runTest(input)).isInstanceOf(Instant.class); source = "msg.single_value"; assertThat(runTest(input)).isInstanceOf(String.class); diff --git a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel index a7e4c628c..3e32bbd2a 100644 --- a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel @@ -17,6 +17,7 @@ java_library( deps = [ "//common:cel_descriptors", "//common/internal:default_instance_message_factory", + "//common/internal:proto_time_utils", "//common/types", "//common/types:type_providers", "//common/values", diff --git a/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java b/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java index 0b27fa452..041c0f52d 100644 --- a/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java +++ b/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java @@ -31,6 +31,7 @@ import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelDescriptors; import dev.cel.common.internal.DefaultInstanceMessageFactory; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.CelType; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; @@ -41,6 +42,8 @@ import dev.cel.runtime.CelUnknownSet; import dev.cel.testing.testrunner.RegistryUtils; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -252,6 +255,19 @@ public static Value toValue(Object object, CelType type) throws Exception { } return Value.newBuilder().setMapValue(builder.build()).build(); } + + if (object instanceof Instant) { + return Value.newBuilder() + .setObjectValue(Any.pack(ProtoTimeUtils.toProtoTimestamp((Instant) object))) + .build(); + } + + if (object instanceof Duration) { + return Value.newBuilder() + .setObjectValue(Any.pack(ProtoTimeUtils.toProtoDuration((Duration) object))) + .build(); + } + if (object instanceof Message) { return Value.newBuilder().setObjectValue(Any.pack((Message) object)).build(); }