From 969b4290b9934b94b1a0113e04e37ff44b2a536e Mon Sep 17 00:00:00 2001 From: JacobStocklass <35313153+JacobStocklass@users.noreply.github.com> Date: Thu, 18 Mar 2021 22:37:55 +0000 Subject: [PATCH] feat: Add CivilTimeEncoder to encode and decode DateTime/Time as numerics (#937) * Adding Time Encoding Integration Test Placeholder * Removing Stress test from this branch and moving it to the appropriate branch * Add integration test to make sure that encoding and decoding across a table insertion holds up * Added Integration test, renamed functions and got rid of redundant functions * Fix License Header * Java Lang set to 8 in order to use Java Local Time * Removing nano functions, cleaning up comments * Added round trip test to unit tests * Moving to threeten time instead of java time * Removing Java Time Dependency * Lint * Adding Time Encoding Integration Test Placeholder * Removing Stress test from this branch and moving it to the appropriate branch * Add integration test to make sure that encoding and decoding across a table insertion holds up * Added Integration test, renamed functions and got rid of redundant functions * Fix License Header * Java Lang set to 8 in order to use Java Local Time * Removing nano functions, cleaning up comments * Added round trip test to unit tests * Moving to threeten time instead of java time * Removing Java Time Dependency * Lint * Remove E2E test for another PR. Split Unit tests into better named tests * Lint * Combining Encode and Decode Test for easier reading * Removing unused methods --- .../storage/v1beta2/CivilTimeEncoder.java | 314 ++++++++++++++++ .../storage/v1beta2/CivilTimeEncoderTest.java | 334 ++++++++++++++++++ 2 files changed, 648 insertions(+) create mode 100644 google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1beta2/CivilTimeEncoder.java create mode 100644 google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1beta2/CivilTimeEncoderTest.java diff --git a/google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1beta2/CivilTimeEncoder.java b/google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1beta2/CivilTimeEncoder.java new file mode 100644 index 0000000000..1ab8d9eb17 --- /dev/null +++ b/google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1beta2/CivilTimeEncoder.java @@ -0,0 +1,314 @@ +/* + * 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 com.google.common.base.Preconditions.checkArgument; + +import org.threeten.bp.DateTimeException; +import org.threeten.bp.LocalDateTime; +import org.threeten.bp.LocalTime; +import org.threeten.bp.temporal.ChronoUnit; + +/** + * Ported from ZetaSQL CivilTimeEncoder Original code can be found at: + * https://github.com/google/zetasql/blob/master/java/com/google/zetasql/CivilTimeEncoder.java + * Encoder for TIME and DATETIME values, according to civil_time encoding. + * + *

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/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..5711a05617 --- /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 + @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) { + } + } +}