import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.util.Calendar;
public static final LocalTime BASE_LT = LocalTime.of(0, 0);
public static final LocalDateTime BASE_LDT = LocalDateTime.of(BASE_LD, BASE_LT);
+ private static final LocalDate BASE_EXT_LD = LocalDate.of(1, 1, 1);
+ private static final LocalTime BASE_EXT_LT = LocalTime.of(0, 0);
+ private static final LocalDateTime BASE_EXT_LDT =
+ LocalDateTime.of(BASE_EXT_LD, BASE_EXT_LT);
+ private static final byte[] EXT_LDT_TRAILER = {':', '7', 0x00};
+
private static final DateTimeFactory DEF_DATE_TIME_FACTORY =
new DefaultDateTimeFactory();
return readNumericValue(buffer);
case GUID:
return readGUIDValue(buffer, order);
+ case EXT_DATE_TIME:
+ return readExtendedDateValue(buffer);
case UNKNOWN_0D:
case UNKNOWN_11:
// treat like "binary" data
return getDateTimeFactory().fromDateBits(this, dateBits);
}
+ /**
+ * Decodes an "extended" date/time value.
+ */
+ private static Object readExtendedDateValue(ByteBuffer buffer) {
+ // format: <19digits>:<19digits>:7 0x00
+ long numDays = readExtDateLong(buffer, 19);
+ buffer.get();
+ long seconds = readExtDateLong(buffer, 12);
+ // there are 7 fractional digits
+ long nanos = readExtDateLong(buffer, 7) * 100L;
+ ByteUtil.forward(buffer, EXT_LDT_TRAILER.length);
+
+ return BASE_EXT_LDT
+ .plusDays(numDays)
+ .plusSeconds(seconds)
+ .plusNanos(nanos);
+ }
+
+ /**
+ * Reads the given number of ascii encoded characters as a long value.
+ */
+ private static long readExtDateLong(ByteBuffer buffer, int numChars) {
+ long val = 0L;
+ for(int i = 0; i < numChars; ++i) {
+ char digit = (char)buffer.get();
+ long inc = digit - '0';
+ val = (val * 10L) + inc;
+ }
+ return val;
+ }
+
/**
* Returns a java long time value converted from an access date double.
* @usage _advanced_method_
}
}
+ /**
+ * Writes an "extended" date/time value.
+ */
+ private void writeExtendedDateValue(ByteBuffer buffer, Object value)
+ throws InvalidValueException
+ {
+ LocalDateTime ldt = BASE_EXT_LDT;
+ if(value != null) {
+ ldt = toLocalDateTime(value, this);
+ }
+
+ LocalDate ld = ldt.toLocalDate();
+ LocalTime lt = ldt.toLocalTime();
+
+ long numDays = BASE_EXT_LD.until(ld, ChronoUnit.DAYS);
+ long numSeconds = BASE_EXT_LT.until(lt, ChronoUnit.SECONDS);
+ long nanos = lt.getNano();
+
+ // format: <19digits>:<19digits>:7 0x00
+ writeExtDateLong(buffer, numDays, 19);
+ buffer.put((byte)':');
+ writeExtDateLong(buffer, numSeconds, 12);
+ // there are 7 fractional digits
+ writeExtDateLong(buffer, (nanos / 100L), 7);
+
+ buffer.put(EXT_LDT_TRAILER);
+ }
+
+ /**
+ * Writes the given long value as the given number of ascii encoded
+ * characters.
+ */
+ private static void writeExtDateLong(
+ ByteBuffer buffer, long val, int numChars) {
+ // we write the desired number of digits in reverse order
+ int end = buffer.position();
+ int start = end + numChars - 1;
+ for(int i = start; i >= end; --i) {
+ char digit = (char)('0' + (char)(val % 10L));
+ buffer.put(i, (byte)digit);
+ val /= 10L;
+ }
+ ByteUtil.forward(buffer, numChars);
+ }
+
/**
* Returns an access date double converted from a java Date/Calendar/Number
* time value.
}
private static LocalDateTime toLocalDateTime(
+ Object value, DateTimeContext dtc) {
+ if(value instanceof TemporalAccessor) {
+ return temporalToLocalDateTime((TemporalAccessor)value, dtc);
+ }
+ Instant inst = Instant.ofEpochMilli(toDateLong(value));
+ return LocalDateTime.ofInstant(inst, dtc.getZoneId());
+ }
+
+ private static LocalDateTime temporalToLocalDateTime(
TemporalAccessor value, DateTimeContext dtc) {
// handle some common Temporal types
if(value instanceof Instant) {
return (Instant)value;
}
- return toLocalDateTime(value, dtc).atZone(dtc.getZoneId()).toInstant();
+ return temporalToLocalDateTime(value, dtc).atZone(dtc.getZoneId())
+ .toInstant();
}
static double toLocalDateDouble(long time) {
case BIG_INT:
buffer.putLong(toNumber(obj).longValue());
break;
+ case EXT_DATE_TIME:
+ writeExtendedDateValue(buffer, obj);
+ break;
case UNSUPPORTED_FIXEDLEN:
byte[] bytes = toByteArray(obj);
if(bytes.length != getLength()) {
case BIG_INT:
return ((value instanceof Long) ? value :
toNumber(value, db).longValue());
+ case EXT_DATE_TIME:
+ return toLocalDateTime(value, db);
default:
// some variation of binary data
return toByteArray(value);
value = Instant.ofEpochMilli(toDateLong(value));
}
return ColumnImpl.toDateDouble(
- toLocalDateTime((TemporalAccessor)value, dtc));
+ temporalToLocalDateTime((TemporalAccessor)value, dtc));
}
@Override
public Object toInternalValue(DatabaseImpl db, Object value) {
- if(value instanceof TemporalAccessor) {
- return toLocalDateTime((TemporalAccessor)value, db);
- }
- Instant inst = Instant.ofEpochMilli(toDateLong(value));
- return LocalDateTime.ofInstant(inst, db.getZoneId());
+ return toLocalDateTime(value, db);
}
}
protected static final byte[] EMPTY_PREFIX = new byte[0];
+ private static final byte[] ASC_EXT_DATE_TRAILER =
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02};
+ private static final byte[] DESC_EXT_DATE_TRAILER =
+ flipBytes(ByteUtil.copyOf(ASC_EXT_DATE_TRAILER, ASC_EXT_DATE_TRAILER.length));
+
static final short COLUMN_UNUSED = -1;
public static final byte ASCENDING_COLUMN_FLAG = (byte)0x01;
return new GuidColumnDescriptor(col, flags);
case BINARY:
return new BinaryColumnDescriptor(col, flags);
+ case EXT_DATE_TIME:
+ return new ExtDateColumnDescriptor(col, flags);
default:
// we can't modify this index at this point in time
}
}
+ /**
+ * ColumnDescriptor for extended date/time based columns.
+ */
+ private static final class ExtDateColumnDescriptor extends ColumnDescriptor
+ {
+ private ExtDateColumnDescriptor(ColumnImpl column, byte flags)
+ throws IOException
+ {
+ super(column, flags);
+ }
+
+ @Override
+ protected void writeNonNullValue(Object value, ByteStream bout)
+ throws IOException
+ {
+ byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
+
+ // entry (which is 42 bytes of data) is encoded in blocks of 8 bytes
+ // separated by '\t' char
+
+ // note that for desc, all bytes are flipped _except_ separator char
+ byte[] trailer = ASC_EXT_DATE_TRAILER;
+ if(!isAscending()) {
+ flipBytes(valueBytes);
+ trailer = DESC_EXT_DATE_TRAILER;
+ }
+
+ // first 5 blocks are all value data
+ int valIdx = 0;
+ for(int i = 0; i < 5; ++i) {
+ bout.write(valueBytes, valIdx, 8);
+ bout.write((byte)0x09);
+ valIdx += 8;
+ }
+
+ // last two data bytes and then the trailer
+ bout.write(valueBytes, valIdx, 2);
+ bout.write(trailer);
+ }
+ }
/**
* ColumnDescriptor for columns which we cannot currently write.
V16_CALC_TYPES.addAll(V14_CALC_TYPES);
}
+ private static final Set<DataType> V16_UNSUPP_TYPES =
+ EnumSet.of(DataType.EXT_DATE_TIME);
+ private static final Set<DataType> V12_UNSUPP_TYPES =
+ EnumSet.of(DataType.BIG_INT);
+ private static final Set<DataType> V3_UNSUPP_TYPES =
+ EnumSet.of(DataType.COMPLEX_TYPE);
+
+ static {
+ V12_UNSUPP_TYPES.addAll(V16_UNSUPP_TYPES);
+ V3_UNSUPP_TYPES.addAll(V12_UNSUPP_TYPES);
+ }
+
/** the JetFormat constants for the Jet database version "3" */
public static final JetFormat VERSION_3 = new Jet3Format();
/** the JetFormat constants for the Jet database version "4" */
@Override
public boolean isSupportedDataType(DataType type) {
- return ((type != DataType.COMPLEX_TYPE) &&
- (type != DataType.BIG_INT));
+ return !V3_UNSUPP_TYPES.contains(type);
}
@Override
@Override
public boolean isSupportedDataType(DataType type) {
- return ((type != DataType.COMPLEX_TYPE) &&
- (type != DataType.BIG_INT));
+ return !V3_UNSUPP_TYPES.contains(type);
}
@Override
@Override
public boolean isSupportedDataType(DataType type) {
- return (type != DataType.BIG_INT);
+ return !V12_UNSUPP_TYPES.contains(type);
}
@Override
@Override
public boolean isSupportedDataType(DataType type) {
- return true;
+ return !V16_UNSUPP_TYPES.contains(type);
}
@Override