blob: fce400db33ab7400a49cbe374ab698708614aca4 [file] [log] [blame]
/*
* 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);
}
}
}