Browse Source

[Java.time] Calculate week of a year with ISO rules (#48209)

Reverting the change introducing IsoLocal.ROOT and introducing IsoCalendarDataProvider that defaults start of the week to Monday and requires minimum 4 days in first week of a year. This extension is using java SPI mechanism and defaults for Locale.ROOT only. 
It require jvm property java.locale.providers to be set with SPI,COMPAT

closes #41670
Przemyslaw Gomulka 6 years ago
parent
commit
54d6da5432
23 changed files with 248 additions and 226 deletions
  1. 3 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy
  2. 1 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/test/RestIntegTestTask.groovy
  3. 1 1
      distribution/src/config/jvm.options
  4. 142 142
      server/src/main/java/org/elasticsearch/common/time/DateFormatters.java
  5. 4 4
      server/src/main/java/org/elasticsearch/common/time/EpochTime.java
  6. 16 15
      server/src/main/java/org/elasticsearch/common/time/IsoCalendarDataProvider.java
  7. 1 2
      server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java
  8. 2 2
      server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java
  9. 2 3
      server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java
  10. 5 5
      server/src/main/java/org/elasticsearch/script/JodaCompatibleZonedDateTime.java
  11. 1 0
      server/src/main/resources/META-INF/services/java.util.spi.CalendarDataProvider
  12. 1 1
      server/src/test/java/org/elasticsearch/common/joda/JavaJodaTimeDuellingTests.java
  13. 27 1
      server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java
  14. 14 0
      server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java
  15. 1 1
      server/src/test/java/org/elasticsearch/index/mapper/DateFieldMapperTests.java
  16. 2 2
      server/src/test/java/org/elasticsearch/script/JodaCompatibleZonedDateTimeTests.java
  17. 6 6
      server/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java
  18. 2 15
      x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java
  19. 5 5
      x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/proto/StringUtils.java
  20. 3 3
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTimeField.java
  21. 3 13
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/NonIsoDateTimeProcessor.java
  22. 3 3
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java
  23. 3 2
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/NonIsoDateTimeProcessorTests.java

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

@@ -861,6 +861,9 @@ class BuildPlugin implements Plugin<Project> {
                         'tests.security.manager': 'true',
                         'jna.nosys': 'true'
 
+                //TODO remove once jvm.options are added to test system properties
+                test.systemProperty ('java.locale.providers','SPI,COMPAT')
+
                 // ignore changing test seed when build is passed -Dignore.tests.seed for cacheability experimentation
                 if (System.getProperty('ignore.tests.seed') != null) {
                     nonInputProperties.systemProperty('tests.seed', project.property('testSeed'))

+ 1 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/test/RestIntegTestTask.groovy

@@ -24,6 +24,7 @@ import org.elasticsearch.gradle.testclusters.RestTestRunnerTask
 import org.elasticsearch.gradle.tool.Boilerplate
 import org.elasticsearch.gradle.tool.ClasspathUtils
 import org.gradle.api.DefaultTask
+import org.gradle.api.JavaVersion
 import org.gradle.api.Task
 import org.gradle.api.file.FileCopyDetails
 import org.gradle.api.tasks.Copy

+ 1 - 1
distribution/src/config/jvm.options

@@ -104,4 +104,4 @@ ${error.file}
 -Xlog:gc*,gc+age=trace,safepoint:file=${loggc}:utctime,pid,tags:filecount=32,filesize=64m
 # due to internationalization enhancements in JDK 9 Elasticsearch need to set the provider to COMPAT otherwise
 # time/date parsing will break in an incompatible way for some date patterns and locals
--Djava.locale.providers=COMPAT
+-Djava.locale.providers=SPI,COMPAT

File diff suppressed because it is too large
+ 142 - 142
server/src/main/java/org/elasticsearch/common/time/DateFormatters.java


+ 4 - 4
server/src/main/java/org/elasticsearch/common/time/EpochTime.java

@@ -125,13 +125,13 @@ class EpochTime {
         .optionalStart() // optional is used so isSupported will be called when printing
         .appendFraction(NANOS_OF_SECOND, 0, 9, true)
         .optionalEnd()
-        .toFormatter(IsoLocale.ROOT);
+        .toFormatter(Locale.ROOT);
 
     // this supports seconds ending in dot
     private static final DateTimeFormatter SECONDS_FORMATTER2 = new DateTimeFormatterBuilder()
         .appendValue(SECONDS, 1, 19, SignStyle.NORMAL)
         .appendLiteral('.')
-        .toFormatter(IsoLocale.ROOT);
+        .toFormatter(Locale.ROOT);
 
     // this supports milliseconds without any fraction
     private static final DateTimeFormatter MILLISECONDS_FORMATTER1 = new DateTimeFormatterBuilder()
@@ -139,13 +139,13 @@ class EpochTime {
         .optionalStart()
         .appendFraction(NANOS_OF_MILLI, 0, 6, true)
         .optionalEnd()
-        .toFormatter(IsoLocale.ROOT);
+        .toFormatter(Locale.ROOT);
 
     // this supports milliseconds ending in dot
     private static final DateTimeFormatter MILLISECONDS_FORMATTER2 = new DateTimeFormatterBuilder()
         .append(MILLISECONDS_FORMATTER1)
         .appendLiteral('.')
-        .toFormatter(IsoLocale.ROOT);
+        .toFormatter(Locale.ROOT);
 
     static final DateFormatter SECONDS_FORMATTER = new JavaDateFormatter("epoch_second", SECONDS_FORMATTER1,
         builder -> builder.parseDefaulting(ChronoField.NANO_OF_SECOND, 999_999_999L),

+ 16 - 15
libs/core/src/main/java/org/elasticsearch/common/time/IsoLocale.java → server/src/main/java/org/elasticsearch/common/time/IsoCalendarDataProvider.java

@@ -18,23 +18,24 @@
  */
 package org.elasticsearch.common.time;
 
+import java.util.Calendar;
 import java.util.Locale;
+import java.util.spi.CalendarDataProvider;
 
-/**
- * Locale constants to be used across elasticsearch code base.
- * java.util.Locale.ROOT should not be used as it defaults start of the week incorrectly to Sunday.
- */
-public final class IsoLocale {
-    private IsoLocale() {
-        throw new UnsupportedOperationException();
+public class IsoCalendarDataProvider extends CalendarDataProvider {
+
+    @Override
+    public int getFirstDayOfWeek(Locale locale) {
+        return Calendar.MONDAY;
     }
 
-    /**
-     * We want to use Locale.ROOT but with a start of the week as defined in ISO8601 to be compatible with the behaviour in joda-time
-     * https://github.com/elastic/elasticsearch/issues/42588
-     * @see java.time.temporal.WeekFields#of(Locale)
-      */
-    public static final Locale ROOT = new Locale.Builder()
-        .setLocale(Locale.ROOT)
-        .setUnicodeLocaleKeyword("fw", "mon").build();
+    @Override
+    public int getMinimalDaysInFirstWeek(Locale locale) {
+        return 4;
+    }
+
+    @Override
+    public Locale[] getAvailableLocales() {
+        return new Locale[]{Locale.ROOT};
+    }
 }

+ 1 - 2
server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java

@@ -43,7 +43,6 @@ import org.elasticsearch.common.time.DateFormatter;
 import org.elasticsearch.common.time.DateFormatters;
 import org.elasticsearch.common.time.DateMathParser;
 import org.elasticsearch.common.time.DateUtils;
-import org.elasticsearch.common.time.IsoLocale;
 import org.elasticsearch.common.util.LocaleUtils;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
@@ -136,7 +135,7 @@ public final class DateFieldMapper extends FieldMapper {
         public Builder(String name) {
             super(name, new DateFieldType(), new DateFieldType());
             builder = this;
-            locale = IsoLocale.ROOT;
+            locale = Locale.ROOT;
         }
 
         @Override

+ 2 - 2
server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java

@@ -29,7 +29,6 @@ import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.collect.CopyOnWriteHashMap;
 import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.time.IsoLocale;
 import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
@@ -42,6 +41,7 @@ import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 public class ObjectMapper extends Mapper implements Cloneable {
@@ -530,7 +530,7 @@ public class ObjectMapper extends Mapper implements Cloneable {
             builder.field("type", CONTENT_TYPE);
         }
         if (dynamic != null) {
-            builder.field("dynamic", dynamic.name().toLowerCase(IsoLocale.ROOT));
+            builder.field("dynamic", dynamic.name().toLowerCase(Locale.ROOT));
         }
         if (enabled != Defaults.ENABLED) {
             builder.field("enabled", enabled);

+ 2 - 3
server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java

@@ -37,7 +37,6 @@ import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.time.DateFormatter;
 import org.elasticsearch.common.time.DateMathParser;
-import org.elasticsearch.common.time.IsoLocale;
 import org.elasticsearch.common.util.LocaleUtils;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
@@ -80,7 +79,7 @@ public class RangeFieldMapper extends FieldMapper {
 
     public static class Builder extends FieldMapper.Builder<Builder, RangeFieldMapper> {
         private Boolean coerce;
-        private Locale locale = IsoLocale.ROOT;
+        private Locale locale = Locale.ROOT;
         private String pattern;
 
         public Builder(String name, RangeType type) {
@@ -424,7 +423,7 @@ public class RangeFieldMapper extends FieldMapper {
         }
         if (fieldType().rangeType == RangeType.DATE
                 && (includeDefaults || (fieldType().dateTimeFormatter() != null
-                && fieldType().dateTimeFormatter().locale() != IsoLocale.ROOT))) {
+                && fieldType().dateTimeFormatter().locale() != Locale.ROOT))) {
             builder.field("locale", fieldType().dateTimeFormatter().locale());
         }
         if (includeDefaults || coerce.explicit()) {

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

@@ -23,6 +23,7 @@ import org.apache.logging.log4j.LogManager;
 import org.elasticsearch.common.SuppressForbidden;
 import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.time.DateFormatter;
+import org.elasticsearch.common.time.DateFormatters;
 import org.elasticsearch.common.time.DateUtils;
 import org.joda.time.DateTime;
 
@@ -50,7 +51,6 @@ import java.time.temporal.TemporalField;
 import java.time.temporal.TemporalQuery;
 import java.time.temporal.TemporalUnit;
 import java.time.temporal.ValueRange;
-import java.time.temporal.WeekFields;
 import java.util.Locale;
 import java.util.Objects;
 
@@ -474,14 +474,14 @@ public class JodaCompatibleZonedDateTime
 
     @Deprecated
     public int getWeekOfWeekyear() {
-        logDeprecatedMethod("getWeekOfWeekyear()", "get(WeekFields.ISO.weekOfWeekBasedYear())");
-        return dt.get(WeekFields.ISO.weekOfWeekBasedYear());
+        logDeprecatedMethod("getWeekOfWeekyear()", "get(DateFormatters.WEEK_FIELDS.weekOfWeekBasedYear())");
+        return dt.get(DateFormatters.WEEK_FIELDS.weekOfWeekBasedYear());
     }
 
     @Deprecated
     public int getWeekyear() {
-        logDeprecatedMethod("getWeekyear()", "get(WeekFields.ISO.weekBasedYear())");
-        return dt.get(WeekFields.ISO.weekBasedYear());
+        logDeprecatedMethod("getWeekyear()", "get(DateFormatters.WEEK_FIELDS.weekBasedYear())");
+        return dt.get(DateFormatters.WEEK_FIELDS.weekBasedYear());
     }
 
     @Deprecated

+ 1 - 0
server/src/main/resources/META-INF/services/java.util.spi.CalendarDataProvider

@@ -0,0 +1 @@
+org.elasticsearch.common.time.IsoCalendarDataProvider

+ 1 - 1
server/src/test/java/org/elasticsearch/common/joda/JavaJodaTimeDuellingTests.java

@@ -37,9 +37,9 @@ import java.time.format.DateTimeFormatter;
 import java.time.temporal.TemporalAccessor;
 import java.util.Locale;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.core.IsEqual.equalTo;
 
 public class JavaJodaTimeDuellingTests extends ESTestCase {
     @Override

+ 27 - 1
server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java

@@ -40,6 +40,32 @@ import static org.hamcrest.Matchers.sameInstance;
 
 public class DateFormattersTests extends ESTestCase {
 
+    public void testWeekBasedDates() {
+        // as per WeekFields.ISO first week starts on Monday and has minimum 4 days
+        DateFormatter dateFormatter = DateFormatters.forPattern("YYYY-ww");
+
+        // first week of 2016 starts on Monday 2016-01-04 as previous week in 2016 has only 3 days
+        assertThat(DateFormatters.from(dateFormatter.parse("2016-01")) ,
+            equalTo(ZonedDateTime.of(2016,01,04, 0,0,0,0,ZoneOffset.UTC)));
+
+        // first week of 2015 starts on Monday 2014-12-29 because 4days belong to 2019
+        assertThat(DateFormatters.from(dateFormatter.parse("2015-01")) ,
+            equalTo(ZonedDateTime.of(2014,12,29, 0,0,0,0,ZoneOffset.UTC)));
+
+
+        // as per WeekFields.ISO first week starts on Monday and has minimum 4 days
+         dateFormatter = DateFormatters.forPattern("YYYY");
+
+        // first week of 2016 starts on Monday 2016-01-04 as previous week in 2016 has only 3 days
+        assertThat(DateFormatters.from(dateFormatter.parse("2016")) ,
+            equalTo(ZonedDateTime.of(2016,01,04, 0,0,0,0,ZoneOffset.UTC)));
+
+        // first week of 2015 starts on Monday 2014-12-29 because 4days belong to 2019
+        assertThat(DateFormatters.from(dateFormatter.parse("2015")) ,
+            equalTo(ZonedDateTime.of(2014,12,29, 0,0,0,0,ZoneOffset.UTC)));
+    }
+
+
     // this is not in the duelling tests, because the epoch millis parser in joda time drops the milliseconds after the comma
     // but is able to parse the rest
     // as this feature is supported it also makes sense to make it exact
@@ -115,7 +141,7 @@ public class DateFormattersTests extends ESTestCase {
     }
 
     public void testLocales() {
-        assertThat(DateFormatters.forPattern("strict_date_optional_time").locale(), is(IsoLocale.ROOT));
+        assertThat(DateFormatters.forPattern("strict_date_optional_time").locale(), is(Locale.ROOT));
         Locale locale = randomLocale(random());
         assertThat(DateFormatters.forPattern("strict_date_optional_time").withLocale(locale).locale(), is(locale));
     }

+ 14 - 0
server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java

@@ -50,6 +50,20 @@ public class JavaDateMathParserTests extends ESTestCase {
         assertDateEquals(gotMillis, "297276785531", "297276785531");
     }
 
+    public void testWeekDates() {
+        DateFormatter formatter = DateFormatter.forPattern("YYYY-ww");
+        assertDateMathEquals(formatter.toDateMathParser(), "2016-01", "2016-01-04T23:59:59.999Z", 0, true, ZoneOffset.UTC);
+
+        formatter = DateFormatter.forPattern("YYYY");
+        assertDateMathEquals(formatter.toDateMathParser(), "2016", "2016-01-04T23:59:59.999Z", 0, true, ZoneOffset.UTC);
+
+        formatter = DateFormatter.forPattern("YYYY-ww");
+        assertDateMathEquals(formatter.toDateMathParser(), "2015-01", "2014-12-29T23:59:59.999Z", 0, true, ZoneOffset.UTC);
+
+        formatter = DateFormatter.forPattern("YYYY");
+        assertDateMathEquals(formatter.toDateMathParser(), "2015", "2014-12-29T23:59:59.999Z", 0, true, ZoneOffset.UTC);
+    }
+
     public void testBasicDates() {
         assertDateMathEquals("2014-05-30", "2014-05-30T00:00:00.000");
         assertDateMathEquals("2014-05-30T20", "2014-05-30T20:00:00.000");

+ 1 - 1
server/src/test/java/org/elasticsearch/index/mapper/DateFieldMapperTests.java

@@ -235,7 +235,7 @@ public class DateFieldMapperTests extends ESSingleNodeTestCase {
         mapper.parse(new SourceToParse("test", "1", BytesReference
                 .bytes(XContentFactory.jsonBuilder()
                         .startObject()
-                        .field("field", "Mi., 06 Dez. 2000 02:55:00 -0800")
+                        .field("field", "Mi, 06 Dez 2000 02:55:00 -0800")
                         .endObject()),
                 XContentType.JSON));
     }

+ 2 - 2
server/src/test/java/org/elasticsearch/script/JodaCompatibleZonedDateTimeTests.java

@@ -213,12 +213,12 @@ public class JodaCompatibleZonedDateTimeTests extends ESTestCase {
 
     public void testWeekOfWeekyear() {
         assertMethodDeprecation(() -> assertThat(javaTime.getWeekOfWeekyear(), equalTo(jodaTime.getWeekOfWeekyear())),
-            "getWeekOfWeekyear()", "get(WeekFields.ISO.weekOfWeekBasedYear())");
+            "getWeekOfWeekyear()", "get(DateFormatters.WEEK_FIELDS.weekOfWeekBasedYear())");
     }
 
     public void testWeekyear() {
         assertMethodDeprecation(() -> assertThat(javaTime.getWeekyear(), equalTo(jodaTime.getWeekyear())),
-            "getWeekyear()", "get(WeekFields.ISO.weekBasedYear())");
+            "getWeekyear()", "get(DateFormatters.WEEK_FIELDS.weekBasedYear())");
     }
 
     public void testYearOfCentury() {

+ 6 - 6
server/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java

@@ -1579,21 +1579,21 @@ public class SearchQueryIT extends ESIntegTestCase {
                 .endObject().endObject().endObject()));
 
         indexRandom(true,
-            client().prepareIndex("test", "type1", "1").setSource("date_field", "Mi., 06 Dez. 2000 02:55:00 -0800"),
-            client().prepareIndex("test", "type1", "2").setSource("date_field", "Do., 07 Dez. 2000 02:55:00 -0800")
+            client().prepareIndex("test", "type1", "1").setSource("date_field", "Mi, 06 Dez 2000 02:55:00 -0800"),
+            client().prepareIndex("test", "type1", "2").setSource("date_field", "Do, 07 Dez 2000 02:55:00 -0800")
         );
 
         SearchResponse searchResponse = client().prepareSearch("test")
             .setQuery(QueryBuilders.rangeQuery("date_field")
-                .gte("Di., 05 Dez. 2000 02:55:00 -0800")
-                .lte("Do., 07 Dez. 2000 00:00:00 -0800"))
+                .gte("Di, 05 Dez 2000 02:55:00 -0800")
+                .lte("Do, 07 Dez 2000 00:00:00 -0800"))
             .get();
         assertHitCount(searchResponse, 1L);
 
         searchResponse = client().prepareSearch("test")
             .setQuery(QueryBuilders.rangeQuery("date_field")
-                .gte("Di., 05 Dez. 2000 02:55:00 -0800")
-                .lte("Fr., 08 Dez. 2000 00:00:00 -0800"))
+                .gte("Di, 05 Dez 2000 02:55:00 -0800")
+                .lte("Fr, 08 Dez 2000 00:00:00 -0800"))
             .get();
         assertHitCount(searchResponse, 2L);
     }

+ 2 - 15
x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java

@@ -17,23 +17,11 @@ import static org.hamcrest.Matchers.startsWith;
 /**
  * Due to changes in JDK9 where locale data is used from CLDR, the licence message will differ in jdk 8 and jdk9+
  * https://openjdk.java.net/jeps/252
+ * We run ES with -Djava.locale.providers=SPI,COMPAT and same option has to be applied when running this test from IDE
  */
 public class LicenseServiceTests extends ESTestCase {
 
-    public void testLogExpirationWarningOnJdk9AndNewer() {
-        assumeTrue("this is for JDK9+", JavaVersion.current().compareTo(JavaVersion.parse("9")) >= 0);
-
-        long time = LocalDate.of(2018, 11, 15).atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli();
-        final boolean expired = randomBoolean();
-        final String message = LicenseService.buildExpirationMessage(time, expired).toString();
-        if (expired) {
-            assertThat(message, startsWith("LICENSE [EXPIRED] ON [THU, NOV 15, 2018].\n"));
-        } else {
-            assertThat(message, startsWith("License [will expire] on [Thu, Nov 15, 2018].\n"));
-        }
-    }
-
-    public void testLogExpirationWarningOnJdk8() {
+    public void testLogExpirationWarning() {
         assumeTrue("this is for JDK8 only", JavaVersion.current().equals(JavaVersion.parse("8")));
 
         long time = LocalDate.of(2018, 11, 15).atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli();
@@ -45,5 +33,4 @@ public class LicenseServiceTests extends ESTestCase {
             assertThat(message, startsWith("License [will expire] on [Thursday, November 15, 2018].\n"));
         }
     }
-
 }

+ 5 - 5
x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/proto/StringUtils.java

@@ -6,8 +6,6 @@
 
 package org.elasticsearch.xpack.sql.proto;
 
-import org.elasticsearch.common.time.IsoLocale;
-
 import java.sql.Timestamp;
 import java.time.Duration;
 import java.time.OffsetTime;
@@ -15,6 +13,7 @@ import java.time.Period;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
@@ -26,6 +25,7 @@ import static java.time.temporal.ChronoField.NANO_OF_SECOND;
 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
 
 public final class StringUtils {
+
     public static final String EMPTY = "";
     
     public static final DateTimeFormatter ISO_DATE_WITH_MILLIS = new DateTimeFormatterBuilder()
@@ -39,7 +39,7 @@ public final class StringUtils {
             .appendValue(SECOND_OF_MINUTE, 2)
             .appendFraction(MILLI_OF_SECOND, 3, 3, true)
             .appendOffsetId()
-            .toFormatter(IsoLocale.ROOT);
+            .toFormatter(Locale.ROOT);
 
     public static final DateTimeFormatter ISO_DATE_WITH_NANOS = new DateTimeFormatterBuilder()
         .parseCaseInsensitive()
@@ -52,7 +52,7 @@ public final class StringUtils {
         .appendValue(SECOND_OF_MINUTE, 2)
         .appendFraction(NANO_OF_SECOND, 3, 9, true)
         .appendOffsetId()
-        .toFormatter(IsoLocale.ROOT);
+        .toFormatter(Locale.ROOT);
 
     public static final DateTimeFormatter ISO_TIME_WITH_MILLIS = new DateTimeFormatterBuilder()
         .parseCaseInsensitive()
@@ -63,7 +63,7 @@ public final class StringUtils {
         .appendValue(SECOND_OF_MINUTE, 2)
         .appendFraction(MILLI_OF_SECOND, 3, 3, true)
         .appendOffsetId()
-        .toFormatter(IsoLocale.ROOT);
+        .toFormatter(Locale.ROOT);
 
     private static final int SECONDS_PER_MINUTE = 60;
     private static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * 60;

+ 3 - 3
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTimeField.java

@@ -5,13 +5,13 @@
  */
 package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
 
-import org.elasticsearch.common.time.IsoLocale;
 import org.elasticsearch.xpack.sql.util.StringUtils;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.stream.Collectors;
 
@@ -21,7 +21,7 @@ public interface DateTimeField {
         Map<String, D> nameToPart = new HashMap<>();
 
         for (D datePart : values) {
-            String lowerCaseName = datePart.name().toLowerCase(IsoLocale.ROOT);
+            String lowerCaseName = datePart.name().toLowerCase(Locale.ROOT);
 
             nameToPart.put(lowerCaseName, datePart);
             for (String alias : datePart.aliases()) {
@@ -36,7 +36,7 @@ public interface DateTimeField {
     }
 
     static <D extends DateTimeField> D resolveMatch(Map<String, D> resolutionMap, String possibleMatch) {
-        return resolutionMap.get(possibleMatch.toLowerCase(IsoLocale.ROOT));
+        return resolutionMap.get(possibleMatch.toLowerCase(Locale.ROOT));
     }
 
     static List<String> findSimilar(Iterable<String> similars, String match) {

+ 3 - 13
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/NonIsoDateTimeProcessor.java

@@ -10,14 +10,12 @@ import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 
 import java.io.IOException;
-import java.time.LocalDateTime;
+import java.time.DayOfWeek;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoField;
-import java.util.Calendar;
-import java.util.Locale;
+import java.time.temporal.WeekFields;
 import java.util.Objects;
-import java.util.TimeZone;
 import java.util.function.Function;
 
 public class NonIsoDateTimeProcessor extends BaseDateTimeProcessor {
@@ -30,15 +28,7 @@ public class NonIsoDateTimeProcessor extends BaseDateTimeProcessor {
             return dayOfWeek == 8 ? 1 : dayOfWeek;
         }),
         WEEK_OF_YEAR(zdt -> {
-            // by ISO 8601 standard, the first week of a year is the first week with a majority (4 or more) of its days in January.
-            // Other Locales may have their own standards (see Arabic or Japanese calendars).
-            LocalDateTime ld = zdt.toLocalDateTime();
-            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(zdt.getZone()), Locale.ROOT);
-            cal.clear();
-            cal.set(ld.get(ChronoField.YEAR), ld.get(ChronoField.MONTH_OF_YEAR) - 1, ld.get(ChronoField.DAY_OF_MONTH),
-                    ld.get(ChronoField.HOUR_OF_DAY), ld.get(ChronoField.MINUTE_OF_HOUR), ld.get(ChronoField.SECOND_OF_MINUTE));
-
-            return cal.get(Calendar.WEEK_OF_YEAR);
+            return zdt.get(WeekFields.of(DayOfWeek.SUNDAY, 1).weekOfWeekBasedYear());
         });
 
         private final Function<ZonedDateTime, Integer> apply;

+ 3 - 3
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java

@@ -5,7 +5,6 @@
  */
 package org.elasticsearch.xpack.sql.analysis.analyzer;
 
-import org.elasticsearch.common.time.IsoLocale;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.xpack.sql.TestUtils;
 import org.elasticsearch.xpack.sql.analysis.index.EsIndex;
@@ -30,6 +29,7 @@ import org.elasticsearch.xpack.sql.type.TypesTests;
 
 import java.util.Arrays;
 import java.util.LinkedHashMap;
+import java.util.Locale;
 import java.util.Map;
 
 import static java.util.Collections.emptyMap;
@@ -587,7 +587,7 @@ public class VerifierErrorMessagesTests extends ESTestCase {
     }
 
     public void testInvalidTypeForStringFunction_WithOneArgNumeric() {
-        String functionName = randomFrom(Arrays.asList(Char.class, Space.class)).getSimpleName().toUpperCase(IsoLocale.ROOT);
+        String functionName = randomFrom(Arrays.asList(Char.class, Space.class)).getSimpleName().toUpperCase(Locale.ROOT);
         assertEquals("1:8: argument of [" + functionName + "('foo')] must be [integer], found value ['foo'] type [keyword]",
             error("SELECT " + functionName + "('foo')"));
         assertEquals("1:8: argument of [" + functionName + "(1.2)] must be [integer], found value [1.2] type [double]",
@@ -619,7 +619,7 @@ public class VerifierErrorMessagesTests extends ESTestCase {
     }
 
     public void testInvalidTypeForNumericFunction_WithTwoArgs() {
-        String functionName = randomFrom(Arrays.asList(Round.class, Truncate.class)).getSimpleName().toUpperCase(IsoLocale.ROOT);
+        String functionName = randomFrom(Arrays.asList(Round.class, Truncate.class)).getSimpleName().toUpperCase(Locale.ROOT);
         assertEquals("1:8: first argument of [" + functionName + "('foo', 2)] must be [numeric], found value ['foo'] type [keyword]",
             error("SELECT " + functionName + "('foo', 2)"));
         assertEquals("1:8: second argument of [" + functionName + "(1.2, 'bar')] must be [integer], found value ['bar'] type [keyword]",

+ 3 - 2
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/NonIsoDateTimeProcessorTests.java

@@ -16,7 +16,6 @@ import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.Da
 import static org.elasticsearch.xpack.sql.util.DateUtils.UTC;
 
 public class NonIsoDateTimeProcessorTests extends AbstractSqlWireSerializingTestCase<NonIsoDateTimeProcessor> {
-    
 
     public static NonIsoDateTimeProcessor randomNonISODateTimeProcessor() {
         return new NonIsoDateTimeProcessor(randomFrom(NonIsoDateTimeExtractor.values()), UTC);
@@ -45,6 +44,8 @@ public class NonIsoDateTimeProcessorTests extends AbstractSqlWireSerializingTest
 
     public void testNonISOWeekOfYearInUTC() {
         NonIsoDateTimeProcessor proc = new NonIsoDateTimeProcessor(NonIsoDateTimeExtractor.WEEK_OF_YEAR, UTC);
+        // 1 Jan 1988 is Friday - under Sunday,1 rule it is the first week of the year (under ISO rule it would be 53 of the previous year
+        // hence the 5th Jan 1988 Tuesday is the second week of a year
         assertEquals(2, proc.process(dateTime(568372930000L)));  //1988-01-05T09:22:10Z[UTC]
         assertEquals(6, proc.process(dateTime(981278530000L)));  //2001-02-04T09:22:10Z[UTC]
         assertEquals(7, proc.process(dateTime(224241730000L)));  //1977-02-08T09:22:10Z[UTC]
@@ -94,4 +95,4 @@ public class NonIsoDateTimeProcessorTests extends AbstractSqlWireSerializingTest
         assertEquals(6, proc.process(dateTime(333451330000L)));
         assertEquals(5, proc.process(dateTime(874660930000L)));
     }
-}
+}

Some files were not shown because too many files changed in this diff