diff --git a/cel-c/internal/timestampconv.cc b/cel-c/internal/timestampconv.cc index 605b7a2..b6deb82 100644 --- a/cel-c/internal/timestampconv.cc +++ b/cel-c/internal/timestampconv.cc @@ -60,9 +60,16 @@ bool _cel_Timestamp_FromRFC3339(cel_Timestamp* cel_nonnull out, return false; } - absl::Duration duration = time - absl::UnixEpoch(); - int64_t seconds = absl::IDivDuration(duration, absl::Seconds(1), &duration); - int32_t nanos = static_cast(absl::ToInt64Nanoseconds(duration)); + // `absl::ParseTime` accepts "infinite-past" and "infinite-future" regardless + // of the format, neither of which are valid timestamps. + if (time == absl::InfinitePast() || time == absl::InfiniteFuture()) { + return false; + } + // `absl::ToUnixSeconds` rounds towards negative infinity, so the remainder is + // always in [0s, 1s), even for timestamps before the Unix epoch. + int64_t seconds = absl::ToUnixSeconds(time); + int32_t nanos = static_cast( + absl::ToInt64Nanoseconds(time - absl::FromUnixSeconds(seconds))); if (!cel_Timestamp_Valid(seconds, nanos)) { return false; diff --git a/cel-c/internal/timestampconv_test.cc b/cel-c/internal/timestampconv_test.cc index bdda8fc..627a367 100644 --- a/cel-c/internal/timestampconv_test.cc +++ b/cel-c/internal/timestampconv_test.cc @@ -141,6 +141,26 @@ TEST_F(CelTimestampConvTest, FromRFC3339Valid) { cel_Timestamp_ToUnix(ts, &seconds, &nanos); EXPECT_EQ(seconds, -1); EXPECT_EQ(nanos, 0); + + // Pre-epoch timestamps with fractional seconds keep nanos non-negative. + EXPECT_TRUE(_cel_Timestamp_FromRFC3339( + &ts, cel_StringView_From("1969-12-31T23:59:58.5Z"))); + cel_Timestamp_ToUnix(ts, &seconds, &nanos); + EXPECT_EQ(seconds, -2); + EXPECT_EQ(nanos, 500000000); + + EXPECT_TRUE(_cel_Timestamp_FromRFC3339( + &ts, cel_StringView_From("0001-01-01T00:00:00.000000001Z"))); + cel_Timestamp_ToUnix(ts, &seconds, &nanos); + EXPECT_EQ(seconds, cel_Timestamp_kMinSeconds); + EXPECT_EQ(nanos, 1); + + // Sub-nanosecond precision is floored. + EXPECT_TRUE(_cel_Timestamp_FromRFC3339( + &ts, cel_StringView_From("1969-12-31T23:59:59.9999999999Z"))); + cel_Timestamp_ToUnix(ts, &seconds, &nanos); + EXPECT_EQ(seconds, -1); + EXPECT_EQ(nanos, 999999999); } TEST_F(CelTimestampConvTest, ToCivilTime) { @@ -251,6 +271,10 @@ TEST_F(CelTimestampConvTest, FromRFC3339Invalid) { cel_Timestamp ts; EXPECT_FALSE(_cel_Timestamp_FromRFC3339(&ts, cel_StringView_From(""))); EXPECT_FALSE(_cel_Timestamp_FromRFC3339(&ts, cel_StringView_From("invalid"))); + EXPECT_FALSE( + _cel_Timestamp_FromRFC3339(&ts, cel_StringView_From("infinite-past"))); + EXPECT_FALSE( + _cel_Timestamp_FromRFC3339(&ts, cel_StringView_From("infinite-future"))); EXPECT_FALSE( _cel_Timestamp_FromRFC3339(&ts, cel_StringView_From("2023-03-15"))); EXPECT_FALSE(_cel_Timestamp_FromRFC3339(