diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java index 5bf6b4e9f93a..7f1f07754b57 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java @@ -16,6 +16,11 @@ package com.google.cloud.bigquery; +import static org.threeten.bp.temporal.ChronoField.HOUR_OF_DAY; +import static org.threeten.bp.temporal.ChronoField.MINUTE_OF_HOUR; +import static org.threeten.bp.temporal.ChronoField.NANO_OF_SECOND; +import static org.threeten.bp.temporal.ChronoField.SECOND_OF_MINUTE; + import com.google.api.services.bigquery.model.QueryParameterType; import com.google.auto.value.AutoValue; import com.google.common.base.Function; @@ -30,6 +35,7 @@ import org.threeten.bp.Instant; import org.threeten.bp.ZoneOffset; import org.threeten.bp.format.DateTimeFormatter; +import org.threeten.bp.format.DateTimeFormatterBuilder; import org.threeten.bp.format.DateTimeParseException; /** @@ -61,7 +67,32 @@ public abstract class QueryParameterValue implements Serializable { private static final DateTimeFormatter timestampFormatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSxxx").withZone(ZoneOffset.UTC); + new DateTimeFormatterBuilder() + .parseLenient() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral(' ') + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalStart() + .appendFraction(NANO_OF_SECOND, 6, 9, true) + .optionalStart() + .appendOffset("+HHMM", "+00:00") + .optionalEnd() + .toFormatter() + .withZone(ZoneOffset.UTC); + private static final DateTimeFormatter timestampValidator = + new DateTimeFormatterBuilder() + .parseLenient() + .append(timestampFormatter) + .optionalStart() + .appendOffsetId() + .optionalEnd() + .toFormatter() + .withZone(ZoneOffset.UTC); private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS"); @@ -301,7 +332,7 @@ private static String valueToStringOrNull(T value, StandardSQLTypeName type) return timestampFormatter.format(Instant.ofEpochMilli(((Long) value) / 1000)); } else if (value instanceof String) { // verify that the String is in the right format - checkFormat(value, timestampFormatter); + checkFormat(value, timestampValidator); return (String) value; } break; @@ -335,6 +366,7 @@ private static String valueToStringOrNull(T value, StandardSQLTypeName type) private static void checkFormat(Object value, DateTimeFormatter formatter) { try { + formatter.parse((String) value); } catch (DateTimeParseException e) { throw new IllegalArgumentException(e.getMessage(), e); diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java index 485b81e5e497..9d4ea478f24a 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java @@ -189,6 +189,25 @@ public void testTimestamp() { assertThat(value.getArrayValues()).isNull(); } + @Test + public void testTimestampWithDateTimeFormatterBuilder() { + QueryParameterValue value = QueryParameterValue.timestamp("2019-02-14 12:34:45.938993Z"); + assertThat(value.getValue()).isEqualTo("2019-02-14 12:34:45.938993Z"); + assertThat(value.getType()).isEqualTo(StandardSQLTypeName.TIMESTAMP); + assertThat(value.getArrayType()).isNull(); + assertThat(value.getArrayValues()).isNull(); + QueryParameterValue value1 = QueryParameterValue.timestamp("2019-02-14 12:34:45.938993+0000"); + assertThat(value1.getValue()).isEqualTo("2019-02-14 12:34:45.938993+0000"); + assertThat(value1.getType()).isEqualTo(StandardSQLTypeName.TIMESTAMP); + assertThat(value1.getArrayType()).isNull(); + assertThat(value1.getArrayValues()).isNull(); + QueryParameterValue value2 = QueryParameterValue.timestamp("2019-02-14 12:34:45.102+00:00"); + assertThat(value2.getValue()).isEqualTo("2019-02-14 12:34:45.102+00:00"); + assertThat(value2.getType()).isEqualTo(StandardSQLTypeName.TIMESTAMP); + assertThat(value2.getArrayType()).isNull(); + assertThat(value2.getArrayValues()).isNull(); + } + @Test(expected = IllegalArgumentException.class) public void testInvalidTimestamp() { // missing the time @@ -269,6 +288,29 @@ public void testTimestampArray() { value.getArrayValues()); } + @Test + public void testTimestampArrayWithDateTimeFormatterBuilder() { + QueryParameterValue value = + QueryParameterValue.array( + new String[] { + "2019-02-14 12:34:45.938993Z", + "2019-02-14 12:34:45.938993+0000", + "2019-02-14 12:34:45.102+00:00" + }, + StandardSQLTypeName.TIMESTAMP); + assertThat(value.getValue()).isNull(); + assertThat(value.getType()).isEqualTo(StandardSQLTypeName.ARRAY); + assertThat(value.getArrayType()).isEqualTo(StandardSQLTypeName.TIMESTAMP); + assertArrayDataEquals( + new String[] { + "2019-02-14 12:34:45.938993Z", + "2019-02-14 12:34:45.938993+0000", + "2019-02-14 12:34:45.102+00:00" + }, + StandardSQLTypeName.TIMESTAMP, + value.getArrayValues()); + } + @Test public void testFromEmptyArray() { QueryParameterType typePb =