Skip to content

Commit d253fb3

Browse files
authored
SQL: Replace joda with java time (#38437)
Replace remaining usages of joda classes with java time. Fixes: #37703
1 parent 479c0c7 commit d253fb3

File tree

8 files changed

+56
-96
lines changed

8 files changed

+56
-96
lines changed

x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/TypeConverterTests.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,18 @@
1010
import org.elasticsearch.common.xcontent.XContentHelper;
1111
import org.elasticsearch.common.xcontent.json.JsonXContent;
1212
import org.elasticsearch.test.ESTestCase;
13-
import org.joda.time.DateTime;
14-
import org.joda.time.ReadableDateTime;
1513

1614
import java.sql.Timestamp;
15+
import java.time.Clock;
16+
import java.time.Duration;
17+
import java.time.ZoneId;
18+
import java.time.ZonedDateTime;
1719

1820
import static org.hamcrest.Matchers.instanceOf;
1921

2022

2123
public class TypeConverterTests extends ESTestCase {
2224

23-
2425
public void testFloatAsNative() throws Exception {
2526
assertThat(convertAsNative(42.0f, EsType.FLOAT), instanceOf(Float.class));
2627
assertThat(convertAsNative(42.0, EsType.FLOAT), instanceOf(Float.class));
@@ -40,21 +41,17 @@ public void testDoubleAsNative() throws Exception {
4041
}
4142

4243
public void testTimestampAsNative() throws Exception {
43-
DateTime now = DateTime.now();
44+
ZonedDateTime now = ZonedDateTime.now(Clock.tick(Clock.system(ZoneId.of("Z")), Duration.ofMillis(1)));
4445
assertThat(convertAsNative(now, EsType.DATETIME), instanceOf(Timestamp.class));
45-
assertEquals(now.getMillis(), ((Timestamp) convertAsNative(now, EsType.DATETIME)).getTime());
46+
assertEquals(now.toInstant().toEpochMilli(), ((Timestamp) convertAsNative(now, EsType.DATETIME)).getTime());
4647
}
4748

4849
private Object convertAsNative(Object value, EsType type) throws Exception {
4950
// Simulate sending over XContent
5051
XContentBuilder builder = JsonXContent.contentBuilder();
5152
builder.startObject();
5253
builder.field("value");
53-
if (value instanceof ReadableDateTime) {
54-
builder.value(((ReadableDateTime) value).getMillis());
55-
} else {
56-
builder.value(value);
57-
}
54+
builder.value(value);
5855
builder.endObject();
5956
builder.close();
6057
Object copy = XContentHelper.convertToMap(BytesReference.bytes(builder), false, builder.contentType()).v2().get("value");

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/execution/search/extractor/FieldHitExtractor.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
1515
import org.elasticsearch.xpack.sql.type.DataType;
1616
import org.elasticsearch.xpack.sql.util.DateUtils;
17-
import org.joda.time.DateTime;
1817

1918
import java.io.IOException;
2019
import java.util.ArrayDeque;
@@ -132,10 +131,6 @@ private Object unwrapMultiValue(Object values) {
132131
if (values instanceof String) {
133132
return DateUtils.asDateTime(Long.parseLong(values.toString()));
134133
}
135-
// returned by nested types...
136-
if (values instanceof DateTime) {
137-
return DateUtils.asDateTime((DateTime) values);
138-
}
139134
}
140135
if (values instanceof Long || values instanceof Double || values instanceof String || values instanceof Boolean) {
141136
return values;

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/parser/ExpressionBuilder.java

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -111,25 +111,25 @@
111111
import org.elasticsearch.xpack.sql.type.DataType;
112112
import org.elasticsearch.xpack.sql.type.DataTypeConversion;
113113
import org.elasticsearch.xpack.sql.type.DataTypes;
114-
import org.elasticsearch.xpack.sql.util.DateUtils;
115114
import org.elasticsearch.xpack.sql.util.StringUtils;
116-
import org.joda.time.DateTime;
117-
import org.joda.time.format.DateTimeFormatter;
118-
import org.joda.time.format.DateTimeFormatterBuilder;
119-
import org.joda.time.format.ISODateTimeFormat;
120115

121116
import java.time.Duration;
117+
import java.time.LocalTime;
122118
import java.time.Period;
119+
import java.time.format.DateTimeParseException;
123120
import java.time.temporal.TemporalAmount;
124121
import java.util.EnumSet;
125122
import java.util.List;
126123
import java.util.Locale;
127124
import java.util.Map;
128125
import java.util.StringJoiner;
129126

127+
import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
130128
import static java.util.Collections.emptyList;
131129
import static java.util.Collections.singletonList;
132130
import static org.elasticsearch.xpack.sql.type.DataTypeConversion.conversionFor;
131+
import static org.elasticsearch.xpack.sql.util.DateUtils.asDateOnly;
132+
import static org.elasticsearch.xpack.sql.util.DateUtils.ofEscapedLiteral;
133133

134134
abstract class ExpressionBuilder extends IdentifierBuilder {
135135

@@ -791,13 +791,11 @@ public Literal visitDateEscapedLiteral(DateEscapedLiteralContext ctx) {
791791
String string = string(ctx.string());
792792
Source source = source(ctx);
793793
// parse yyyy-MM-dd
794-
DateTime dt = null;
795794
try {
796-
dt = ISODateTimeFormat.date().parseDateTime(string);
797-
} catch(IllegalArgumentException ex) {
795+
return new Literal(source, asDateOnly(string), DataType.DATE);
796+
} catch(DateTimeParseException ex) {
798797
throw new ParsingException(source, "Invalid date received; {}", ex.getMessage());
799798
}
800-
return new Literal(source, DateUtils.asDateOnly(dt), DataType.DATE);
801799
}
802800

803801
@Override
@@ -806,10 +804,10 @@ public Literal visitTimeEscapedLiteral(TimeEscapedLiteralContext ctx) {
806804
Source source = source(ctx);
807805

808806
// parse HH:mm:ss
809-
DateTime dt = null;
807+
LocalTime lt = null;
810808
try {
811-
dt = ISODateTimeFormat.hourMinuteSecond().parseDateTime(string);
812-
} catch (IllegalArgumentException ex) {
809+
lt = LocalTime.parse(string, ISO_LOCAL_TIME);
810+
} catch (DateTimeParseException ex) {
813811
throw new ParsingException(source, "Invalid time received; {}", ex.getMessage());
814812
}
815813

@@ -822,18 +820,11 @@ public Literal visitTimestampEscapedLiteral(TimestampEscapedLiteralContext ctx)
822820

823821
Source source = source(ctx);
824822
// parse yyyy-mm-dd hh:mm:ss(.f...)
825-
DateTime dt = null;
826823
try {
827-
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
828-
.append(ISODateTimeFormat.date())
829-
.appendLiteral(" ")
830-
.append(ISODateTimeFormat.hourMinuteSecondFraction())
831-
.toFormatter();
832-
dt = formatter.parseDateTime(string);
833-
} catch (IllegalArgumentException ex) {
824+
return new Literal(source, ofEscapedLiteral(string), DataType.DATETIME);
825+
} catch (DateTimeParseException ex) {
834826
throw new ParsingException(source, "Invalid timestamp received; {}", ex.getMessage());
835827
}
836-
return new Literal(source, DateUtils.asDateTime(dt), DataType.DATETIME);
837828
}
838829

839830
@Override

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypeConversion.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.xpack.sql.util.DateUtils;
1212

1313
import java.time.ZonedDateTime;
14+
import java.time.format.DateTimeParseException;
1415
import java.util.Locale;
1516
import java.util.function.DoubleFunction;
1617
import java.util.function.Function;
@@ -546,8 +547,8 @@ private static Function<Object, Object> fromString(Function<String, Object> conv
546547
return converter.apply(value.toString());
547548
} catch (NumberFormatException e) {
548549
throw new SqlIllegalArgumentException(e, "cannot cast [{}] to [{}]", value, to);
549-
} catch (IllegalArgumentException e) {
550-
throw new SqlIllegalArgumentException(e, "cannot cast [{}] to [{}]:{}", value, to, e.getMessage());
550+
} catch (DateTimeParseException | IllegalArgumentException e) {
551+
throw new SqlIllegalArgumentException(e, "cannot cast [{}] to [{}]: {}", value, to, e.getMessage());
551552
}
552553
};
553554
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/util/DateUtils.java

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,35 @@
66

77
package org.elasticsearch.xpack.sql.util;
88

9+
import org.elasticsearch.common.time.DateFormatter;
10+
import org.elasticsearch.common.time.DateFormatters;
911
import org.elasticsearch.xpack.sql.proto.StringUtils;
10-
import org.joda.time.DateTime;
11-
import org.joda.time.format.DateTimeFormatter;
12-
import org.joda.time.format.ISODateTimeFormat;
1312

1413
import java.time.Instant;
15-
import java.time.LocalDateTime;
14+
import java.time.LocalDate;
1615
import java.time.ZoneId;
17-
import java.time.ZoneOffset;
1816
import java.time.ZonedDateTime;
17+
import java.time.format.DateTimeFormatter;
18+
import java.time.format.DateTimeFormatterBuilder;
1919

2020
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
21+
import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
2122

2223
public final class DateUtils {
2324

24-
private static final long DAY_IN_MILLIS = 60 * 60 * 24 * 1000;
25-
26-
// TODO: do we have a java.time based parser we can use instead?
27-
private static final DateTimeFormatter UTC_DATE_FORMATTER = ISODateTimeFormat.dateOptionalTimeParser().withZoneUTC();
28-
2925
public static final ZoneId UTC = ZoneId.of("Z");
3026
public static final String DATE_PARSE_FORMAT = "epoch_millis";
3127

28+
private static final DateTimeFormatter DATE_TIME_ESCAPED_LITERAL_FORMATTER = new DateTimeFormatterBuilder()
29+
.append(ISO_LOCAL_DATE)
30+
.appendLiteral(" ")
31+
.append(ISO_LOCAL_TIME)
32+
.toFormatter().withZone(UTC);
33+
34+
private static final DateFormatter UTC_DATE_TIME_FORMATTER = DateFormatter.forPattern("date_optional_time").withZone(UTC);
35+
36+
private static final long DAY_IN_MILLIS = 60 * 60 * 24 * 1000L;
37+
3238
private DateUtils() {}
3339

3440
/**
@@ -56,22 +62,7 @@ public static ZonedDateTime asDateTime(long millis, ZoneId id) {
5662
* Parses the given string into a Date (SQL DATE type) using UTC as a default timezone.
5763
*/
5864
public static ZonedDateTime asDateOnly(String dateFormat) {
59-
return asDateOnly(UTC_DATE_FORMATTER.parseDateTime(dateFormat));
60-
}
61-
62-
public static ZonedDateTime asDateOnly(DateTime dateTime) {
63-
LocalDateTime ldt = LocalDateTime.of(
64-
dateTime.getYear(),
65-
dateTime.getMonthOfYear(),
66-
dateTime.getDayOfMonth(),
67-
0,
68-
0,
69-
0,
70-
0);
71-
72-
return ZonedDateTime.ofStrict(ldt,
73-
ZoneOffset.ofTotalSeconds(dateTime.getZone().getOffset(dateTime) / 1000),
74-
org.elasticsearch.common.time.DateUtils.dateTimeZoneToZoneId(dateTime.getZone()));
65+
return LocalDate.parse(dateFormat, ISO_LOCAL_DATE).atStartOfDay(UTC);
7566
}
7667

7768
public static ZonedDateTime asDateOnly(ZonedDateTime zdt) {
@@ -82,25 +73,13 @@ public static ZonedDateTime asDateOnly(ZonedDateTime zdt) {
8273
* Parses the given string into a DateTime using UTC as a default timezone.
8374
*/
8475
public static ZonedDateTime asDateTime(String dateFormat) {
85-
return asDateTime(UTC_DATE_FORMATTER.parseDateTime(dateFormat));
76+
return DateFormatters.from(UTC_DATE_TIME_FORMATTER.parse(dateFormat)).withZoneSameInstant(UTC);
8677
}
8778

88-
public static ZonedDateTime asDateTime(DateTime dateTime) {
89-
LocalDateTime ldt = LocalDateTime.of(
90-
dateTime.getYear(),
91-
dateTime.getMonthOfYear(),
92-
dateTime.getDayOfMonth(),
93-
dateTime.getHourOfDay(),
94-
dateTime.getMinuteOfHour(),
95-
dateTime.getSecondOfMinute(),
96-
dateTime.getMillisOfSecond() * 1_000_000);
97-
98-
return ZonedDateTime.ofStrict(ldt,
99-
ZoneOffset.ofTotalSeconds(dateTime.getZone().getOffset(dateTime) / 1000),
100-
org.elasticsearch.common.time.DateUtils.dateTimeZoneToZoneId(dateTime.getZone()));
79+
public static ZonedDateTime ofEscapedLiteral(String dateFormat) {
80+
return ZonedDateTime.parse(dateFormat, DATE_TIME_ESCAPED_LITERAL_FORMATTER.withZone(UTC));
10181
}
10282

103-
10483
public static String toString(ZonedDateTime dateTime) {
10584
return StringUtils.toString(dateTime);
10685
}

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTimeTestUtils.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,15 @@
77
package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
88

99
import org.elasticsearch.xpack.sql.util.DateUtils;
10-
import org.joda.time.DateTime;
11-
import org.joda.time.DateTimeZone;
1210

1311
import java.time.ZonedDateTime;
1412

15-
import static org.junit.Assert.assertEquals;
16-
1713
public class DateTimeTestUtils {
1814

1915
private DateTimeTestUtils() {}
2016

2117
public static ZonedDateTime dateTime(int year, int month, int day, int hour, int minute) {
22-
DateTime dateTime = new DateTime(year, month, day, hour, minute, DateTimeZone.UTC);
23-
ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, 0, 0, DateUtils.UTC);
24-
assertEquals(dateTime.getMillis() / 1000, zdt.toEpochSecond());
25-
return zdt;
18+
return ZonedDateTime.of(year, month, day, hour, minute, 0, 0, DateUtils.UTC);
2619
}
2720

2821
public static ZonedDateTime dateTime(long millisSinceEpoch) {

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/EscapedFunctionsTests.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ public void testDateLiteral() {
175175

176176
public void testDateLiteralValidation() {
177177
ParsingException ex = expectThrows(ParsingException.class, () -> dateLiteral("2012-13-01"));
178-
assertEquals("line 1:2: Invalid date received; Cannot parse \"2012-13-01\": Value 13 for monthOfYear must be in the range [1,12]",
178+
assertEquals("line 1:2: Invalid date received; Text '2012-13-01' could not be parsed: " +
179+
"Invalid value for MonthOfYear (valid values 1 - 12): 13",
179180
ex.getMessage());
180181
}
181182

@@ -186,7 +187,8 @@ public void testTimeLiteralUnsupported() {
186187

187188
public void testTimeLiteralValidation() {
188189
ParsingException ex = expectThrows(ParsingException.class, () -> timeLiteral("10:10:65"));
189-
assertEquals("line 1:2: Invalid time received; Cannot parse \"10:10:65\": Value 65 for secondOfMinute must be in the range [0,59]",
190+
assertEquals("line 1:2: Invalid time received; Text '10:10:65' could not be parsed: " +
191+
"Invalid value for SecondOfMinute (valid values 0 - 59): 65",
190192
ex.getMessage());
191193
}
192194

@@ -198,7 +200,7 @@ public void testTimestampLiteral() {
198200
public void testTimestampLiteralValidation() {
199201
ParsingException ex = expectThrows(ParsingException.class, () -> timestampLiteral("2012-01-01T10:01:02.3456"));
200202
assertEquals(
201-
"line 1:2: Invalid timestamp received; Invalid format: \"2012-01-01T10:01:02.3456\" is malformed at \"T10:01:02.3456\"",
203+
"line 1:2: Invalid timestamp received; Text '2012-01-01T10:01:02.3456' could not be parsed at index 10",
202204
ex.getMessage());
203205
}
204206

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/type/DataTypeConversionTests.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,10 @@ public void testConversionToDate() {
150150
Conversion conversion = conversionFor(KEYWORD, to);
151151
assertNull(conversion.convert(null));
152152

153-
assertEquals(date(0L), conversion.convert("1970-01-01T00:10:01Z"));
154-
assertEquals(date(1483228800000L), conversion.convert("2017-01-01T00:11:00Z"));
155-
assertEquals(date(-1672531200000L), conversion.convert("1917-01-01T00:11:00Z"));
156-
assertEquals(date(18000000L), conversion.convert("1970-01-01T03:10:20-05:00"));
153+
assertEquals(date(0L), conversion.convert("1970-01-01"));
154+
assertEquals(date(1483228800000L), conversion.convert("2017-01-01"));
155+
assertEquals(date(-1672531200000L), conversion.convert("1917-01-01"));
156+
assertEquals(date(18000000L), conversion.convert("1970-01-01"));
157157

158158
// double check back and forth conversion
159159

@@ -162,7 +162,7 @@ public void testConversionToDate() {
162162
Conversion back = conversionFor(KEYWORD, DATE);
163163
assertEquals(DateUtils.asDateOnly(zdt), back.convert(forward.convert(zdt)));
164164
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert("0xff"));
165-
assertEquals("cannot cast [0xff] to [date]:Invalid format: \"0xff\" is malformed at \"xff\"", e.getMessage());
165+
assertEquals("cannot cast [0xff] to [date]: Text '0xff' could not be parsed at index 0", e.getMessage());
166166
}
167167
}
168168

@@ -199,6 +199,7 @@ public void testConversionToDateTime() {
199199
Conversion conversion = conversionFor(KEYWORD, to);
200200
assertNull(conversion.convert(null));
201201

202+
assertEquals(dateTime(0L), conversion.convert("1970-01-01"));
202203
assertEquals(dateTime(1000L), conversion.convert("1970-01-01T00:00:01Z"));
203204
assertEquals(dateTime(1483228800000L), conversion.convert("2017-01-01T00:00:00Z"));
204205
assertEquals(dateTime(1483228800000L), conversion.convert("2017-01-01T00:00:00Z"));
@@ -211,7 +212,8 @@ public void testConversionToDateTime() {
211212
Conversion back = conversionFor(KEYWORD, DATETIME);
212213
assertEquals(dt, back.convert(forward.convert(dt)));
213214
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert("0xff"));
214-
assertEquals("cannot cast [0xff] to [datetime]:Invalid format: \"0xff\" is malformed at \"xff\"", e.getMessage());
215+
assertEquals("cannot cast [0xff] to [datetime]: failed to parse date field [0xff] with format [date_optional_time]",
216+
e.getMessage());
215217
}
216218
}
217219

0 commit comments

Comments
 (0)