浏览代码

Scripting: Conditionally use java time api in scripting (#31441)

This commit adds a boolean system property, `es.scripting.use_java_time`,
which controls the concrete return type used by doc values within
scripts. The return type of accessing doc values for a date field is
changed to Object, essentially duck typing the type to allow
co-existence during the transition from joda time to java time.
Ryan Ernst 7 年之前
父节点
当前提交
478f6d6cf1

+ 5 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy

@@ -777,11 +777,16 @@ class BuildPlugin implements Plugin<Project> {
                     systemProperty property.getKey(), property.getValue()
                 }
             }
+
+            // TODO: remove this once joda time is removed from scriptin in 7.0
+            systemProperty 'es.scripting.use_java_time', 'true'
+
             // Set the system keystore/truststore password if we're running tests in a FIPS-140 JVM
             if (project.inFipsJvm) {
                 systemProperty 'javax.net.ssl.trustStorePassword', 'password'
                 systemProperty 'javax.net.ssl.keyStorePassword', 'password'
             }
+
             boolean assertionsEnabled = Boolean.parseBoolean(System.getProperty('tests.asserts', 'true'))
             enableSystemAssertions assertionsEnabled
             enableAssertions assertionsEnabled

+ 3 - 0
docs/build.gradle

@@ -37,6 +37,9 @@ integTestCluster {
   extraConfigFile 'hunspell/en_US/en_US.dic', '../server/src/test/resources/indices/analyze/conf_dir/hunspell/en_US/en_US.dic'
   // Whitelist reindexing from the local node so we can test it.
   setting 'reindex.remote.whitelist', '127.0.0.1:*'
+
+  // TODO: remove this for 7.0, this exists to allow the doc examples in 6.x to continue using the defaults
+  systemProperty 'es.scripting.use_java_time', 'false'
 }
 
 // remove when https://github.com/elastic/elasticsearch/issues/31305 is fixed

+ 6 - 1
docs/painless/painless-getting-started.asciidoc

@@ -198,7 +198,7 @@ POST hockey/player/1/_update
 ==== Dates
 
 Date fields are exposed as
-`ReadableDateTime`
+`ReadableDateTime` or
 so they support methods like
 `getYear`,
 and `getDayOfWeek`.
@@ -220,6 +220,11 @@ GET hockey/_search
 }
 ----------------------------------------------------------------
 // CONSOLE
+// TEST[warning:The joda time api for doc values is deprecated. Use -Des.scripting.use_java_time=true to use the java time api for date field doc values]
+
+NOTE: Date fields are changing in 7.0 to be exposed as `ZonedDateTime`
+from Java 8's time API. To switch to this functionality early, 
+add `-Des.scripting.use_java_time=true` to `jvm.options`.
 
 [float]
 [[modules-scripting-painless-regex]]

+ 1 - 0
docs/reference/aggregations/bucket/datehistogram-aggregation.asciidoc

@@ -425,6 +425,7 @@ POST /sales/_search?size=0
 --------------------------------------------------
 // CONSOLE
 // TEST[setup:sales]
+// TEST[warning:The joda time api for doc values is deprecated. Use -Des.scripting.use_java_time=true to use the java time api for date field doc values]
 
 Response:
 

+ 1 - 2
modules/lang-painless/build.gradle

@@ -17,8 +17,6 @@
  * under the License.
  */
 
