From 662f0dc3ff7bac808c66360fb38bf218d50bebf6 Mon Sep 17 00:00:00 2001 From: Sean Huh Date: Fri, 23 Oct 2026 18:31:18 +0000 Subject: [PATCH] Align timestamp and duration bounds with protobuf well-known types --- doc/langdef.md | 9 +- tests/simple/testdata/timestamps.textproto | 126 +++++++++++++++++- tests/simple/testdata/wrappers.textproto | 146 +++++++++++++++++++++ 3 files changed, 275 insertions(+), 6 deletions(-) diff --git a/doc/langdef.md b/doc/langdef.md index 2e9b3d47..fc9eab4c 100644 --- a/doc/langdef.md +++ b/doc/langdef.md @@ -1198,9 +1198,14 @@ raised for conversions which exceed the range of the target type. There are a few additional considerations to keep in mind with respect to how and when certain types will overflow: -* Duration values are limited to a single int64 value, or roughly +-290 years. +* Duration values are limited to the range supported by + `google.protobuf.Duration`: `seconds` in `[-315576000000, 315576000000]` and + `nanos` in `[-999999999, 999999999]` (with the same sign as `seconds`), or + roughly +-10,000 years. * Timestamp values are limited to the range of values which can be serialized - as a string: ["0001-01-01T00:00:00Z", "9999-12-31T23:59:59.999999999Z"]. + as a string: ["0001-01-01T00:00:00Z", "9999-12-31T23:59:59.999999999Z"] + (`seconds` in `[-62135596800, 253402300799]` and `nanos` in + `[0, 999999999]`). * Double to int conversions are limited to (minInt, maxInt) non-inclusive. Note, that whether the minimum or maximum integer value will roundtrip successfully diff --git a/tests/simple/testdata/timestamps.textproto b/tests/simple/testdata/timestamps.textproto index 89e873d7..8c64132c 100644 --- a/tests/simple/testdata/timestamps.textproto +++ b/tests/simple/testdata/timestamps.textproto @@ -40,6 +40,40 @@ section { expr: "string(duration('1000000s'))" value: { string_value: "1000000s" } } + test { + name: "toString_duration_max" + expr: "string(duration('315576000000s999999999ns'))" + value: { string_value: "315576000000.999999999s" } + } + test { + name: "toString_duration_min" + expr: "string(duration('-315576000000s999999999ns'))" + value: { string_value: "-315576000000.999999999s" } + } + test { + name: "from_string_max" + expr: "duration('315576000000s999999999ns')" + value { + object_value { + [type.googleapis.com/google.protobuf.Duration] { + seconds: 315576000000 + nanos: 999999999 + } + } + } + } + test { + name: "from_string_min" + expr: "duration('-315576000000s999999999ns')" + value { + object_value { + [type.googleapis.com/google.protobuf.Duration] { + seconds: -315576000000 + nanos: -999999999 + } + } + } + } test { name: "toType_duration" expr: "type(duration('1000000s'))" @@ -263,6 +297,26 @@ section { expr: "duration('900s') - duration('42s') == duration('858s')" value: { bool_value: true } } + test { + name: "add_max_duration_to_min_time" + expr: "timestamp('0001-01-01T00:00:00Z') + duration('315537897599s999999999ns') == timestamp('9999-12-31T23:59:59.999999999Z')" + value: { bool_value: true } + } + test { + name: "subtract_max_duration_from_max_time" + expr: "timestamp('9999-12-31T23:59:59.999999999Z') - duration('315537897599s999999999ns') == timestamp('0001-01-01T00:00:00Z')" + value: { bool_value: true } + } + test { + name: "add_duration_to_duration_max" + expr: "duration('315576000000s') + duration('999999999ns') == duration('315576000000s999999999ns')" + value: { bool_value: true } + } + test { + name: "subtract_duration_from_duration_min" + expr: "duration('-315576000000s') - duration('999999999ns') == duration('-315576000000s999999999ns')" + value: { bool_value: true } + } } section { name: "comparisons" @@ -432,15 +486,23 @@ section { test { name: "sub_time_duration_over" expr: "timestamp('9999-12-31T23:59:59Z') - timestamp('0001-01-01T00:00:00Z')" - eval_error { - errors { message: "range" } + value { + object_value { + [type.googleapis.com/google.protobuf.Duration] { + seconds: 315537897599 + } + } } } test { name: "sub_time_duration_under" expr: "timestamp('0001-01-01T00:00:00Z') - timestamp('9999-12-31T23:59:59Z')" - eval_error { - errors { message: "range" } + value { + object_value { + [type.googleapis.com/google.protobuf.Duration] { + seconds: -315537897599 + } + } } } } @@ -461,6 +523,34 @@ section { errors { message: "range" } } } + test { + name: "from_string_under_boundary" + expr: "duration('-315576000001s')" + eval_error { + errors { message: "range" } + } + } + test { + name: "from_string_over_boundary" + expr: "duration('315576000001s')" + eval_error { + errors { message: "range" } + } + } + test { + name: "from_string_nanos_under_boundary" + expr: "duration('-315576000000s1000000000ns')" + eval_error { + errors { message: "range" } + } + } + test { + name: "from_string_nanos_over_boundary" + expr: "duration('315576000000s1000000000ns')" + eval_error { + errors { message: "range" } + } + } test { name: "add_under" expr: "duration('-200000000000s') + duration('-200000000000s')" @@ -475,6 +565,20 @@ section { errors { message: "range" } } } + test { + name: "add_nanos_under" + expr: "duration('-315576000000s999999999ns') + duration('-1ns')" + eval_error { + errors { message: "range" } + } + } + test { + name: "add_nanos_over" + expr: "duration('315576000000s999999999ns') + duration('1ns')" + eval_error { + errors { message: "range" } + } + } test { name: "sub_under" expr: "duration('-200000000000s') - duration('200000000000s')" @@ -489,4 +593,18 @@ section { errors { message: "range" } } } + test { + name: "sub_nanos_under" + expr: "duration('-315576000000s999999999ns') - duration('1ns')" + eval_error { + errors { message: "range" } + } + } + test { + name: "sub_nanos_over" + expr: "duration('315576000000s999999999ns') - duration('-1ns')" + eval_error { + errors { message: "range" } + } + } } diff --git a/tests/simple/testdata/wrappers.textproto b/tests/simple/testdata/wrappers.textproto index 4ec9e82c..a6b93a5b 100644 --- a/tests/simple/testdata/wrappers.textproto +++ b/tests/simple/testdata/wrappers.textproto @@ -345,6 +345,84 @@ section { string_value: "1000000s" } } + test { + name: "max_value" + expr: "google.protobuf.Duration{seconds: 315576000000, nanos: 999999999}" + value { + object_value { + [type.googleapis.com/google.protobuf.Duration] { + seconds: 315576000000 + nanos: 999999999 + } + } + } + } + test { + name: "min_value" + expr: "google.protobuf.Duration{seconds: -315576000000, nanos: -999999999}" + value { + object_value { + [type.googleapis.com/google.protobuf.Duration] { + seconds: -315576000000 + nanos: -999999999 + } + } + } + } + test { + name: "seconds_over" + expr: "google.protobuf.Duration{seconds: 315576000001}" + eval_error { + errors { + message: "range" + } + } + } + test { + name: "seconds_under" + expr: "google.protobuf.Duration{seconds: -315576000001}" + eval_error { + errors { + message: "range" + } + } + } + test { + name: "nanos_over" + expr: "google.protobuf.Duration{nanos: 1000000000}" + eval_error { + errors { + message: "range" + } + } + } + test { + name: "nanos_under" + expr: "google.protobuf.Duration{nanos: -1000000000}" + eval_error { + errors { + message: "range" + } + } + } + test { + name: "positive_seconds_negative_nanos" + expr: "google.protobuf.Duration{seconds: 1, nanos: -1}" + eval_error { + errors { + message: "sign" + } + } + } + test { + name: "negative_seconds_positive_nanos" + expr: "google.protobuf.Duration{seconds: -1, nanos: 1}" + eval_error { + errors { + message: "sign" + } + } + } } # google.protobuf.Timestamp @@ -358,6 +436,74 @@ section { string_value: "9999-12-31T23:59:59.999999999Z" } } + test { + name: "max_value" + expr: "google.protobuf.Timestamp{seconds: 253402300799, nanos: 999999999}" + value { + object_value { + [type.googleapis.com/google.protobuf.Timestamp] { + seconds: 253402300799 + nanos: 999999999 + } + } + } + } + test { + name: "min_value" + expr: "google.protobuf.Timestamp{seconds: -62135596800, nanos: 0}" + value { + object_value { + [type.googleapis.com/google.protobuf.Timestamp] { + seconds: -62135596800 + } + } + } + } + test { + name: "seconds_over" + expr: "google.protobuf.Timestamp{seconds: 253402300800}" + eval_error { + errors { + message: "range" + } + } + } + test { + name: "seconds_under" + expr: "google.protobuf.Timestamp{seconds: -62135596801}" + eval_error { + errors { + message: "range" + } + } + } + test { + name: "nanos_over" + expr: "google.protobuf.Timestamp{nanos: 1000000000}" + eval_error { + errors { + message: "range" + } + } + } + test { + name: "nanos_under" + expr: "google.protobuf.Timestamp{nanos: -1}" + eval_error { + errors { + message: "range" + } + } + } + test { + name: "negative_seconds_negative_nanos" + expr: "google.protobuf.Timestamp{seconds: -1, nanos: -1}" + eval_error { + errors { + message: "range" + } + } + } } # google.protobuf.Empty