Skip to content

Commit 506c2b6

Browse files
l46kokcopybara-github
authored andcommitted
Restore the behavior for protobuf timestamp/duration field values to reside outside RFC3339 range
Related: #890 PiperOrigin-RevId: 845523949
1 parent 298c386 commit 506c2b6

File tree

6 files changed

+100
-11
lines changed

6 files changed

+100
-11
lines changed

common/src/main/java/dev/cel/common/internal/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ cel_android_library(
429429
deps = [
430430
"//common/annotations",
431431
"@maven//:com_google_errorprone_error_prone_annotations",
432+
"@maven//:com_google_guava_guava",
432433
"@maven_android//:com_google_guava_guava",
433434
"@maven_android//:com_google_protobuf_protobuf_javalite",
434435
],

common/src/main/java/dev/cel/common/internal/ProtoTimeUtils.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static com.google.common.math.LongMath.checkedMultiply;
1919
import static com.google.common.math.LongMath.checkedSubtract;
2020

21+
import com.google.common.annotations.VisibleForTesting;
2122
import com.google.common.base.Strings;
2223
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2324
import com.google.protobuf.Duration;
@@ -49,12 +50,16 @@
4950
public final class ProtoTimeUtils {
5051

5152
// Timestamp for "0001-01-01T00:00:00Z"
52-
private static final long TIMESTAMP_SECONDS_MIN = -62135596800L;
53-
53+
@VisibleForTesting
54+
static final long TIMESTAMP_SECONDS_MIN = -62135596800L;
5455
// Timestamp for "9999-12-31T23:59:59Z"
55-
private static final long TIMESTAMP_SECONDS_MAX = 253402300799L;
56-
private static final long DURATION_SECONDS_MIN = -315576000000L;
57-
private static final long DURATION_SECONDS_MAX = 315576000000L;
56+
@VisibleForTesting
57+
static final long TIMESTAMP_SECONDS_MAX = 253402300799L;
58+
@VisibleForTesting
59+
static final long DURATION_SECONDS_MIN = -315576000000L;
60+
@VisibleForTesting
61+
static final long DURATION_SECONDS_MAX = 315576000000L;
62+
5863
private static final int MILLIS_PER_SECOND = 1000;
5964

6065
private static final int NANOS_PER_SECOND = 1000000000;
@@ -344,7 +349,8 @@ public static Timestamp parse(String value) throws ParseException {
344349
}
345350
}
346351
try {
347-
return normalizedTimestamp(seconds, nanos);
352+
Timestamp timestamp = normalizedTimestamp(seconds, nanos);
353+
return checkValid(timestamp);
348354
} catch (IllegalArgumentException e) {
349355
ParseException ex =
350356
new ParseException(
@@ -532,8 +538,7 @@ private static Timestamp normalizedTimestamp(long seconds, int nanos) {
532538
nanos = nanos + NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding)
533539
seconds = checkedSubtract(seconds, 1);
534540
}
535-
Timestamp timestamp = Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
536-
return checkValid(timestamp);
541+
return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
537542
}
538543

539544
private static Duration normalizedDuration(long seconds, int nanos) {
@@ -549,8 +554,7 @@ private static Duration normalizedDuration(long seconds, int nanos) {
549554
nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting)
550555
seconds++; // no overflow since seconds is negative (and we're incrementing)
551556
}
552-
Duration duration = Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build();
553-
return checkValid(duration);
557+
return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build();
554558
}
555559

556560
private static String formatNanos(int nanos) {

common/src/test/java/dev/cel/common/internal/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ java_library(
2828
"//common/internal:errors",
2929
"//common/internal:proto_equality",
3030
"//common/internal:proto_message_factory",
31+
"//common/internal:proto_time_utils",
3132
"//common/internal:well_known_proto",
3233
"//common/src/test/resources:default_instance_message_test_protos_java_proto",
3334
"//common/src/test/resources:service_conflicting_name_java_proto",
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.common.internal;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static dev.cel.common.internal.ProtoTimeUtils.DURATION_SECONDS_MAX;
19+
import static dev.cel.common.internal.ProtoTimeUtils.DURATION_SECONDS_MIN;
20+
21+
import com.google.protobuf.Duration;
22+
import com.google.protobuf.Timestamp;
23+
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
24+
import java.time.Instant;
25+
import org.junit.Test;
26+
import org.junit.runner.RunWith;
27+
28+
@RunWith(TestParameterInjector.class)
29+
public class ProtoTimeUtilsTest {
30+
31+
@Test
32+
public void toJavaInstant_overRfc3339Range() {
33+
Timestamp ts =
34+
Timestamp.newBuilder().setSeconds(ProtoTimeUtils.TIMESTAMP_SECONDS_MAX + 1).build();
35+
36+
Instant instant = ProtoTimeUtils.toJavaInstant(ts);
37+
38+
assertThat(instant).isEqualTo(Instant.ofEpochSecond(ProtoTimeUtils.TIMESTAMP_SECONDS_MAX + 1));
39+
}
40+
41+
@Test
42+
public void toJavaInstant_underRfc3339Range() {
43+
Timestamp ts =
44+
Timestamp.newBuilder().setSeconds(ProtoTimeUtils.TIMESTAMP_SECONDS_MIN - 1).build();
45+
46+
Instant instant = ProtoTimeUtils.toJavaInstant(ts);
47+
48+
assertThat(instant).isEqualTo(Instant.ofEpochSecond(ProtoTimeUtils.TIMESTAMP_SECONDS_MIN - 1));
49+
}
50+
51+
@Test
52+
public void toJavaDuration_overRfc3339Range() {
53+
Duration d = Duration.newBuilder()
54+
.setSeconds(DURATION_SECONDS_MAX + 1)
55+
.build();
56+
57+
java.time.Duration duration = ProtoTimeUtils.toJavaDuration(d);
58+
59+
assertThat(duration).isEqualTo(java.time.Duration.ofSeconds(DURATION_SECONDS_MAX + 1));
60+
}
61+
62+
@Test
63+
public void toJavaDuration_underRfc3339Range() {
64+
Duration d = Duration.newBuilder()
65+
.setSeconds(DURATION_SECONDS_MIN - 1)
66+
.build();
67+
68+
java.time.Duration duration = ProtoTimeUtils.toJavaDuration(d);
69+
70+
assertThat(duration).isEqualTo(java.time.Duration.ofSeconds(DURATION_SECONDS_MIN - 1));
71+
}
72+
}

runtime/src/test/resources/wrappers.baseline

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,9 @@ declare dyn_var {
8383
}
8484
=====>
8585
bindings: {dyn_var=NULL_VALUE}
86-
result: NULL_VALUE
86+
result: NULL_VALUE
87+
88+
Source: google.protobuf.Timestamp{ seconds: 253402300800 }
89+
=====>
90+
bindings: {}
91+
result: +10000-01-01T00:00:00Z

testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,6 +2005,12 @@ public void wrappers() throws Exception {
20052005
declareVariable("dyn_var", SimpleType.DYN);
20062006
source = "dyn_var";
20072007
runTest(ImmutableMap.of("dyn_var", NullValue.NULL_VALUE));
2008+
2009+
clearAllDeclarations();
2010+
// Currently allowed, but will be an error
2011+
// See https://github.com/google/cel-spec/pull/501
2012+
source = "google.protobuf.Timestamp{ seconds: 253402300800 }";
2013+
runTest();
20082014
}
20092015

20102016
@Test

0 commit comments

Comments
 (0)