Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,12 @@ class BuildPlugin implements Plugin<Project> {
if ((ext.get('runtimeJavaVersion') as JavaVersion) >= JavaVersion.VERSION_1_9) {
test.jvmArgs '--illegal-access=warn'
}
//TODO remove once jvm.options are added to test system properties
if ((ext.get('runtimeJavaVersion') as JavaVersion) == JavaVersion.VERSION_1_8) {
test.systemProperty ('java.locale.providers','SPI,JRE')
} else if ((ext.get('runtimeJavaVersion') as JavaVersion) >= JavaVersion.VERSION_1_9) {
test.systemProperty ('java.locale.providers','SPI,COMPAT')
}
}

test.jvmArgumentProviders.add(nonInputProperties)
Expand Down Expand Up @@ -876,6 +882,7 @@ class BuildPlugin implements Plugin<Project> {
'tests.security.manager': 'true',
'jna.nosys': 'true'


// 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'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import java.time.temporal.ChronoField;
import java.time.temporal.IsoFields;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalQueries;
import java.time.temporal.TemporalQuery;
import java.time.temporal.WeekFields;
Expand All @@ -53,6 +52,7 @@
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;

public class DateFormatters {
public static final WeekFields WEEK_FIELDS = WeekFields.of(DayOfWeek.MONDAY,4);

private static final DateTimeFormatter TIME_ZONE_FORMATTER_NO_COLON = new DateTimeFormatterBuilder()
.appendOffset("+HHmm", "Z")
Expand Down Expand Up @@ -946,14 +946,14 @@ public class DateFormatters {
* Returns a formatter for a four digit weekyear
*/
private static final DateFormatter STRICT_WEEKYEAR = new JavaDateFormatter("strict_weekyear", new DateTimeFormatterBuilder()
.appendValue(WeekFields.ISO.weekBasedYear(), 4, 10, SignStyle.EXCEEDS_PAD)
.appendValue(WEEK_FIELDS.weekBasedYear(), 4, 10, SignStyle.EXCEEDS_PAD)
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT));

private static final DateTimeFormatter STRICT_WEEKYEAR_WEEK_FORMATTER = new DateTimeFormatterBuilder()
.appendValue(WeekFields.ISO.weekBasedYear(), 4, 10, SignStyle.EXCEEDS_PAD)
.appendValue(WEEK_FIELDS.weekBasedYear(), 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral("-W")
.appendValue(WeekFields.ISO.weekOfWeekBasedYear(), 2, 2, SignStyle.NOT_NEGATIVE)
.appendValue(WEEK_FIELDS.weekOfWeekBasedYear(), 2, 2, SignStyle.NOT_NEGATIVE)
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT);

Expand All @@ -972,7 +972,7 @@ public class DateFormatters {
new DateTimeFormatterBuilder()
.append(STRICT_WEEKYEAR_WEEK_FORMATTER)
.appendLiteral("-")
.appendValue(WeekFields.ISO.dayOfWeek())
.appendValue(WEEK_FIELDS.dayOfWeek())
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT));

Expand Down Expand Up @@ -1162,7 +1162,7 @@ public class DateFormatters {
* Returns a formatter for a four digit weekyear. (YYYY)
*/
private static final DateFormatter WEEK_YEAR = new JavaDateFormatter("week_year",
new DateTimeFormatterBuilder().appendValue(WeekFields.ISO.weekBasedYear()).toFormatter(Locale.ROOT)
new DateTimeFormatterBuilder().appendValue(WEEK_FIELDS.weekBasedYear()).toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT));

/*
Expand Down Expand Up @@ -1591,9 +1591,9 @@ public class DateFormatters {
*/
private static final DateFormatter WEEKYEAR_WEEK = new JavaDateFormatter("weekyear_week", STRICT_WEEKYEAR_WEEK_FORMATTER,
new DateTimeFormatterBuilder()
.appendValue(WeekFields.ISO.weekBasedYear())
.appendValue(WEEK_FIELDS.weekBasedYear())
.appendLiteral("-W")
.appendValue(WeekFields.ISO.weekOfWeekBasedYear())
.appendValue(WEEK_FIELDS.weekOfWeekBasedYear())
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT)
);
Expand All @@ -1606,15 +1606,15 @@ public class DateFormatters {
new DateTimeFormatterBuilder()
.append(STRICT_WEEKYEAR_WEEK_FORMATTER)
.appendLiteral("-")
.appendValue(WeekFields.ISO.dayOfWeek())
.appendValue(WEEK_FIELDS.dayOfWeek())
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT),
new DateTimeFormatterBuilder()
.appendValue(WeekFields.ISO.weekBasedYear())
.appendValue(WEEK_FIELDS.weekBasedYear())
.appendLiteral("-W")
.appendValue(WeekFields.ISO.weekOfWeekBasedYear())
.appendValue(WEEK_FIELDS.weekOfWeekBasedYear())
.appendLiteral("-")
.appendValue(WeekFields.ISO.dayOfWeek())
.appendValue(WEEK_FIELDS.dayOfWeek())
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT)
);
Expand Down Expand Up @@ -1858,7 +1858,7 @@ public static ZonedDateTime from(TemporalAccessor accessor) {
} else if (isLocalDateSet) {
return localDate.atStartOfDay(zoneId);
} else if (isLocalTimeSet) {
return of(getLocaldate(accessor), localTime, zoneId);
return of(getLocalDate(accessor), localTime, zoneId);
} else if (accessor.isSupported(ChronoField.YEAR) || accessor.isSupported(ChronoField.YEAR_OF_ERA) ) {
if (accessor.isSupported(MONTH_OF_YEAR)) {
return getFirstOfMonth(accessor).atStartOfDay(zoneId);
Expand All @@ -1868,26 +1868,29 @@ public static ZonedDateTime from(TemporalAccessor accessor) {
}
} else if (accessor.isSupported(MONTH_OF_YEAR)) {
// missing year, falling back to the epoch and then filling
return getLocaldate(accessor).atStartOfDay(zoneId);
} else if (accessor.isSupported(WeekFields.ISO.weekBasedYear())) {
if (accessor.isSupported(WeekFields.ISO.weekOfWeekBasedYear())) {
return Year.of(accessor.get(WeekFields.ISO.weekBasedYear()))
.atDay(1)
.with(WeekFields.ISO.weekOfWeekBasedYear(), accessor.getLong(WeekFields.ISO.weekOfWeekBasedYear()))
.atStartOfDay(zoneId);
} else {
return Year.of(accessor.get(WeekFields.ISO.weekBasedYear()))
.atDay(1)
.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY))
.atStartOfDay(zoneId);
}
return getLocalDate(accessor).atStartOfDay(zoneId);
} else if (accessor.isSupported(WEEK_FIELDS.weekBasedYear())) {
return localDateFromWeekBasedDate(accessor).atStartOfDay(zoneId);
}

