This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2596
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit de40eb9fe698696c26892f534dea1fe9c7e2d605
Author: Stephen Mallette <stepm...@amazon.com>
AuthorDate: Fri Sep 10 15:45:55 2021 -0400

    TINKERPOP-2596 Added datetime() to gremlin-language
    
    Added GroovyTranslator support by way of a new type serializer - in this 
way, standard Date/Timestamp translation of old can be preserved.
---
 CHANGELOG.asciidoc                                 |   1 +
 docs/src/reference/gremlin-variants.asciidoc       |  14 +++
 docs/src/reference/the-traversal.asciidoc          |   5 +
 docs/src/upgrade/release-3.5.x.asciidoc            |  30 +++++
 .../tinkerpop/gremlin/jsr223/CoreImports.java      |   6 +
 .../language/grammar/GenericLiteralVisitor.java    |   9 ++
 .../traversal/translator/GroovyTranslator.java     |  29 +++++
 .../tinkerpop/gremlin/util/DatetimeHelper.java     | 124 +++++++++++++++++++++
 .../grammar/GeneralLiteralVisitorTest.java         |  46 ++++++++
 .../traversal/translator/GroovyTranslatorTest.java |  13 +++
 .../tinkerpop/gremlin/util/DatetimeHelperTest.java | 105 +++++++++++++++++
 .../server/GremlinServerHttpIntegrateTest.java     |  14 +++
 12 files changed, 396 insertions(+)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 4f4eff4..457cf8d 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -28,6 +28,7 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Added the `ConnectedComponent` tokens required to properly process the 
`with()` of the `connectedComponent()` step.
 * Fixed `DotNetTranslator` bugs where translations produced Gremlin that 
failed due to ambiguous step calls to `has()`.
 * Fixed bug where `RepeatUnrollStrategy`, `InlineFilterStrategy` and 
`MessagePassingReductionStrategy` were all being applied more than necessary.
+* Modified grammar to accept the `datetime()` function so that Gremlin scripts 
have a way to natively construct a `Date`.
 * Ensured `PathRetractionStrategy` is applied after `InlineFilterStrategy` 
which prevents an error in traverser mapping in certain conditions.
 * Deprecated `JsonBuilder` serialization for GraphSON and Gryo.
 * Allowed `null` string values in the Gremlin grammar.
diff --git a/docs/src/reference/gremlin-variants.asciidoc 
b/docs/src/reference/gremlin-variants.asciidoc
index 53778c5..3bd8029 100644
--- a/docs/src/reference/gremlin-variants.asciidoc
+++ b/docs/src/reference/gremlin-variants.asciidoc
@@ -670,6 +670,20 @@ In Groovy, `as`, `in`, and `not` are reserved words. 
Gremlin-Groovy does not all
 statically from the anonymous traversal `+__+` and therefore, must always be 
prefixed with `+__.+` For instance:
 `+g.V().as('a').in().as('b').where(__.not(__.as('a').out().as('b')))+`
 
+Since Groovy has access to the full JVM as Java does, it is possible to 
construct `Date`-like objects directly, but
+the Gremlin language does offer a `datetime()` function that is exposed in the 
Gremlin Console and as a function for
+Gremlin scripts sent to Gremlin Server. The function accepts the following 
forms of dates and/or times using a default
+time zone offset of UTC(+00:00):
+
+* `T00:35:44`
+* `T00:35:44Z`
+* `2018-03-22`
+* `2018-03-22T00:35:44`
+* `2018-03-22T00:35:44Z`
+* `2018-03-22T00:35:44.741`
+* `2018-03-22T00:35:44.741Z`
+* `2018-03-22T00:35:44.741+1600`
+
 [[gremlin-python]]
 == Gremlin-Python
 
diff --git a/docs/src/reference/the-traversal.asciidoc 
b/docs/src/reference/the-traversal.asciidoc
index 1b662eb..305c3ae 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -4886,4 +4886,9 @@ System.out.println(s.parameters);
 // OUTPUT: Optional[{_args_0=person, _args_2=marko, _args_1=name, _args_4=age, 
_args_3=knows}]
 ----
 
