The valid range and number of bits required by each date/time field is as the following: + * + *
| Field | Range | #Bits |
|---|---|---|
| Year | [1, 9999] | 14 |
| Month | [1, 12] | 4 |
| Day | [1, 31] | 5 |
| Hour | [0, 23] | 5 |
| Minute | [0, 59] | 6 |
| Second | [0, 59]* | 6 |
| Micros | [0, 999999] | 20 |
| Nanos | [0, 999999999] | 30 |
* Leap second is not supported. + * + *
When encoding the TIME or DATETIME into a bit field, larger date/time field is on the more + * significant side. + */ +public final class CivilTimeEncoder { + private static final int NANO_LENGTH = 30; + private static final int MICRO_LENGTH = 20; + + private static final int NANO_SHIFT = 0; + private static final int MICRO_SHIFT = 0; + private static final int SECOND_SHIFT = 0; + private static final int MINUTE_SHIFT = 6; + private static final int HOUR_SHIFT = 12; + private static final int DAY_SHIFT = 17; + private static final int MONTH_SHIFT = 22; + private static final int YEAR_SHIFT = 26; + + private static final long NANO_MASK = 0x3FFFFFFFL; + private static final long MICRO_MASK = 0xFFFFFL; + private static final long SECOND_MASK = 0x3FL; + private static final long MINUTE_MASK = 0xFC0L; + private static final long HOUR_MASK = 0x1F000L; + private static final long DAY_MASK = 0x3E0000L; + private static final long MONTH_MASK = 0x3C00000L; + private static final long YEAR_MASK = 0xFFFC000000L; + + private static final long TIME_SECONDS_MASK = 0x1FFFFL; + private static final long TIME_MICROS_MASK = 0x1FFFFFFFFFL; + private static final long TIME_NANOS_MASK = 0x7FFFFFFFFFFFL; + private static final long DATETIME_SECONDS_MASK = 0xFFFFFFFFFFL; + private static final long DATETIME_MICROS_MASK = 0xFFFFFFFFFFFFFFFL; + + /** + * Encodes {@code time} as a 4-byte integer with seconds precision. + * + *
Encoding is as the following: + * + *
+ * 3 2 1 + * MSB 10987654321098765432109876543210 LSB + * | H || M || S | + *+ * + * @see #decodePacked32TimeSeconds(int) + */ + @SuppressWarnings("GoodTime-ApiWithNumericTimeUnit") + private static int encodePacked32TimeSeconds(LocalTime time) { + checkValidTimeSeconds(time); + int bitFieldTimeSeconds = 0x0; + bitFieldTimeSeconds |= time.getHour() << HOUR_SHIFT; + bitFieldTimeSeconds |= time.getMinute() << MINUTE_SHIFT; + bitFieldTimeSeconds |= time.getSecond() << SECOND_SHIFT; + return bitFieldTimeSeconds; + } + + /** + * Decodes {@code bitFieldTimeSeconds} as a {@link LocalTime} with seconds precision. + * + *
Encoding is as the following: + * + *
+ * 3 2 1 + * MSB 10987654321098765432109876543210 LSB + * | H || M || S | + *+ * + * @see #encodePacked32TimeSeconds(LocalTime) + */ + @SuppressWarnings("GoodTime-ApiWithNumericTimeUnit") + private static LocalTime decodePacked32TimeSeconds(int bitFieldTimeSeconds) { + checkValidBitField(bitFieldTimeSeconds, TIME_SECONDS_MASK); + int hourOfDay = getFieldFromBitField(bitFieldTimeSeconds, HOUR_MASK, HOUR_SHIFT); + int minuteOfHour = getFieldFromBitField(bitFieldTimeSeconds, MINUTE_MASK, MINUTE_SHIFT); + int secondOfMinute = getFieldFromBitField(bitFieldTimeSeconds, SECOND_MASK, SECOND_SHIFT); + // LocalTime validates the input parameters. + try { + return LocalTime.of(hourOfDay, minuteOfHour, secondOfMinute); + } catch (DateTimeException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + /** + * Encodes {@code time} as a 8-byte integer with microseconds precision. + * + *
Encoding is as the following: + * + *
+ * 6 5 4 3 2 1 + * MSB 3210987654321098765432109876543210987654321098765432109876543210 LSB + * | H || M || S ||-------micros-----| + *+ * + * @see #decodePacked64TimeMicros(long) + * @see #encodePacked64TimeMicros(LocalTime) + */ + @SuppressWarnings("GoodTime") + public static long encodePacked64TimeMicros(LocalTime time) { + checkValidTimeMicros(time); + return (((long) encodePacked32TimeSeconds(time)) << MICRO_LENGTH) | (time.getNano() / 1_000L); + } + + /** + * Decodes {@code bitFieldTimeMicros} as a {@link LocalTime} with microseconds precision. + * + *
Encoding is as the following: + * + *
+ * 6 5 4 3 2 1 + * MSB 3210987654321098765432109876543210987654321098765432109876543210 LSB + * | H || M || S ||-------micros-----| + *+ * + * @see #encodePacked64TimeMicros(LocalTime) + */ + @SuppressWarnings("GoodTime-ApiWithNumericTimeUnit") + public static LocalTime decodePacked64TimeMicros(long bitFieldTimeMicros) { + checkValidBitField(bitFieldTimeMicros, TIME_MICROS_MASK); + int bitFieldTimeSeconds = (int) (bitFieldTimeMicros >> MICRO_LENGTH); + LocalTime timeSeconds = decodePacked32TimeSeconds(bitFieldTimeSeconds); + int microOfSecond = getFieldFromBitField(bitFieldTimeMicros, MICRO_MASK, MICRO_SHIFT); + checkValidMicroOfSecond(microOfSecond); + LocalTime time = timeSeconds.withNano(microOfSecond * 1000); + checkValidTimeMicros(time); + return time; + } + + /** + * Encodes {@code dateTime} as a 8-byte integer with seconds precision. + * + *
Encoding is as the following: + * + *
+ * 6 5 4 3 2 1 + * MSB 3210987654321098765432109876543210987654321098765432109876543210 LSB + * |--- year ---||m || D || H || M || S | + *+ * + * @see #decodePacked64DatetimeSeconds(long) + */ + @SuppressWarnings("GoodTime-ApiWithNumericTimeUnit") + private static long encodePacked64DatetimeSeconds(LocalDateTime dateTime) { + checkValidDateTimeSeconds(dateTime); + long bitFieldDatetimeSeconds = 0x0L; + bitFieldDatetimeSeconds |= (long) dateTime.getYear() << YEAR_SHIFT; + bitFieldDatetimeSeconds |= (long) dateTime.getMonthValue() << MONTH_SHIFT; + bitFieldDatetimeSeconds |= (long) dateTime.getDayOfMonth() << DAY_SHIFT; + bitFieldDatetimeSeconds |= (long) encodePacked32TimeSeconds(dateTime.toLocalTime()); + return bitFieldDatetimeSeconds; + } + + /** + * Decodes {@code bitFieldDatetimeSeconds} as a {@link LocalDateTime} with seconds precision. + * + *
Encoding is as the following: + * + *
+ * 6 5 4 3 2 1 + * MSB 3210987654321098765432109876543210987654321098765432109876543210 LSBa + * |--- year ---||m || D || H || M || S | + *+ * + * @see #encodePacked64DatetimeSeconds(LocalDateTime) + */ + @SuppressWarnings("GoodTime-ApiWithNumericTimeUnit") + private static LocalDateTime decodePacked64DatetimeSeconds(long bitFieldDatetimeSeconds) { + checkValidBitField(bitFieldDatetimeSeconds, DATETIME_SECONDS_MASK); + int bitFieldTimeSeconds = (int) (bitFieldDatetimeSeconds & TIME_SECONDS_MASK); + LocalTime timeSeconds = decodePacked32TimeSeconds(bitFieldTimeSeconds); + int year = getFieldFromBitField(bitFieldDatetimeSeconds, YEAR_MASK, YEAR_SHIFT); + int monthOfYear = getFieldFromBitField(bitFieldDatetimeSeconds, MONTH_MASK, MONTH_SHIFT); + int dayOfMonth = getFieldFromBitField(bitFieldDatetimeSeconds, DAY_MASK, DAY_SHIFT); + try { + LocalDateTime dateTime = + LocalDateTime.of( + year, + monthOfYear, + dayOfMonth, + timeSeconds.getHour(), + timeSeconds.getMinute(), + timeSeconds.getSecond()); + checkValidDateTimeSeconds(dateTime); + return dateTime; + } catch (DateTimeException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + /** + * Encodes {@code dateTime} as a 8-byte integer with microseconds precision. + * + *
Encoding is as the following: + * + *
+ * 6 5 4 3 2 1 + * MSB 3210987654321098765432109876543210987654321098765432109876543210 LSB + * |--- year ---||m || D || H || M || S ||-------micros-----| + *+ * + * @see #decodePacked64DatetimeMicros(long) + */ + @SuppressWarnings({"GoodTime-ApiWithNumericTimeUnit", "JavaLocalDateTimeGetNano"}) + public static long encodePacked64DatetimeMicros(LocalDateTime dateTime) { + checkValidDateTimeMicros(dateTime); + return (encodePacked64DatetimeSeconds(dateTime) << MICRO_LENGTH) + | (dateTime.getNano() / 1_000L); + } + + /** + * Decodes {@code bitFieldDatetimeMicros} as a {@link LocalDateTime} with microseconds precision. + * + *
Encoding is as the following: + * + *
+ * 6 5 4 3 2 1 + * MSB 3210987654321098765432109876543210987654321098765432109876543210 LSB + * |--- year ---||m || D || H || M || S ||-------micros-----| + *+ * + * @see #encodePacked64DatetimeMicros(LocalDateTime) + */ + @SuppressWarnings("GoodTime-ApiWithNumericTimeUnit") + public static LocalDateTime decodePacked64DatetimeMicros(long bitFieldDatetimeMicros) { + checkValidBitField(bitFieldDatetimeMicros, DATETIME_MICROS_MASK); + long bitFieldDatetimeSeconds = bitFieldDatetimeMicros >> MICRO_LENGTH; + LocalDateTime dateTimeSeconds = decodePacked64DatetimeSeconds(bitFieldDatetimeSeconds); + int microOfSecond = getFieldFromBitField(bitFieldDatetimeMicros, MICRO_MASK, MICRO_SHIFT); + checkValidMicroOfSecond(microOfSecond); + LocalDateTime dateTime = dateTimeSeconds.withNano(microOfSecond * 1_000); + checkValidDateTimeMicros(dateTime); + return dateTime; + } + + private static int getFieldFromBitField(long bitField, long mask, int shift) { + return (int) ((bitField & mask) >> shift); + } + + private static void checkValidTimeSeconds(LocalTime time) { + checkArgument(time.getHour() >= 0 && time.getHour() <= 23); + checkArgument(time.getMinute() >= 0 && time.getMinute() <= 59); + checkArgument(time.getSecond() >= 0 && time.getSecond() <= 59); + } + + private static void checkValidDateTimeSeconds(LocalDateTime dateTime) { + checkArgument(dateTime.getYear() >= 1 && dateTime.getYear() <= 9999); + checkArgument(dateTime.getMonthValue() >= 1 && dateTime.getMonthValue() <= 12); + checkArgument(dateTime.getDayOfMonth() >= 1 && dateTime.getDayOfMonth() <= 31); + checkValidTimeSeconds(dateTime.toLocalTime()); + } + + private static void checkValidTimeMicros(LocalTime time) { + checkValidTimeSeconds(time); + checkArgument(time.equals(time.truncatedTo(ChronoUnit.MICROS))); + } + + private static void checkValidDateTimeMicros(LocalDateTime dateTime) { + checkValidDateTimeSeconds(dateTime); + checkArgument(dateTime.equals(dateTime.truncatedTo(ChronoUnit.MICROS))); + } + + private static void checkValidMicroOfSecond(int microOfSecond) { + checkArgument(microOfSecond >= 0 && microOfSecond <= 999999); + } + + private static void checkValidBitField(long bitField, long mask) { + checkArgument((bitField & ~mask) == 0x0L); + } + + private CivilTimeEncoder() {} +} diff --git a/google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1beta2/StreamWriter.java b/google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1beta2/StreamWriter.java index 59eb681545..4a937b6be5 100644 --- a/google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1beta2/StreamWriter.java +++ b/google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1beta2/StreamWriter.java @@ -61,11 +61,9 @@ import org.threeten.bp.Duration; /** - * A BigQuery Stream Writer that can be used to write data into BigQuery Table. - * - *
This is to be used to managed streaming write when you are working with PENDING streams or - * want to explicitly manage offset. In that most common cases when writing with COMMITTED stream - * without offset, please use a simpler writer {@code DirectWriter}. + * This is to be used to managed streaming write when you are working with PENDING streams or want + * to explicitly manage offset. In that most common cases when writing with COMMITTED stream without + * offset, please use a simpler writer {@code DirectWriter}. * *
A {@link StreamWrier} provides built-in capabilities to: handle batching of messages; * controlling memory utilization (through flow control) and request cleanup (only keeps write @@ -80,7 +78,10 @@ * *
{@link StreamWriter} will use the credentials set on the channel, which uses application
* default credentials through {@link GoogleCredentials#getApplicationDefault} by default.
+ *
+ * @deprecated use {@link #StreamWriterV2()} instead.
*/
+@Deprecated
public class StreamWriter implements AutoCloseable {
private static final Logger LOG = Logger.getLogger(StreamWriter.class.getName());
diff --git a/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1alpha2/BQTableSchemaToProtoDescriptorTest.java b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1alpha2/BQTableSchemaToProtoDescriptorTest.java
index 85bba343ac..63c51cf56c 100644
--- a/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1alpha2/BQTableSchemaToProtoDescriptorTest.java
+++ b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1alpha2/BQTableSchemaToProtoDescriptorTest.java
@@ -201,7 +201,7 @@ public void testStructComplex() throws Exception {
.build();
final Table.TableFieldSchema TEST_TIME =
Table.TableFieldSchema.newBuilder()
- .setType(Table.TableFieldSchema.Type.TIME)
+ .setType(Table.TableFieldSchema.Type.INT64)
.setMode(Table.TableFieldSchema.Mode.NULLABLE)
.setName("test_time")
.build();
diff --git a/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/BQTableSchemaToProtoDescriptorTest.java b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/BQTableSchemaToProtoDescriptorTest.java
index 4cb658f638..e5d0676975 100644
--- a/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/BQTableSchemaToProtoDescriptorTest.java
+++ b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/BQTableSchemaToProtoDescriptorTest.java
@@ -38,13 +38,13 @@ public class BQTableSchemaToProtoDescriptorTest {
.put(TableFieldSchema.Type.BOOL, BoolType.getDescriptor())
.put(TableFieldSchema.Type.BYTES, BytesType.getDescriptor())
.put(TableFieldSchema.Type.DATE, Int32Type.getDescriptor())
- .put(TableFieldSchema.Type.DATETIME, StringType.getDescriptor())
+ .put(TableFieldSchema.Type.DATETIME, Int64Type.getDescriptor())
.put(TableFieldSchema.Type.DOUBLE, DoubleType.getDescriptor())
.put(TableFieldSchema.Type.GEOGRAPHY, StringType.getDescriptor())
.put(TableFieldSchema.Type.INT64, Int64Type.getDescriptor())
.put(TableFieldSchema.Type.NUMERIC, StringType.getDescriptor())
.put(TableFieldSchema.Type.STRING, StringType.getDescriptor())
- .put(TableFieldSchema.Type.TIME, StringType.getDescriptor())
+ .put(TableFieldSchema.Type.TIME, Int64Type.getDescriptor())
.put(TableFieldSchema.Type.TIMESTAMP, Int64Type.getDescriptor())
.build();
diff --git a/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/CivilTimeEncoderTest.java b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/CivilTimeEncoderTest.java
new file mode 100644
index 0000000000..673d3b0eab
--- /dev/null
+++ b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/CivilTimeEncoderTest.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.bigquery.storage.v1beta2;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.logging.Logger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.threeten.bp.LocalDateTime;
+import org.threeten.bp.LocalTime;
+
+@RunWith(JUnit4.class)
+public class CivilTimeEncoderTest {
+ private static final Logger LOG = Logger.getLogger(CivilTimeEncoderTest.class.getName());
+
+ // Time
+ @Test
+ public void encodeAndDecodePacked64TimeMicros_validTime() {
+ // 00:00:00.000000
+ // 0b000000000000000000000000000|00000|000000|000000|00000000000000000000
+ // 0x0
+ assertEquals(0x0L, CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(0, 0, 0, 0)));
+ assertEquals(LocalTime.of(0, 0, 0, 0), CivilTimeEncoder.decodePacked64TimeMicros(0x0L));
+
+ // 00:01:02.003000
+ // 0b000000000000000000000000000|00000|000001|000010|00000000101110111000
+ // 0x4200BB8
+ assertEquals(
+ 0x4200BB8L, CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(0, 1, 2, 3_000_000)));
+ assertEquals(
+ LocalTime.of(0, 1, 2, 3_000_000), CivilTimeEncoder.decodePacked64TimeMicros(0x4200BB8L));
+
+ // 12:00:00.000000
+ // 0b000000000000000000000000000|01100|000000|000000|00000000000000000000
+ // 0xC00000000
+ assertEquals(
+ 0xC00000000L, CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(12, 0, 0, 0)));
+ assertEquals(
+ LocalTime.of(12, 0, 0, 0), CivilTimeEncoder.decodePacked64TimeMicros(0xC00000000L));
+
+ // 13:14:15.016000
+ // 0b000000000000000000000000000|01101|001110|001111|00000011111010000000
+ // 0xD38F03E80
+ assertEquals(
+ 0xD38F03E80L,
+ CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(13, 14, 15, 16_000_000)));
+ assertEquals(
+ LocalTime.of(13, 14, 15, 16_000_000),
+ CivilTimeEncoder.decodePacked64TimeMicros(0xD38F03E80L));
+
+ // 23:59:59.999000
+ // 0b000000000000000000000000000|10111|111011|111011|11110011111001011000
+ // 0x17EFBF3E58
+ assertEquals(
+ 0x17EFBF3E58L,
+ CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(23, 59, 59, 999_000_000)));
+ assertEquals(
+ LocalTime.of(23, 59, 59, 999_000_000),
+ CivilTimeEncoder.decodePacked64TimeMicros(0x17EFBF3E58L));
+ }
+
+ @Test
+ public void encodePacked64TimeMicros_giveErrorWhenPrecisionIsLost() {
+ try { // 00:00:00.000000999
+ // 0b000000000000000000000000000|00000|000000|000000|00000000000000000000
+ // 0x0
+ assertEquals(0x0L, CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(0, 0, 0, 999)));
+ Assert.fail();
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void decodePacked64TimeMicros_invalidBitField_throwsIllegalArgumentException() {
+ try {
+ // 00:00:00.000000
+ // 0b000000000000000000000000001|00000|000000|000000|00000000000000000000
+ // 0x2000000000
+ CivilTimeEncoder.decodePacked64TimeMicros(0x2000000000L);
+ Assert.fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals(null, e.getMessage());
+ }
+ }
+
+ @Test
+ public void decodePacked64TimeMicros_invalidMicroOfSecond_throwsIllegalArgumentException() {
+ try {
+ // 00:00:00.1000000
+ // 0b000000000000000000000000000|00000|000000|000000|11110100001001000000
+ // 0xF4240
+ CivilTimeEncoder.decodePacked64TimeMicros(0xF4240L);
+ Assert.fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals(null, e.getMessage());
+ }
+ }
+
+ @Test
+ public void decodePacked64TimeMicros_invalidSecondOfMinute_throwsIllegalArgumentException() {
+ try {
+ // 00:00:60.000000
+ // 0b000000000000000000000000000|00000|000000|111100|00000000000000000000
+ // 0x3C00000
+ CivilTimeEncoder.decodePacked64TimeMicros(0x3C00000L);
+ Assert.fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Invalid value for SecondOfMinute (valid values 0 - 59): 60", e.getMessage());
+ }
+ }
+
+ @Test
+ public void decodePacked64TimeMicros_invalidMinuteOfHour_throwsIllegalArgumentException() {
+ try {
+ // 00:60:00.000000
+ // 0b000000000000000000000000000|00000|111100|000000|00000000000000000000
+ // 0xF0000000
+ CivilTimeEncoder.decodePacked64TimeMicros(0xF0000000L);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void decodePacked64TimeMicros_invalidHourOfDay_throwsIllegalArgumentException() {
+ try {
+ // 24:00:00.000000
+ // 0b000000000000000000000000000|11000|000000|000000|00000000000000000000
+ // 0x1800000000
+ CivilTimeEncoder.decodePacked64TimeMicros(0x1800000000L);
+ Assert.fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Invalid value for HourOfDay (valid values 0 - 23): 24", e.getMessage());
+ }
+ }
+
+ // Date Time Tests
+ @Test
+ public void encodeAndDecodePacked64DatetimeMicros_validDateTime() {
+ // 0001/01/01 00:00:00
+ // 0b0000000000000000000000|00000000000001|0001|00001|00000|000000|000000
+ // 0x4420000
+ assertEquals(
+ 0x442000000000L,
+ CivilTimeEncoder.encodePacked64DatetimeMicros(LocalDateTime.of(1, 1, 1, 0, 0, 0, 0)));
+ assertEquals(
+ LocalDateTime.of(1, 1, 1, 0, 0, 0, 0),
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x442000000000L));
+
+ // 0001/02/03 00:01:02
+ // 0b0000000000000000000000|00000000000001|0010|00011|00000|000001|000010
+ // 0x4860042
+ assertEquals(
+ 0x486004200BB8L,
+ CivilTimeEncoder.encodePacked64DatetimeMicros(
+ LocalDateTime.of(1, 2, 3, 0, 1, 2, 3_000_000)));
+ assertEquals(
+ LocalDateTime.of(1, 2, 3, 0, 1, 2, 3_000_000),
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x486004200BB8L));
+
+ // 0001/01/01 12:00:00
+ // 0b0000000000000000000000|00000000000001|0001|00001|01100|000000|000000
+ // 0x442C000
+ assertEquals(
+ 0x442C00000000L,
+ CivilTimeEncoder.encodePacked64DatetimeMicros(LocalDateTime.of(1, 1, 1, 12, 0, 0, 0)));
+ assertEquals(
+ LocalDateTime.of(1, 1, 1, 12, 0, 0, 0),
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x442C00000000L));
+
+ // 0001/01/01 13:14:15
+ // 0b0000000000000000000000|00000000000001|0001|00001|01101|001110|001111
+ // 0x442D38F
+ assertEquals(
+ 0x442D38F03E80L,
+ CivilTimeEncoder.encodePacked64DatetimeMicros(
+ LocalDateTime.of(1, 1, 1, 13, 14, 15, 16_000_000)));
+ assertEquals(
+ LocalDateTime.of(1, 1, 1, 13, 14, 15, 16_000_000),
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x442D38F03E80L));
+
+ // 9999/12/31 23:59:59
+ // 0b0000000000000000000000|10011100001111|1100|11111|10111|111011|111011
+ // 0x9C3F3F7EFB
+ assertEquals(
+ 0x9C3F3F7EFBF3E58L,
+ CivilTimeEncoder.encodePacked64DatetimeMicros(
+ LocalDateTime.of(9999, 12, 31, 23, 59, 59, 999_000_000)));
+ assertEquals(
+ LocalDateTime.of(9999, 12, 31, 23, 59, 59, 999_000_000),
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x9C3F3F7EFBF3E58L));
+ }
+
+ @Test
+ public void encodePacked64DateTimeMicros_giveErrorWhenPrecisionIsLost() {
+ // 0001/01/01 00:00:00.000000999
+ // 0b0000000000000000000000|00000000000001|0001|00001|00000|000000|000000
+ // 0x4420000
+ try {
+ CivilTimeEncoder.encodePacked64DatetimeMicros(LocalDateTime.of(1, 1, 1, 0, 0, 0, 999));
+ Assert.fail();
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void encodePacked64DatetimeMicros_invalidYear_throwsIllegalArgumentException() {
+ // 10000/01/01 00:00:00.000000
+ // 0b00|10011100010000|0001|00001|00000|000000|000000|00000000000000000000
+ // 0x9C4042000000000
+ LocalDateTime dateTime = LocalDateTime.of(10000, 1, 1, 0, 0, 0, 0);
+ try {
+ CivilTimeEncoder.encodePacked64DatetimeMicros(dateTime);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void decodePacked64DatetimeMicros_validBitFieldDatetimeMicros() {}
+
+ @Test
+ public void decodePacked64DatetimeMicros_invalidBitField() {
+ try {
+ // 0001/01/01 00:00:00
+ // 0b0000000000000000000001|00000000000001|0001|00001|00000|000000|000000
+ // 0x10004420000
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x10004420000L);
+ Assert.fail();
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void decodePacked64DatetimeMicros_invalidMicroOfSecond_throwsIllegalArgumentException() {
+ try {
+ // 0001/01/01 00:00:00.1000000
+ // 0b00|00000000000001|0001|00001|00000|000000|000000|11110100001001000000
+ // 0x4420000F4240
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x4420000F4240L);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void decodePacked64DatetimeMicros_invalidSecondOfMinute_throwsIllegalArgumentException() {
+ try {
+ // 0001/01/01 00:00:60.000000
+ // 0b00|00000000000001|0001|00001|00000|000000|111100|00000000000000000000
+ // 0x442003C00000
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x442003C00000L);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void decodePacked64DatetimeMicros_invalidMinuteOfHour_throwsIllegalArgumentException() {
+ try {
+ // 0001/01/01 00:60:00.000000
+ // 0b00|00000000000001|0001|00001|00000|111100|000000|00000000000000000000
+ // 0x4420F0000000
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x4420F0000000L);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void decodePacked64DatetimeMicros_invalidHourOfDay_throwsIllegalArgumentException() {
+ try {
+ // 0001/01/01 24:00:00.000000
+ // 0b00|00000000000001|0001|00001|11000|000000|000000|00000000000000000000
+ // 0x443800000000
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x443800000000L);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void decodePacked64DatetimeMicros_invalidDayOfMonth_throwsIllegalArgumentException() {
+ try {
+ // 0001/01/00 00:00:00.000000
+ // 0b00|00000000000001|0001|00000|00000|000000|000000|00000000000000000000
+ // 0x440000000000
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x440000000000L);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void decodePacked64DatetimeMicros_invalidMonthOfYear_throwsIllegalArgumentException() {
+ try {
+ // 0001/13/01 00:00:00.000000
+ // 0b00|00000000000001|1101|00001|00000|000000|000000|00000000000000000000
+ // 0x742000000000
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x742000000000L);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void decodePacked64DatetimeMicros_invalidYear_throwsIllegalArgumentException() {
+ try {
+ // 10000/01/01 00:00:00.000000
+ // 0b00|10011100010000|0001|00001|00000|000000|000000|00000000000000000000
+ // 0x9C4042000000000
+ CivilTimeEncoder.decodePacked64DatetimeMicros(0x9C4042000000000L);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+}
diff --git a/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/JsonStreamWriterTest.java b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/JsonStreamWriterTest.java
index 0f68ae9c79..1205aa9615 100644
--- a/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/JsonStreamWriterTest.java
+++ b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/JsonStreamWriterTest.java
@@ -48,6 +48,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.threeten.bp.Instant;
+import org.threeten.bp.LocalTime;
@RunWith(JUnit4.class)
public class JsonStreamWriterTest {
@@ -410,7 +411,7 @@ public void testSingleAppendComplexJson() throws Exception {
.setTestNumeric("1.23456")
.setTestGeo("POINT(1,1)")
.setTestTimestamp(12345678)
- .setTestTime("01:00:01")
+ .setTestTime(CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(1, 0, 1)))
.build();
JSONObject complex_lvl2 = new JSONObject();
complex_lvl2.put("test_int", 3);
@@ -431,7 +432,7 @@ public void testSingleAppendComplexJson() throws Exception {
json.put("test_numeric", "1.23456");
json.put("test_geo", "POINT(1,1)");
json.put("test_timestamp", 12345678);
- json.put("test_time", "01:00:01");
+ json.put("test_time", CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(1, 0, 1)));
JSONArray jsonArr = new JSONArray();
jsonArr.put(json);
diff --git a/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/it/ITBigQueryTimeEncoderTest.java b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/it/ITBigQueryTimeEncoderTest.java
new file mode 100644
index 0000000000..28647b2ccf
--- /dev/null
+++ b/google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/it/ITBigQueryTimeEncoderTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.bigquery.storage.v1beta2.it;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.api.core.ApiFuture;
+import com.google.cloud.ServiceOptions;
+import com.google.cloud.bigquery.BigQuery;
+import com.google.cloud.bigquery.DatasetInfo;
+import com.google.cloud.bigquery.Field.Mode;
+import com.google.cloud.bigquery.FieldValueList;
+import com.google.cloud.bigquery.Schema;
+import com.google.cloud.bigquery.StandardSQLTypeName;
+import com.google.cloud.bigquery.StandardTableDefinition;
+import com.google.cloud.bigquery.TableId;
+import com.google.cloud.bigquery.TableInfo;
+import com.google.cloud.bigquery.TableResult;
+import com.google.cloud.bigquery.storage.v1beta2.AppendRowsResponse;
+import com.google.cloud.bigquery.storage.v1beta2.BigQueryWriteClient;
+import com.google.cloud.bigquery.storage.v1beta2.CivilTimeEncoder;
+import com.google.cloud.bigquery.storage.v1beta2.JsonStreamWriter;
+import com.google.cloud.bigquery.storage.v1beta2.TableName;
+import com.google.cloud.bigquery.testing.RemoteBigQueryHelper;
+import com.google.protobuf.Descriptors;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.concurrent.ExecutionException;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.threeten.bp.LocalDateTime;
+import org.threeten.bp.LocalTime;
+
+public class ITBigQueryTimeEncoderTest {
+ private static final String DATASET = RemoteBigQueryHelper.generateDatasetName();
+ private static final String TABLE = "testtable";
+ private static final String DESCRIPTION = "BigQuery Write Java manual client test dataset";
+
+ private static BigQueryWriteClient client;
+ private static TableInfo tableInfo;
+ private static BigQuery bigquery;
+
+ @BeforeClass
+ public static void beforeClass() throws IOException {
+ client = BigQueryWriteClient.create();
+
+ RemoteBigQueryHelper bigqueryHelper = RemoteBigQueryHelper.create();
+ bigquery = bigqueryHelper.getOptions().getService();
+ DatasetInfo datasetInfo =
+ DatasetInfo.newBuilder(/* datasetId = */ DATASET).setDescription(DESCRIPTION).build();
+ bigquery.create(datasetInfo);
+ tableInfo =
+ TableInfo.newBuilder(
+ TableId.of(DATASET, TABLE),
+ StandardTableDefinition.of(
+ Schema.of(
+ com.google.cloud.bigquery.Field.newBuilder(
+ "test_str", StandardSQLTypeName.STRING)
+ .build(),
+ com.google.cloud.bigquery.Field.newBuilder(
+ "test_time_micros", StandardSQLTypeName.TIME)
+ .setMode(Mode.REPEATED)
+ .build(),
+ com.google.cloud.bigquery.Field.newBuilder(
+ "test_datetime_micros", StandardSQLTypeName.DATETIME)
+ .setMode(Mode.REPEATED)
+ .build())))
+ .build();
+ bigquery.create(tableInfo);
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ if (client != null) {
+ client.close();
+ }
+ if (bigquery != null) {
+ RemoteBigQueryHelper.forceDelete(bigquery, DATASET);
+ }
+ }
+
+ @Test
+ public void TestTimeEncoding()
+ throws IOException, InterruptedException, ExecutionException,
+ Descriptors.DescriptorValidationException {
+ TableName parent = TableName.of(ServiceOptions.getDefaultProjectId(), DATASET, TABLE);
+ try (JsonStreamWriter jsonStreamWriter =
+ JsonStreamWriter.newBuilder(parent.toString(), tableInfo.getDefinition().getSchema())
+ .createDefaultStream()
+ .build()) {
+ JSONObject row = new JSONObject();
+ row.put("test_str", "Start of the day");
+ row.put(
+ "test_time_micros",
+ new JSONArray(
+ new long[] {
+ CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(13, 14, 15, 16_000_000)),
+ CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(23, 59, 59, 999_999_000)),
+ CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(0, 0, 0, 0)),
+ CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(1, 2, 3, 4_000)),
+ CivilTimeEncoder.encodePacked64TimeMicros(LocalTime.of(5, 6, 7, 8_000))
+ }));
+ row.put(
+ "test_datetime_micros",
+ new JSONArray(
+ new long[] {
+ CivilTimeEncoder.encodePacked64DatetimeMicros(
+ LocalDateTime.of(1, 1, 1, 12, 0, 0, 0)),
+ CivilTimeEncoder.encodePacked64DatetimeMicros(
+ LocalDateTime.of(1995, 5, 19, 10, 30, 45, 0)),
+ CivilTimeEncoder.encodePacked64DatetimeMicros(
+ LocalDateTime.of(2000, 1, 1, 0, 0, 0, 0)),
+ CivilTimeEncoder.encodePacked64DatetimeMicros(
+ LocalDateTime.of(2026, 3, 11, 5, 45, 12, 9_000_000)),
+ CivilTimeEncoder.encodePacked64DatetimeMicros(
+ LocalDateTime.of(2050, 1, 2, 3, 4, 5, 6_000)),
+ }));
+ JSONArray jsonArr = new JSONArray(new JSONObject[] {row});
+ ApiFuture