// we should not reach this piece of code, everything being parsed we should be able to
// convert to a zoned date time! If not, we have to extend the above methods
throw new IllegalArgumentException("temporal accessor [" + accessor + "] cannot be converted to zoned date time");
}

private static LocalDate localDateFromWeekBasedDate(TemporalAccessor accessor) {
if (accessor.isSupported(WEEK_FIELDS.weekOfWeekBasedYear())) {
return LocalDate.ofEpochDay(0)
.with(WEEK_FIELDS.weekBasedYear(), accessor.get(WEEK_FIELDS.weekBasedYear()))
.with(WEEK_FIELDS.weekOfWeekBasedYear(), accessor.get(WEEK_FIELDS.weekOfWeekBasedYear()))
.with(ChronoField.DAY_OF_WEEK, WEEK_FIELDS.getFirstDayOfWeek().getValue());
} else {
return LocalDate.ofEpochDay(0)
.with(WEEK_FIELDS.weekBasedYear(), accessor.get(WEEK_FIELDS.weekBasedYear()))
.with(ChronoField.DAY_OF_WEEK, WEEK_FIELDS.getFirstDayOfWeek().getValue());
}
}

/**
* extending the java.time.temporal.TemporalQueries.LOCAL_DATE implementation to also create local dates
* when YearOfEra was used instead of Year.
Expand Down Expand Up @@ -1915,9 +1918,11 @@ public String toString() {
}
};

private static LocalDate getLocaldate(TemporalAccessor accessor) {
int year = getYear(accessor);
if (accessor.isSupported(MONTH_OF_YEAR)) {
private static LocalDate getLocalDate(TemporalAccessor accessor) {
if (accessor.isSupported(WEEK_FIELDS.weekBasedYear())) {
return localDateFromWeekBasedDate(accessor);
} else if (accessor.isSupported(MONTH_OF_YEAR)) {
int year = getYear(accessor);
if (accessor.isSupported(DAY_OF_MONTH)) {
return LocalDate.of(year, accessor.get(MONTH_OF_YEAR), accessor.get(DAY_OF_MONTH));
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.common.time;

import java.util.Calendar;
import java.util.Locale;
import java.util.spi.CalendarDataProvider;

public class IsoCalendarDataProvider extends CalendarDataProvider {

@Override
public int getFirstDayOfWeek(Locale locale) {
return Calendar.MONDAY;
}

@Override
public int getMinimalDaysInFirstWeek(Locale locale) {
return 4;
}

@Override
public Locale[] getAvailableLocales() {
return new Locale[]{Locale.ROOT};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;

Expand Down Expand Up @@ -50,7 +51,6 @@
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;

Expand Down Expand Up @@ -474,14 +474,14 @@ public int getSecondOfMinute() {

@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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.elasticsearch.common.time.IsoCalendarDataProvider
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.elasticsearch.common.time;

import org.elasticsearch.bootstrap.JavaVersion;
import org.elasticsearch.test.ESTestCase;

import java.time.Clock;
Expand All @@ -40,6 +41,35 @@

public class DateFormattersTests extends ESTestCase {

public void testWeekBasedDates() {
assumeFalse("won't work in jdk8 " +
"because SPI mechanism is not looking at classpath - needs ISOCalendarDataProvider in jre's ext/libs",
JavaVersion.current().equals(JavaVersion.parse("8")));

// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.elasticsearch.common.time;

import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.bootstrap.JavaVersion;
import org.elasticsearch.test.ESTestCase;

import java.time.Instant;
Expand Down Expand Up @@ -50,6 +51,24 @@ public void testOverridingLocaleOrZoneAndCompositeRoundUpParser() {
assertDateEquals(gotMillis, "297276785531", "297276785531");
}

public void testWeekDates() {
assumeFalse("won't work in jdk8 " +
"because SPI mechanism is not looking at classpath - needs ISOCalendarDataProvider in jre's ext/libs",
JavaVersion.current().equals(JavaVersion.parse("8")));

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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ public void testChangeLocale() throws IOException {
mapper.parse(new SourceToParse("test", "type", "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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,12 @@ public void testSecondOfMinute() {

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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1678,21 +1678,21 @@ public void testRangeQueryWithLocaleMapping() throws Exception {
.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);
}
Expand Down
Loading