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