-
-
 esplugin {
   description 'An easy, safe and fast scripting language for Elasticsearch'
   classname 'org.elasticsearch.painless.PainlessPlugin'
@@ -26,6 +24,7 @@ esplugin {
 
 integTestCluster {
   module project.project(':modules:mapper-extras')
+  systemProperty 'es.scripting.use_java_time', 'true'
 }
 
 dependencies {

+ 2 - 2
modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt

@@ -77,8 +77,8 @@ class org.elasticsearch.index.fielddata.ScriptDocValues$Longs {
 }
 
 class org.elasticsearch.index.fielddata.ScriptDocValues$Dates {
-  org.joda.time.ReadableDateTime get(int)
-  org.joda.time.ReadableDateTime getValue()
+  Object get(int)
+  Object getValue()
   List getValues()
 }
 

+ 2 - 2
modules/lang-painless/src/test/resources/rest-api-spec/test/painless/20_scriptfield.yml

@@ -108,7 +108,7 @@ setup:
                 script_fields:
                     bar:
                         script:
-                            source: "doc.date.value.dayOfWeek"
+                            source: "doc.date.value.dayOfWeek.value"
 
     - match: { hits.hits.0.fields.bar.0: 7}
 
@@ -123,7 +123,7 @@ setup:
                             source: >
                               StringBuilder b = new StringBuilder();
                               for (def date : doc.dates) {
-                                b.append(" ").append(date.getDayOfWeek());
+                                b.append(" ").append(date.getDayOfWeek().value);
                               }
                               return b.toString().trim()
 

+ 2 - 2
modules/lang-painless/src/test/resources/rest-api-spec/test/painless/50_script_doc_values.yml

@@ -95,7 +95,7 @@ setup:
                     field:
                         script:
                             source: "doc.date.get(0)"
-    - match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12.000Z' }
+    - match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12Z' }
 
     - do:
         search:
@@ -104,7 +104,7 @@ setup:
                     field:
                         script:
                             source: "doc.date.value"
-    - match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12.000Z' }
+    - match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12Z' }
 
 ---
 "geo_point":

+ 70 - 30
server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java

@@ -19,7 +19,6 @@
 
 package org.elasticsearch.index.fielddata;
 
-
 import org.apache.lucene.index.SortedNumericDocValues;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.BytesRef;
@@ -29,18 +28,23 @@ import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.geo.GeoUtils;
 import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.logging.ESLoggerFactory;
-import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.MutableDateTime;
-import org.joda.time.ReadableDateTime;
 
 import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.util.AbstractList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.function.UnaryOperator;
 
+import static org.elasticsearch.common.Booleans.parseBoolean;
 
 /**
  * Script level doc values, the assumption is that any implementation will
@@ -52,6 +56,7 @@ import java.util.function.UnaryOperator;
  * values form multiple documents.
  */
 public abstract class ScriptDocValues<T> extends AbstractList<T> {
+
     /**
      * Set the current doc ID.
      */
@@ -142,31 +147,55 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
         }
     }
 
-    public static final class Dates extends ScriptDocValues<ReadableDateTime> {
-        protected static final DeprecationLogger deprecationLogger = new DeprecationLogger(ESLoggerFactory.getLogger(Dates.class));
+    public static final class Dates extends ScriptDocValues<Object> {
 
-        private static final ReadableDateTime EPOCH = new DateTime(0, DateTimeZone.UTC);
+        /** Whether scripts should expose dates as java time objects instead of joda time. */
+        private static final boolean USE_JAVA_TIME = parseBoolean(System.getProperty("es.scripting.use_java_time"), false);
+
+        private static final DeprecationLogger deprecationLogger = new DeprecationLogger(ESLoggerFactory.getLogger(Dates.class));
 
         private final SortedNumericDocValues in;
+
+        /**
+         * Method call to add deprecation message. Normally this is
+         * {@link #deprecationLogger} but tests override.
+         */
+        private final Consumer<String> deprecationCallback;
+
         /**
-         * Values wrapped in {@link MutableDateTime}. Null by default an allocated on first usage so we allocate a reasonably size. We keep
-         * this array so we don't have allocate new {@link MutableDateTime}s on every usage. Instead we reuse them for every document.
+         * Whether java time or joda time should be used. This is normally {@link #USE_JAVA_TIME} but tests override it.
          */
-        private MutableDateTime[] dates;
+        private final boolean useJavaTime;
+
+        /**
+         * Values wrapped in a date time object. The concrete type depends on the system property {@code es.scripting.use_java_time}.
+         * When that system property is {@code false}, the date time objects are of type {@link MutableDateTime}. When the system
+         * property is {@code true}, the date time objects are of type {@link java.time.ZonedDateTime}.
+         */
+        private Object[] dates;
         private int count;
 
         /**
          * Standard constructor.
          */
         public Dates(SortedNumericDocValues in) {
+            this(in, message -> deprecationLogger.deprecatedAndMaybeLog("scripting_joda_time_deprecation", message), USE_JAVA_TIME);
+        }
+
+        /**
+         * Constructor for testing with a deprecation callback.
+         */
+        Dates(SortedNumericDocValues in, Consumer<String> deprecationCallback, boolean useJavaTime) {
             this.in = in;
+            this.deprecationCallback = deprecationCallback;
+            this.useJavaTime = useJavaTime;
         }
 
         /**
          * Fetch the first field value or 0 millis after epoch if there are no
          * in.
          */
-        public ReadableDateTime getValue() {
+        public Object getValue() {
             if (count == 0) {
                 throw new IllegalStateException("A document doesn't have a value for a field! " +
                     "Use doc[<field>].size()==0 to check if a document is missing a field!");
@@ -175,7 +204,7 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
         }
 
         @Override
-        public ReadableDateTime get(int index) {
+        public Object get(int index) {
             if (index >= count) {
                 throw new IndexOutOfBoundsException(
                         "attempted to fetch the [" + index + "] date when there are only ["
@@ -206,31 +235,42 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
             if (count == 0) {
                 return;
             }
-            if (dates == null) {
-                // Happens for the document. We delay allocating dates so we can allocate it with a reasonable size.
-                dates = new MutableDateTime[count];
-                for (int i = 0; i < dates.length; i++) {
-                    dates[i] = new MutableDateTime(in.nextValue(), DateTimeZone.UTC);
+            if (useJavaTime) {
+                if (dates == null || count > dates.length) {
+                    // Happens for the document. We delay allocating dates so we can allocate it with a reasonable size.
+                    dates = new ZonedDateTime[count];
                 }
-                return;
-            }
-            if (count > dates.length) {
-                // Happens when we move to a new document and it has more dates than any documents before it.
-                MutableDateTime[] backup = dates;
-                dates = new MutableDateTime[count];
-                System.arraycopy(backup, 0, dates, 0, backup.length);
-                for (int i = 0; i < backup.length; i++) {
-                    dates[i].setMillis(in.nextValue());
+                for (int i = 0; i < count; ++i) {
+                    dates[i] = ZonedDateTime.ofInstant(Instant.ofEpochMilli(in.nextValue()), ZoneOffset.UTC);
                 }
-                for (int i = backup.length; i < dates.length; i++) {
+            } else {
+                deprecated("The joda time api for doc values is deprecated. Use -Des.scripting.use_java_time=true" +
+                           " to use the java time api for date field doc values");
+                if (dates == null || count > dates.length) {
+                    // Happens for the document. We delay allocating dates so we can allocate it with a reasonable size.
+                    dates = new MutableDateTime[count];
+                }
+                for (int i = 0; i < count; i++) {
                     dates[i] = new MutableDateTime(in.nextValue(), DateTimeZone.UTC);
                 }
-                return;
-            }
-            for (int i = 0; i < count; i++) {
-                dates[i] = new MutableDateTime(in.nextValue(), DateTimeZone.UTC);
             }
         }
+
+        /**
+         * Log a deprecation log, with the server's permissions, not the permissions of the
+         * script calling this method. We need to do this to prevent errors when rolling
+         * the log file.
+         */
+        private void deprecated(String message) {
+            // Intentionally not calling SpecialPermission.check because this is supposed to be called by scripts
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    deprecationCallback.accept(message);
+                    return null;
+                }
+            });
+        }
     }
 
     public static final class Doubles extends ScriptDocValues<Double> {

+ 5 - 6
server/src/main/java/org/elasticsearch/script/ScriptModule.java

@@ -19,6 +19,11 @@
 
 package org.elasticsearch.script;
 
+import org.elasticsearch.common.settings.ClusterSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.plugins.ScriptPlugin;
+import org.elasticsearch.search.aggregations.pipeline.movfn.MovingFunctionScript;
+
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -27,12 +32,6 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import org.elasticsearch.common.settings.ClusterSettings;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.plugins.ScriptPlugin;
-import org.elasticsearch.search.aggregations.pipeline.movfn.MovingFunctionScript;
-
-
 /**
  * Manages building {@link ScriptService}.
  */

+ 6 - 0
server/src/main/java/org/elasticsearch/search/aggregations/support/values/ScriptDoubleValues.java

@@ -27,6 +27,7 @@ import org.joda.time.ReadableInstant;
 
 import java.io.IOException;
 import java.lang.reflect.Array;
+import java.time.ZonedDateTime;
 import java.util.Collection;
 
 /**
@@ -54,6 +55,9 @@ public class ScriptDoubleValues extends SortingNumericDoubleValues implements Sc
         } else if (value instanceof ReadableInstant) {
             resize(1);
             values[0] = ((ReadableInstant) value).getMillis();
+        } else if (value instanceof ZonedDateTime) {
+            resize(1);
+            values[0] = ((ZonedDateTime) value).toInstant().toEpochMilli();
         } else if (value.getClass().isArray()) {
             int length = Array.getLength(value);
             if (length == 0) {
@@ -89,6 +93,8 @@ public class ScriptDoubleValues extends SortingNumericDoubleValues implements Sc
         } else if (o instanceof ReadableInstant) {
             // Dates are exposed in scripts as ReadableDateTimes but aggregations want them to be numeric
             return ((ReadableInstant) o).getMillis();
+        } else if (o instanceof ZonedDateTime) {
+            return ((ZonedDateTime) o).toInstant().toEpochMilli();
         } else if (o instanceof Boolean) {
             // We do expose boolean fields as boolean in scripts, however aggregations still expect
             // that scripts return the same internal representation as regular fields, so boolean

+ 3 - 0
server/src/main/java/org/elasticsearch/search/aggregations/support/values/ScriptLongValues.java

@@ -28,6 +28,7 @@ import org.joda.time.ReadableInstant;
 
 import java.io.IOException;
 import java.lang.reflect.Array;
+import java.time.ZonedDateTime;
 import java.util.Collection;
 import java.util.Iterator;
 
@@ -91,6 +92,8 @@ public class ScriptLongValues extends AbstractSortingNumericDocValues implements
         } else if (o instanceof ReadableInstant) {
             // Dates are exposed in scripts as ReadableDateTimes but aggregations want them to be numeric
             return ((ReadableInstant) o).getMillis();
+        } else if (o instanceof ZonedDateTime) {
+            return ((ZonedDateTime) o).toInstant().toEpochMilli();
         } else if (o instanceof Boolean) {
             // We do expose boolean fields as boolean in scripts, however aggregations still expect
             // that scripts return the same internal representation as regular fields, so boolean

+ 60 - 14
server/src/test/java/org/elasticsearch/index/fielddata/ScriptDocValuesDatesTests.java

@@ -23,29 +23,74 @@ import org.elasticsearch.index.fielddata.ScriptDocValues.Dates;
 import org.elasticsearch.test.ESTestCase;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
-import org.joda.time.ReadableDateTime;
 
 import java.io.IOException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
+import static org.hamcrest.Matchers.containsInAnyOrder;
 
 public class ScriptDocValuesDatesTests extends ESTestCase {
-    public void test() throws IOException {
+
+    public void testJavaTime() throws IOException {
+        assertDateDocValues(true);
+    }
+
+    public void testJodaTimeBwc() throws IOException {
+        assertDateDocValues(false, "The joda time api for doc values is deprecated." +
+            " Use -Des.scripting.use_java_time=true to use the java time api for date field doc values");
+    }
+
+    public void assertDateDocValues(boolean useJavaTime, String... expectedWarnings) throws IOException {
+        final Function<Long, Object> datetimeCtor;
+        if (useJavaTime) {
+            datetimeCtor = millis -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneOffset.UTC);
+        } else {
+            datetimeCtor = millis -> new DateTime(millis, DateTimeZone.UTC);
+        }
         long[][] values = new long[between(3, 10)][];
-        ReadableDateTime[][] expectedDates = new ReadableDateTime[values.length][];
+        Object[][] expectedDates = new Object[values.length][];
         for (int d = 0; d < values.length; d++) {
             values[d] = new long[randomBoolean() ? randomBoolean() ? 0 : 1 : between(2, 100)];
-            expectedDates[d] = new ReadableDateTime[values[d].length];
+            expectedDates[d] = new Object[values[d].length];
             for (int i = 0; i < values[d].length; i++) {
-                expectedDates[d][i] = new DateTime(randomNonNegativeLong(), DateTimeZone.UTC);
-                values[d][i] = expectedDates[d][i].getMillis();
+                values[d][i] = randomNonNegativeLong();
+                expectedDates[d][i] = datetimeCtor.apply(values[d][i]);
             }
         }
-        Dates dates = wrap(values);
+
+        Set<String> warnings = new HashSet<>();
+        Dates dates = wrap(values, deprecationMessage -> {
+            warnings.add(deprecationMessage);
+            /* Create a temporary directory to prove we are running with the
+             * server's permissions. */
+            createTempDir();
+        }, useJavaTime);
+        // each call to get or getValue will be run with limited permissions, just as they are in scripts
+        PermissionCollection noPermissions = new Permissions();
+        AccessControlContext noPermissionsAcc = new AccessControlContext(
+            new ProtectionDomain[] {
+                new ProtectionDomain(null, noPermissions)
+            }
+        );
+
         for (int round = 0; round < 10; round++) {
             int d = between(0, values.length - 1);
             dates.setNextDocId(d);
             if (expectedDates[d].length > 0) {
-                assertEquals(expectedDates[d][0] , dates.getValue());
+                Object dateValue = AccessController.doPrivileged((PrivilegedAction<Object>) dates::getValue, noPermissionsAcc);
+                assertEquals(expectedDates[d][0] , dateValue);
             } else {
                 Exception e = expectThrows(IllegalStateException.class, () -> dates.getValue());
                 assertEquals("A document doesn't have a value for a field! " +
@@ -54,15 +99,16 @@ public class ScriptDocValuesDatesTests extends ESTestCase {
 
             assertEquals(values[d].length, dates.size());
             for (int i = 0; i < values[d].length; i++) {
-                assertEquals(expectedDates[d][i], dates.get(i));
+                final int ndx = i;
+                Object dateValue = AccessController.doPrivileged((PrivilegedAction<Object>) () -> dates.get(ndx), noPermissionsAcc);
+                assertEquals(expectedDates[d][i], dateValue);
             }
-
-            Exception e = expectThrows(UnsupportedOperationException.class, () -> dates.add(new DateTime()));
-            assertEquals("doc values are unmodifiable", e.getMessage());
         }
+
+        assertThat(warnings, containsInAnyOrder(expectedWarnings));
     }
 
-    private Dates wrap(long[][] values) {
+    private Dates wrap(long[][] values, Consumer<String> deprecationHandler, boolean useJavaTime) {
         return new Dates(new AbstractSortedNumericDocValues() {
             long[] current;
             int i;
@@ -81,6 +127,6 @@ public class ScriptDocValuesDatesTests extends ESTestCase {
             public long nextValue() {
                 return current[i++];
             }
-        });
+        }, deprecationHandler, useJavaTime);
     }
 }

+ 9 - 13
server/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java

@@ -47,15 +47,10 @@ import org.elasticsearch.search.lookup.FieldLookup;
 import org.elasticsearch.search.sort.SortOrder;
 import org.elasticsearch.test.ESIntegTestCase;
 import org.elasticsearch.test.InternalSettingsPlugin;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.joda.time.ReadableDateTime;
-import org.joda.time.base.BaseDateTime;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.DateTimeFormatter;
 
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Base64;
@@ -64,6 +59,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -115,7 +111,7 @@ public class SearchFieldsIT extends ESIntegTestCase {
             scripts.put("doc['date'].date.millis", vars -> {
                 Map<?, ?> doc = (Map) vars.get("doc");
                 ScriptDocValues.Dates dates = (ScriptDocValues.Dates) doc.get("date");
-                return dates.getValue().getMillis();
+                return ((ZonedDateTime) dates.getValue()).toInstant().toEpochMilli();
             });
 
             scripts.put("_fields['num1'].value", vars -> fieldsScript(vars, "num1"));
@@ -805,8 +801,8 @@ public class SearchFieldsIT extends ESIntegTestCase {
         assertThat(searchResponse.getHits().getAt(0).getFields().get("long_field").getValue(), equalTo((Object) 4L));
         assertThat(searchResponse.getHits().getAt(0).getFields().get("float_field").getValue(), equalTo((Object) 5.0));
         assertThat(searchResponse.getHits().getAt(0).getFields().get("double_field").getValue(), equalTo((Object) 6.0d));
-        BaseDateTime dateField = searchResponse.getHits().getAt(0).getFields().get("date_field").getValue();
-        assertThat(dateField.getMillis(), equalTo(date.toInstant().toEpochMilli()));
+        ZonedDateTime dateField = searchResponse.getHits().getAt(0).getFields().get("date_field").getValue();
+        assertThat(dateField.toInstant().toEpochMilli(), equalTo(date.toInstant().toEpochMilli()));
         assertThat(searchResponse.getHits().getAt(0).getFields().get("boolean_field").getValue(), equalTo((Object) true));
         assertThat(searchResponse.getHits().getAt(0).getFields().get("text_field").getValue(), equalTo("foo"));
         assertThat(searchResponse.getHits().getAt(0).getFields().get("keyword_field").getValue(), equalTo("foo"));
@@ -946,10 +942,10 @@ public class SearchFieldsIT extends ESIntegTestCase {
         assertAcked(prepareCreate("test").addMapping("type", mapping));
         ensureGreen("test");
 
-        DateTime date = new DateTime(1990, 12, 29, 0, 0, DateTimeZone.UTC);
-        DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
+        ZonedDateTime date = ZonedDateTime.of(1990, 12, 29, 0, 0, 0, 0, ZoneOffset.UTC);
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ROOT);
 
-        index("test", "type", "1", "text_field", "foo", "date_field", formatter.print(date));
+        index("test", "type", "1", "text_field", "foo", "date_field", formatter.format(date));
         refresh("test");
 
         SearchRequestBuilder builder = client().prepareSearch().setQuery(matchAllQuery())
@@ -977,7 +973,7 @@ public class SearchFieldsIT extends ESIntegTestCase {
         DocumentField dateField = fields.get("date_field");
         assertThat(dateField.getName(), equalTo("date_field"));
 
-        ReadableDateTime fetchedDate = dateField.getValue();
+        ZonedDateTime fetchedDate = dateField.getValue();
         assertThat(fetchedDate, equalTo(date));
     }