Add a unit test demonstrating broken `Timestamps.parse()` behavior.

PiperOrigin-RevId: 698179372
diff --git a/java/util/src/test/java/com/google/protobuf/util/TimestampsTest.java b/java/util/src/test/java/com/google/protobuf/util/TimestampsTest.java
index f52e0f8..ef59bc7 100644
--- a/java/util/src/test/java/com/google/protobuf/util/TimestampsTest.java
+++ b/java/util/src/test/java/com/google/protobuf/util/TimestampsTest.java
@@ -146,22 +146,22 @@
     assertThat(value.getNanos()).isEqualTo(999000000);
 
     // Test that 3, 6, or 9 digits are used for the fractional part.
-    value = Timestamp.newBuilder().setNanos(10).build();
-    assertThat(Timestamps.toString(value)).isEqualTo("1970-01-01T00:00:00.000000010Z");
-    value = Timestamp.newBuilder().setNanos(10000).build();
-    assertThat(Timestamps.toString(value)).isEqualTo("1970-01-01T00:00:00.000010Z");
-    value = Timestamp.newBuilder().setNanos(10000000).build();
-    assertThat(Timestamps.toString(value)).isEqualTo("1970-01-01T00:00:00.010Z");
+    assertThat(Timestamps.toString(Timestamp.newBuilder().setNanos(10).build()))
+        .isEqualTo("1970-01-01T00:00:00.000000010Z");
+    assertThat(Timestamps.toString(Timestamp.newBuilder().setNanos(10000).build()))
+        .isEqualTo("1970-01-01T00:00:00.000010Z");
+    assertThat(Timestamps.toString(Timestamp.newBuilder().setNanos(10000000).build()))
+        .isEqualTo("1970-01-01T00:00:00.010Z");
 
     // Test that parsing accepts timezone offsets.
-    value = Timestamps.parse("1970-01-01T00:00:00.010+08:00");
-    assertThat(Timestamps.toString(value)).isEqualTo("1969-12-31T16:00:00.010Z");
-    value = Timestamps.parse("1970-01-01T00:00:00.010-08:00");
-    assertThat(Timestamps.toString(value)).isEqualTo("1970-01-01T08:00:00.010Z");
-    value = Timestamps.parseUnchecked("1970-01-01T00:00:00.010+08:00");
-    assertThat(Timestamps.toString(value)).isEqualTo("1969-12-31T16:00:00.010Z");
-    value = Timestamps.parseUnchecked("1970-01-01T00:00:00.010-08:00");
-    assertThat(Timestamps.toString(value)).isEqualTo("1970-01-01T08:00:00.010Z");
+    assertThat(Timestamps.toString(Timestamps.parse("1970-01-01T00:00:00.010+08:00")))
+        .isEqualTo("1969-12-31T16:00:00.010Z");
+    assertThat(Timestamps.toString(Timestamps.parse("1970-01-01T00:00:00.010-08:00")))
+        .isEqualTo("1970-01-01T08:00:00.010Z");
+    assertThat(Timestamps.toString(Timestamps.parseUnchecked("1970-01-01T00:00:00.010+08:00")))
+        .isEqualTo("1969-12-31T16:00:00.010Z");
+    assertThat(Timestamps.toString(Timestamps.parseUnchecked("1970-01-01T00:00:00.010-08:00")))
+        .isEqualTo("1970-01-01T08:00:00.010Z");
   }
 
   private volatile boolean stopParsingThreads = false;
