From 899dbf42436e2be6d8219099af68da96ed9a6acf Mon Sep 17 00:00:00 2001 From: lutovich Date: Tue, 27 Mar 2018 11:35:46 +0200 Subject: [PATCH] Access temporal values in ValueWriter Instead of their serialized form. This makes it easier for different `ValueWriter` implementations to use different serialization strategies. For example property store and schema index convert zoned date and time to UTC. This makes it convenient to compare time/date-time. When Bolt server uses same serialization it forces all clients (driver) to convert from and to UTC, which is especially problematic for DateTime containing timezone ID. After this change Bolt server should be able to send and receive local time/date-time with timezone information without doing double conversion. No conversion would then happen in clients. --- .../neo4j/bolt/v1/messaging/Neo4jPackV1.java | 21 ++- .../neo4j/bolt/v2/messaging/Neo4jPackV2.java | 65 +++++--- .../internal/codegen/ParameterConverter.java | 33 ++-- .../runtime/interpreted/CastSupport.scala | 23 ++- .../neo4j/kernel/api/index/ArrayEncoder.java | 34 ++-- .../impl/index/schema/NativeSchemaKey.java | 4 +- .../kernel/impl/store/PropertyStore.java | 3 +- .../store/TemporalValueWriterAdapter.java | 152 ++++++++++++++++++ .../impl/util/BaseToObjectValueWriter.java | 31 ++-- .../neo4j/values/storable/DateTimeValue.java | 12 +- .../org/neo4j/values/storable/DateValue.java | 2 +- .../neo4j/values/storable/DurationValue.java | 8 +- .../values/storable/LocalDateTimeValue.java | 2 +- .../neo4j/values/storable/LocalTimeValue.java | 3 +- .../org/neo4j/values/storable/TimeValue.java | 17 +- .../neo4j/values/storable/ValueWriter.java | 38 ++--- .../org/neo4j/values/utils/PrettyPrinter.java | 71 ++++---- .../TimeUtil.java => utils/TemporalUtil.java} | 31 ++-- .../values/storable/BufferValueWriter.java | 33 ++-- .../values/storable/DateTimeValueTest.java | 15 +- .../neo4j/values/storable/DateValueTest.java | 5 +- .../storable/LocalDateTimeValueTest.java | 9 +- .../values/storable/LocalTimeValueTest.java | 5 +- .../values/storable/ThrowingValueWriter.java | 21 ++- .../neo4j/values/storable/TimeValueTest.java | 30 ++-- .../neo4j/values/utils/PrettyPrinterTest.java | 97 ++++++++++- .../neo4j/values/utils/TemporalUtilTest.java | 90 +++++++++++ 27 files changed, 552 insertions(+), 303 deletions(-) create mode 100644 community/kernel/src/main/java/org/neo4j/kernel/impl/store/TemporalValueWriterAdapter.java rename community/values/src/main/java/org/neo4j/values/{storable/TimeUtil.java => utils/TemporalUtil.java} (72%) create mode 100644 community/values/src/test/java/org/neo4j/values/utils/TemporalUtilTest.java diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/Neo4jPackV1.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/Neo4jPackV1.java index 233eacd4cbf89..579d2bd1ccf29 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/Neo4jPackV1.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/Neo4jPackV1.java @@ -20,6 +20,11 @@ package org.neo4j.bolt.v1.messaging; import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -299,37 +304,31 @@ public void writeDuration( long months, long days, long seconds, int nanos ) thr } @Override - public void writeDate( long epochDay ) throws IOException + public void writeDate( LocalDate localDate ) throws IOException { throw new BoltIOException( Status.Request.Invalid, "Date is not yet supported as a return type in Bolt" ); } @Override - public void writeLocalTime( long nanoOfDay ) throws IOException + public void writeLocalTime( LocalTime localTime ) throws IOException { throw new BoltIOException( Status.Request.Invalid, "LocalTime is not yet supported as a return type in Bolt" ); } @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws IOException + public void writeTime( OffsetTime offsetTime ) throws IOException { throw new BoltIOException( Status.Request.Invalid, "Time is not yet supported as a return type in Bolt" ); } @Override - public void writeLocalDateTime( long epochSecond, int nano ) throws IOException + public void writeLocalDateTime( LocalDateTime localDateTime ) throws IOException { throw new BoltIOException( Status.Request.Invalid, "LocalDateTime is not yet supported as a return type in Bolt" ); } @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws IOException - { - throw new BoltIOException( Status.Request.Invalid, "DateTime is not yet supported as a return type in Bolt" ); - } - - @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws IOException + public void writeDateTime( ZonedDateTime zonedDateTime ) throws IOException { throw new BoltIOException( Status.Request.Invalid, "DateTime is not yet supported as a return type in Bolt" ); } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v2/messaging/Neo4jPackV2.java b/community/bolt/src/main/java/org/neo4j/bolt/v2/messaging/Neo4jPackV2.java index 7b5ac057cd616..2708068fe96d1 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v2/messaging/Neo4jPackV2.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v2/messaging/Neo4jPackV2.java @@ -20,8 +20,13 @@ package org.neo4j.bolt.v2.messaging; import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Arrays; import org.neo4j.bolt.v1.messaging.Neo4jPack; @@ -36,9 +41,10 @@ import org.neo4j.values.storable.LocalDateTimeValue; import org.neo4j.values.storable.LocalTimeValue; import org.neo4j.values.storable.PointValue; -import org.neo4j.values.storable.TimeUtil; import org.neo4j.values.storable.TimeValue; +import org.neo4j.values.utils.TemporalUtil; +import static java.time.ZoneOffset.UTC; import static org.neo4j.values.storable.DateTimeValue.datetime; import static org.neo4j.values.storable.DateValue.epochDate; import static org.neo4j.values.storable.DurationValue.duration; @@ -123,51 +129,70 @@ public void writeDuration( long months, long days, long seconds, int nanos ) thr } @Override - public void writeDate( long epochDay ) throws IOException + public void writeDate( LocalDate localDate ) throws IOException { + long epochDay = localDate.toEpochDay(); + packStructHeader( 1, DATE ); pack( epochDay ); } @Override - public void writeLocalTime( long nanoOfDay ) throws IOException + public void writeLocalTime( LocalTime localTime ) throws IOException { + long nanoOfDay = localTime.toNanoOfDay(); + packStructHeader( 1, LOCAL_TIME ); pack( nanoOfDay ); } @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws IOException + public void writeTime( OffsetTime offsetTime ) throws IOException { + long nanosOfDayLocal = offsetTime.toLocalTime().toNanoOfDay(); + int offsetSeconds = offsetTime.getOffset().getTotalSeconds(); + packStructHeader( 2, TIME ); - pack( TimeUtil.nanosOfDayToLocal( nanosOfDayUTC, offsetSeconds ) ); + pack( nanosOfDayLocal ); pack( offsetSeconds ); } @Override - public void writeLocalDateTime( long epochSecond, int nano ) throws IOException + public void writeLocalDateTime( LocalDateTime localDateTime ) throws IOException { + long epochSecond = localDateTime.toEpochSecond( UTC ); + int nano = localDateTime.getNano(); + packStructHeader( 2, LOCAL_DATE_TIME ); pack( epochSecond ); pack( nano ); } @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws IOException + public void writeDateTime( ZonedDateTime zonedDateTime ) throws IOException { - packStructHeader( 3, DATE_TIME_WITH_ZONE_OFFSET ); - pack( epochSecondUTC ); - pack( nano ); - pack( offsetSeconds ); - } + long epochSecondUTC = zonedDateTime.toEpochSecond(); + int nano = zonedDateTime.getNano(); - @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws IOException - { - packStructHeader( 3, DATE_TIME_WITH_ZONE_NAME ); - pack( epochSecondUTC ); - pack( nano ); - pack( zoneId ); + ZoneId zone = zonedDateTime.getZone(); + if ( zone instanceof ZoneOffset ) + { + int offsetSeconds = ((ZoneOffset) zone).getTotalSeconds(); + + packStructHeader( 3, DATE_TIME_WITH_ZONE_OFFSET ); + pack( epochSecondUTC ); + pack( nano ); + pack( offsetSeconds ); + } + else + { + String zoneId = zone.getId(); + + packStructHeader( 3, DATE_TIME_WITH_ZONE_NAME ); + pack( epochSecondUTC ); + pack( nano ); + pack( zoneId ); + } } } @@ -247,7 +272,7 @@ private TimeValue unpackTime() throws IOException { long nanosOfDayLocal = unpackLong(); int offsetSeconds = unpackInteger(); - return time( TimeUtil.nanosOfDayToUTC( nanosOfDayLocal, offsetSeconds ), ZoneOffset.ofTotalSeconds( offsetSeconds ) ); + return time( TemporalUtil.nanosOfDayToUTC( nanosOfDayLocal, offsetSeconds ), ZoneOffset.ofTotalSeconds( offsetSeconds ) ); } private LocalDateTimeValue unpackLocalDateTime() throws IOException diff --git a/community/cypher/cypher/src/main/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java b/community/cypher/cypher/src/main/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java index f82f65eadea66..4155ef69d5e4d 100644 --- a/community/cypher/cypher/src/main/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java +++ b/community/cypher/cypher/src/main/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java @@ -20,13 +20,10 @@ package org.neo4j.cypher.internal.codegen; import java.lang.reflect.Array; -import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetTime; -import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.ArrayDeque; import java.util.ArrayList; @@ -47,13 +44,11 @@ import org.neo4j.values.storable.DurationValue; import org.neo4j.values.storable.TextArray; import org.neo4j.values.storable.TextValue; -import org.neo4j.values.storable.TimeUtil; import org.neo4j.values.storable.Values; import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.NodeValue; import org.neo4j.values.virtual.RelationshipValue; -import static java.time.ZoneOffset.UTC; import static org.neo4j.helpers.collection.Iterators.iteratorsEqual; /** @@ -365,41 +360,33 @@ public void writeDuration( long months, long days, long seconds, int nanos ) } @Override - public void writeDate( long epochDay ) + public void writeDate( LocalDate localDate ) { - writeValue( LocalDate.ofEpochDay( epochDay ) ); + writeValue( localDate ); } @Override - public void writeLocalTime( long nanoOfDay ) + public void writeLocalTime( LocalTime localTime ) { - writeValue( LocalTime.ofNanoOfDay( nanoOfDay ) ); + writeValue( localTime ); } @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) + public void writeTime( OffsetTime offsetTime ) { - writeValue( OffsetTime.of( - LocalTime.ofNanoOfDay( TimeUtil.nanosOfDayToLocal( nanosOfDayUTC, offsetSeconds ) ), - ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); + writeValue( offsetTime ); } @Override - public void writeLocalDateTime( long epochSecond, int nano ) + public void writeLocalDateTime( LocalDateTime localDateTime ) { - writeValue( LocalDateTime.ofInstant( Instant.ofEpochSecond( epochSecond, nano ), UTC ) ); + writeValue( localDateTime ); } @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) + public void writeDateTime( ZonedDateTime zonedDateTime ) { - writeValue( ZonedDateTime.ofInstant( Instant.ofEpochSecond( epochSecondUTC, nano ), ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); - } - - @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) - { - writeValue( ZonedDateTime.ofInstant( Instant.ofEpochSecond( epochSecondUTC, nano ), ZoneId.of( zoneId ) ) ); + writeValue( zonedDateTime ); } private interface Writer diff --git a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala index 52d265d78b828..0cee1792bb9a1 100644 --- a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala +++ b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala @@ -241,23 +241,20 @@ object CastSupport { override def writeDuration(months: Long, days: Long, seconds: Long, nanos: Int): Unit = write(DurationValue.duration(months, days, seconds, nanos)) - override def writeDate(epochDay: Long): Unit = - write(DateValue.epochDate(epochDay).asObject()) + override def writeDate(localDate: LocalDate): Unit = + write(localDate) - override def writeLocalTime(nanoOfDay: Long): Unit = - write(LocalTimeValue.localTime(nanoOfDay).asObject()) + override def writeLocalTime(localTime: LocalTime): Unit = + write(localTime) - override def writeTime(nanosOfDayUTC: Long, offsetSeconds: Int): Unit = - write(TimeValue.time(nanosOfDayUTC, ZoneOffset.ofTotalSeconds(offsetSeconds)).asObject()) + override def writeTime(offsetTime: OffsetTime): Unit = + write(offsetTime) - override def writeLocalDateTime(epochSecond: Long, nano: Int): Unit = - write(LocalDateTimeValue.localDateTime(epochSecond,nano).asObject()) + override def writeLocalDateTime(localDateTime: LocalDateTime): Unit = + write(localDateTime) - override def writeDateTime(epochSecondUTC: Long, nano: Int, offsetSeconds: Int): Unit = - write(DateTimeValue.datetime(epochSecondUTC, nano, ZoneOffset.ofTotalSeconds(offsetSeconds)).asObject()) - - override def writeDateTime(epochSecondUTC: Long, nano: Int, zoneId: String): Unit = - write(DateTimeValue.datetime(epochSecondUTC, nano, ZoneId.of(zoneId)).asObject()) + override def writeDateTime(zonedDateTime: ZonedDateTime): Unit = + write(zonedDateTime) } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/index/ArrayEncoder.java b/community/kernel/src/main/java/org/neo4j/kernel/api/index/ArrayEncoder.java index 5e5593025e379..1ec9e47eba1dd 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/index/ArrayEncoder.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/index/ArrayEncoder.java @@ -19,8 +19,11 @@ */ package org.neo4j.kernel.api.index; -import java.time.ZoneId; -import java.time.ZoneOffset; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.Base64; import org.neo4j.string.UTF8; @@ -166,44 +169,37 @@ public void writeDuration( long months, long days, long seconds, int nanos ) thr } @Override - public void writeDate( long epochDay ) throws RuntimeException + public void writeDate( LocalDate localDate ) throws RuntimeException { - builder.append( DateValue.epochDate( epochDay ).prettyPrint() ); + builder.append( DateValue.date( localDate ).prettyPrint() ); builder.append( '|' ); } @Override - public void writeLocalTime( long nanoOfDay ) throws RuntimeException + public void writeLocalTime( LocalTime localTime ) throws RuntimeException { - builder.append( LocalTimeValue.localTime( nanoOfDay ).prettyPrint() ); + builder.append( LocalTimeValue.localTime( localTime ).prettyPrint() ); builder.append( '|' ); } @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeException + public void writeTime( OffsetTime offsetTime ) throws RuntimeException { - builder.append( TimeValue.time( nanosOfDayUTC, ZoneOffset.ofTotalSeconds( offsetSeconds ) ).prettyPrint() ); + builder.append( TimeValue.time( offsetTime ).prettyPrint() ); builder.append( '|' ); } @Override - public void writeLocalDateTime( long epochSecond, int nano ) throws RuntimeException + public void writeLocalDateTime( LocalDateTime localDateTime ) throws RuntimeException { - builder.append( LocalDateTimeValue.localDateTime( epochSecond, nano ).prettyPrint() ); + builder.append( LocalDateTimeValue.localDateTime( localDateTime ).prettyPrint() ); builder.append( '|' ); } @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws RuntimeException + public void writeDateTime( ZonedDateTime zonedDateTime ) throws RuntimeException { - builder.append( DateTimeValue.datetime( epochSecondUTC, nano, ZoneOffset.ofTotalSeconds( offsetSeconds ) ).prettyPrint() ); - builder.append( '|' ); - } - - @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws RuntimeException - { - builder.append( DateTimeValue.datetime( epochSecondUTC, nano, ZoneId.of( zoneId ) ).prettyPrint() ); + builder.append( DateTimeValue.datetime( zonedDateTime ).prettyPrint() ); builder.append( '|' ); } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaKey.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaKey.java index dd5604d0d7d59..1d83b1c77a77f 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaKey.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaKey.java @@ -20,15 +20,15 @@ package org.neo4j.kernel.impl.index.schema; import org.neo4j.index.internal.gbptree.GBPTree; +import org.neo4j.kernel.impl.store.TemporalValueWriterAdapter; import org.neo4j.values.storable.Value; -import org.neo4j.values.storable.ValueWriter; /** * Includes value and entity id (to be able to handle non-unique values). * This is the abstraction of what NativeSchemaIndex with friends need from a schema key. * Note that it says nothing about how keys are compared, serialized, read, written, etc. That is the job of Layout. */ -abstract class NativeSchemaKey> extends ValueWriter.Adapter +abstract class NativeSchemaKey> extends TemporalValueWriterAdapter { private static final boolean DEFAULT_COMPARE_ID = true; diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java index 64256e586c33d..f3d10372e9bc3 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java @@ -53,7 +53,6 @@ import org.neo4j.values.storable.ArrayValue; import org.neo4j.values.storable.CoordinateReferenceSystem; import org.neo4j.values.storable.Value; -import org.neo4j.values.storable.ValueWriter; import static org.neo4j.kernel.impl.store.DynamicArrayStore.getRightArray; import static org.neo4j.kernel.impl.store.NoStoreHeaderFormat.NO_STORE_HEADER_FORMAT; @@ -411,7 +410,7 @@ private static ByteBuffer grow( ByteBuffer buffer, int required ) return ByteBuffer.allocate( capacity ).order( ByteOrder.LITTLE_ENDIAN ).put( buffer ); } - private static class PropertyBlockValueWriter implements ValueWriter + private static class PropertyBlockValueWriter extends TemporalValueWriterAdapter { private final PropertyBlock block; private final int keyId; diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/TemporalValueWriterAdapter.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/TemporalValueWriterAdapter.java new file mode 100644 index 0000000000000..a073ec66c3280 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/TemporalValueWriterAdapter.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.store; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.neo4j.values.storable.ValueWriter; +import org.neo4j.values.utils.TemporalUtil; + +import static java.time.ZoneOffset.UTC; + +/** + * A {@link ValueWriter} that defines format for all temporal types, except duration. + * Subclasses will not be able to override methods like {@link #writeDate(LocalDate)}. They should instead override {@link #writeDate(long)} that + * defines how {@link LocalDate} is serialized. + *

+ * Primary purpose of this class is to share serialization format between property store writer and schema indexes. + * + * @param the error type. + */ +public abstract class TemporalValueWriterAdapter extends ValueWriter.Adapter +{ + @Override + public final void writeDate( LocalDate localDate ) throws E + { + writeDate( localDate.toEpochDay() ); + } + + @Override + public final void writeLocalTime( LocalTime localTime ) throws E + { + writeLocalTime( localTime.toNanoOfDay() ); + } + + @Override + public final void writeTime( OffsetTime offsetTime ) throws E + { + long nanosOfDayUTC = TemporalUtil.getNanosOfDayUTC( offsetTime ); + int offsetSeconds = offsetTime.getOffset().getTotalSeconds(); + writeTime( nanosOfDayUTC, offsetSeconds ); + } + + @Override + public final void writeLocalDateTime( LocalDateTime localDateTime ) throws E + { + long epochSecond = localDateTime.toEpochSecond( UTC ); + int nano = localDateTime.getNano(); + writeLocalDateTime( epochSecond, nano ); + } + + @Override + public final void writeDateTime( ZonedDateTime zonedDateTime ) throws E + { + long epochSecondUTC = zonedDateTime.toEpochSecond(); + int nano = zonedDateTime.getNano(); + + ZoneId zone = zonedDateTime.getZone(); + if ( zone instanceof ZoneOffset ) + { + int offsetSeconds = ((ZoneOffset) zone).getTotalSeconds(); + writeDateTime( epochSecondUTC, nano, offsetSeconds ); + } + else + { + String zoneId = zone.getId(); + writeDateTime( epochSecondUTC, nano, zoneId ); + } + } + + /** + * Write date value obtained from {@link LocalDate} in {@link #writeDate(LocalDate)}. + * + * @param epochDay the epoch day. + */ + protected void writeDate( long epochDay ) throws E + { + } + + /** + * Write local time value obtained from {@link LocalTime} in {@link #writeLocalTime(LocalTime)}. + * + * @param nanoOfDay the nanosecond of the day. + */ + protected void writeLocalTime( long nanoOfDay ) throws E + { + } + + /** + * Write time value obtained from {@link OffsetTime} in {@link #writeTime(OffsetTime)}. + * + * @param nanosOfDayUTC nanoseconds of day in UTC. will be between -18h and +42h + * @param offsetSeconds time zone offset in seconds + */ + protected void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws E + { + } + + /** + * Write local date-time value obtained from {@link LocalDateTime} in {@link #writeLocalDateTime(LocalDateTime)}. + * + * @param epochSecond the epoch second in UTC. + * @param nano the nanosecond. + */ + protected void writeLocalDateTime( long epochSecond, int nano ) throws E + { + } + + /** + * Write zoned date-time value obtained from {@link ZonedDateTime} in {@link #writeDateTime(ZonedDateTime)}. + * + * @param epochSecondUTC the epoch second in UTC (no offset). + * @param nano the nanosecond. + * @param offsetSeconds the offset in seconds. + */ + protected void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws E + { + } + + /** + * Write zoned date-time value obtained from {@link ZonedDateTime} in {@link #writeDateTime(ZonedDateTime)}. + * + * @param epochSecondUTC the epoch second in UTC (no offset). + * @param nano the nanosecond. + * @param zoneId the timezone id. + */ + protected void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws E + { + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java index bc7ea26b0be60..1eae6f80c56a7 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java @@ -20,13 +20,10 @@ package org.neo4j.kernel.impl.util; import java.lang.reflect.Array; -import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetTime; -import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.ArrayDeque; import java.util.ArrayList; @@ -54,7 +51,6 @@ import org.neo4j.values.virtual.NodeValue; import org.neo4j.values.virtual.RelationshipValue; -import static java.time.ZoneOffset.UTC; import static org.neo4j.helpers.collection.Iterators.iteratorsEqual; /** @@ -417,40 +413,33 @@ public void writeDuration( long months, long days, long seconds, int nanos ) } @Override - public void writeDate( long epochDay ) throws RuntimeException + public void writeDate( LocalDate localDate ) throws RuntimeException { - writeValue( LocalDate.ofEpochDay( epochDay ) ); + writeValue( localDate ); } @Override - public void writeLocalTime( long nanoOfDay ) throws RuntimeException + public void writeLocalTime( LocalTime localTime ) throws RuntimeException { - writeValue( LocalTime.ofNanoOfDay( nanoOfDay ) ); + writeValue( localTime ); } @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeException + public void writeTime( OffsetTime offsetTime ) throws RuntimeException { - writeValue( OffsetTime.ofInstant( Instant.ofEpochSecond( 0, nanosOfDayUTC ), - ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); + writeValue( offsetTime ); } @Override - public void writeLocalDateTime( long epochSecond, int nano ) throws RuntimeException + public void writeLocalDateTime( LocalDateTime localDateTime ) throws RuntimeException { - writeValue( LocalDateTime.ofInstant( Instant.ofEpochSecond(epochSecond, nano), UTC ) ); + writeValue( localDateTime ); } @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws RuntimeException + public void writeDateTime( ZonedDateTime zonedDateTime ) throws RuntimeException { - writeValue( ZonedDateTime.ofInstant( Instant.ofEpochSecond(epochSecondUTC, nano), ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); - } - - @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws RuntimeException - { - writeValue( ZonedDateTime.ofInstant( Instant.ofEpochSecond(epochSecondUTC, nano), ZoneId.of( zoneId ) ) ); + writeValue( zonedDateTime ); } private interface Writer diff --git a/community/values/src/main/java/org/neo4j/values/storable/DateTimeValue.java b/community/values/src/main/java/org/neo4j/values/storable/DateTimeValue.java index a6f63a84a952f..6134e58566a05 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/DateTimeValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/DateTimeValue.java @@ -459,17 +459,7 @@ public boolean equals( Value other ) @Override public void writeTo( ValueWriter writer ) throws E { - Instant instant = value.toInstant(); - ZoneId zone = value.getZone(); - if ( zone instanceof ZoneOffset ) - { - ZoneOffset offset = (ZoneOffset) zone; - writer.writeDateTime( instant.getEpochSecond(), instant.getNano(), offset.getTotalSeconds() ); - } - else - { - writer.writeDateTime( instant.getEpochSecond(), instant.getNano(), zone.getId() ); - } + writer.writeDateTime( value ); } @Override diff --git a/community/values/src/main/java/org/neo4j/values/storable/DateValue.java b/community/values/src/main/java/org/neo4j/values/storable/DateValue.java index 4da6016eaf41d..caeefc74c5103 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/DateValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/DateValue.java @@ -260,7 +260,7 @@ public boolean equals( Value other ) @Override public void writeTo( ValueWriter writer ) throws E { - writer.writeDate( value.toEpochDay() ); + writer.writeDate( value ); } @Override diff --git a/community/values/src/main/java/org/neo4j/values/storable/DurationValue.java b/community/values/src/main/java/org/neo4j/values/storable/DurationValue.java index 60ff6e1a5eee4..318be0fb0a57c 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/DurationValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/DurationValue.java @@ -55,10 +55,10 @@ import static java.util.regex.Pattern.CASE_INSENSITIVE; import static org.neo4j.values.storable.NumberType.NO_NUMBER; import static org.neo4j.values.storable.NumberValue.safeCastFloatingPoint; -import static org.neo4j.values.storable.TimeUtil.AVG_DAYS_PER_MONTH; -import static org.neo4j.values.storable.TimeUtil.AVG_SECONDS_PER_MONTH; -import static org.neo4j.values.storable.TimeUtil.NANOS_PER_SECOND; -import static org.neo4j.values.storable.TimeUtil.SECONDS_PER_DAY; +import static org.neo4j.values.utils.TemporalUtil.AVG_DAYS_PER_MONTH; +import static org.neo4j.values.utils.TemporalUtil.AVG_SECONDS_PER_MONTH; +import static org.neo4j.values.utils.TemporalUtil.NANOS_PER_SECOND; +import static org.neo4j.values.utils.TemporalUtil.SECONDS_PER_DAY; /** * We use our own implementation because neither {@link java.time.Duration} nor {@link java.time.Period} fits our needs. diff --git a/community/values/src/main/java/org/neo4j/values/storable/LocalDateTimeValue.java b/community/values/src/main/java/org/neo4j/values/storable/LocalDateTimeValue.java index 34b7fffe3d986..df18e05de07f7 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/LocalDateTimeValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/LocalDateTimeValue.java @@ -347,7 +347,7 @@ public boolean equals( Value other ) @Override public void writeTo( ValueWriter writer ) throws E { - writer.writeLocalDateTime( value.toEpochSecond( UTC ), value.getNano() ); + writer.writeLocalDateTime( value ); } @Override diff --git a/community/values/src/main/java/org/neo4j/values/storable/LocalTimeValue.java b/community/values/src/main/java/org/neo4j/values/storable/LocalTimeValue.java index 73fb52dea1f6f..d696045d262fe 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/LocalTimeValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/LocalTimeValue.java @@ -28,7 +28,6 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoField; import java.time.temporal.TemporalUnit; import java.time.temporal.UnsupportedTemporalTypeException; import java.util.Map; @@ -255,7 +254,7 @@ public boolean equals( Value other ) @Override public void writeTo( ValueWriter writer ) throws E { - writer.writeLocalTime( value.getLong( ChronoField.NANO_OF_DAY ) ); + writer.writeLocalTime( value ); } @Override diff --git a/community/values/src/main/java/org/neo4j/values/storable/TimeValue.java b/community/values/src/main/java/org/neo4j/values/storable/TimeValue.java index 5f944ca678fe7..e8bbb570d2535 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/TimeValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/TimeValue.java @@ -28,7 +28,6 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoField; import java.time.temporal.TemporalUnit; import java.time.temporal.UnsupportedTemporalTypeException; import java.util.HashMap; @@ -40,6 +39,7 @@ import org.neo4j.values.AnyValue; import org.neo4j.values.StructureBuilder; import org.neo4j.values.ValueMapper; +import org.neo4j.values.utils.TemporalUtil; import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.VirtualValues; @@ -49,7 +49,6 @@ import static org.neo4j.values.storable.DateTimeValue.parseZoneName; import static org.neo4j.values.storable.LocalTimeValue.optInt; import static org.neo4j.values.storable.LocalTimeValue.parseTime; -import static org.neo4j.values.storable.TimeUtil.NANOS_PER_SECOND; public final class TimeValue extends TemporalValue { @@ -260,15 +259,8 @@ protected TimeValue selectTime( private TimeValue( OffsetTime value ) { // truncate the offset to whole minutes - this.value = TimeUtil.truncateOffsetToMinutes( value ); - this.nanosOfDayUTC = getNanosOfDayUTC( this.value ); - } - - private static long getNanosOfDayUTC( OffsetTime value ) - { - long secondsOfDayLocal = value.getLong( ChronoField.SECOND_OF_DAY ); - long secondsOffset = value.getOffset().getTotalSeconds(); - return ( secondsOfDayLocal - secondsOffset ) * NANOS_PER_SECOND + value.getNano(); + this.value = TemporalUtil.truncateOffsetToMinutes( value ); + this.nanosOfDayUTC = TemporalUtil.getNanosOfDayUTC( this.value ); } @Override @@ -340,8 +332,7 @@ public boolean equals( Value other ) @Override public void writeTo( ValueWriter writer ) throws E { - int zoneOffsetSeconds = value.getOffset().getTotalSeconds(); - writer.writeTime( nanosOfDayUTC, zoneOffsetSeconds ); + writer.writeTime( value ); } @Override diff --git a/community/values/src/main/java/org/neo4j/values/storable/ValueWriter.java b/community/values/src/main/java/org/neo4j/values/storable/ValueWriter.java index f9d7798d3b114..345320b59b664 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/ValueWriter.java +++ b/community/values/src/main/java/org/neo4j/values/storable/ValueWriter.java @@ -20,6 +20,11 @@ package org.neo4j.values.storable; import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; /** * Writer of values. @@ -85,23 +90,15 @@ default void writeUTF8( byte[] bytes, int offset, int length ) throws E void writeDuration( long months, long days, long seconds, int nanos ) throws E; - void writeDate( long epochDay ) throws E; + void writeDate( LocalDate localDate ) throws E; - void writeLocalTime( long nanoOfDay ) throws E; + void writeLocalTime( LocalTime localTime ) throws E; - /** - * Write time value - * - * @param nanosOfDayUTC nanoseconds of day in UTC. will be between -18h and +42h - * @param offsetSeconds time zone offset in seconds - */ - void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws E; + void writeTime( OffsetTime offsetTime ) throws E; - void writeLocalDateTime( long epochSecond, int nano ) throws E; + void writeLocalDateTime( LocalDateTime localDateTime ) throws E; - void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws E; - - void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws E; + void writeDateTime( ZonedDateTime zonedDateTime ) throws E; class Adapter implements ValueWriter { @@ -181,32 +178,27 @@ public void writeDuration( long months, long days, long seconds, int nanos ) } @Override - public void writeDate( long epochDay ) throws E - { // no-op - } - - @Override - public void writeLocalTime( long nanoOfDay ) throws E + public void writeDate( LocalDate localDate ) throws E { // no-op } @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws E + public void writeLocalTime( LocalTime localTime ) throws E { // no-op } @Override - public void writeLocalDateTime( long epochSecond, int nano ) throws E + public void writeTime( OffsetTime offsetTime ) throws E { // no-op } @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws E + public void writeLocalDateTime( LocalDateTime localDateTime ) throws E { // no-op } @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws E + public void writeDateTime( ZonedDateTime zonedDateTime ) throws E { // no-op } } diff --git a/community/values/src/main/java/org/neo4j/values/utils/PrettyPrinter.java b/community/values/src/main/java/org/neo4j/values/utils/PrettyPrinter.java index 580aaef83b6d4..86da7175707f2 100644 --- a/community/values/src/main/java/org/neo4j/values/utils/PrettyPrinter.java +++ b/community/values/src/main/java/org/neo4j/values/utils/PrettyPrinter.java @@ -19,17 +19,22 @@ */ package org.neo4j.values.utils; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Deque; import org.neo4j.values.AnyValueWriter; +import org.neo4j.values.storable.CoordinateReferenceSystem; import org.neo4j.values.storable.TextArray; import org.neo4j.values.storable.TextValue; -import org.neo4j.values.storable.CoordinateReferenceSystem; -import org.neo4j.values.virtual.RelationshipValue; import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.NodeValue; +import org.neo4j.values.virtual.RelationshipValue; import static java.lang.String.format; @@ -179,63 +184,43 @@ public void writeDuration( long months, long days, long seconds, int nanos ) thr } @Override - public void writeDate( long epochDay ) throws RuntimeException + public void writeDate( LocalDate localDate ) throws RuntimeException { - append( "{date: {epochDay: " ); - append( Long.toString( epochDay ) ); - append( "}}" ); + append( "{date: " ); + append( quote( localDate.toString() ) ); + append( "}" ); } @Override - public void writeLocalTime( long nanoOfDay ) throws RuntimeException + public void writeLocalTime( LocalTime localTime ) throws RuntimeException { - append( "{localTime: {nanosOfDay: " ); - append( Long.toString( nanoOfDay ) ); - append( "}}" ); + append( "{localTime: " ); + append( quote( localTime.toString() ) ); + append( "}" ); } @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeException + public void writeTime( OffsetTime offsetTime ) throws RuntimeException { - append( "{time: {nanosOfDayUTC: " ); - append( Long.toString( nanosOfDayUTC ) ); - append( ", offsetSeconds: " ); - append( Long.toString( offsetSeconds ) ); - append( "}}" ); + append( "{time: " ); + append( quote( offsetTime.toString() ) ); + append( "}" ); } @Override - public void writeLocalDateTime( long epochSecond, int nano ) throws RuntimeException + public void writeLocalDateTime( LocalDateTime localDateTime ) throws RuntimeException { - append( "{localDateTime: {epochDay: " ); - append( Long.toString( epochSecond ) ); - append( ", nanosOfDay: " ); - append( Long.toString( nano ) ); - append( "}}" ); - } - - @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws RuntimeException - { - append( "{datetime: {epochDay: " ); - append( Long.toString( epochSecondUTC ) ); - append( ", nanosOfDay: " ); - append( Long.toString( nano ) ); - append( ", offsetSeconds: " ); - append( Long.toString( offsetSeconds ) ); - append( "}}" ); + append( "{localDateTime: " ); + append( quote( localDateTime.toString() ) ); + append( "}" ); } @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws RuntimeException - { - append( "{datetime: {epochDay: " ); - append( Long.toString( epochSecondUTC ) ); - append( ", nanosOfDay: " ); - append( Long.toString( nano ) ); - append( ", timezone: \"" ); - append( zoneId ); - append( "\"}}" ); + public void writeDateTime( ZonedDateTime zonedDateTime ) throws RuntimeException + { + append( "{datetime: " ); + append( quote( zonedDateTime.toString() ) ); + append( "}" ); } @Override diff --git a/community/values/src/main/java/org/neo4j/values/storable/TimeUtil.java b/community/values/src/main/java/org/neo4j/values/utils/TemporalUtil.java similarity index 72% rename from community/values/src/main/java/org/neo4j/values/storable/TimeUtil.java rename to community/values/src/main/java/org/neo4j/values/utils/TemporalUtil.java index 5e14888dfbdd8..dc5c3b283ad92 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/TimeUtil.java +++ b/community/values/src/main/java/org/neo4j/values/utils/TemporalUtil.java @@ -17,40 +17,25 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.values.storable; +package org.neo4j.values.utils; import java.time.OffsetTime; import java.time.ZoneOffset; import static java.time.temporal.ChronoUnit.DAYS; -@SuppressWarnings( "WeakerAccess" ) -public class TimeUtil +public final class TemporalUtil { public static final long NANOS_PER_SECOND = 1_000_000_000L; public static final long SECONDS_PER_DAY = DAYS.getDuration().getSeconds(); - public static final long NANOS_PER_DAY = NANOS_PER_SECOND * SECONDS_PER_DAY; /** 30.4375 days = 30 days, 10 hours, 30 minutes */ public static final double AVG_DAYS_PER_MONTH = 365.2425 / 12; public static final long AVG_SECONDS_PER_MONTH = 2_629_746; - private TimeUtil() + private TemporalUtil() { } - public static long asValidTime( long nanosPerDay ) - { - if ( nanosPerDay < 0 ) - { - return nanosPerDay + NANOS_PER_DAY; - } - if ( nanosPerDay >= NANOS_PER_DAY ) - { - return nanosPerDay - NANOS_PER_DAY; - } - return nanosPerDay; - } - public static OffsetTime truncateOffsetToMinutes( OffsetTime value ) { int offsetMinutes = value.getOffset().getTotalSeconds() / 60; @@ -58,13 +43,15 @@ public static OffsetTime truncateOffsetToMinutes( OffsetTime value ) return value.withOffsetSameInstant( truncatedOffset ); } - public static long nanosOfDayToLocal( long nanosOfDayUTC, int offsetSeconds ) + public static long nanosOfDayToUTC( long nanosOfDayLocal, int offsetSeconds ) { - return nanosOfDayUTC + offsetSeconds * NANOS_PER_SECOND; + return nanosOfDayLocal - offsetSeconds * NANOS_PER_SECOND; } - public static long nanosOfDayToUTC( long nanosOfDayLocal, int offsetSeconds ) + public static long getNanosOfDayUTC( OffsetTime value ) { - return nanosOfDayLocal - offsetSeconds * NANOS_PER_SECOND; + long secondsOfDayLocal = value.toLocalTime().toSecondOfDay(); + long secondsOffset = value.getOffset().getTotalSeconds(); + return (secondsOfDayLocal - secondsOffset) * NANOS_PER_SECOND + value.getNano(); } } diff --git a/community/values/src/test/java/org/neo4j/values/storable/BufferValueWriter.java b/community/values/src/test/java/org/neo4j/values/storable/BufferValueWriter.java index cdc6d9c816858..944557d2e3fbc 100644 --- a/community/values/src/test/java/org/neo4j/values/storable/BufferValueWriter.java +++ b/community/values/src/test/java/org/neo4j/values/storable/BufferValueWriter.java @@ -21,8 +21,11 @@ import org.hamcrest.Matchers; -import java.time.ZoneId; -import java.time.ZoneOffset; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -184,39 +187,33 @@ public void writeDuration( long months, long days, long seconds, int nanos ) } @Override - public void writeDate( long epochDay ) throws RuntimeException + public void writeDate( LocalDate localDate ) throws RuntimeException { - buffer.add( DateValue.epochDate( epochDay ) ); + buffer.add( DateValue.date( localDate ) ); } @Override - public void writeLocalTime( long nanoOfDay ) throws RuntimeException + public void writeLocalTime( LocalTime localTime ) throws RuntimeException { - buffer.add( LocalTimeValue.localTime( nanoOfDay ) ); + buffer.add( LocalTimeValue.localTime( localTime ) ); } @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeException + public void writeTime( OffsetTime offsetTime ) throws RuntimeException { - buffer.add( TimeValue.time( nanosOfDayUTC, ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); + buffer.add( TimeValue.time( offsetTime ) ); } @Override - public void writeLocalDateTime( long epochSecond, int nano ) throws RuntimeException + public void writeLocalDateTime( LocalDateTime localDateTime ) throws RuntimeException { - buffer.add( LocalDateTimeValue.localDateTime( epochSecond, nano ) ); + buffer.add( LocalDateTimeValue.localDateTime( localDateTime ) ); } @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws RuntimeException + public void writeDateTime( ZonedDateTime zonedDateTime ) throws RuntimeException { - buffer.add( DateTimeValue.datetime( epochSecondUTC, nano, ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); - } - - @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws RuntimeException - { - buffer.add( DateTimeValue.datetime( epochSecondUTC, nano, ZoneId.of( zoneId ) ) ); + buffer.add( DateTimeValue.datetime( zonedDateTime ) ); } @SuppressWarnings( "WeakerAccess" ) diff --git a/community/values/src/test/java/org/neo4j/values/storable/DateTimeValueTest.java b/community/values/src/test/java/org/neo4j/values/storable/DateTimeValueTest.java index c63d175a44874..2570e1e19c234 100644 --- a/community/values/src/test/java/org/neo4j/values/storable/DateTimeValueTest.java +++ b/community/values/src/test/java/org/neo4j/values/storable/DateTimeValueTest.java @@ -44,7 +44,6 @@ import static org.neo4j.values.storable.DateValue.date; import static org.neo4j.values.storable.FrozenClockRule.assertEqualTemporal; import static org.neo4j.values.storable.InputMappingStructureBuilder.fromValues; -import static org.neo4j.values.storable.LocalDateTimeValue.inUTC; import static org.neo4j.values.storable.LocalDateTimeValue.localDateTime; import static org.neo4j.values.storable.LocalTimeValue.localTime; import static org.neo4j.values.storable.TimeValue.time; @@ -147,21 +146,12 @@ public void shouldWriteDateTime() } ) { List values = new ArrayList<>( 1 ); - List locals = new ArrayList<>( 1 ); ValueWriter writer = new ThrowingValueWriter.AssertOnly() { @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws RuntimeException + public void writeDateTime( ZonedDateTime zonedDateTime ) { - values.add( datetime( epochSecondUTC, nano, ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); - locals.add( localDateTime( epochSecondUTC, nano ) ); - } - - @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws RuntimeException - { - values.add( datetime( epochSecondUTC, nano, ZoneId.of( zoneId ) ) ); - locals.add( localDateTime( epochSecondUTC, nano ) ); + values.add( datetime( zonedDateTime ) ); } }; @@ -170,7 +160,6 @@ public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws // then assertEquals( singletonList( value ), values ); - assertEquals( singletonList( inUTC( value ) ), locals ); } } diff --git a/community/values/src/test/java/org/neo4j/values/storable/DateValueTest.java b/community/values/src/test/java/org/neo4j/values/storable/DateValueTest.java index 64c5cf051632d..ee33e409efd8e 100644 --- a/community/values/src/test/java/org/neo4j/values/storable/DateValueTest.java +++ b/community/values/src/test/java/org/neo4j/values/storable/DateValueTest.java @@ -34,7 +34,6 @@ import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import static org.neo4j.values.storable.DateValue.date; -import static org.neo4j.values.storable.DateValue.epochDate; import static org.neo4j.values.storable.DateValue.ordinalDate; import static org.neo4j.values.storable.DateValue.parse; import static org.neo4j.values.storable.DateValue.quarterDate; @@ -189,9 +188,9 @@ public void shouldWriteDate() ValueWriter writer = new ThrowingValueWriter.AssertOnly() { @Override - public void writeDate( long epochDay ) throws RuntimeException + public void writeDate( LocalDate localDate ) { - values.add( epochDate( epochDay ) ); + values.add( date( localDate ) ); } }; diff --git a/community/values/src/test/java/org/neo4j/values/storable/LocalDateTimeValueTest.java b/community/values/src/test/java/org/neo4j/values/storable/LocalDateTimeValueTest.java index 63d8227728df3..58f71fc06ef5f 100644 --- a/community/values/src/test/java/org/neo4j/values/storable/LocalDateTimeValueTest.java +++ b/community/values/src/test/java/org/neo4j/values/storable/LocalDateTimeValueTest.java @@ -19,11 +19,12 @@ */ package org.neo4j.values.storable; +import org.junit.Test; + +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import org.junit.Test; - import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.neo4j.values.storable.DateValue.date; @@ -60,9 +61,9 @@ public void shouldWriteDateTime() ValueWriter writer = new ThrowingValueWriter.AssertOnly() { @Override - public void writeLocalDateTime( long epochSecond, int nano ) throws RuntimeException + public void writeLocalDateTime( LocalDateTime localDateTime ) { - values.add( localDateTime( epochSecond, nano ) ); + values.add( localDateTime( localDateTime ) ); } }; diff --git a/community/values/src/test/java/org/neo4j/values/storable/LocalTimeValueTest.java b/community/values/src/test/java/org/neo4j/values/storable/LocalTimeValueTest.java index ea7e10bf5acbf..a97a6c0103583 100644 --- a/community/values/src/test/java/org/neo4j/values/storable/LocalTimeValueTest.java +++ b/community/values/src/test/java/org/neo4j/values/storable/LocalTimeValueTest.java @@ -22,6 +22,7 @@ import org.junit.Test; import java.time.DateTimeException; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; @@ -97,9 +98,9 @@ public void shouldWriteLocalTime() ValueWriter writer = new ThrowingValueWriter.AssertOnly() { @Override - public void writeLocalTime( long nanoOfDay ) throws RuntimeException + public void writeLocalTime( LocalTime localTime ) { - values.add( localTime( nanoOfDay ) ); + values.add( localTime( localTime ) ); } }; diff --git a/community/values/src/test/java/org/neo4j/values/storable/ThrowingValueWriter.java b/community/values/src/test/java/org/neo4j/values/storable/ThrowingValueWriter.java index 9c4cc32fc82ab..4a1dceb930f6b 100644 --- a/community/values/src/test/java/org/neo4j/values/storable/ThrowingValueWriter.java +++ b/community/values/src/test/java/org/neo4j/values/storable/ThrowingValueWriter.java @@ -19,6 +19,11 @@ */ package org.neo4j.values.storable; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.function.Supplier; public abstract class ThrowingValueWriter implements ValueWriter @@ -137,37 +142,31 @@ public void writeDuration( long months, long days, long seconds, int nanos ) thr } @Override - public void writeDate( long epochDay ) throws E + public void writeDate( LocalDate localDate ) throws E { throw exception( "writeDate" ); } @Override - public void writeLocalTime( long nanoOfDay ) throws E + public void writeLocalTime( LocalTime localTime ) throws E { throw exception( "writeLocalTime" ); } @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws E + public void writeTime( OffsetTime offsetTime ) throws E { throw exception( "writeTime" ); } @Override - public void writeLocalDateTime( long epochSecond, int nano ) throws E + public void writeLocalDateTime( LocalDateTime localDateTime ) throws E { throw exception( "writeLocalDateTime" ); } @Override - public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws E - { - throw exception( "writeDateTime" ); - } - - @Override - public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws E + public void writeDateTime( ZonedDateTime zonedDateTime ) throws E { throw exception( "writeDateTime" ); } diff --git a/community/values/src/test/java/org/neo4j/values/storable/TimeValueTest.java b/community/values/src/test/java/org/neo4j/values/storable/TimeValueTest.java index 0449e636d8d7d..10e4caf7c7444 100644 --- a/community/values/src/test/java/org/neo4j/values/storable/TimeValueTest.java +++ b/community/values/src/test/java/org/neo4j/values/storable/TimeValueTest.java @@ -22,10 +22,12 @@ import org.junit.Test; import java.time.DateTimeException; +import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import static java.time.ZoneOffset.UTC; @@ -34,9 +36,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; -import static org.neo4j.values.storable.LocalTimeValue.inUTC; -import static org.neo4j.values.storable.LocalTimeValue.localTime; -import static org.neo4j.values.storable.TimeUtil.asValidTime; import static org.neo4j.values.storable.TimeValue.parse; import static org.neo4j.values.storable.TimeValue.time; import static org.neo4j.values.utils.AnyValueTestUtil.assertEqual; @@ -111,14 +110,12 @@ public void shouldWriteTime() } ) { List values = new ArrayList<>( 1 ); - List locals = new ArrayList<>( 1 ); ValueWriter writer = new ThrowingValueWriter.AssertOnly() { @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeException + public void writeTime( OffsetTime offsetTime ) { - values.add( time( nanosOfDayUTC, ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); - locals.add( localTime( asValidTime( nanosOfDayUTC ) ) ); + values.add( time( offsetTime ) ); } }; @@ -127,7 +124,6 @@ public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeExc // then assertEquals( singletonList( time ), values ); - assertEquals( singletonList( inUTC( time ) ), locals ); } } @@ -216,11 +212,10 @@ public void shouldWriteDerivedValueThatIsEqual() TimeValue value1 = time( 42, ZoneOffset.of( "-18:00" ) ); TimeValue value2 = time( value1.temporal() ); - NanoOfDayAndOffset nanoAndOffset1 = write( value1 ); - NanoOfDayAndOffset nanoAndOffset2 = write( value2 ); + OffsetTime offsetTime1 = write( value1 ); + OffsetTime offsetTime2 = write( value2 ); - assertEquals( nanoAndOffset1.nanosOfDay, nanoAndOffset2.nanosOfDay ); - assertEquals( nanoAndOffset1.offsetSeconds, nanoAndOffset2.offsetSeconds ); + assertEquals( offsetTime1, offsetTime2 ); } @Test @@ -246,19 +241,18 @@ private DateTimeException assertCannotParse( String text ) throw new AssertionError( text ); } - private static NanoOfDayAndOffset write( TimeValue value ) + private static OffsetTime write( TimeValue value ) { - NanoOfDayAndOffset result = new NanoOfDayAndOffset(); + AtomicReference result = new AtomicReference<>(); value.writeTo( new ThrowingValueWriter.AssertOnly() { @Override - public void writeTime( long nanosOfDayUTC, int offsetSeconds ) + public void writeTime( OffsetTime offsetTime ) { - result.nanosOfDay = nanosOfDayUTC; - result.offsetSeconds = offsetSeconds; + result.set( offsetTime ); } } ); - return result; + return result.get(); } private static class NanoOfDayAndOffset diff --git a/community/values/src/test/java/org/neo4j/values/utils/PrettyPrinterTest.java b/community/values/src/test/java/org/neo4j/values/utils/PrettyPrinterTest.java index 5bbbb1cde7a36..ee8f8ff0c832d 100644 --- a/community/values/src/test/java/org/neo4j/values/utils/PrettyPrinterTest.java +++ b/community/values/src/test/java/org/neo4j/values/utils/PrettyPrinterTest.java @@ -21,26 +21,40 @@ import org.junit.Test; +import java.time.ZoneOffset; import java.util.HashMap; import org.neo4j.values.AnyValue; import org.neo4j.values.storable.CoordinateReferenceSystem; +import org.neo4j.values.storable.DateTimeValue; +import org.neo4j.values.storable.DateValue; +import org.neo4j.values.storable.DurationValue; +import org.neo4j.values.storable.LocalDateTimeValue; +import org.neo4j.values.storable.LocalTimeValue; +import org.neo4j.values.storable.PointValue; import org.neo4j.values.storable.TextArray; import org.neo4j.values.storable.TextValue; +import org.neo4j.values.storable.TimeValue; import org.neo4j.values.storable.Value; import org.neo4j.values.storable.Values; -import org.neo4j.values.storable.PointValue; -import org.neo4j.values.virtual.RelationshipReference; -import org.neo4j.values.virtual.RelationshipValue; import org.neo4j.values.virtual.ListValue; import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.NodeReference; import org.neo4j.values.virtual.NodeValue; import org.neo4j.values.virtual.PathValue; +import org.neo4j.values.virtual.RelationshipReference; +import org.neo4j.values.virtual.RelationshipValue; import org.neo4j.values.virtual.VirtualValues; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.neo4j.values.storable.DateTimeValue.datetime; +import static org.neo4j.values.storable.DateValue.date; +import static org.neo4j.values.storable.DurationValue.duration; +import static org.neo4j.values.storable.LocalDateTimeValue.localDateTime; +import static org.neo4j.values.storable.LocalTimeValue.localTime; +import static org.neo4j.values.storable.TimeValue.time; import static org.neo4j.values.storable.Values.byteValue; import static org.neo4j.values.storable.Values.intValue; import static org.neo4j.values.storable.Values.stringValue; @@ -312,6 +326,83 @@ public void shouldBeAbleToUseAnyQuoteMark() assertThat( printer.value(), equalTo( "__(ツ)__" ) ); } + @Test + public void shouldHandleDuration() + { + DurationValue duration = duration( 12, 45, 90, 9911 ); + PrettyPrinter printer = new PrettyPrinter(); + + duration.writeTo( printer ); + + assertEquals( "{duration: {months: 12, days: 45, seconds: 90, nanos: 9911}}", printer.value() ); + } + + @Test + public void shouldHandleDate() + { + DateValue date = date( 1991, 9, 24 ); + PrettyPrinter printer = new PrettyPrinter(); + + date.writeTo( printer ); + + assertEquals( "{date: \"1991-09-24\"}", printer.value() ); + } + + @Test + public void shouldHandleLocalTime() + { + LocalTimeValue localTime = localTime( 18, 39, 24, 111222777 ); + PrettyPrinter printer = new PrettyPrinter(); + + localTime.writeTo( printer ); + + assertEquals( "{localTime: \"18:39:24.111222777\"}", printer.value() ); + } + + @Test + public void shouldHandleTime() + { + TimeValue time = time( 11, 19, 11, 123456789, ZoneOffset.ofHoursMinutes( -9, -30 ) ); + PrettyPrinter printer = new PrettyPrinter(); + + time.writeTo( printer ); + + assertEquals( "{time: \"11:19:11.123456789-09:30\"}", printer.value() ); + } + + @Test + public void shouldHandleLocalDateTime() + { + LocalDateTimeValue localDateTime = localDateTime( 2015, 8, 8, 8, 40, 29, 999888111 ); + PrettyPrinter printer = new PrettyPrinter(); + + localDateTime.writeTo( printer ); + + assertEquals( "{localDateTime: \"2015-08-08T08:40:29.999888111\"}", printer.value() ); + } + + @Test + public void shouldHandleDateTimeWithTimeZoneId() + { + DateTimeValue datetime = datetime( 2045, 2, 7, 12, 00, 40, 999888999, "Europe/London" ); + PrettyPrinter printer = new PrettyPrinter(); + + datetime.writeTo( printer ); + + assertEquals( "{datetime: \"2045-02-07T12:00:40.999888999Z[Europe/London]\"}", printer.value() ); + } + + @Test + public void shouldHandleDateTimeWithTimeZoneOffset() + { + DateTimeValue datetime = datetime( 1988, 4, 19, 10, 12, 59, 112233445, ZoneOffset.ofHoursMinutes( 3, 15 ) ); + PrettyPrinter printer = new PrettyPrinter(); + + datetime.writeTo( printer ); + + assertEquals( "{datetime: \"1988-04-19T10:12:59.112233445+03:15\"}", printer.value() ); + } + private MapValue props( Object... keyValue ) { HashMap map = new HashMap<>( keyValue.length ); diff --git a/community/values/src/test/java/org/neo4j/values/utils/TemporalUtilTest.java b/community/values/src/test/java/org/neo4j/values/utils/TemporalUtilTest.java new file mode 100644 index 0000000000000..ab045a7c78f13 --- /dev/null +++ b/community/values/src/test/java/org/neo4j/values/utils/TemporalUtilTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.values.utils; + +import org.junit.Test; + +import java.time.Duration; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneOffset; + +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.junit.Assert.assertEquals; + +public class TemporalUtilTest +{ + @Test + public void shouldDoNothingForOffsetWithoutSeconds() + { + OffsetTime time = OffsetTime.of( 23, 30, 10, 0, ZoneOffset.ofHoursMinutes( -5, -30 ) ); + + OffsetTime truncatedTime = TemporalUtil.truncateOffsetToMinutes( time ); + + assertEquals( time, truncatedTime ); + } + + @Test + public void shouldTruncateOffsetSeconds() + { + OffsetTime time = OffsetTime.of( 14, 55, 50, 0, ZoneOffset.ofHoursMinutesSeconds( 2, 15, 45 ) ); + + OffsetTime truncatedTime = TemporalUtil.truncateOffsetToMinutes( time ); + + assertEquals( OffsetTime.of( 14, 55, 5, 0, ZoneOffset.ofHoursMinutes( 2, 15 ) ), truncatedTime ); + } + + @Test + public void shouldConvertNanosOfDayToUTCWhenOffsetIsZero() + { + int nanosOfDayLocal = 42; + + long nanosOfDayUTC = TemporalUtil.nanosOfDayToUTC( nanosOfDayLocal, 0 ); + + assertEquals( nanosOfDayLocal, nanosOfDayUTC ); + } + + @Test + public void shouldConvertNanosOfDayToUTC() + { + int nanosOfDayLocal = 42; + Duration offsetDuration = Duration.ofMinutes( 35 ); + + long nanosOfDayUTC = TemporalUtil.nanosOfDayToUTC( nanosOfDayLocal, (int) offsetDuration.getSeconds() ); + + assertEquals( nanosOfDayLocal - offsetDuration.toNanos(), nanosOfDayUTC ); + } + + @Test + public void shouldGetNanosOfDayUTC() + { + LocalTime localTime = LocalTime.of( 14, 19, 18, 123999 ); + ZoneOffset offset = ZoneOffset.ofHours( -12 ); + OffsetTime time = OffsetTime.of( localTime, offset ); + + long nanosOfDayUTC = TemporalUtil.getNanosOfDayUTC( time ); + + long expectedNanosOfDayUTC = Duration.ofSeconds( localTime.toSecondOfDay() ) + .minus( offset.getTotalSeconds(), SECONDS ) + .toNanos(); + + assertEquals( expectedNanosOfDayUTC + localTime.getNano(), nanosOfDayUTC ); + } +}