+Finally, the `GroovyTranslator` can take a `TypeTranslator` argument which 
allows some customization of how types get
+converted to script form. The `DefaultTypeTranslator` is used if a specific 
implementation is not specified. A built-in
+alternative to this implementation is the `LanguageTypeTranslator` which will 
prefer use of the Gremlin language
+`datetime()` function rather than the JVM specific `Date` and `Timestamp` 
conversions. This translator can be helpful
+when generating scripts that will be sent to Gremlin Server or Remote Graph 
Providers supporting the `datetime()` form.
 
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc 
b/docs/src/upgrade/release-3.5.x.asciidoc
index a2e6d0a..ca3bf36 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -30,6 +30,36 @@ complete list of all the modifications that are part of this 
release.
 
 === Upgrading for Users
 
+==== datetime()
+
+Gremlin in native programming languages can all construct a native date and 
time object. In Java, that would probably
+be a `java.util.Date` object while in Javascript it would likely be the 
Node.js `Date`. In any of these cases, these
+native objects would be serialized to millisecond-precision offset from the 
unix epoch to be sent over the wire to the
+server (in embedded mode for Java, it would be up to the graph database to 
determine how the date is handled).
+
+The gap is in Gremlin scripts which do not have a way to natively construct 
dates and times other than by using Groovy
+variants. As TinkerPop moves toward a more secure method of processing Gremlin 
scripts by way of the `gremlin-language`
+model, it was clear that this gap needed to be filled. The new `datetime()` 
function can take a ISO-8601 formatted
+datetime and internally produce a `Date` with a default time zone offset of 
UTC (+00:00).
+
+This functionality, while syntax of `gremlin-language`, is also exposed as a 
component of `gremlin-groovy` so that it
+can be used in the Gremlin Console and through the `GremlinScriptEngine` in 
Gremlin Server.
+
+[source,text]
+----
+gremlin> datetime('2022-10-02').toGMTString()
+==>2 Oct 2022 00:00:00 GMT
+gremlin> datetime('2022-10-02T00:00:00Z').toGMTString()
+==>2 Oct 2022 00:00:00 GMT
+gremlin> datetime('2022-10-02T00:00:00-0400').toGMTString()
+==>2 Oct 2022 04:00:00 GMT
+----
+
+The above examples use the Java `Date` method `toGMTString()` to properly 
format the date for demonstration purposes.
+From a Gremlin language perspective there are no functions that can be called 
on the return value of `datetime()`.
+
+See:link:https://issues.apache.org/jira/browse/TINKERPOP-2596[TINKERPOP-2596]
+
 ==== Refinements to null
 
 Release 3.5.0 introduce the ability for there to be traversers that contained 
a `null` value. Since that time it has
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
index 2897506..60865c7 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
@@ -140,6 +140,7 @@ import 
org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceEdge;
 import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceProperty;
 import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceVertex;
 import 
org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceVertexProperty;
+import org.apache.tinkerpop.gremlin.util.DatetimeHelper;
 import org.apache.tinkerpop.gremlin.util.Gremlin;
 import org.apache.tinkerpop.gremlin.util.TimeUtil;
 import org.apache.tinkerpop.gremlin.util.function.Lambda;