@@ -240,10 +240,10 @@
   @GwtIncompatible("Depends on String.format which is not supported in Xplat.")
   @J2ObjCIncompatible
   public void testTimestampInvalidFormatValueTooSmall() throws Exception {
+    // Value too small.
+    Timestamp value =
+        Timestamp.newBuilder().setSeconds(Timestamps.TIMESTAMP_SECONDS_MIN - 1).build();
     try {
-      // Value too small.
-      Timestamp value =
-          Timestamp.newBuilder().setSeconds(Timestamps.TIMESTAMP_SECONDS_MIN - 1).build();
       Timestamps.toString(value);
       fail("IllegalArgumentException is expected.");
     } catch (IllegalArgumentException expected) {
@@ -254,10 +254,10 @@
   @GwtIncompatible("Depends on String.format which is not supported in Xplat.")
   @J2ObjCIncompatible
   public void testTimestampInvalidFormatValueTooLarge() throws Exception {
+    // Value too large.
+    Timestamp value =
+        Timestamp.newBuilder().setSeconds(Timestamps.TIMESTAMP_SECONDS_MAX + 1).build();
     try {
-      // Value too large.
-      Timestamp value =
-          Timestamp.newBuilder().setSeconds(Timestamps.TIMESTAMP_SECONDS_MAX + 1).build();
       Timestamps.toString(value);
       fail("IllegalArgumentException is expected.");
     } catch (IllegalArgumentException expected) {
@@ -268,9 +268,9 @@
   @GwtIncompatible("Depends on String.format which is not supported in Xplat.")
   @J2ObjCIncompatible
   public void testTimestampInvalidFormatNanosTooSmall() throws Exception {
+    // Invalid nanos value.
+    Timestamp value = Timestamp.newBuilder().setNanos(-1).build();
     try {
-      // Invalid nanos value.
-      Timestamp value = Timestamp.newBuilder().setNanos(-1).build();
       Timestamps.toString(value);
       fail("IllegalArgumentException is expected.");
     } catch (IllegalArgumentException expected) {
@@ -281,9 +281,9 @@
   @GwtIncompatible("Depends on String.format which is not supported in Xplat.")
   @J2ObjCIncompatible
   public void testTimestampInvalidFormatNanosTooLarge() throws Exception {
+    // Invalid nanos value.
+    Timestamp value = Timestamp.newBuilder().setNanos(1000000000).build();
     try {
-      // Invalid nanos value.
-      Timestamp value = Timestamp.newBuilder().setNanos(1000000000).build();
       Timestamps.toString(value);
       fail("IllegalArgumentException is expected.");
     } catch (IllegalArgumentException expected) {
@@ -294,15 +294,16 @@
   @GwtIncompatible("ParseException is not supported in Xplat")
   @J2ObjCIncompatible
   public void testTimestampInvalidFormatDateTooSmall() {
+    final String value = "0000-01-01T00:00:00Z";
     try {
-      Timestamps.parse("0000-01-01T00:00:00Z");
+      Timestamps.parse(value);
       fail();
     } catch (ParseException expected) {
       assertThat(expected).hasMessageThat().isNotNull();
       assertThat(expected).hasCauseThat().isNotNull();
     }
     try {
-      Timestamps.parseUnchecked("0000-01-01T00:00:00Z");
+      Timestamps.parseUnchecked(value);
       fail("IllegalArgumentException is expected.");
     } catch (IllegalArgumentException expected) {
       assertThat(expected).hasMessageThat().isNotNull();
@@ -313,126 +314,71 @@
   @GwtIncompatible("ParseException is not supported in Xplat")
   @J2ObjCIncompatible
   public void testTimestampInvalidFormatDateTooLarge() {
-    try {
-      Timestamps.parse("10000-01-01T00:00:00Z");
-      fail();
-    } catch (ParseException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
-    try {
-      Timestamps.parseUnchecked("10000-01-01T00:00:00Z");
-      fail("IllegalArgumentException is expected.");
-    } catch (IllegalArgumentException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
+    assertParseFails("10000-01-01T00:00:00Z");
   }
 
   @Test
   @GwtIncompatible("ParseException is not supported in Xplat")
   @J2ObjCIncompatible
   public void testTimestampInvalidFormatMissingT() {
-    try {
-      Timestamps.parse("1970-01-01 00:00:00Z");
-      fail();
-    } catch (ParseException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
-    try {
-      Timestamps.parseUnchecked("1970-01-01 00:00:00Z");
-      fail("IllegalArgumentException is expected.");
-    } catch (IllegalArgumentException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
+    assertParseFails("1970-01-01 00:00:00Z");
   }
 
   @Test
   @GwtIncompatible("ParseException is not supported in Xplat")
   @J2ObjCIncompatible
   public void testTimestampInvalidFormatMissingZ() {
-    try {
-      Timestamps.parse("1970-01-01T00:00:00");
-      fail("ParseException is expected.");
-    } catch (ParseException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
-    try {
-      Timestamps.parseUnchecked("1970-01-01T00:00:00");
-      fail("IllegalArgumentException is expected.");
-    } catch (IllegalArgumentException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
+    assertParseFails("1970-01-01T00:00:00");
   }
 
   @Test
   @GwtIncompatible("ParseException is not supported in Xplat")
   @J2ObjCIncompatible
   public void testTimestampInvalidOffset() {
-    try {
-      Timestamps.parse("1970-01-01T00:00:00+0000");
-      fail("ParseException is expected.");
-    } catch (ParseException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
-    try {
-      Timestamps.parseUnchecked("1970-01-01T00:00:00+0000");
-      fail("IllegalArgumentException is expected.");
-    } catch (IllegalArgumentException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
+    assertParseFails("1970-01-01T00:00:00+0000");
   }
 
   @Test
   @GwtIncompatible("ParseException is not supported in Xplat")
   @J2ObjCIncompatible
   public void testTimestampInvalidOffsetWithDot() {
-    try {
-      Timestamps.parse("2021-08-19T10:24:25-07.:00");
-      fail("ParseException is expected.");
-    } catch (ParseException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
-    try {
-      Timestamps.parseUnchecked("2021-08-19T10:24:25-07.:00");
-      fail("IllegalArgumentException is expected.");
-    } catch (IllegalArgumentException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
+    assertParseFails("2021-08-19T10:24:25-07.:00");
   }
 
   @Test
   @GwtIncompatible("ParseException is not supported in Xplat")
   @J2ObjCIncompatible
   public void testTimestampInvalidTrailingText() {
-    try {
-      Timestamps.parse("1970-01-01T00:00:00Z0");
-      fail("ParseException is expected.");
-    } catch (ParseException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
-    try {
-      Timestamps.parseUnchecked("1970-01-01T00:00:00Z0");
-      fail("IllegalArgumentException is expected.");
-    } catch (IllegalArgumentException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
+    assertParseFails("1970-01-01T00:00:00Z0");
   }
 
   @Test
   @GwtIncompatible("ParseException is not supported in Xplat")
   @J2ObjCIncompatible
   public void testTimestampInvalidNanoSecond() {
+    assertParseFails("1970-01-01T00:00:00.ABCZ");
+  }
+
+  @Test
+  @GwtIncompatible("ParseException is not supported in Xplat")
+  @J2ObjCIncompatible
+  public void testTimestampParseInvalidMonth() throws Exception {
+    final String value = "2000-40-01T00:00:00Z";
+    final String expected = "2003-04-01T00:00:00Z";
+    // TODO: b/379874415 - this shouldn't parse successfully
+    assertThat(Timestamps.parse(value)).isEqualTo(Timestamps.parse(expected));
+    assertThat(Timestamps.parseUnchecked(value)).isEqualTo(Timestamps.parse(expected));
+  }
+
+  @GwtIncompatible("ParseException is not supported in Xplat")
+  @J2ObjCIncompatible
+  private static void assertParseFails(String value) {
     try {
-      Timestamps.parse("1970-01-01T00:00:00.ABCZ");
+      Timestamps.parse(value);
       fail("ParseException is expected.");
     } catch (ParseException expected) {
       assertThat(expected).hasMessageThat().isNotNull();
     }
-    try {
-      Timestamps.parseUnchecked("1970-01-01T00:00:00.ABCZ");
-      fail("IllegalArgumentException is expected.");
-    } catch (IllegalArgumentException expected) {
-      assertThat(expected).hasMessageThat().isNotNull();
-    }
   }
 
   @Test
@@ -444,28 +390,28 @@
     assertThat(Timestamps.toMicros(timestamp)).isEqualTo(1111111);
     assertThat(Timestamps.toMillis(timestamp)).isEqualTo(1111);
     assertThat(Timestamps.toSeconds(timestamp)).isEqualTo(1);
-    timestamp = Timestamps.fromNanos(1111111111);
-    assertThat(Timestamps.toString(timestamp)).isEqualTo("1970-01-01T00:00:01.111111111Z");
-    timestamp = Timestamps.fromMicros(1111111);
-    assertThat(Timestamps.toString(timestamp)).isEqualTo("1970-01-01T00:00:01.111111Z");
-    timestamp = Timestamps.fromMillis(1111);
-    assertThat(Timestamps.toString(timestamp)).isEqualTo("1970-01-01T00:00:01.111Z");
-    timestamp = Timestamps.fromSeconds(1);
-    assertThat(Timestamps.toString(timestamp)).isEqualTo("1970-01-01T00:00:01Z");
+
+    assertThat(Timestamps.toString(Timestamps.fromNanos(1111111111)))
+        .isEqualTo("1970-01-01T00:00:01.111111111Z");
+    assertThat(Timestamps.toString(Timestamps.fromMicros(1111111)))
+        .isEqualTo("1970-01-01T00:00:01.111111Z");
+    assertThat(Timestamps.toString(Timestamps.fromMillis(1111)))
+        .isEqualTo("1970-01-01T00:00:01.111Z");
+    assertThat(Timestamps.toString(Timestamps.fromSeconds(1))).isEqualTo("1970-01-01T00:00:01Z");
 
     timestamp = Timestamps.parse("1969-12-31T23:59:59.111111111Z");
     assertThat(Timestamps.toNanos(timestamp)).isEqualTo(-888888889);
     assertThat(Timestamps.toMicros(timestamp)).isEqualTo(-888889);
     assertThat(Timestamps.toMillis(timestamp)).isEqualTo(-889);
     assertThat(Timestamps.toSeconds(timestamp)).isEqualTo(-1);
-    timestamp = Timestamps.fromNanos(-888888889);
-    assertThat(Timestamps.toString(timestamp)).isEqualTo("1969-12-31T23:59:59.111111111Z");
-    timestamp = Timestamps.fromMicros(-888889);
-    assertThat(Timestamps.toString(timestamp)).isEqualTo("1969-12-31T23:59:59.111111Z");
-    timestamp = Timestamps.fromMillis(-889);
-    assertThat(Timestamps.toString(timestamp)).isEqualTo("1969-12-31T23:59:59.111Z");
-    timestamp = Timestamps.fromSeconds(-1);
-    assertThat(Timestamps.toString(timestamp)).isEqualTo("1969-12-31T23:59:59Z");
+
+    assertThat(Timestamps.toString(Timestamps.fromNanos(-888888889)))
+        .isEqualTo("1969-12-31T23:59:59.111111111Z");
+    assertThat(Timestamps.toString(Timestamps.fromMicros(-888889)))
+        .isEqualTo("1969-12-31T23:59:59.111111Z");
+    assertThat(Timestamps.toString(Timestamps.fromMillis(-889)))
+        .isEqualTo("1969-12-31T23:59:59.111Z");
+    assertThat(Timestamps.toString(Timestamps.fromSeconds(-1))).isEqualTo("1969-12-31T23:59:59Z");
   }
 
   @Test
@@ -662,8 +608,9 @@
   @GwtIncompatible("ParseException is not supported in Xplat")
   @J2ObjCIncompatible
   public void testOverflowsArithmeticException() throws Exception {
+    Timestamp timestamp = Timestamps.parse("9999-12-31T23:59:59.999999999Z");
     try {
-      Timestamps.toNanos(Timestamps.parse("9999-12-31T23:59:59.999999999Z"));
+      Timestamps.toNanos(timestamp);
       fail("Expected an ArithmeticException to be thrown");
     } catch (ArithmeticException expected) {
     }