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 f504f97ce2353..8cf979d8dad9d 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 @@ -220,7 +220,7 @@ public DateTimeValue buildInternal() boolean selectingDate = fields.containsKey( Field.date ); boolean selectingTime = fields.containsKey( Field.time ); boolean selectingDateTime = fields.containsKey( Field.datetime ); - boolean selectingEpoch = fields.containsKey( Field.epoch ); + boolean selectingEpoch = fields.containsKey( Field.epochSeconds ) || fields.containsKey( Field.epochMillis ); boolean selectingTimeZone; ZonedDateTime result; if ( selectingDateTime ) @@ -238,13 +238,26 @@ public DateTimeValue buildInternal() } else if ( selectingEpoch ) { - AnyValue epochField = fields.get( Field.epoch ); - if ( !(epochField instanceof IntegralValue) ) + if( fields.containsKey( Field.epochSeconds ) ) { - throw new IllegalArgumentException( String.format( "Cannot construct date time from: %s", epochField ) ); + AnyValue epochField = fields.get( Field.epochSeconds ); + if ( !(epochField instanceof IntegralValue) ) + { + throw new IllegalArgumentException( String.format( "Cannot construct date time from: %s", epochField ) ); + } + IntegralValue epochSeconds = (IntegralValue) epochField; + result = ZonedDateTime.ofInstant( Instant.ofEpochMilli( epochSeconds.longValue() * 1000 ), timezone() ); + } + else + { + AnyValue epochField = fields.get( Field.epochMillis ); + if ( !(epochField instanceof IntegralValue) ) + { + throw new IllegalArgumentException( String.format( "Cannot construct date time from: %s", epochField ) ); + } + IntegralValue epochMillis = (IntegralValue) epochField; + result = ZonedDateTime.ofInstant( Instant.ofEpochMilli( epochMillis.longValue() ), timezone() ); } - IntegralValue epoch = (IntegralValue) epochField; - result = ZonedDateTime.ofInstant( Instant.ofEpochMilli( epoch.longValue() ), timezone() ); selectingTimeZone = false; } else if ( selectingTime || selectingDate ) diff --git a/community/values/src/main/java/org/neo4j/values/storable/TemporalValue.java b/community/values/src/main/java/org/neo4j/values/storable/TemporalValue.java index c303c2ff47f1e..3630ad806142d 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/TemporalValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/TemporalValue.java @@ -270,13 +270,20 @@ public final int get( TemporalField field ) public final AnyValue get( String fieldName ) { Field field = Field.fields.get( fieldName.toLowerCase() ); - if ( field == Field.epoch ) + if ( field == Field.epochSeconds || field == Field.epochMillis ) { T temp = temporal(); if ( temp instanceof ChronoZonedDateTime ) { ChronoZonedDateTime zdt = (ChronoZonedDateTime) temp; - return Values.longValue( zdt.toInstant().toEpochMilli() ); + if ( field == Field.epochSeconds ) + { + return Values.longValue( zdt.toInstant().toEpochMilli() / 1000 ); + } + else + { + return Values.longValue( zdt.toInstant().toEpochMilli() ); + } } else { @@ -291,6 +298,10 @@ public final AnyValue get( String fieldName ) { return Values.stringValue( getZoneOffset().toString() ); } + if ( field == Field.offsetMinutes ) + { + return Values.intValue( getZoneOffset().getTotalSeconds() / 60 ); + } if ( field == null || field.field == null ) { throw new UnsupportedTemporalTypeException( "No such field: " + fieldName ); @@ -564,6 +575,15 @@ void assign( Builder> builder, AnyValue value ) throw new IllegalArgumentException( "Not supported: " + name() ); } }, + offsetMinutes//
+ { //+ + @Override + void assign( Builder> builder, AnyValue value ) + { + throw new IllegalArgumentException( "Not supported: " + name() ); + } + }, // time zone timezone//
{ //@@ -652,7 +672,30 @@ boolean isGroupSelector() return true; } }, - epoch//
+ epochSeconds//+ { //+ + @Override + void assign( Builder> builder, AnyValue value ) + { + if ( !builder.supportsEpoch() ) + { + throw new IllegalArgumentException( "Not supported: " + name() ); + } + if ( builder.state == null ) + { + builder.state = new DateTimeBuilder( ); + } + builder.state = builder.state.assign( this, value ); + } + + @Override + boolean isGroupSelector() + { + return true; + } + }, + epochMillis//{ //@Override @@ -763,7 +806,7 @@ void checkAssignments( boolean requiresDate ) DateTimeBuilder assign( Field field, AnyValue value ) { - if ( field == Field.datetime || field == Field.epoch ) + if ( field == Field.datetime || field == Field.epochSeconds || field == Field.epochMillis ) { return new SelectDateTimeDTBuilder( date, time ).assign( field, value ); } @@ -806,7 +849,8 @@ else if ( field == Field.time || field.field != null && field.field.isTimeBased( private static class SelectDateTimeDTBuilder extends DateTimeBuilder { private AnyValue datetime; - private AnyValue epoch; + private AnyValue epochSeconds; + private AnyValue epochMillis; SelectDateTimeDTBuilder( DateBuilder date, ConstructTime time ) { @@ -824,23 +868,43 @@ DateTimeBuilder assign( Field field, AnyValue value ) { if ( field == Field.date || field == Field.time ) { - throw new IllegalArgumentException( field.name() + " cannot be selected together with datetime or epoch." ); + throw new IllegalArgumentException( field.name() + " cannot be selected together with datetime or epochSeconds or epochMillis." ); } else if ( field == Field.datetime ) { - if ( epoch != null ) + if ( epochSeconds != null ) { - throw new IllegalArgumentException( field.name() + " cannot be selected together with epoch." ); + throw new IllegalArgumentException( field.name() + " cannot be selected together with epochSeconds." ); + } + else if ( epochMillis != null ) + { + throw new IllegalArgumentException( field.name() + " cannot be selected together with epochMillis." ); } datetime = assignment( Field.datetime, datetime, value ); } - else if ( field == Field.epoch ) + else if ( field == Field.epochSeconds ) { - if ( datetime != null ) + if ( epochMillis != null ) + { + throw new IllegalArgumentException( field.name() + " cannot be selected together with epochMillis." ); + } + else if ( datetime != null ) + { + throw new IllegalArgumentException( field.name() + " cannot be selected together with datetime." ); + } + epochSeconds = assignment( Field.epochSeconds, epochSeconds, value ); + } + else if ( field == Field.epochMillis ) + { + if ( epochSeconds != null ) + { + throw new IllegalArgumentException( field.name() + " cannot be selected together with epochSeconds." ); + } + else if ( datetime != null ) { throw new IllegalArgumentException( field.name() + " cannot be selected together with datetime." ); } - epoch = assignment( Field.epoch, epoch, value ); + epochMillis = assignment( Field.epochMillis, epochMillis, value ); } else { @@ -860,7 +924,7 @@ private static class SelectDateOrTimeDTBuilder extends DateTimeBuilder @Override DateTimeBuilder assign( Field field, AnyValue value ) { - if ( field == Field.datetime || field == Field.epoch ) + if ( field == Field.datetime || field == Field.epochSeconds || field == Field.epochMillis ) { throw new IllegalArgumentException( field.name() + " cannot be selected together with date or time." ); } diff --git a/enterprise/cypher/acceptance-spec-suite/src/test/resources/cypher/features/TemporalAccessorAcceptance.feature b/enterprise/cypher/acceptance-spec-suite/src/test/resources/cypher/features/TemporalAccessorAcceptance.feature index cddc33ce6295a..b79b835ba370a 100644 --- a/enterprise/cypher/acceptance-spec-suite/src/test/resources/cypher/features/TemporalAccessorAcceptance.feature +++ b/enterprise/cypher/acceptance-spec-suite/src/test/resources/cypher/features/TemporalAccessorAcceptance.feature @@ -61,11 +61,11 @@ Feature: TemporalAccessorAcceptance When executing query: """ WITH time({hour:12, minute:31, second:14, nanosecond: 645876123, timezone:'+01:00'}) as d - RETURN d.hour, d.minute, d.second, d.millisecond, d.microsecond, d.nanosecond, d.timezone, d.offset + RETURN d.hour, d.minute, d.second, d.millisecond, d.microsecond, d.nanosecond, d.timezone, d.offset, d.offsetMinutes """ Then the result should be, in order: - | d.hour | d.minute | d.second | d.millisecond | d.microsecond | d.nanosecond | d.timezone | d.offset | - | 12 | 31 | 14 | 645 | 645876 | 645876123 | '+01:00' | '+01:00' | + | d.hour | d.minute | d.second | d.millisecond | d.microsecond | d.nanosecond | d.timezone | d.offset | d.offsetMinutes | + | 12 | 31 | 14 | 645 | 645876 | 645876123 | '+01:00' | '+01:00' | 60 | And no side effects Scenario: Should provide accessors for local date time @@ -88,11 +88,11 @@ Feature: TemporalAccessorAcceptance WITH datetime({year:1984, month:11, day:11, hour:12, minute:31, second:14, nanosecond: 645876123, timezone:'Europe/Stockholm'}) as d RETURN d.year, d.quarter, d.month, d.week, d.weekYear, d.day, d.ordinalDay, d.weekDay, d.dayOfQuarter, d.hour, d.minute, d.second, d.millisecond, d.microsecond, d.nanosecond, - d.timezone, d.offset, d.epoch + d.timezone, d.offset, d.offsetMinutes, d.epochSeconds, d.epochMillis """ Then the result should be, in order: - | d.year | d.quarter | d.month | d.week | d.weekYear | d.day | d.ordinalDay | d.weekDay | d.dayOfQuarter | d.hour | d.minute | d.second | d.millisecond | d.microsecond | d.nanosecond | d.timezone | d.offset | d.epoch | - | 1984 | 4 | 11 | 45 | 1984 | 11 | 316 | 7 | 42 | 12 | 31 | 14 | 645 | 645876 | 645876123 | 'Europe/Stockholm' | '+01:00' | 469020674645 | + | d.year | d.quarter | d.month | d.week | d.weekYear | d.day | d.ordinalDay | d.weekDay | d.dayOfQuarter | d.hour | d.minute | d.second | d.millisecond | d.microsecond | d.nanosecond | d.timezone | d.offset | d.offsetMinutes | d.epochSeconds | d.epochMillis | + | 1984 | 4 | 11 | 45 | 1984 | 11 | 316 | 7 | 42 | 12 | 31 | 14 | 645 | 645876 | 645876123 | 'Europe/Stockholm' | '+01:00' | 60 | 469020674 | 469020674645 | And no side effects Scenario: Should provide accessors for duration diff --git a/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/TemporalAcceptanceTest.scala b/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/TemporalAcceptanceTest.scala index e4d91e6de5f79..79e03dbb16e40 100644 --- a/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/TemporalAcceptanceTest.scala +++ b/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/TemporalAcceptanceTest.scala @@ -41,7 +41,8 @@ class TemporalAcceptanceTest extends ExecutionEngineFunSuite with QueryStatistic shouldReturnSomething(s"$s.realtime('America/Los_Angeles')") shouldReturnSomething(s"$s({timezone: '+01:00'})") } - shouldReturnSomething("datetime({epoch:timestamp()})") + shouldReturnSomething("datetime({epochMillis:timestamp()})") + shouldReturnSomething("datetime({epochSeconds:timestamp() / 1000})") } // Failing when skipping certain values in create or specifying conflicting values @@ -115,8 +116,9 @@ class TemporalAcceptanceTest extends ExecutionEngineFunSuite with QueryStatistic "{year:1984, month: 2, quarter:11}", "{year:1984, month: 2, dayOfQuarter:11}", "{year:1984, week: 2, day:11}", "{year:1984, week: 2, quarter:11}", "{year:1984, week: 2, dayOfQuarter:11}", "{year:1984, quarter: 2, day:11}", "{year:1984, quarter: 2, dayOfWeek:6}", "{datetime: datetime(), date: date()}", - "{datetime: datetime(), time: time()}", "{datetime: datetime(), epoch: timestamp()}", "{date: date(), epoch: timestamp()}", - "{time: time(), epoch: timestamp()}") + "{datetime: datetime(), time: time()}", "{datetime: datetime(), epochSeconds:1}", "{datetime: datetime(), epochMillis: timestamp()}", + "{date: date(), epochSeconds:1}","{date: date(), epochMillis: timestamp()}", + "{time: time(), epochSeconds:1}", "{time: time(), epochMillis: timestamp()}", "{epochSeconds:1, epochMillis: timestamp()}") shouldNotConstructWithArg("datetime", queries) } @@ -450,13 +452,13 @@ class TemporalAcceptanceTest extends ExecutionEngineFunSuite with QueryStatistic test("should not provide undefined accessors for date") { shouldNotHaveAccessor("date", Seq("hour", "minute", "second", "millisecond", "microsecond", "nanosecond", - "timezone", "offset", "epoch", + "timezone", "offset", "offsetMinutes", "epochSeconds", "epochMillis", "years", "months", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds", "nanoseconds")) } test("should not provide undefined accessors for local time") { shouldNotHaveAccessor("localtime", Seq("year", "quarter", "month", "week", "weekYear", "day", "ordinalDay", "weekDay", "dayOfQuarter", - "timezone", "offset", "epoch", + "timezone", "offset", "offsetMinutes", "epochSeconds", "epochMillis", "years", "months", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds", "nanoseconds")) } @@ -466,7 +468,7 @@ class TemporalAcceptanceTest extends ExecutionEngineFunSuite with QueryStatistic } test("should not provide undefined accessors for local date time") { - shouldNotHaveAccessor("localdatetime", Seq("timezone", "offset", "epoch", + shouldNotHaveAccessor("localdatetime", Seq("timezone", "offset", "offsetMinutes", "epochSeconds", "epochMillis", "years", "months", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds", "nanoseconds")) } @@ -477,7 +479,7 @@ class TemporalAcceptanceTest extends ExecutionEngineFunSuite with QueryStatistic test("should not provide undefined accessors for duration") { shouldNotHaveAccessor("duration", Seq("year", "quarter", "month", "week", "weekYear", "day", "ordinalDay", "weekDay", "dayOfQuarter", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", - "timezone", "offset", "epoch"), "{days: 14, hours:16, minutes: 12}") + "timezone", "offset", "offsetMinutes", "epochSeconds", "epochMillis"), "{days: 14, hours:16, minutes: 12}") } // Duration between