This is an automated email from the ASF dual-hosted git repository. cutlerb pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/master by this push: new fb8704d ARROW-2015: [Java] Replace Joda time with Java 8 time fb8704d is described below commit fb8704d73852b4487f5d7f748a9fce2d2c13d584 Author: Bryan Cutler <cutl...@gmail.com> AuthorDate: Fri Mar 8 14:30:38 2019 -0800 ARROW-2015: [Java] Replace Joda time with Java 8 time This removes the use of the Joda time library in Arrow Java and replaces it with Java 8 java.time, as recommended by JSR-310. Arrow APIs that previously return a Joda time object will now return a java.time object. This includes the `getObject` method of date, time, timestamp and interval vectors. Most classes have an equivalent counterpart in java.time and usually required only a small change to use the new API. Some of the notable changes: * IntervalDayVector` previously used `Period` as a friendly-type, now uses java.time.Duration to give intervals in days and time. `InvervalYearVector` still uses `Period` for intervals in year, month, day. * `LocalDateTime` is similar but the constructor `LocalDateTime.of` accepts fraction of a second as nanoseconds, where with Joda time it could be in milliseconds. * `DateUtility` removed methods for Period conversions because they are done better in java.time.Period now. Also added methods to convert internal time vals to `LocalDateTime` in UTC that is used in vector and holder reader classes. Author: Bryan Cutler <cutl...@gmail.com> Closes #2966 from BryanCutler/remove-joda-time-ARROW-2015 and squashes the following commits: b5d3c0e <Bryan Cutler> fixed style checks 51468d7 <Bryan Cutler> Replace Joda time with Java 8 time --- java/gandiva/pom.xml | 6 -- .../arrow/gandiva/evaluator/BaseEvaluatorTest.java | 4 +- java/vector/pom.xml | 5 -- .../src/main/codegen/data/ValueVectorTypes.tdd | 2 +- .../src/main/codegen/includes/vv_imports.ftl | 9 ++- .../codegen/templates/AbstractFieldReader.java | 6 +- .../main/codegen/templates/HolderReaderImpl.java | 19 ++++-- .../src/main/codegen/templates/NullReader.java | 6 +- .../src/main/codegen/templates/UnionReader.java | 6 +- .../org/apache/arrow/vector/DateMilliVector.java | 8 +-- .../org/apache/arrow/vector/IntervalDayVector.java | 8 +-- .../apache/arrow/vector/IntervalYearVector.java | 9 ++- .../org/apache/arrow/vector/TimeMilliVector.java | 10 +-- .../apache/arrow/vector/TimeStampMicroVector.java | 11 ++-- .../apache/arrow/vector/TimeStampMilliVector.java | 8 +-- .../apache/arrow/vector/TimeStampNanoVector.java | 9 ++- .../apache/arrow/vector/TimeStampSecVector.java | 8 +-- .../org/apache/arrow/vector/util/DateUtility.java | 77 +++++++++++++--------- .../main/java/org/joda/time/LocalDateTimes.java | 29 -------- .../java/org/apache/arrow/vector/TestCopyFrom.java | 23 ++++--- .../vector/complex/writer/TestComplexWriter.java | 10 +-- .../org/apache/arrow/vector/ipc/BaseFileTest.java | 48 ++++++++------ 22 files changed, 153 insertions(+), 168 deletions(-) diff --git a/java/gandiva/pom.xml b/java/gandiva/pom.xml index 3469c9f..ded334c 100644 --- a/java/gandiva/pom.xml +++ b/java/gandiva/pom.xml @@ -62,12 +62,6 @@ <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> - <dependency> - <groupId>joda-time</groupId> - <artifactId>joda-time</artifactId> - <version>2.10</version> - <scope>test</scope> - </dependency> </dependencies> <profiles> <profile> diff --git a/java/gandiva/src/test/java/org/apache/arrow/gandiva/evaluator/BaseEvaluatorTest.java b/java/gandiva/src/test/java/org/apache/arrow/gandiva/evaluator/BaseEvaluatorTest.java index 97c2883..2fd5a24 100644 --- a/java/gandiva/src/test/java/org/apache/arrow/gandiva/evaluator/BaseEvaluatorTest.java +++ b/java/gandiva/src/test/java/org/apache/arrow/gandiva/evaluator/BaseEvaluatorTest.java @@ -19,6 +19,7 @@ package org.apache.arrow.gandiva.evaluator; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -37,7 +38,6 @@ import org.apache.arrow.vector.ipc.message.ArrowRecordBatch; import org.apache.arrow.vector.types.FloatingPointPrecision; import org.apache.arrow.vector.types.pojo.ArrowType; import org.apache.arrow.vector.types.pojo.Schema; -import org.joda.time.Instant; import org.junit.After; import org.junit.Before; @@ -265,7 +265,7 @@ class BaseEvaluatorTest { ArrowBuf buffer = allocator.buffer(dates.length * 8); for (int i = 0; i < dates.length; i++) { Instant instant = Instant.parse(dates[i]); - buffer.writeLong(instant.getMillis()); + buffer.writeLong(instant.toEpochMilli()); } return buffer; diff --git a/java/vector/pom.xml b/java/vector/pom.xml index 3f32752..37b042e 100644 --- a/java/vector/pom.xml +++ b/java/vector/pom.xml @@ -32,11 +32,6 @@ <version>${project.version}</version> </dependency> <dependency> - <groupId>joda-time</groupId> - <artifactId>joda-time</artifactId> - <version>2.9.9</version> - </dependency> - <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> diff --git a/java/vector/src/main/codegen/data/ValueVectorTypes.tdd b/java/vector/src/main/codegen/data/ValueVectorTypes.tdd index 71eed91..329422f 100644 --- a/java/vector/src/main/codegen/data/ValueVectorTypes.tdd +++ b/java/vector/src/main/codegen/data/ValueVectorTypes.tdd @@ -106,7 +106,7 @@ javaType: "ArrowBuf", boxedType: "ArrowBuf", minor: [ - { class: "IntervalDay", millisecondsOffset: 4, friendlyType: "Period", fields: [ {name: "days", type:"int"}, {name: "milliseconds", type:"int"}] } + { class: "IntervalDay", millisecondsOffset: 4, friendlyType: "Duration", fields: [ {name: "days", type:"int"}, {name: "milliseconds", type:"int"}] } ] }, { diff --git a/java/vector/src/main/codegen/includes/vv_imports.ftl b/java/vector/src/main/codegen/includes/vv_imports.ftl index 7bd884c..920c528 100644 --- a/java/vector/src/main/codegen/includes/vv_imports.ftl +++ b/java/vector/src/main/codegen/includes/vv_imports.ftl @@ -54,11 +54,10 @@ import java.sql.Time; import java.sql.Timestamp; import java.math.BigDecimal; import java.math.BigInteger; - -import org.joda.time.DateTime; -import org.joda.time.LocalDateTime; -import org.joda.time.Period; - +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.Period; +import java.time.ZonedDateTime; diff --git a/java/vector/src/main/codegen/templates/AbstractFieldReader.java b/java/vector/src/main/codegen/templates/AbstractFieldReader.java index d1d2bdf..05c1296 100644 --- a/java/vector/src/main/codegen/templates/AbstractFieldReader.java +++ b/java/vector/src/main/codegen/templates/AbstractFieldReader.java @@ -49,9 +49,9 @@ abstract class AbstractFieldReader extends AbstractBaseReader implements FieldRe return null; } - <#list ["Object", "BigDecimal", "Integer", "Long", "Boolean", - "Character", "LocalDateTime", "Period", "Double", "Float", - "Text", "String", "Byte", "Short", "byte[]"] as friendlyType> + <#list ["Object", "BigDecimal", "Short", "Integer", "Long", "Boolean", + "LocalDateTime", "Duration", "Period", "Double", "Float", + "Character", "Text", "String", "Byte", "byte[]"] as friendlyType> <#assign safeType=friendlyType /> <#if safeType=="byte[]"><#assign safeType="ByteArray" /></#if> public ${friendlyType} read${safeType}(int arrayIndex) { diff --git a/java/vector/src/main/codegen/templates/HolderReaderImpl.java b/java/vector/src/main/codegen/templates/HolderReaderImpl.java index 264e8c1..629bb73 100644 --- a/java/vector/src/main/codegen/templates/HolderReaderImpl.java +++ b/java/vector/src/main/codegen/templates/HolderReaderImpl.java @@ -37,11 +37,6 @@ package org.apache.arrow.vector.complex.impl; <#include "/@includes/vv_imports.ftl" /> -import java.math.BigDecimal; -import java.math.BigInteger; - -import org.joda.time.Period; - // Source code generated using FreeMarker template ${.template_name} @SuppressWarnings("unused") @@ -118,8 +113,9 @@ public class ${holderMode}${name}HolderReaderImpl extends AbstractFieldReader { return text; </#if> <#elseif minor.class == "IntervalDay"> - Period p = new Period(); - return p.plusDays(holder.days).plusMillis(holder.milliseconds); + return Duration.ofDays(holder.days).plusMillis(holder.milliseconds); + <#elseif minor.class == "IntervalYear"> + return Period.ofMonths(holder.value); <#elseif minor.class == "Bit" > return new Boolean(holder.value != 0); <#elseif minor.class == "Decimal"> @@ -131,6 +127,15 @@ public class ${holderMode}${name}HolderReaderImpl extends AbstractFieldReader { byte[] value = new byte [holder.byteWidth]; holder.buffer.getBytes(0, value, 0, holder.byteWidth); return value; + <#elseif minor.class == "TimeStampSec"> + final long millis = java.util.concurrent.TimeUnit.SECONDS.toMillis(holder.value); + return DateUtility.getLocalDateTimeFromEpochMilli(millis); + <#elseif minor.class == "TimeStampMilli" || minor.class == "DateMilli" || minor.class == "TimeMilli"> + return DateUtility.getLocalDateTimeFromEpochMilli(holder.value); + <#elseif minor.class == "TimeStampMicro"> + return DateUtility.getLocalDateTimeFromEpochMicro(holder.value); + <#elseif minor.class == "TimeStampNano"> + return DateUtility.getLocalDateTimeFromEpochNano(holder.value); <#else> ${friendlyType} value = new ${friendlyType}(this.holder.value); return value; diff --git a/java/vector/src/main/codegen/templates/NullReader.java b/java/vector/src/main/codegen/templates/NullReader.java index d4ed6e8..f5ef5ae 100644 --- a/java/vector/src/main/codegen/templates/NullReader.java +++ b/java/vector/src/main/codegen/templates/NullReader.java @@ -126,9 +126,9 @@ public class NullReader extends AbstractBaseReader implements FieldReader{ throw new IllegalArgumentException(String.format("You tried to read a %s type when you are using a ValueReader of type %s.", name, this.getClass().getSimpleName())); } - <#list ["Object", "BigDecimal", "Integer", "Long", "Boolean", - "Character", "LocalDateTime", "Period", "Double", "Float", - "Text", "String", "Byte", "Short", "byte[]"] as friendlyType> + <#list ["Object", "BigDecimal", "Short", "Integer", "Long", "Boolean", + "LocalDateTime", "Duration", "Period", "Double", "Float", + "Character", "Text", "String", "Byte", "byte[]"] as friendlyType> <#assign safeType=friendlyType /> <#if safeType=="byte[]"><#assign safeType="ByteArray" /></#if> diff --git a/java/vector/src/main/codegen/templates/UnionReader.java b/java/vector/src/main/codegen/templates/UnionReader.java index f9e8859..bfb6f19 100644 --- a/java/vector/src/main/codegen/templates/UnionReader.java +++ b/java/vector/src/main/codegen/templates/UnionReader.java @@ -131,9 +131,9 @@ public class UnionReader extends AbstractFieldReader { writer.data.copyFrom(idx(), writer.idx(), data); } - <#list ["Object", "Integer", "Long", "Boolean", - "Character", "LocalDateTime", "Double", "Float", - "Text", "Byte", "Short", "byte[]"] as friendlyType> + <#list ["Object", "BigDecimal", "Short", "Integer", "Long", "Boolean", + "LocalDateTime", "Duration", "Period", "Double", "Float", + "Character", "Text", "Byte", "byte[]"] as friendlyType> <#assign safeType=friendlyType /> <#if safeType=="byte[]"><#assign safeType="ByteArray" /></#if> diff --git a/java/vector/src/main/java/org/apache/arrow/vector/DateMilliVector.java b/java/vector/src/main/java/org/apache/arrow/vector/DateMilliVector.java index 4736fba..fdd832c 100644 --- a/java/vector/src/main/java/org/apache/arrow/vector/DateMilliVector.java +++ b/java/vector/src/main/java/org/apache/arrow/vector/DateMilliVector.java @@ -17,6 +17,8 @@ package org.apache.arrow.vector; +import java.time.LocalDateTime; + import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.vector.complex.impl.DateMilliReaderImpl; import org.apache.arrow.vector.complex.reader.FieldReader; @@ -24,8 +26,8 @@ import org.apache.arrow.vector.holders.DateMilliHolder; import org.apache.arrow.vector.holders.NullableDateMilliHolder; import org.apache.arrow.vector.types.Types.MinorType; import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.util.DateUtility; import org.apache.arrow.vector.util.TransferPair; -import org.joda.time.LocalDateTime; import io.netty.buffer.ArrowBuf; @@ -130,9 +132,7 @@ public class DateMilliVector extends BaseFixedWidthVector { return null; } else { final long millis = valueBuffer.getLong(index * TYPE_WIDTH); - final LocalDateTime localDateTime = new org.joda.time.LocalDateTime(millis, - org.joda.time.DateTimeZone.UTC); - return localDateTime; + return DateUtility.getLocalDateTimeFromEpochMilli(millis); } } diff --git a/java/vector/src/main/java/org/apache/arrow/vector/IntervalDayVector.java b/java/vector/src/main/java/org/apache/arrow/vector/IntervalDayVector.java index 4c65427..2dcc986 100644 --- a/java/vector/src/main/java/org/apache/arrow/vector/IntervalDayVector.java +++ b/java/vector/src/main/java/org/apache/arrow/vector/IntervalDayVector.java @@ -17,6 +17,8 @@ package org.apache.arrow.vector; +import java.time.Duration; + import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.vector.complex.impl.IntervalDayReaderImpl; import org.apache.arrow.vector.complex.reader.FieldReader; @@ -25,7 +27,6 @@ import org.apache.arrow.vector.holders.NullableIntervalDayHolder; import org.apache.arrow.vector.types.Types.MinorType; import org.apache.arrow.vector.types.pojo.FieldType; import org.apache.arrow.vector.util.TransferPair; -import org.joda.time.Period; import io.netty.buffer.ArrowBuf; @@ -130,15 +131,14 @@ public class IntervalDayVector extends BaseFixedWidthVector { * @param index position of element * @return element at given index */ - public Period getObject(int index) { + public Duration getObject(int index) { if (isSet(index) == 0) { return null; } else { final int startIndex = index * TYPE_WIDTH; final int days = valueBuffer.getInt(startIndex); final int milliseconds = valueBuffer.getInt(startIndex + MILLISECOND_OFFSET); - final Period p = new Period(); - return p.plusDays(days).plusMillis(milliseconds); + return Duration.ofDays(days).plusMillis(milliseconds); } } diff --git a/java/vector/src/main/java/org/apache/arrow/vector/IntervalYearVector.java b/java/vector/src/main/java/org/apache/arrow/vector/IntervalYearVector.java index 8562e12..1de643b 100644 --- a/java/vector/src/main/java/org/apache/arrow/vector/IntervalYearVector.java +++ b/java/vector/src/main/java/org/apache/arrow/vector/IntervalYearVector.java @@ -17,6 +17,8 @@ package org.apache.arrow.vector; +import java.time.Period; + import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.vector.complex.impl.IntervalYearReaderImpl; import org.apache.arrow.vector.complex.reader.FieldReader; @@ -25,7 +27,6 @@ import org.apache.arrow.vector.holders.NullableIntervalYearHolder; import org.apache.arrow.vector.types.Types.MinorType; import org.apache.arrow.vector.types.pojo.FieldType; import org.apache.arrow.vector.util.TransferPair; -import org.joda.time.Period; /** * IntervalYearVector implements a fixed width (4 bytes) vector of @@ -129,10 +130,8 @@ public class IntervalYearVector extends BaseFixedWidthVector { return null; } else { final int interval = valueBuffer.getInt(index * TYPE_WIDTH); - final int years = (interval / org.apache.arrow.vector.util.DateUtility.yearsToMonths); - final int months = (interval % org.apache.arrow.vector.util.DateUtility.yearsToMonths); - final Period p = new Period(); - return p.plusYears(years).plusMonths(months); + // TODO: verify interval is in months + return Period.ofMonths(interval); } } diff --git a/java/vector/src/main/java/org/apache/arrow/vector/TimeMilliVector.java b/java/vector/src/main/java/org/apache/arrow/vector/TimeMilliVector.java index 83cde52..d7aa927 100644 --- a/java/vector/src/main/java/org/apache/arrow/vector/TimeMilliVector.java +++ b/java/vector/src/main/java/org/apache/arrow/vector/TimeMilliVector.java @@ -17,6 +17,8 @@ package org.apache.arrow.vector; +import java.time.LocalDateTime; + import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.vector.complex.impl.TimeMilliReaderImpl; import org.apache.arrow.vector.complex.reader.FieldReader; @@ -24,8 +26,8 @@ import org.apache.arrow.vector.holders.NullableTimeMilliHolder; import org.apache.arrow.vector.holders.TimeMilliHolder; import org.apache.arrow.vector.types.Types.MinorType; import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.util.DateUtility; import org.apache.arrow.vector.util.TransferPair; -import org.joda.time.LocalDateTime; import io.netty.buffer.ArrowBuf; @@ -129,9 +131,9 @@ public class TimeMilliVector extends BaseFixedWidthVector { if (isSet(index) == 0) { return null; } - org.joda.time.LocalDateTime ldt = new org.joda.time.LocalDateTime(get(index), - org.joda.time.DateTimeZone.UTC); - return ldt; + final int millis = valueBuffer.getInt(index * TYPE_WIDTH); + // TODO: this doesn't seem right, time not from epoch + return DateUtility.getLocalDateTimeFromEpochMilli(millis); } /** diff --git a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroVector.java b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroVector.java index 3e0273c..ea71fb7 100644 --- a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroVector.java +++ b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroVector.java @@ -17,6 +17,9 @@ package org.apache.arrow.vector; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.vector.complex.impl.TimeStampMicroReaderImpl; import org.apache.arrow.vector.complex.reader.FieldReader; @@ -24,8 +27,8 @@ import org.apache.arrow.vector.holders.NullableTimeStampMicroHolder; import org.apache.arrow.vector.holders.TimeStampMicroHolder; import org.apache.arrow.vector.types.Types.MinorType; import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.util.DateUtility; import org.apache.arrow.vector.util.TransferPair; -import org.joda.time.LocalDateTime; /** * TimeStampMicroVector implements a fixed width vector (8 bytes) of @@ -114,12 +117,8 @@ public class TimeStampMicroVector extends TimeStampVector { if (isSet(index) == 0) { return null; } else { - /* value is truncated when converting microseconds to milliseconds in order to use DateTime type */ final long micros = valueBuffer.getLong(index * TYPE_WIDTH); - final long millis = java.util.concurrent.TimeUnit.MICROSECONDS.toMillis(micros); - final org.joda.time.LocalDateTime localDateTime = new org.joda.time.LocalDateTime(millis, - org.joda.time.DateTimeZone.UTC); - return localDateTime; + return DateUtility.getLocalDateTimeFromEpochMicro(micros); } } diff --git a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliVector.java b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliVector.java index d28b735..b05749e 100644 --- a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliVector.java +++ b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliVector.java @@ -17,6 +17,8 @@ package org.apache.arrow.vector; +import java.time.LocalDateTime; + import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.vector.complex.impl.TimeStampMilliReaderImpl; import org.apache.arrow.vector.complex.reader.FieldReader; @@ -24,8 +26,8 @@ import org.apache.arrow.vector.holders.NullableTimeStampMilliHolder; import org.apache.arrow.vector.holders.TimeStampMilliHolder; import org.apache.arrow.vector.types.Types.MinorType; import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.util.DateUtility; import org.apache.arrow.vector.util.TransferPair; -import org.joda.time.LocalDateTime; /** * TimeStampMilliVector implements a fixed width vector (8 bytes) of @@ -115,9 +117,7 @@ public class TimeStampMilliVector extends TimeStampVector { return null; } else { final long millis = valueBuffer.getLong(index * TYPE_WIDTH); - final org.joda.time.LocalDateTime localDateTime = new org.joda.time.LocalDateTime(millis, - org.joda.time.DateTimeZone.UTC); - return localDateTime; + return DateUtility.getLocalDateTimeFromEpochMilli(millis); } } diff --git a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoVector.java b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoVector.java index 77ba527..ccc17de 100644 --- a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoVector.java +++ b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoVector.java @@ -17,6 +17,8 @@ package org.apache.arrow.vector; +import java.time.LocalDateTime; + import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.vector.complex.impl.TimeStampNanoReaderImpl; import org.apache.arrow.vector.complex.reader.FieldReader; @@ -24,8 +26,8 @@ import org.apache.arrow.vector.holders.NullableTimeStampNanoHolder; import org.apache.arrow.vector.holders.TimeStampNanoHolder; import org.apache.arrow.vector.types.Types.MinorType; import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.util.DateUtility; import org.apache.arrow.vector.util.TransferPair; -import org.joda.time.LocalDateTime; /** * TimeStampNanoVector implements a fixed width vector (8 bytes) of @@ -115,10 +117,7 @@ public class TimeStampNanoVector extends TimeStampVector { return null; } else { final long nanos = valueBuffer.getLong(index * TYPE_WIDTH); - final long millis = java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(nanos); - final org.joda.time.LocalDateTime localDateTime = new org.joda.time.LocalDateTime(millis, - org.joda.time.DateTimeZone.UTC); - return localDateTime; + return DateUtility.getLocalDateTimeFromEpochNano(nanos); } } diff --git a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampSecVector.java b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampSecVector.java index 51e1f70..2293c10 100644 --- a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampSecVector.java +++ b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampSecVector.java @@ -17,6 +17,8 @@ package org.apache.arrow.vector; +import java.time.LocalDateTime; + import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.vector.complex.impl.TimeStampSecReaderImpl; import org.apache.arrow.vector.complex.reader.FieldReader; @@ -24,8 +26,8 @@ import org.apache.arrow.vector.holders.NullableTimeStampSecHolder; import org.apache.arrow.vector.holders.TimeStampSecHolder; import org.apache.arrow.vector.types.Types.MinorType; import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.util.DateUtility; import org.apache.arrow.vector.util.TransferPair; -import org.joda.time.LocalDateTime; /** * TimeStampSecVector implements a fixed width vector (8 bytes) of @@ -116,9 +118,7 @@ public class TimeStampSecVector extends TimeStampVector { } else { final long secs = valueBuffer.getLong(index * TYPE_WIDTH); final long millis = java.util.concurrent.TimeUnit.SECONDS.toMillis(secs); - final org.joda.time.LocalDateTime localDateTime = new org.joda.time.LocalDateTime(millis, - org.joda.time.DateTimeZone.UTC); - return localDateTime; + return DateUtility.getLocalDateTimeFromEpochMilli(millis); } } diff --git a/java/vector/src/main/java/org/apache/arrow/vector/util/DateUtility.java b/java/vector/src/main/java/org/apache/arrow/vector/util/DateUtility.java index 3eeda2c..5fa806a 100644 --- a/java/vector/src/main/java/org/apache/arrow/vector/util/DateUtility.java +++ b/java/vector/src/main/java/org/apache/arrow/vector/util/DateUtility.java @@ -17,14 +17,12 @@ package org.apache.arrow.vector.util; -import org.joda.time.DateTimeZone; -import org.joda.time.LocalDateTime; -import org.joda.time.LocalDateTimes; -import org.joda.time.Period; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.DateTimeFormatterBuilder; -import org.joda.time.format.DateTimeParser; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoUnit; import com.carrotsearch.hppc.ObjectIntHashMap; @@ -620,10 +618,10 @@ public class DateUtility { } } - public static final DateTimeFormatter formatDate = DateTimeFormat.forPattern("yyyy-MM-dd"); - public static final DateTimeFormatter formatTimeStampMilli = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS"); - public static final DateTimeFormatter formatTimeStampTZ = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS ZZZ"); - public static final DateTimeFormatter formatTime = DateTimeFormat.forPattern("HH:mm:ss.SSS"); + public static final DateTimeFormatter formatDate = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + public static final DateTimeFormatter formatTimeStampMilli = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + public static final DateTimeFormatter formatTimeStampTZ = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS ZZZ"); + public static final DateTimeFormatter formatTime = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); public static DateTimeFormatter dateTimeTZFormat = null; public static DateTimeFormatter timeFormat = null; @@ -649,10 +647,10 @@ public class DateUtility { public static DateTimeFormatter getDateTimeFormatter() { if (dateTimeTZFormat == null) { - DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd"); - DateTimeParser optionalTime = DateTimeFormat.forPattern(" HH:mm:ss").getParser(); - DateTimeParser optionalSec = DateTimeFormat.forPattern(".SSS").getParser(); - DateTimeParser optionalZone = DateTimeFormat.forPattern(" ZZZ").getParser(); + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter optionalTime = DateTimeFormatter.ofPattern(" HH:mm:ss"); + DateTimeFormatter optionalSec = DateTimeFormatter.ofPattern(".SSS"); + DateTimeFormatter optionalZone = DateTimeFormatter.ofPattern(" ZZZ"); dateTimeTZFormat = new DateTimeFormatterBuilder().append(dateFormatter).appendOptional(optionalTime) .appendOptional(optionalSec).appendOptional(optionalZone).toFormatter(); @@ -664,29 +662,46 @@ public class DateUtility { // Function returns time formatter used to parse time strings public static DateTimeFormatter getTimeFormatter() { if (timeFormat == null) { - DateTimeFormatter timeFormatter = DateTimeFormat.forPattern("HH:mm:ss"); - DateTimeParser optionalSec = DateTimeFormat.forPattern(".SSS").getParser(); + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); + DateTimeFormatter optionalSec = DateTimeFormatter.ofPattern(".SSS"); timeFormat = new DateTimeFormatterBuilder().append(timeFormatter).appendOptional(optionalSec).toFormatter(); } return timeFormat; } - public static int monthsFromPeriod(Period period) { - return (period.getYears() * yearsToMonths) + period.getMonths(); - } - - public static int millisFromPeriod(final Period period) { - return (period.getHours() * hoursToMillis) + - (period.getMinutes() * minutesToMillis) + - (period.getSeconds() * secondsToMillis) + - (period.getMillis()); + /** + * Convert milliseconds from epoch to a LocalDateTime with UTC offset. + * + * @param epochMillis milliseconds from epoch + * @return LocalDateTime object with UTC offset + */ + public static LocalDateTime getLocalDateTimeFromEpochMilli(long epochMillis) { + final LocalDateTime localDateTime = LocalDateTime.ofInstant( + Instant.ofEpochMilli(epochMillis), ZoneOffset.UTC.normalized()); + return localDateTime; } - public static long toMillis(LocalDateTime localDateTime) { - return LocalDateTimes.getLocalMillis(localDateTime); + /** + * Convert microseconds from epoch to a LocalDateTime with UTC offset. + * + * @param epochMicros microseconds from epoch + * @return LocalDateTime object with UTC offset + */ + public static LocalDateTime getLocalDateTimeFromEpochMicro(long epochMicros) { + final long millis = java.util.concurrent.TimeUnit.MICROSECONDS.toMillis(epochMicros); + final long addl_micros = epochMicros - (millis * 1000); + return DateUtility.getLocalDateTimeFromEpochMilli(millis).plus(addl_micros, ChronoUnit.MICROS); } - public static int toMillisOfDay(final LocalDateTime localDateTime) { - return localDateTime.toDateTime(DateTimeZone.UTC).millisOfDay().get(); + /** + * Convert nanoseconds from epoch to a LocalDateTime with UTC offset. + * + * @param epochNanos nanoseconds from epoch + * @return LocalDateTime object with UTC offset + */ + public static LocalDateTime getLocalDateTimeFromEpochNano(long epochNanos) { + final long millis = java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(epochNanos); + final long addl_nanos = epochNanos - (millis * 1000 * 1000); + return DateUtility.getLocalDateTimeFromEpochMilli(millis).plusNanos(addl_nanos); } } diff --git a/java/vector/src/main/java/org/joda/time/LocalDateTimes.java b/java/vector/src/main/java/org/joda/time/LocalDateTimes.java deleted file mode 100644 index a80cd1f..0000000 --- a/java/vector/src/main/java/org/joda/time/LocalDateTimes.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.joda.time; - -/** - * Workaround to access package protected fields in JODA. - */ -public class LocalDateTimes { - - public static long getLocalMillis(LocalDateTime localDateTime) { - return localDateTime.getLocalMillis(); - } - -} diff --git a/java/vector/src/test/java/org/apache/arrow/vector/TestCopyFrom.java b/java/vector/src/test/java/org/apache/arrow/vector/TestCopyFrom.java index 08932ef..9419ef7 100644 --- a/java/vector/src/test/java/org/apache/arrow/vector/TestCopyFrom.java +++ b/java/vector/src/test/java/org/apache/arrow/vector/TestCopyFrom.java @@ -23,11 +23,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import java.math.BigDecimal; +import java.time.Duration; +import java.time.Period; import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.memory.RootAllocator; import org.apache.arrow.vector.types.Types.MinorType; -import org.joda.time.Period; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -575,9 +576,9 @@ public class TestCopyFrom { if ((i & 1) == 0) { assertNull(vector1.getObject(i)); } else { - final Period p = vector1.getObject(i); - assertEquals(days + i, p.getDays()); - assertEquals(milliseconds + i, p.getMillis()); + final Duration d = vector1.getObject(i); + assertEquals(days + i, d.toDays()); + assertEquals(milliseconds + i, d.minusDays(days + i).toMillis()); } } @@ -604,9 +605,9 @@ public class TestCopyFrom { if (((i & 1) == 0) || (i >= initialCapacity)) { assertNull(vector2.getObject(i)); } else { - final Period p = vector2.getObject(i); - assertEquals(days + i, p.getDays()); - assertEquals(milliseconds + i, p.getMillis()); + final Duration d = vector2.getObject(i); + assertEquals(days + i, d.toDays()); + assertEquals(milliseconds + i, d.minusDays(days + i).toMillis()); } } } @@ -629,11 +630,9 @@ public class TestCopyFrom { continue; } vector1.setSafe(i, interval + i); - final Period p = new Period(); final int years = (interval + i) / org.apache.arrow.vector.util.DateUtility.yearsToMonths; final int months = (interval + i) % org.apache.arrow.vector.util.DateUtility.yearsToMonths; - periods[i] = p.plusYears(years).plusMonths(months); - ; + periods[i] = Period.ofYears(years).plusMonths(months).normalized(); } vector1.setValueCount(initialCapacity); @@ -648,7 +647,7 @@ public class TestCopyFrom { if ((i & 1) == 0) { assertNull(vector1.getObject(i)); } else { - final Period p = vector1.getObject(i); + final Period p = vector1.getObject(i).normalized(); assertEquals(interval + i, vector1.get(i)); assertEquals(periods[i], p); } @@ -677,7 +676,7 @@ public class TestCopyFrom { if (((i & 1) == 0) || (i >= initialCapacity)) { assertNull(vector2.getObject(i)); } else { - final Period p = vector2.getObject(i); + final Period p = vector2.getObject(i).normalized(); assertEquals(periods[i], p); } } diff --git a/java/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java b/java/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java index 61c1b92..d67fd6e 100644 --- a/java/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java +++ b/java/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java @@ -20,6 +20,7 @@ package org.apache.arrow.vector.complex.writer; import static org.junit.Assert.*; import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -70,7 +71,6 @@ import org.apache.arrow.vector.util.JsonStringArrayList; import org.apache.arrow.vector.util.JsonStringHashMap; import org.apache.arrow.vector.util.Text; import org.apache.arrow.vector.util.TransferPair; -import org.joda.time.LocalDateTime; import org.junit.Assert; import org.junit.Test; @@ -654,7 +654,7 @@ public class TestComplexWriter { public void timeStampSecWriter() throws Exception { // test values final long expectedSecs = 981173106L; - final LocalDateTime expectedSecDateTime = new LocalDateTime(2001, 2, 3, 4, 5, 6, 0); + final LocalDateTime expectedSecDateTime = LocalDateTime.of(2001, 2, 3, 4, 5, 6, 0); // write NonNullableStructVector parent = NonNullableStructVector.empty("parent", allocator); @@ -698,7 +698,7 @@ public class TestComplexWriter { public void timeStampMilliWriters() throws Exception { // test values final long expectedMillis = 981173106123L; - final LocalDateTime expectedMilliDateTime = new LocalDateTime(2001, 2, 3, 4, 5, 6, 123); + final LocalDateTime expectedMilliDateTime = LocalDateTime.of(2001, 2, 3, 4, 5, 6, 123 * 1_000_000); // write NonNullableStructVector parent = NonNullableStructVector.empty("parent", allocator); @@ -754,7 +754,7 @@ public class TestComplexWriter { public void timeStampMicroWriters() throws Exception { // test values final long expectedMicros = 981173106123456L; - final LocalDateTime expectedMicroDateTime = new LocalDateTime(2001, 2, 3, 4, 5, 6, 123); + final LocalDateTime expectedMicroDateTime = LocalDateTime.of(2001, 2, 3, 4, 5, 6, 123456 * 1000); // write NonNullableStructVector parent = NonNullableStructVector.empty("parent", allocator); @@ -801,7 +801,7 @@ public class TestComplexWriter { public void timeStampNanoWriters() throws Exception { // test values final long expectedNanos = 981173106123456789L; - final LocalDateTime expectedNanoDateTime = new LocalDateTime(2001, 2, 3, 4, 5, 6, 123); + final LocalDateTime expectedNanoDateTime = LocalDateTime.of(2001, 2, 3, 4, 5, 6, 123456789); // write NonNullableStructVector parent = NonNullableStructVector.empty("parent", allocator); diff --git a/java/vector/src/test/java/org/apache/arrow/vector/ipc/BaseFileTest.java b/java/vector/src/test/java/org/apache/arrow/vector/ipc/BaseFileTest.java index 059dcb0..e724293 100644 --- a/java/vector/src/test/java/org/apache/arrow/vector/ipc/BaseFileTest.java +++ b/java/vector/src/test/java/org/apache/arrow/vector/ipc/BaseFileTest.java @@ -25,6 +25,10 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.Arrays; import java.util.List; @@ -54,6 +58,7 @@ import org.apache.arrow.vector.complex.writer.IntWriter; import org.apache.arrow.vector.complex.writer.TimeMilliWriter; import org.apache.arrow.vector.complex.writer.TimeStampMilliTZWriter; import org.apache.arrow.vector.complex.writer.TimeStampMilliWriter; +import org.apache.arrow.vector.complex.writer.TimeStampNanoWriter; import org.apache.arrow.vector.dictionary.Dictionary; import org.apache.arrow.vector.dictionary.DictionaryEncoder; import org.apache.arrow.vector.dictionary.DictionaryProvider; @@ -62,10 +67,7 @@ import org.apache.arrow.vector.types.pojo.ArrowType; import org.apache.arrow.vector.types.pojo.DictionaryEncoding; import org.apache.arrow.vector.types.pojo.Field; import org.apache.arrow.vector.types.pojo.FieldType; -import org.apache.arrow.vector.util.DateUtility; import org.apache.arrow.vector.util.Text; -import org.joda.time.DateTimeZone; -import org.joda.time.LocalDateTime; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -82,18 +84,14 @@ public class BaseFileTest { protected static final int COUNT = 10; protected BufferAllocator allocator; - private DateTimeZone defaultTimezone = DateTimeZone.getDefault(); - @Before public void init() { - DateTimeZone.setDefault(DateTimeZone.forOffsetHours(2)); allocator = new RootAllocator(Integer.MAX_VALUE); } @After public void tearDown() { allocator.close(); - DateTimeZone.setDefault(defaultTimezone); } protected void writeData(int count, StructVector parent) { @@ -188,7 +186,7 @@ public class BaseFileTest { } private LocalDateTime makeDateTimeFromCount(int i) { - return new LocalDateTime(2000 + i, 1 + i, 1 + i, i, i, i, i); + return LocalDateTime.of(2000 + i, 1 + i, 1 + i, i, i, i, i * 100_000_000 + i); } protected void writeDateTimeData(int count, StructVector parent) { @@ -199,20 +197,27 @@ public class BaseFileTest { TimeMilliWriter timeWriter = rootWriter.timeMilli("time"); TimeStampMilliWriter timeStampMilliWriter = rootWriter.timeStampMilli("timestamp-milli"); TimeStampMilliTZWriter timeStampMilliTZWriter = rootWriter.timeStampMilliTZ("timestamp-milliTZ", "Europe/Paris"); + TimeStampNanoWriter timeStampNanoWriter = rootWriter.timeStampNano("timestamp-nano"); for (int i = 0; i < count; i++) { LocalDateTime dt = makeDateTimeFromCount(i); // Number of days in milliseconds since epoch, stored as 64-bit integer, only date part is used dateWriter.setPosition(i); - long dateLong = DateUtility.toMillis(dt.minusMillis(dt.getMillisOfDay())); + long dateLong = dt.toLocalDate().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(); dateWriter.writeDateMilli(dateLong); // Time is a value in milliseconds since midnight, stored as 32-bit integer timeWriter.setPosition(i); - timeWriter.writeTimeMilli(dt.getMillisOfDay()); - // Timestamp is milliseconds since the epoch, stored as 64-bit integer + int milliOfDay = (int) java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(dt.toLocalTime().toNanoOfDay()); + timeWriter.writeTimeMilli(milliOfDay); + // Timestamp as milliseconds since the epoch, stored as 64-bit integer timeStampMilliWriter.setPosition(i); - timeStampMilliWriter.writeTimeStampMilli(DateUtility.toMillis(dt)); + timeStampMilliWriter.writeTimeStampMilli(dt.toInstant(ZoneOffset.UTC).toEpochMilli()); + // Timestamp as milliseconds since epoch with timezone timeStampMilliTZWriter.setPosition(i); - timeStampMilliTZWriter.writeTimeStampMilliTZ(DateUtility.toMillis(dt)); + timeStampMilliTZWriter.writeTimeStampMilliTZ(dt.atZone(ZoneId.of("Europe/Paris")).toInstant().toEpochMilli()); + // Timestamp as nanoseconds since epoch + timeStampNanoWriter.setPosition(i); + long tsNanos = dt.toInstant(ZoneOffset.UTC).toEpochMilli() * 1_000_000 + i; // need to add back in nano val + timeStampNanoWriter.writeTimeStampNano(tsNanos); } writer.setValueCount(count); } @@ -221,16 +226,19 @@ public class BaseFileTest { Assert.assertEquals(count, root.getRowCount()); printVectors(root.getFieldVectors()); for (int i = 0; i < count; i++) { - long dateVal = ((DateMilliVector) root.getVector("date")).get(i); LocalDateTime dt = makeDateTimeFromCount(i); - LocalDateTime dateExpected = dt.minusMillis(dt.getMillisOfDay()); - Assert.assertEquals(DateUtility.toMillis(dateExpected), dateVal); - long timeVal = ((TimeMilliVector) root.getVector("time")).get(i); - Assert.assertEquals(dt.getMillisOfDay(), timeVal); + LocalDateTime dtMilli = dt.minusNanos(i); + LocalDateTime dateVal = ((DateMilliVector) root.getVector("date")).getObject(i); + LocalDateTime dateExpected = dt.toLocalDate().atStartOfDay(); + Assert.assertEquals(dateExpected, dateVal); + LocalTime timeVal = ((TimeMilliVector) root.getVector("time")).getObject(i).toLocalTime(); + Assert.assertEquals(dtMilli.toLocalTime(), timeVal); Object timestampMilliVal = root.getVector("timestamp-milli").getObject(i); - Assert.assertEquals(dt, timestampMilliVal); + Assert.assertEquals(dtMilli, timestampMilliVal); Object timestampMilliTZVal = root.getVector("timestamp-milliTZ").getObject(i); - Assert.assertEquals(DateUtility.toMillis(dt), timestampMilliTZVal); + Assert.assertEquals(dt.atZone(ZoneId.of("Europe/Paris")).toInstant().toEpochMilli(), timestampMilliTZVal); + Object timestampNanoVal = root.getVector("timestamp-nano").getObject(i); + Assert.assertEquals(dt, timestampNanoVal); } }