@@ -107,9 +107,10 @@ public abstract class QueryParameterValue implements Serializable {
107107 .toFormatter ()
108108 .withZone (ZoneOffset .UTC );
109109 // Regex to identify >9 digits in the fraction part (e.g. `.123456789123`)
110- // Matches the dot, followed by 10+ digits, followed by non-digits (like +00) or end of string
110+ // Matches the dot, followed by 10+ digits (fractional part), followed by non-digits (like `+00`)
111+ // or end of string
111112 private static final Pattern ISO8601_TIMESTAMP_HIGH_PRECISION_PATTERN =
112- Pattern .compile ("( \\ .\\ d{10,})(?:\\ D|$)" );
113+ Pattern .compile ("\\ .( \\ d{10,})(?:\\ D|$)" );
113114
114115 private static final DateTimeFormatter dateFormatter = DateTimeFormatter .ofPattern ("yyyy-MM-dd" );
115116 private static final DateTimeFormatter timeFormatter =
@@ -550,30 +551,25 @@ private static <T> String valueToStringOrNull(T value, StandardSQLTypeName type)
550551 */
551552 @ VisibleForTesting
552553 static void validateTimestamp (String timestamp ) {
553- // Match if there is greater than nanosecond precision (>9 fractional digits )
554+ // Check if the string has greater than nanosecond precision (>9 digits in fractional second )
554555 Matcher matcher = ISO8601_TIMESTAMP_HIGH_PRECISION_PATTERN .matcher (timestamp );
555556 if (matcher .find ()) {
556- // Group 1 is the fractional part including the dot (e.g., ".123456789123")
557+ // Group 1 is the fractional second part of the ISO8601 string
557558 String fraction = matcher .group (1 );
558-
559- // The fraction part includes the dot, so we check its length.
560- // It should be at most 13 characters long (. + 12 digits).
561- if (fraction .length () > 13 ) {
559+ // Pos 10-12 of the fractional second are guaranteed to be digits. The regex only
560+ // matches the fraction section as long as they are digits.
561+ if (fraction .length () > 12 ) {
562562 throw new IllegalArgumentException (
563- "Fractional portion of ISO8601 supports up to picosecond (12 digits)" );
563+ "Fractional second portion of ISO8601 only supports up to picosecond (12 digits) in BigQuery " );
564564 }
565565
566- // Truncate to . + 9 digits
567- String truncatedFraction = fraction .substring (0 , 10 );
568- // Replace the entire fractional portion with the nanosecond portion. Digits exceeding the
569- // nanosecond will be checked separately.
570- String truncatedTimestamp =
571- timestamp .replaceFirst (Pattern .quote (fraction ), truncatedFraction );
572-
573- // It is valid as long as DateTimeFormatter doesn't throw an exception AND
574- // the fractional portion past nanosecond precision is valid (up to picosecond)
575- checkFormat (truncatedTimestamp , TIMESTAMP_VALIDATOR );
576- return ;
566+ // Replace the entire fractional second portion with just the nanosecond portion.
567+ // The new timestamp will be validated against the JDK's DateTimeFormatter
568+ String truncatedFraction = fraction .substring (0 , 9 );
569+ timestamp =
570+ new StringBuilder (timestamp )
571+ .replace (matcher .start (1 ), matcher .end (1 ), truncatedFraction )
572+ .toString ();
577573 }
578574
579575 // It is valid as long as DateTimeFormatter doesn't throw an exception
0 commit comments