| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.ignite.internal.schema.row; |
| |
| import java.time.LocalDate; |
| import java.time.LocalTime; |
| import org.apache.ignite.internal.schema.TemporalNativeType; |
| |
| /** |
| * Helper class for temporal type conversions. |
| * <p> |
| * Provides methods to encode/decode temporal types in a compact way for further writing to row. |
| * Conversion preserves natural type order. |
| * <p> |
| * DATE is a fixed-length type which compacted representation keeps ordering, value is signed and fit into a 3-bytes. |
| * Thus, DATE value can be compared by bytes where first byte is signed and others - unsigned. |
| * Thus temporal functions, like YEAR(), can easily extracts fields with a mask, |
| * <p> |
| * Date compact structure: |
| * ┌──────────────┬─────────┬────────┐ |
| * │ Year(signed) │ Month │ Day │ |
| * ├──────────────┼─────────┼────────┤ |
| * │ 15 bits │ 4 bits │ 5 bits │ |
| * └──────────────┴─────────┴────────┘ |
| * <p> |
| * TIME is a fixed-length type supporting accuracy from 1 second up to 1 nanosecond. |
| * Compacted time representation keeps ordering, values fits to 4-6 bytes value. |
| * The first 18 bits is used for hours, minutes and seconds, and the last bits for fractional seconds: |
| * 14 for millisecond precision and 30 for nanosecond. |
| * Values of a type of any intermediate precisions is normalized to the type, |
| * then stored as shortest possible structure without a precision lost. |
| * <p> |
| * Time compact structure: |
| * ┌─────────┬─────────┬──────────┬─────────────┐ |
| * │ Hours │ Minutes │ Seconds │ Sub-seconds │ |
| * ├─────────┼─────────┼──────────┼─────────────┤ |
| * │ 6 bit │ 6 bits │ 6 bit │ 14 bits │ - 32-bits in total. |
| * │ 6 bit │ 6 bits │ 6 bit │ 30 bits │ - 48-bits in total. |
| * └─────────┴─────────┴──────────┴─────────────┘ |
| * <p> |
| * DATETIME is just a concatenation of DATE and TIME values. |
| * <p> |
| * TIMESTAMP has similar structure to {@link java.time.Instant} and supports precision from 1 second up to 1 nanosecond. |
| * Fractional seconds part is stored in a separate bit sequence which is omitted for {@code 0} accuracy. |
| * <p> |
| * Total value size is 8/12 bytes depending on the type precision. |
| * <p> |
| * Timestamp compact structure: |
| * ┌──────────────────────────┬─────────────┐ |
| * │ Seconds since the epoch │ Sub-seconds │ |
| * ├──────────────────────────┼─────────────┤ |
| * │ 64 bits │ 0/32 bits │ |
| * └──────────────────────────┴─────────────┘ |
| * |
| * @see org.apache.ignite.internal.schema.row.Row |
| * @see org.apache.ignite.internal.schema.row.RowAssembler |
| */ |
| public class TemporalTypesHelper { |
| /** Month field length. */ |
| public static final int MONTH_FIELD_LENGTH = 4; |
| |
| /** Day field length. */ |
| public static final int DAY_FIELD_LENGTH = 5; |
| |
| /** Hours field length. */ |
| public static final int HOUR_FIELD_LENGTH = 5; |
| |
| /** Minutes field length. */ |
| public static final int MINUTES_FIELD_LENGTH = 6; |
| |
| /** Seconds field length. */ |
| public static final int SECONDS_FIELD_LENGTH = 6; |
| |
| /** Max year boundary. */ |
| public static final int MAX_YEAR = (1 << 14) - 1; |
| |
| /** Min year boundary. */ |
| public static final int MIN_YEAR = -(1 << 14); |
| |
| /** Fractional part length for millis precision. */ |
| public static final int MILLISECOND_PART_LEN = 14; |
| |
| /** Fractional part mask for millis precision. */ |
| public static final long MILLISECOND_PART_MASK = (1L << MILLISECOND_PART_LEN) - 1; |
| |
| /** Fractional part length for nanos precision. */ |
| public static final int NANOSECOND_PART_LEN = 30; |
| |
| /** Fractional part mask for nanos precision. */ |
| public static final long NANOSECOND_PART_MASK = (1L << NANOSECOND_PART_LEN) - 1; |
| |
| /** |
| * @param len Mask length in bits. |
| * @return Mask. |
| */ |
| private static int mask(int len) { |
| return (1 << len) - 1; |
| } |
| |
| /** |
| * Compact LocalDate. |
| * |
| * @param date Date. |
| * @return Encoded date. |
| */ |
| public static int encodeDate(LocalDate date) { |
| int val = date.getYear() << MONTH_FIELD_LENGTH; |
| val = (val | date.getMonthValue()) << DAY_FIELD_LENGTH; |
| val |= date.getDayOfMonth(); |
| |
| return val & (0x00FF_FFFF); |
| } |
| |
| /** |
| * Expands to LocalDate. |
| * |
| * @param date Encoded date. |
| * @return LocalDate instance. |
| */ |
| public static LocalDate decodeDate(int date) { |
| date = (date << 8) >> 8; // Restore sign. |
| |
| int day = (date) & mask(DAY_FIELD_LENGTH); |
| int mon = (date >>= DAY_FIELD_LENGTH) & mask(MONTH_FIELD_LENGTH); |
| int year = (date >> MONTH_FIELD_LENGTH); // Sign matters. |
| |
| return LocalDate.of(year, mon, day); |
| } |
| |
| /** |
| * Encode LocalTime to long as concatenation of 2 int values: |
| * encoded time with precision of seconds and fractional seconds. |
| * |
| * @param type Native temporal type. |
| * @param localTime Time. |
| * @return Encoded local time. |
| * @see #NANOSECOND_PART_LEN |
| * @see #MILLISECOND_PART_LEN |
| */ |
| public static long encodeTime(TemporalNativeType type, LocalTime localTime) { |
| int time = localTime.getHour() << (MINUTES_FIELD_LENGTH + SECONDS_FIELD_LENGTH); |
| time |= localTime.getMinute() << SECONDS_FIELD_LENGTH; |
| time |= localTime.getSecond(); |
| |
| int fractional = truncateTo(type.precision(), localTime.getNano()); |
| |
| return ((long)time << 32) | fractional; |
| } |
| |
| /** |
| * Decode to LocalTime. |
| * |
| * @param type Type. |
| * @param time Encoded time. |
| * @return LocalTime instance. |
| */ |
| public static LocalTime decodeTime(TemporalNativeType type, long time) { |
| int fractional = (int)time; |
| int time0 = (int)(time >>> 32); |
| |
| int sec = time0 & mask(SECONDS_FIELD_LENGTH); |
| int min = (time0 >>>= SECONDS_FIELD_LENGTH) & mask(MINUTES_FIELD_LENGTH); |
| int hour = (time0 >>> MINUTES_FIELD_LENGTH) & mask(HOUR_FIELD_LENGTH); |
| |
| // Convert to nanoseconds. |
| switch (type.precision()) { |
| case 0: |
| break; |
| case 1: |
| case 2: |
| case 3: { |
| fractional *= 1_000_000; |
| break; |
| } |
| case 4: |
| case 5: |
| case 6: { |
| fractional *= 1_000; |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return LocalTime.of(hour, min, sec, fractional); |
| } |
| |
| /** |
| * Normalize nanoseconds regarding the precision. |
| * |
| * @param nanos Nanoseconds. |
| * @param precision Meaningful digits. |
| * @return Normalized nanoseconds. |
| */ |
| public static int normalizeNanos(int nanos, int precision) { |
| switch (precision) { |
| case 0: |
| nanos = 0; |
| break; |
| case 1: |
| nanos = (nanos / 100_000_000) * 100_000_000; // 100ms precision. |
| break; |
| case 2: |
| nanos = (nanos / 10_000_000) * 10_000_000; // 10ms precision. |
| break; |
| case 3: { |
| nanos = (nanos / 1_000_000) * 1_000_000; // 1ms precision. |
| break; |
| } |
| case 4: { |
| nanos = (nanos / 100_000) * 100_000; // 100mcs precision. |
| break; |
| } |
| case 5: { |
| nanos = (nanos / 10_000) * 10_000; // 10mcs precision. |
| break; |
| } |
| case 6: { |
| nanos = (nanos / 1_000) * 1_000; // 1mcs precision. |
| break; |
| } |
| case 7: { |
| nanos = (nanos / 100) * 100; // 100ns precision. |
| break; |
| } |
| case 8: { |
| nanos = (nanos / 10) * 10; // 10ns precision. |
| break; |
| } |
| case 9: { |
| nanos = nanos; // 1ns precision |
| break; |
| } |
| default: // Should never get here. |
| throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision); |
| } |
| |
| return nanos; |
| } |
| |
| /** |
| * Normalize to given precision and truncate to meaningful time unit. |
| * |
| * @param precision Precision. |
| * @param nanos Seconds' fractional part. |
| * @return Truncated fractional seconds (millis, micros or nanos). |
| */ |
| private static int truncateTo(int precision, int nanos) { |
| switch (precision) { |
| case 0: |
| return 0; |
| case 1: |
| return (nanos / 100_000_000) * 100; // 100ms precision. |
| case 2: |
| return (nanos / 10_000_000) * 10; // 10ms precision. |
| case 3: { |
| return nanos / 1_000_000; // 1ms precision. |
| } |
| case 4: { |
| return (nanos / 100_000) * 100; // 100mcs precision. |
| } |
| case 5: { |
| return (nanos / 10_000) * 10; // 10mcs precision. |
| } |
| case 6: { |
| return nanos / 1_000; // 1mcs precision. |
| } |
| case 7: { |
| return (nanos / 100) * 100; // 100ns precision. |
| } |
| case 8: { |
| return (nanos / 10) * 10; // 10ns precision. |
| } |
| case 9: { |
| return nanos; // 1ns precision |
| } |
| default: // Should never get here. |
| throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision); |
| } |
| } |
| } |