@@ -325,6 +326,11 @@ public final class CoreImports {
         uniqueMethods(Computer.class).forEach(METHOD_IMPORTS::add);
         uniqueMethods(TimeUtil.class).forEach(METHOD_IMPORTS::add);
         uniqueMethods(Lambda.class).forEach(METHOD_IMPORTS::add);
+        try {
+            METHOD_IMPORTS.add(DatetimeHelper.class.getMethod("datetime", 
String.class));
+        } catch (Exception ex) {
+            throw new IllegalStateException("Could not load datetime() 
function to imports");
+        }
 
         ///////////
         // ENUMS //
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
index 5e7a6ca..1529392 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
@@ -26,6 +26,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.apache.tinkerpop.gremlin.util.DatetimeHelper;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -389,6 +390,14 @@ public class GenericLiteralVisitor extends 
GremlinBaseVisitor<Object> {
      * {@inheritDoc}
      */
     @Override
+    public Object visitDateLiteral(final GremlinParser.DateLiteralContext ctx) 
{
+        return DatetimeHelper.parse(getStringLiteral(ctx.stringLiteral()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public Object visitStringLiteral(final GremlinParser.StringLiteralContext 
ctx) {
         // Using Java string unescaping because it coincides with the Groovy 
rules:
         // https://docs.oracle.com/javase/tutorial/java/data/characters.html
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
index c0a9f08..46234c0 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
@@ -39,11 +39,13 @@ import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+import org.apache.tinkerpop.gremlin.util.DatetimeHelper;
 import org.apache.tinkerpop.gremlin.util.function.Lambda;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.sql.Timestamp;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
@@ -380,4 +382,31 @@ public final class GroovyTranslator implements 
Translator.ScriptTranslator {
                     collect(Collectors.joining(", "));
         }
     }
+
+    /**
+     * An extension of the {@link DefaultTypeTranslator} that generates 
Gremlin that is compliant with
+     * {@code gremlin-language} scripts. Specifically, it will convert {@code 
Date} and {@code Timestamp} to use the
+     * {@code datetime()} function. Time zone offsets are resolved to where 
{@code 2018-03-22T00:35:44.741+1600}
+     * would be converted to {@code datetime('2018-03-21T08:35:44.741Z')}. 
More commonly {@code 2018-03-22} would simply
+     * generate {@code datetime('2018-03-22T00:00:00Z')}.
+     */
+    public static class LanguageTypeTranslator extends DefaultTypeTranslator {
+        public LanguageTypeTranslator(final boolean withParameters) {
+            super(withParameters);
+        }
+
+        @Override
+        protected String getSyntax(final Date o) {
+            return getDatetimeSyntax(o.toInstant());
+        }
+
+        @Override
+        protected String getSyntax(final Timestamp o) {
+            return getDatetimeSyntax(o.toInstant());
+        }
+
+        private static String getDatetimeSyntax(final Instant i) {
+            return String.format("datetime('%s')", DatetimeHelper.format(i));
+        }
+    }
 }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/DatetimeHelper.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/DatetimeHelper.java
new file mode 100644
index 0000000..d52b176
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/DatetimeHelper.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.util;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.YearMonth;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.ResolverStyle;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.Date;
+
+import static java.time.ZoneOffset.UTC;
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+import static java.time.format.DateTimeFormatter.ISO_TIME;
+
+public final class DatetimeHelper {
+
+    private static final DateTimeFormatter datetimeFormatter = new 
DateTimeFormatterBuilder()
+            .parseCaseInsensitive()
+            .append(ISO_LOCAL_DATE_TIME)
+            .optionalStart()
+            .appendOffset("+HHMMss", "Z").toFormatter();
+
+    private static final DateTimeFormatter timeFormatter = new 
DateTimeFormatterBuilder()
+            .parseCaseInsensitive()
+            .appendLiteral('T')
+            .append(ISO_TIME).toFormatter();
+
+    private static final DateTimeFormatter yearMonthFormatter = new 
DateTimeFormatterBuilder()
+            .parseCaseInsensitive()
+            .appendValue(ChronoField.YEAR)
+            .appendLiteral('-')
+            
.appendValue(ChronoField.MONTH_OF_YEAR).toFormatter().withResolverStyle(ResolverStyle.LENIENT);
+
+    private static final DateTimeFormatter formatter = new 
DateTimeFormatterBuilder()
+            .appendOptional(datetimeFormatter)
+            .appendOptional(ISO_LOCAL_DATE)
+            .appendOptional(timeFormatter)
+            .appendOptional(yearMonthFormatter)
+            .toFormatter();
+
+    private DatetimeHelper() {}
+
+    /**
+     * Formats an {@code Instant} to a form of {@code 2018-03-22T00:35:44Z} at 
UTC.
+     */
+    public static String format(final Instant d) {
+        return datetimeFormatter.format(d.atZone(UTC));
+    }
+
+    /**
+     * Parses a {@code String} representing a date and/or time to a {@code 
Date} object with a default time zone offset
+     * of UTC (+00:00). It can parse dates in any of the following formats.
+     *
+     * <ul>
+     *     <li>T00:35:44</li>
+     *     <li>T00:35:44Z</li>
+     *     <li>2018-03-22</li>
+     *     <li>2018-03-22T00:35:44</li>
+     *     <li>2018-03-22T00:35:44Z</li>
+     *     <li>2018-03-22T00:35:44.741</li>
+     *     <li>2018-03-22T00:35:44.741Z</li>
+     *     <li>2018-03-22T00:35:44.741+1600</li>
+     * </ul>>
+     *
+     */
+    public static Date parse(final String d) {
+        final TemporalAccessor t = formatter.parse(d);
+
+        if (!t.isSupported(ChronoField.HOUR_OF_DAY)) {
+            // no hours field so it must be a Date or a YearMonth
+            if (!t.isSupported(ChronoField.DAY_OF_MONTH)) {
+                // must be a YearMonth coz no day
+                return 
Date.from(YearMonth.from(t).atDay(1).atStartOfDay(UTC).toInstant());
+            } else {
+                // must be a Date as the day is present
+                return 
Date.from(Instant.ofEpochSecond(LocalDate.from(t).atStartOfDay().toEpochSecond(UTC)));
+            }
+        } else if (!t.isSupported(ChronoField.MONTH_OF_YEAR)) {
+            // no month field so must be a Time
+            final Instant timeOnEpochDay = LocalDate.ofEpochDay(0)
+                    .atTime(LocalTime.from(t))
+                    .atZone(UTC)
+                    .toInstant();
+            return Date.from(timeOnEpochDay);
+        } else if (t.isSupported(ChronoField.OFFSET_SECONDS)) {
+            // has all datetime components including an offset
+            return Date.from(ZonedDateTime.from(t).toInstant());
+        } else {
+            // has all datetime components but no offset so throw in some UTC
+            return Date.from(ZonedDateTime.of(LocalDateTime.from(t), 
UTC).toInstant());
+        }
+    }
+
+    /**
+     * A proxy call to {@link #parse(String)} but allows for syntax similar to 
Gremlin grammar of {@code datetime()}.
+     */
+    public static Date datetime(final String d) {
+        return parse(d);
+    }
+}
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
index 3dd36f3..4984aab 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
@@ -29,7 +29,12 @@ import org.junit.runners.Parameterized;
 import java.lang.reflect.Constructor;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
@@ -439,6 +444,47 @@ public class GeneralLiteralVisitorTest {
     }
 
     @RunWith(Parameterized.class)
+    public static class ValidDatetimeLiteralTest {
+        private static final ZoneId UTC = ZoneId.of("Z");
+
+        @Parameterized.Parameter(value = 0)
+        public String script;
+
+        @Parameterized.Parameter(value = 1)
+        public Date expected;
+
+        @Parameterized.Parameters(name = "{0}")
+        public static Iterable<Object[]> generateTestParameters() {
+            return Arrays.asList(new Object[][]{
+                    {"datetime('2018-03-22T00:35:44.741Z')", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
UTC).toInstant())},
+                    {"datetime('2018-03-22T00:35:44.741-0000')", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
UTC).toInstant())},
+                    {"datetime('2018-03-22T00:35:44.741+0000')", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
UTC).toInstant())},
+                    {"datetime('2018-03-22T00:35:44.741-0300')", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
ZoneOffset.ofHours(-3)).toInstant())},
+                    {"datetime('2018-03-22T00:35:44.741+1600')", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
ZoneOffset.ofHours(16)).toInstant())},
+                    {"datetime('2018-03-22T00:35:44.741')", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
UTC).toInstant())},
+                    {"datetime('2018-03-22T00:35:44Z')", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 0, UTC).toInstant())},
+                    {"datetime('2018-03-22T00:35:44')", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 0, UTC).toInstant())},
+                    {"datetime('2018-03-22')", 
Date.from(ZonedDateTime.of(2018, 03, 22, 0, 0, 0, 0, UTC).toInstant())},
+                    {"datetime('1018-03-22')", 
Date.from(ZonedDateTime.of(1018, 03, 22, 0, 0, 0, 0, UTC).toInstant())},
+                    {"datetime('9018-03-22')", 
Date.from(ZonedDateTime.of(9018, 03, 22, 0, 0, 0, 0, UTC).toInstant())},
+                    {"datetime('T00:00:00')", Date.from(ZonedDateTime.of(1970, 
1, 1, 0, 0, 0, 0, UTC).toInstant())},
+                    {"datetime('T00:00:00Z')", 
Date.from(ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, UTC).toInstant())},
+                    {"datetime('1000-001')", Date.from(ZonedDateTime.of(1000, 
1, 1, 0, 0, 0, 0, UTC).toInstant())},
+            });
+        }
+
+        @Test
+        public void shouldParse() {
+            final GremlinLexer lexer = new 
GremlinLexer(CharStreams.fromString(script));
+            final GremlinParser parser = new GremlinParser(new 
CommonTokenStream(lexer));
+            final GremlinParser.DateLiteralContext ctx = parser.dateLiteral();
+
+            final Date dt = (Date) 
GenericLiteralVisitor.getInstance().visitDateLiteral(ctx);
+            assertEquals(expected, dt);
+        }
+    }
+
+    @RunWith(Parameterized.class)
     public static class ValidBooleanLiteralTest {
         @Parameterized.Parameter(value = 0)
         public String script;
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
index 82d679d..3b75d40 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
@@ -41,6 +41,7 @@ import org.apache.tinkerpop.gremlin.util.function.Lambda;
 import org.junit.Test;
 
 import java.sql.Timestamp;
+import java.time.ZonedDateTime;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Collections;
@@ -49,6 +50,7 @@ import java.util.LinkedHashMap;
 import java.util.UUID;
 import java.util.function.Function;
 
+import static java.time.ZoneOffset.UTC;
 import static 
org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;
 import static 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.hasLabel;
 import static org.junit.Assert.assertEquals;
@@ -156,6 +158,17 @@ public class GroovyTranslatorTest {
     }
 
     @Test
+    public void shouldTranslateDateUsingDatetimeFunction() {
+        final Translator.ScriptTranslator t = GroovyTranslator.of("g",
+                new GroovyTranslator.LanguageTypeTranslator(false));
+        final Date datetime = Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 
44, 741000000, UTC).toInstant());
+        final Date date = Date.from(ZonedDateTime.of(2018, 03, 22, 0, 0, 0, 0, 
UTC).toInstant());
+        
assertEquals("g.inject(datetime('2018-03-22T00:00:00Z'),datetime('2018-03-22T00:35:44.741Z'))",
+                t.translate(g.inject(date, datetime)).getScript());
+
+    }
+
+    @Test
     public void shouldTranslateDirection() {
         assertTranslation("Direction.BOTH", Direction.BOTH);
     }
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/DatetimeHelperTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/DatetimeHelperTest.java
new file mode 100644
index 0000000..f888a87
--- /dev/null
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/DatetimeHelperTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.util;
+
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.Date;
+
+import static java.time.ZoneOffset.UTC;
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Enclosed.class)
+public class DatetimeHelperTest {
+
+    @RunWith(Parameterized.class)
+    public static class DatetimeHelperParseTest {
+
+        @Parameterized.Parameter(value = 0)
+        public String d;
+
+        @Parameterized.Parameter(value = 1)
+        public Date expected;
+
+        @Parameterized.Parameters(name = "{0}")
+        public static Iterable<Object[]> generateTestParameters() {
+            return Arrays.asList(new Object[][]{
+                    {"2018-03-22T00:35:44.741Z", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
UTC).toInstant())},
+                    {"2018-03-22T00:35:44.741-0000", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
UTC).toInstant())},
+                    {"2018-03-22T00:35:44.741+0000", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
UTC).toInstant())},
+                    {"2018-03-22T00:35:44.741-0300", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
ZoneOffset.ofHours(-3)).toInstant())},
+                    {"2018-03-22T00:35:44.741+1600", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
ZoneOffset.ofHours(16)).toInstant())},
+                    {"2018-03-22T00:35:44.741", 
Date.from(ZonedDateTime.of(2018, 03, 22, 00, 35, 44, 741000000, 
UTC).toInstant())},
+                    {"2018-03-22T00:35:44Z", Date.from(ZonedDateTime.of(2018, 
03, 22, 00, 35, 44, 0, UTC).toInstant())},
+                    {"2018-03-22T00:35:44", Date.from(ZonedDateTime.of(2018, 
03, 22, 00, 35, 44, 0, UTC).toInstant())},
+                    {"2018-03-22", Date.from(ZonedDateTime.of(2018, 03, 22, 0, 
0, 0, 0, UTC).toInstant())},
+                    {"1018-03-22", Date.from(ZonedDateTime.of(1018, 03, 22, 0, 
0, 0, 0, UTC).toInstant())},
+                    {"9018-03-22", Date.from(ZonedDateTime.of(9018, 03, 22, 0, 
0, 0, 0, UTC).toInstant())},
+                    {"T00:00:00", Date.from(ZonedDateTime.of(1970, 1, 1, 0, 0, 
0, 0, UTC).toInstant())},
+                    {"T00:00:00Z", Date.from(ZonedDateTime.of(1970, 1, 1, 0, 
0, 0, 0, UTC).toInstant())},
+                    {"1000-001", Date.from(ZonedDateTime.of(1000, 1, 1, 0, 0, 
0, 0, UTC).toInstant())},
+            });
+        }
+
+        @Test
+        public void shouldParse() {
+            assertEquals(expected, DatetimeHelper.parse(d));
+        }
+    }
+
+    @RunWith(Parameterized.class)
+    public static class DatetimeHelperFormatTest {
+
+        @Parameterized.Parameter(value = 0)
+        public String expected;
+
+        @Parameterized.Parameter(value = 1)
+        public Instant d;
+
+        @Parameterized.Parameters(name = "{0}")
+        public static Iterable<Object[]> generateTestParameters() {
+            return Arrays.asList(new Object[][]{
+                    {"2018-03-22T00:35:44.741Z", ZonedDateTime.of(2018, 03, 
22, 00, 35, 44, 741000000, UTC).toInstant()},
+                    {"2018-03-22T00:35:44.741Z", ZonedDateTime.of(2018, 03, 
22, 00, 35, 44, 741000000, UTC).toInstant()},
+                    {"2018-03-22T00:35:44.741Z", ZonedDateTime.of(2018, 03, 
22, 00, 35, 44, 741000000, UTC).toInstant()},
+                    {"2018-03-22T03:35:44.741Z", ZonedDateTime.of(2018, 03, 
22, 00, 35, 44, 741000000, ZoneOffset.ofHours(-3)).toInstant()},
+                    {"2018-03-21T08:35:44.741Z", ZonedDateTime.of(2018, 03, 
22, 00, 35, 44, 741000000, ZoneOffset.ofHours(16)).toInstant()},
+                    {"2018-03-22T00:35:44Z", ZonedDateTime.of(2018, 03, 22, 
00, 35, 44, 0, UTC).toInstant()},
+                    {"2018-03-22T00:00:00Z", ZonedDateTime.of(2018, 03, 22, 0, 
0, 0, 0, UTC).toInstant()},
+                    {"1018-03-22T00:00:00Z", ZonedDateTime.of(1018, 03, 22, 0, 
0, 0, 0, UTC).toInstant()},
+                    {"9018-03-22T00:00:00Z", ZonedDateTime.of(9018, 03, 22, 0, 
0, 0, 0, UTC).toInstant()},
+                    {"1970-01-01T00:00:00Z", ZonedDateTime.of(1970, 1, 1, 0, 
0, 0, 0, UTC).toInstant()},
+                    {"1000-01-01T00:00:00Z", ZonedDateTime.of(1000, 1, 1, 0, 
0, 0, 0, UTC).toInstant()},
+            });
+        }
+
+        @Test
+        public void shouldFormat() {
+            assertEquals(expected, DatetimeHelper.format(d));
+        }
+    }
+}
diff --git 
a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
 
b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
index 05cd939..86c1c6e 100644
--- 
a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
+++ 
b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
@@ -865,4 +865,18 @@ public class GremlinServerHttpIntegrateTest extends 
AbstractGremlinServerIntegra
             assertEquals(0, 
node.get("result").get("data").get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).asInt());
         }
     }
+
+    @Test
+    public void 
should200OnGETWithGremlinQueryStringArgumentCallingDatetimeFunction() throws 
Exception {
+        final CloseableHttpClient httpclient = HttpClients.createDefault();
+        final HttpGet httpget = new 
HttpGet(TestClientFactory.createURLString("?gremlin=datetime%28%272018-03-22T00%3A35%3A44.741%2B1600%27%29"));
+
+        try (final CloseableHttpResponse response = 
httpclient.execute(httpget)) {
+            assertEquals(200, response.getStatusLine().getStatusCode());
+            assertEquals("application/json", 
response.getEntity().getContentType().getValue());
+            final String json = EntityUtils.toString(response.getEntity());
+            final JsonNode node = mapper.readTree(json);
+            assertEquals(1521621344741L, 
node.get("result").get("data").get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).longValue());
+        }
+    }
 }

Reply via email to