From 550b9029dceae7455e588c2daeba773cbc266efb Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Fri, 13 Sep 2024 15:54:20 -0500 Subject: [PATCH] fix: Correctly support flight Date/Time types in JS (#6063) Also removes preview for these types, so they are rendered consistently in the client. Fixes #6057 --- .../impl/preview/ColumnPreviewManager.java | 3 + .../web/client/api/LocalDateWrapper.java | 22 +----- .../web/client/api/LocalTimeWrapper.java | 73 ++++++++++++++----- .../api/barrage/WebChunkReaderFactory.java | 53 +++++++++++++- .../deephaven/web/shared/data/LocalDate.java | 47 ------------ .../deephaven/web/shared/data/LocalTime.java | 57 --------------- 6 files changed, 108 insertions(+), 147 deletions(-) delete mode 100644 web/shared-beans/src/main/java/io/deephaven/web/shared/data/LocalDate.java delete mode 100644 web/shared-beans/src/main/java/io/deephaven/web/shared/data/LocalTime.java diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/preview/ColumnPreviewManager.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/preview/ColumnPreviewManager.java index a8e82e2a9e2..b77f8619bf9 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/preview/ColumnPreviewManager.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/preview/ColumnPreviewManager.java @@ -15,6 +15,8 @@ import org.jpy.PyListWrapper; import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.*; import java.util.function.Function; @@ -159,6 +161,7 @@ public static boolean isColumnTypeDisplayable(Class type) { || io.deephaven.util.type.TypeUtils.isString(type) || NumericTypeUtils.isBigNumeric(type) || Instant.class == type || ZonedDateTime.class == type + || LocalDate.class == type || LocalTime.class == type || isOnWhiteList(type); } diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/LocalDateWrapper.java b/web/client-api/src/main/java/io/deephaven/web/client/api/LocalDateWrapper.java index a750b0f0310..5e3bb30eca8 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/LocalDateWrapper.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/LocalDateWrapper.java @@ -6,29 +6,20 @@ import com.google.gwt.i18n.client.NumberFormat; import com.vertispan.tsdefs.annotations.TsInterface; import com.vertispan.tsdefs.annotations.TsName; -import io.deephaven.web.shared.data.LocalDate; import jsinterop.annotations.JsMethod; -import javax.annotation.Nonnull; - /** * Wrap LocalDate values for use in JS. Provides text formatting for display and access to the underlying value. */ @TsInterface @TsName(namespace = "dh") public class LocalDateWrapper { - private final static NumberFormat YEAR_FORMAT = NumberFormat.getFormat("0000"); - private final static NumberFormat MONTH_DAY_FORMAT = NumberFormat.getFormat("00"); + private static final NumberFormat YEAR_FORMAT = NumberFormat.getFormat("0000"); + private static final NumberFormat MONTH_DAY_FORMAT = NumberFormat.getFormat("00"); private final int year; private final int monthValue, dayOfMonth; - public LocalDateWrapper(@Nonnull LocalDate localDate) { - year = localDate.getYear(); - monthValue = localDate.getMonthValue(); - dayOfMonth = localDate.getDayOfMonth(); - } - public LocalDateWrapper(int year, int monthValue, int dayOfMonth) { this.year = year; this.monthValue = monthValue; @@ -55,15 +46,6 @@ public int getDayOfMonth() { return dayOfMonth; } - @Deprecated - public LocalDate getWrapped() { - LocalDate localDate = new LocalDate(); - localDate.setYear(year); - localDate.setMonthValue((byte) monthValue); - localDate.setDayOfMonth((byte) dayOfMonth); - return localDate; - } - @JsMethod @Override public String toString() { diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/LocalTimeWrapper.java b/web/client-api/src/main/java/io/deephaven/web/client/api/LocalTimeWrapper.java index 10d64798f0d..078884e88fb 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/LocalTimeWrapper.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/LocalTimeWrapper.java @@ -6,10 +6,11 @@ import com.google.gwt.i18n.client.NumberFormat; import com.vertispan.tsdefs.annotations.TsInterface; import com.vertispan.tsdefs.annotations.TsName; -import io.deephaven.web.shared.data.LocalTime; +import io.deephaven.util.QueryConstants; import jsinterop.annotations.JsMethod; -import javax.annotation.Nonnull; +import java.util.function.IntFunction; +import java.util.function.LongFunction; /** * Wrap LocalTime values for use in JS. Provides text formatting for display and access to the underlying value. @@ -17,13 +18,51 @@ @TsInterface @TsName(namespace = "dh") public class LocalTimeWrapper { - private final static NumberFormat TWO_DIGIT_FORMAT = NumberFormat.getFormat("00"); - private final static NumberFormat NANOS_FORMAT = NumberFormat.getFormat("000000000"); + private static final NumberFormat TWO_DIGIT_FORMAT = NumberFormat.getFormat("00"); + private static final NumberFormat NANOS_FORMAT = NumberFormat.getFormat("000000000"); - private final LocalTime localTime; + private final int hour; + private final int minute; + private final int second; + private final int nano; - public LocalTimeWrapper(@Nonnull LocalTime localTime) { - this.localTime = localTime; + public static IntFunction intCreator(int unitPerMicro) { + int nanoPerUnit = 1_000_000_000 / unitPerMicro; + return val -> { + if (val == QueryConstants.NULL_INT) { + return null; + } + int nano = (val % unitPerMicro) * nanoPerUnit; + int secVal = val / unitPerMicro; + int second = (secVal % 60); + secVal /= 60; + int minute = (secVal % 60); + int hour = (secVal / 60); + return new LocalTimeWrapper(hour, minute, second, nano); + }; + } + + public static LongFunction longCreator(int unitPerMicro) { + int nanoPerUnit = 1_000_000_000 / unitPerMicro; + return val -> { + if (val == QueryConstants.NULL_LONG) { + return null; + } + int nano = (int) (val % unitPerMicro) * nanoPerUnit; + int secVal = (int) (val / unitPerMicro); + byte second = (byte) (secVal % 60); + secVal /= 60; + byte minute = (byte) (secVal % 60); + byte hour = (byte) (secVal / 60); + return new LocalTimeWrapper(hour, minute, second, nano); + }; + } + + public LocalTimeWrapper(int hour, int minute, int second, int nano) { + this.hour = hour; + this.minute = minute; + this.second = second; + this.nano = nano; } @JsMethod @@ -33,34 +72,30 @@ public String valueOf() { @JsMethod public int getHour() { - return localTime.getHour(); + return hour; } @JsMethod public int getMinute() { - return localTime.getMinute(); + return minute; } @JsMethod public int getSecond() { - return localTime.getSecond(); + return second; } @JsMethod public int getNano() { - return localTime.getNano(); - } - - public LocalTime getWrapped() { - return localTime; + return nano; } @JsMethod @Override public String toString() { - return TWO_DIGIT_FORMAT.format(localTime.getHour()) - + ":" + TWO_DIGIT_FORMAT.format(localTime.getMinute()) - + ":" + TWO_DIGIT_FORMAT.format(localTime.getSecond()) - + "." + NANOS_FORMAT.format(localTime.getNano()); + return TWO_DIGIT_FORMAT.format(hour) + + ":" + TWO_DIGIT_FORMAT.format(minute) + + ":" + TWO_DIGIT_FORMAT.format(second) + + "." + NANOS_FORMAT.format(nano); } } diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebChunkReaderFactory.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebChunkReaderFactory.java index d183aa491d3..475adf7d686 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebChunkReaderFactory.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebChunkReaderFactory.java @@ -3,6 +3,7 @@ // package io.deephaven.web.client.api.barrage; +import elemental2.core.JsDate; import io.deephaven.base.verify.Assert; import io.deephaven.chunk.WritableByteChunk; import io.deephaven.chunk.WritableChunk; @@ -23,10 +24,13 @@ import io.deephaven.extensions.barrage.chunk.VarListChunkReader; import io.deephaven.extensions.barrage.util.StreamReaderOptions; import io.deephaven.util.BooleanUtils; +import io.deephaven.util.QueryConstants; import io.deephaven.util.datastructures.LongSizedDataStructure; import io.deephaven.web.client.api.BigDecimalWrapper; import io.deephaven.web.client.api.BigIntegerWrapper; import io.deephaven.web.client.api.DateWrapper; +import io.deephaven.web.client.api.LocalDateWrapper; +import io.deephaven.web.client.api.LocalTimeWrapper; import io.deephaven.web.client.api.LongWrapper; import org.apache.arrow.flatbuf.Date; import org.apache.arrow.flatbuf.DateUnit; @@ -170,7 +174,23 @@ public ChunkReader getReader(StreamReaderOptions options, int factor, ChunkReade typeInfo.arrowField().type(t); switch (t.unit()) { case DateUnit.MILLISECOND: - return new LongChunkReader(options).transform(millis -> DateWrapper.of(millis * 1000 * 1000)); + return new LongChunkReader(options).transform(millis -> { + if (millis == QueryConstants.NULL_LONG) { + return null; + } + JsDate jsDate = new JsDate((double) (long) millis); + return new LocalDateWrapper(jsDate.getUTCFullYear(), 1 + jsDate.getUTCMonth(), + jsDate.getUTCDate()); + }); + case DateUnit.DAY: + return new IntChunkReader(options).transform(days -> { + if (days == QueryConstants.NULL_INT) { + return null; + } + JsDate jsDate = new JsDate(((double) (int) days) * 86400000); + return new LocalDateWrapper(jsDate.getUTCFullYear(), 1 + jsDate.getUTCMonth(), + jsDate.getUTCDate()); + }); default: throw new IllegalArgumentException("Unsupported Date unit: " + DateUnit.name(t.unit())); } @@ -179,11 +199,36 @@ public ChunkReader getReader(StreamReaderOptions options, int factor, ChunkReade Time t = new Time(); typeInfo.arrowField().type(t); switch (t.bitWidth()) { - case TimeUnit.NANOSECOND: { - return new LongChunkReader(options).transform(DateWrapper::of); + case 32: { + switch (t.unit()) { + case TimeUnit.SECOND: { + return new IntChunkReader(options) + .transform(LocalTimeWrapper.intCreator(1)::apply); + } + case TimeUnit.MILLISECOND: { + return new IntChunkReader(options) + .transform(LocalTimeWrapper.intCreator(1_000)::apply); + } + default: + throw new IllegalArgumentException("Unsupported Time unit: " + TimeUnit.name(t.unit())); + } + } + case 64: { + switch (t.unit()) { + case TimeUnit.NANOSECOND: { + return new LongChunkReader(options) + .transform(LocalTimeWrapper.longCreator(1_000_000_000)::apply); + } + case TimeUnit.MICROSECOND: { + return new LongChunkReader(options) + .transform(LocalTimeWrapper.longCreator(1_000_000)::apply); + } + default: + throw new IllegalArgumentException("Unsupported Time unit: " + TimeUnit.name(t.unit())); + } } default: - throw new IllegalArgumentException("Unsupported Time unit: " + TimeUnit.name(t.unit())); + throw new IllegalArgumentException("Unsupported Time bitWidth: " + t.bitWidth()); } } case Type.Timestamp: { diff --git a/web/shared-beans/src/main/java/io/deephaven/web/shared/data/LocalDate.java b/web/shared-beans/src/main/java/io/deephaven/web/shared/data/LocalDate.java deleted file mode 100644 index 69a0cc8cc71..00000000000 --- a/web/shared-beans/src/main/java/io/deephaven/web/shared/data/LocalDate.java +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.web.shared.data; - -import java.io.Serializable; - -/** - * A simple container for serializing LocalDate values. This should be better for serialization than java.time.LocalDate - * since we use bytes for month and day, and is compatible with GWT (java.time is not available in GWT). - */ -public class LocalDate implements Serializable { - private int year; - private byte monthValue, dayOfMonth; - - public LocalDate(int year, byte monthValue, byte dayOfMonth) { - this.year = year; - this.monthValue = monthValue; - this.dayOfMonth = dayOfMonth; - } - - public LocalDate() {} - - public int getYear() { - return year; - } - - public void setYear(int year) { - this.year = year; - } - - public byte getMonthValue() { - return monthValue; - } - - public void setMonthValue(byte monthValue) { - this.monthValue = monthValue; - } - - public byte getDayOfMonth() { - return dayOfMonth; - } - - public void setDayOfMonth(byte dayOfMonth) { - this.dayOfMonth = dayOfMonth; - } -} diff --git a/web/shared-beans/src/main/java/io/deephaven/web/shared/data/LocalTime.java b/web/shared-beans/src/main/java/io/deephaven/web/shared/data/LocalTime.java deleted file mode 100644 index 74ea3b1cfb3..00000000000 --- a/web/shared-beans/src/main/java/io/deephaven/web/shared/data/LocalTime.java +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.web.shared.data; - -import java.io.Serializable; - -/** - * A simple container for serializing local time values. This should be better for serialization than - * java.time.LocalTime since we use bytes for hour, minute and second, and is compatible with GWT (java.time is not - * available in GWT). - */ -public class LocalTime implements Serializable { - private byte hour, minute, second; - private int nano; - - public LocalTime(byte hour, byte minute, byte second, int nano) { - this.hour = hour; - this.minute = minute; - this.second = second; - this.nano = nano; - } - - public LocalTime() {} - - public byte getHour() { - return hour; - } - - public void setHour(byte hour) { - this.hour = hour; - } - - public byte getMinute() { - return minute; - } - - public void setMinute(byte minute) { - this.minute = minute; - } - - public byte getSecond() { - return second; - } - - public void setSecond(byte second) { - this.second = second; - } - - public int getNano() { - return nano; - } - - public void setNano(int nano) { - this.nano = nano; - } -}