1616
1717package com .google .cloud .bigquery ;
1818
19+ import static com .google .cloud .bigquery .QueryParameterValue .TIMESTAMP_FORMATTER ;
1920import static com .google .common .truth .Truth .assertThat ;
20- import static java .time .temporal .ChronoField .HOUR_OF_DAY ;
21- import static java .time .temporal .ChronoField .MINUTE_OF_HOUR ;
22- import static java .time .temporal .ChronoField .NANO_OF_SECOND ;
23- import static java .time .temporal .ChronoField .SECOND_OF_MINUTE ;
21+ import static org .junit .Assert .assertThrows ;
2422
2523import com .google .api .services .bigquery .model .QueryParameterType ;
2624import com .google .common .collect .ImmutableMap ;
2927import java .text .ParseException ;
3028import java .time .Instant ;
3129import java .time .Period ;
32- import java .time .ZoneOffset ;
33- import java .time .format .DateTimeFormatter ;
34- import java .time .format .DateTimeFormatterBuilder ;
3530import java .util .ArrayList ;
3631import java .util .Date ;
3732import java .util .HashMap ;
4338
4439public class QueryParameterValueTest {
4540
46- private static final DateTimeFormatter TIMESTAMPFORMATTER =
47- new DateTimeFormatterBuilder ()
48- .parseLenient ()
49- .append (DateTimeFormatter .ISO_LOCAL_DATE )
50- .appendLiteral (' ' )
51- .appendValue (HOUR_OF_DAY , 2 )
52- .appendLiteral (':' )
53- .appendValue (MINUTE_OF_HOUR , 2 )
54- .optionalStart ()
55- .appendLiteral (':' )
56- .appendValue (SECOND_OF_MINUTE , 2 )
57- .optionalStart ()
58- .appendFraction (NANO_OF_SECOND , 6 , 9 , true )
59- .optionalStart ()
60- .appendOffset ("+HHMM" , "+00:00" )
61- .optionalEnd ()
62- .toFormatter ()
63- .withZone (ZoneOffset .UTC );
64-
6541 private static final QueryParameterValue QUERY_PARAMETER_VALUE =
6642 QueryParameterValue .newBuilder ()
6743 .setType (StandardSQLTypeName .STRING )
@@ -326,11 +302,9 @@ public void testStringArray() {
326302
327303 @ Test
328304 public void testTimestampFromLong () {
329- QueryParameterValue value = QueryParameterValue .timestamp (1408452095220000L );
330- assertThat (value .getValue ()).isEqualTo ("2014-08-19 12:41:35.220000+00:00" );
331- assertThat (value .getType ()).isEqualTo (StandardSQLTypeName .TIMESTAMP );
332- assertThat (value .getArrayType ()).isNull ();
333- assertThat (value .getArrayValues ()).isNull ();
305+ // Expects output to be ISO8601 string with microsecond precision
306+ assertTimestampValue (
307+ QueryParameterValue .timestamp (1408452095220000L ), "2014-08-19 12:41:35.220000+00:00" );
334308 }
335309
336310 @ Test
@@ -340,43 +314,77 @@ public void testTimestampWithFormatter() {
340314 long secs = Math .floorDiv (timestampInMicroseconds , microseconds );
341315 int nano = (int ) Math .floorMod (timestampInMicroseconds , microseconds ) * 1000 ;
342316 Instant instant = Instant .ofEpochSecond (secs , nano );
343- String expected = TIMESTAMPFORMATTER .format (instant );
344- assertThat (expected )
345- .isEqualTo (QueryParameterValue .timestamp (timestampInMicroseconds ).getValue ());
317+ String expected = TIMESTAMP_FORMATTER .format (instant );
318+ assertTimestampValue (QueryParameterValue .timestamp (timestampInMicroseconds ), expected );
346319 }
347320
348321 @ Test
349- public void testTimestamp () {
350- QueryParameterValue value = QueryParameterValue .timestamp ("2014-08-19 12:41:35.220000+00:00" );
351- assertThat (value .getValue ()).isEqualTo ("2014-08-19 12:41:35.220000+00:00" );
352- assertThat (value .getType ()).isEqualTo (StandardSQLTypeName .TIMESTAMP );
353- assertThat (value .getArrayType ()).isNull ();
354- assertThat (value .getArrayValues ()).isNull ();
322+ public void testTimestampFromString () {
323+ assertTimestampValue (
324+ QueryParameterValue .timestamp ("2014-08-19 12:41:35.220000+00:00" ),
325+ "2014-08-19 12:41:35.220000+00:00" );
326+ assertTimestampValue (
327+ QueryParameterValue .timestamp ("2025-08-19 12:34:56.123456789+00:00" ),
328+ "2025-08-19 12:34:56.123456789+00:00" );
329+
330+ // The following test cases test more than nanosecond precision
331+ // 10 digits of precision (1 digit more than nanosecond)
332+ assertTimestampValue (
333+ QueryParameterValue .timestamp ("2025-12-08 12:34:56.1234567890+00:00" ),
334+ "2025-12-08 12:34:56.1234567890+00:00" );
335+ // 12 digits (picosecond precision)
336+ assertTimestampValue (
337+ QueryParameterValue .timestamp ("2025-12-08 12:34:56.123456789123+00:00" ),
338+ "2025-12-08 12:34:56.123456789123+00:00" );
339+
340+ // More than picosecond precision
341+ assertThrows (
342+ IllegalArgumentException .class ,
343+ () -> QueryParameterValue .timestamp ("2025-12-08 12:34:56.1234567891234+00:00" ));
344+ assertThrows (
345+ IllegalArgumentException .class ,
346+ () ->
347+ QueryParameterValue .timestamp ("2025-12-08 12:34:56.123456789123456789123456789+00:00" ));
355348 }
356349
357350 @ Test
358351 public void testTimestampWithDateTimeFormatterBuilder () {
359- QueryParameterValue value = QueryParameterValue .timestamp ("2019-02-14 12:34:45.938993Z" );
360- assertThat (value .getValue ()).isEqualTo ("2019-02-14 12:34:45.938993Z" );
361- assertThat (value .getType ()).isEqualTo (StandardSQLTypeName .TIMESTAMP );
362- assertThat (value .getArrayType ()).isNull ();
363- assertThat (value .getArrayValues ()).isNull ();
364- QueryParameterValue value1 = QueryParameterValue .timestamp ("2019-02-14 12:34:45.938993+0000" );
365- assertThat (value1 .getValue ()).isEqualTo ("2019-02-14 12:34:45.938993+0000" );
366- assertThat (value1 .getType ()).isEqualTo (StandardSQLTypeName .TIMESTAMP );
367- assertThat (value1 .getArrayType ()).isNull ();
368- assertThat (value1 .getArrayValues ()).isNull ();
369- QueryParameterValue value2 = QueryParameterValue .timestamp ("2019-02-14 12:34:45.102+00:00" );
370- assertThat (value2 .getValue ()).isEqualTo ("2019-02-14 12:34:45.102+00:00" );
371- assertThat (value2 .getType ()).isEqualTo (StandardSQLTypeName .TIMESTAMP );
372- assertThat (value2 .getArrayType ()).isNull ();
373- assertThat (value2 .getArrayValues ()).isNull ();
352+ assertTimestampValue (
353+ QueryParameterValue .timestamp ("2019-02-14 12:34:45.938993Z" ),
354+ "2019-02-14 12:34:45.938993Z" );
355+ assertTimestampValue (
356+ QueryParameterValue .timestamp ("2019-02-14 12:34:45.938993+0000" ),
357+ "2019-02-14 12:34:45.938993+0000" );
358+ assertTimestampValue (
359+ QueryParameterValue .timestamp ("2019-02-14 12:34:45.102+00:00" ),
360+ "2019-02-14 12:34:45.102+00:00" );
374361 }
375362
376- @ Test (expected = IllegalArgumentException .class )
377- public void testInvalidTimestamp () {
363+ @ Test
364+ public void testInvalidTimestampStringValues () {
365+ assertThrows (IllegalArgumentException .class , () -> QueryParameterValue .timestamp ("abc" ));
366+
378367 // missing the time
379- QueryParameterValue .timestamp ("2014-08-19" );
368+ assertThrows (IllegalArgumentException .class , () -> QueryParameterValue .timestamp ("2014-08-19" ));
369+
370+ // missing the hour
371+ assertThrows (
372+ IllegalArgumentException .class , () -> QueryParameterValue .timestamp ("2014-08-19 12" ));
373+
374+ // can't have the 'T' separator
375+ assertThrows (
376+ IllegalArgumentException .class , () -> QueryParameterValue .timestamp ("2014-08-19T12" ));
377+ assertThrows (
378+ IllegalArgumentException .class ,
379+ () -> QueryParameterValue .timestamp ("2014-08-19T12:34:00.123456" ));
380+
381+ // Fractional part has picosecond length, but fractional part is not a valid number
382+ assertThrows (
383+ IllegalArgumentException .class ,
384+ () -> QueryParameterValue .timestamp ("2014-08-19 12:34:00.123456789abc+00:00" ));
385+ assertThrows (
386+ IllegalArgumentException .class ,
387+ () -> QueryParameterValue .timestamp ("2014-08-19 12:34:00.123456abc789+00:00" ));
380388 }
381389
382390 @ Test
@@ -683,4 +691,11 @@ private static void testRangeDataEquals(String start, String end, FieldElementTy
683691 assertThat (queryParameterValue .getStructValues ()).isNull ();
684692 assertThat (queryParameterValue .getValue ()).isNull ();
685693 }
694+
695+ private void assertTimestampValue (QueryParameterValue value , String expectedStringValue ) {
696+ assertThat (value .getValue ()).isEqualTo (expectedStringValue );
697+ assertThat (value .getType ()).isEqualTo (StandardSQLTypeName .TIMESTAMP );
698+ assertThat (value .getArrayType ()).isNull ();
699+ assertThat (value .getArrayValues ()).isNull ();
700+ }
686701}
0 commit comments