Skip to content

Commit 63098be

Browse files
[7.17] SQL: make date format functions more strict (elastic#112140) (elastic#112546)
1 parent fcf25ff commit 63098be

File tree

4 files changed

+57
-41
lines changed

4 files changed

+57
-41
lines changed

x-pack/plugin/sql/qa/server/src/main/resources/datetime.csv-spec

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,3 +1570,17 @@ SELECT CAST (CAST (birth_date AS VARCHAR) AS TIMESTAMP) a FROM test_emp WHERE YE
15701570
---------------
15711571
1965-01-03T00:00:00Z
15721572
;
1573+
1574+
1575+
// checking regressions after https://github.com/elastic/elasticsearch/pull/110222
1576+
selectDateFunctionsCldr
1577+
schema::ad:s|day_of_week2:s|month2:s|ad2:s
1578+
SELECT DATETIME_FORMAT('2020-04-05T11:22:33.123Z'::date, 'G') AS ad,
1579+
TO_CHAR('2020-04-05T11:22:33.123Z'::date, 'Day') AS day_of_week2,
1580+
TO_CHAR('2020-04-05T11:22:33.123Z'::date, 'Month') AS month2,
1581+
TO_CHAR('2020-04-05T11:22:33.123Z'::date, 'BC') AS ad2;
1582+
1583+
ad | day_of_week2 | month2 | ad2
1584+
---------+----------------+--------------+------------
1585+
AD | "Sunday " | "April " | AD
1586+
;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ protected Function<TemporalAccessor, String> formatterFor(String pattern) {
5151
pattern = pattern.replace(replacement[0], replacement[1]);
5252
}
5353
final String javaPattern = pattern;
54-
return DateTimeFormatter.ofPattern(javaPattern, Locale.ROOT)::format;
54+
return DateTimeFormatter.ofPattern(javaPattern, Locale.ENGLISH)::format;
5555
}
5656
},
5757
DATE_TIME_FORMAT {
5858
@Override
5959
protected Function<TemporalAccessor, String> formatterFor(String pattern) {
60-
return DateTimeFormatter.ofPattern(pattern, Locale.ROOT)::format;
60+
return DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH)::format;
6161
}
6262
},
6363
TO_CHAR {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public final String extract(ZonedDateTime millis, String tzId) {
4040
}
4141

4242
public static final String NAME = "ndt";
43-
private static final DateTimeFormatter DAY_NAME_FORMATTER = DateTimeFormatter.ofPattern("EEEE", Locale.ROOT);
44-
private static final DateTimeFormatter MONTH_NAME_FORMATTER = DateTimeFormatter.ofPattern("MMMM", Locale.ROOT);
43+
private static final DateTimeFormatter DAY_NAME_FORMATTER = DateTimeFormatter.ofPattern("EEEE", Locale.ENGLISH);
44+
private static final DateTimeFormatter MONTH_NAME_FORMATTER = DateTimeFormatter.ofPattern("MMMM", Locale.ENGLISH);
4545

4646
private final NameExtractor extractor;
4747

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

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class ToCharFormatter {
4242
of("HH12").formatFn("hh").numeric(),
4343
of("HH24").formatFn("HH").numeric(),
4444
of("MI").formatFn("mm").numeric(),
45-
of("SS").formatFn("s", x -> String.format(Locale.ROOT, "%02d", parseInt(x))).numeric(),
45+
of("SS").formatFn("s", x -> String.format(Locale.ENGLISH, "%02d", parseInt(x))).numeric(),
4646
of("MS").formatFn("n", nano -> firstDigitsOfNanos(nano, 3)).numericWithLeadingZeros(),
4747
of("US").formatFn("n", nano -> firstDigitsOfNanos(nano, 6)).numericWithLeadingZeros(),
4848
of("FF1").formatFn("n", nano -> firstDigitsOfNanos(nano, 1)).numericWithLeadingZeros(),
@@ -53,14 +53,14 @@ class ToCharFormatter {
5353
of("FF6").formatFn("n", nano -> firstDigitsOfNanos(nano, 6)).numericWithLeadingZeros(),
5454
of("SSSSS").formatFn("A", milliSecondOfDay -> String.valueOf(parseInt(milliSecondOfDay) / 1000)).numeric(),
5555
of("SSSS").formatFn("A", milliSecondOfDay -> String.valueOf(parseInt(milliSecondOfDay) / 1000)).numeric(),
56-
of("AM").formatFn("a", x -> x.toUpperCase(Locale.ROOT)).text(),
57-
of("am").formatFn("a", x -> x.toLowerCase(Locale.ROOT)).text(),
58-
of("PM").formatFn("a", x -> x.toUpperCase(Locale.ROOT)).text(),
59-
of("pm").formatFn("a", x -> x.toLowerCase(Locale.ROOT)).text(),
56+
of("AM").formatFn("a", x -> x.toUpperCase(Locale.ENGLISH)).text(),
57+
of("am").formatFn("a", x -> x.toLowerCase(Locale.ENGLISH)).text(),
58+
of("PM").formatFn("a", x -> x.toUpperCase(Locale.ENGLISH)).text(),
59+
of("pm").formatFn("a", x -> x.toLowerCase(Locale.ENGLISH)).text(),
6060
of("A.M.").formatFn("a", x -> x.charAt(0) + "." + x.charAt(1) + ".").text(),
61-
of("a.m.").formatFn("a", x -> (x.charAt(0) + "." + x.charAt(1) + ".").toLowerCase(Locale.ROOT)).text(),
61+
of("a.m.").formatFn("a", x -> (x.charAt(0) + "." + x.charAt(1) + ".").toLowerCase(Locale.ENGLISH)).text(),
6262
of("P.M.").formatFn("a", x -> x.charAt(0) + "." + x.charAt(1) + ".").text(),
63-
of("p.m.").formatFn("a", x -> (x.charAt(0) + "." + x.charAt(1) + ".").toLowerCase(Locale.ROOT)).text(),
63+
of("p.m.").formatFn("a", x -> (x.charAt(0) + "." + x.charAt(1) + ".").toLowerCase(Locale.ENGLISH)).text(),
6464
of("Y,YYY").formatFn("yyyy", year -> year.charAt(0) + "," + year.substring(1)).numericWithLeadingZeros(),
6565
of("YYYY").formatFn("yyyy").numeric(),
6666
of("YYY").formatFn("yyyy", year -> year.substring(1)).numeric(),
@@ -71,51 +71,53 @@ class ToCharFormatter {
7171
of("IY").formatFn(t -> lastNCharacter(absoluteWeekBasedYear(t), 2)).numeric(),
7272
of("I").formatFn(t -> lastNCharacter(absoluteWeekBasedYear(t), 1)).numeric(),
7373
of("BC").formatFn("G").text(),
74-
of("bc").formatFn("G", x -> x.toLowerCase(Locale.ROOT)).text(),
74+
of("bc").formatFn("G", x -> x.toLowerCase(Locale.ENGLISH)).text(),
7575
of("AD").formatFn("G").text(),
76-
of("ad").formatFn("G", x -> x.toLowerCase(Locale.ROOT)).text(),
76+
of("ad").formatFn("G", x -> x.toLowerCase(Locale.ENGLISH)).text(),
7777
of("B.C.").formatFn("G", x -> x.charAt(0) + "." + x.charAt(1) + ".").text(),
78-
of("b.c.").formatFn("G", x -> (x.charAt(0) + "." + x.charAt(1) + ".").toLowerCase(Locale.ROOT)).text(),
78+
of("b.c.").formatFn("G", x -> (x.charAt(0) + "." + x.charAt(1) + ".").toLowerCase(Locale.ENGLISH)).text(),
7979
of("A.D.").formatFn("G", x -> x.charAt(0) + "." + x.charAt(1) + ".").text(),
80-
of("a.d.").formatFn("G", x -> (x.charAt(0) + "." + x.charAt(1) + ".").toLowerCase(Locale.ROOT)).text(),
81-
of("MONTH").formatFn("MMMM", x -> String.format(Locale.ROOT, "%-9s", x.toUpperCase(Locale.ROOT))).text(),
82-
of("Month").formatFn("MMMM", x -> String.format(Locale.ROOT, "%-9s", x)).text(),
83-
of("month").formatFn("MMMM", x -> String.format(Locale.ROOT, "%-9s", x.toLowerCase(Locale.ROOT))).text(),
84-
of("MON").formatFn("MMM", x -> x.toUpperCase(Locale.ROOT)).text(),
80+
of("a.d.").formatFn("G", x -> (x.charAt(0) + "." + x.charAt(1) + ".").toLowerCase(Locale.ENGLISH)).text(),
81+
of("MONTH").formatFn("MMMM", x -> String.format(Locale.ENGLISH, "%-9s", x.toUpperCase(Locale.ENGLISH))).text(),
82+
of("Month").formatFn("MMMM", x -> String.format(Locale.ENGLISH, "%-9s", x)).text(),
83+
of("month").formatFn("MMMM", x -> String.format(Locale.ENGLISH, "%-9s", x.toLowerCase(Locale.ENGLISH))).text(),
84+
of("MON").formatFn("MMM", x -> x.toUpperCase(Locale.ENGLISH)).text(),
8585
of("Mon").formatFn("MMM").text(),
86-
of("mon").formatFn("MMM", x -> x.toLowerCase(Locale.ROOT)).text(),
86+
of("mon").formatFn("MMM", x -> x.toLowerCase(Locale.ENGLISH)).text(),
8787
of("MM").formatFn("MM").numeric(),
88-
of("DAY").formatFn("EEEE", x -> String.format(Locale.ROOT, "%-9s", x.toUpperCase(Locale.ROOT))).text(),
89-
of("Day").formatFn("EEEE", x -> String.format(Locale.ROOT, "%-9s", x)).text(),
90-
of("day").formatFn("EEEE", x -> String.format(Locale.ROOT, "%-9s", x.toLowerCase(Locale.ROOT))).text(),
91-
of("DY").formatFn("E", x -> x.toUpperCase(Locale.ROOT)).text(),
88+
of("DAY").formatFn("EEEE", x -> String.format(Locale.ENGLISH, "%-9s", x.toUpperCase(Locale.ENGLISH))).text(),
89+
of("Day").formatFn("EEEE", x -> String.format(Locale.ENGLISH, "%-9s", x)).text(),
90+
of("day").formatFn("EEEE", x -> String.format(Locale.ENGLISH, "%-9s", x.toLowerCase(Locale.ENGLISH))).text(),
91+
of("DY").formatFn("E", x -> x.toUpperCase(Locale.ENGLISH)).text(),
9292
of("Dy").formatFn("E").text(),
93-
of("dy").formatFn("E", x -> x.toLowerCase(Locale.ROOT)).text(),
93+
of("dy").formatFn("E", x -> x.toLowerCase(Locale.ENGLISH)).text(),
9494
of("DDD").formatFn("DDD").numeric(),
9595
of("IDDD").formatFn(
9696
t -> String.format(
97-
Locale.ROOT,
97+
Locale.ENGLISH,
9898
"%03d",
9999
(t.get(WeekFields.ISO.weekOfWeekBasedYear()) - 1) * 7 + t.get(ChronoField.DAY_OF_WEEK)
100100
)
101101
).numeric(),
102-
of("DD").formatFn("d", x -> String.format(Locale.ROOT, "%02d", parseInt(x))).numeric(),
102+
of("DD").formatFn("d", x -> String.format(Locale.ENGLISH, "%02d", parseInt(x))).numeric(),
103103
of("ID").formatFn(t -> String.valueOf(t.get(ChronoField.DAY_OF_WEEK))).numeric(),
104104
of("D").formatFn(t -> String.valueOf(t.get(WeekFields.SUNDAY_START.dayOfWeek()))).numeric(),
105105
of("W").formatFn(t -> String.valueOf(t.get(ChronoField.ALIGNED_WEEK_OF_MONTH))).numeric(),
106-
of("WW").formatFn(t -> String.format(Locale.ROOT, "%02d", t.get(ChronoField.ALIGNED_WEEK_OF_YEAR))).numeric(),
107-
of("IW").formatFn(t -> String.format(Locale.ROOT, "%02d", t.get(WeekFields.ISO.weekOfWeekBasedYear()))).numeric(),
106+
of("WW").formatFn(t -> String.format(Locale.ENGLISH, "%02d", t.get(ChronoField.ALIGNED_WEEK_OF_YEAR))).numeric(),
107+
of("IW").formatFn(t -> String.format(Locale.ENGLISH, "%02d", t.get(WeekFields.ISO.weekOfWeekBasedYear()))).numeric(),
108108
of("CC").formatFn(t -> {
109109
int century = yearToCentury(t.get(ChronoField.YEAR));
110-
return String.format(Locale.ROOT, century < 0 ? "%03d" : "%02d", century);
110+
return String.format(Locale.ENGLISH, century < 0 ? "%03d" : "%02d", century);
111111
}).numeric(),
112112
of("J").formatFn(t -> String.valueOf(t.getLong(JulianFields.JULIAN_DAY))).numeric(),
113113
of("Q").formatFn("Q").numeric(),
114-
of("RM").formatFn("MM", month -> String.format(Locale.ROOT, "%-4s", monthToRoman(parseInt(month)))).text(),
115-
of("rm").formatFn("MM", month -> String.format(Locale.ROOT, "%-4s", monthToRoman(parseInt(month)).toLowerCase(Locale.ROOT)))
116-
.text(),
114+
of("RM").formatFn("MM", month -> String.format(Locale.ENGLISH, "%-4s", monthToRoman(parseInt(month)))).text(),
115+
of("rm").formatFn(
116+
"MM",
117+
month -> String.format(Locale.ENGLISH, "%-4s", monthToRoman(parseInt(month)).toLowerCase(Locale.ENGLISH))
118+
).text(),
117119
of("TZ").formatFn(ToCharFormatter::zoneAbbreviationOf).text(),
118-
of("tz").formatFn(t -> zoneAbbreviationOf(t).toLowerCase(Locale.ROOT)).text(),
120+
of("tz").formatFn(t -> zoneAbbreviationOf(t).toLowerCase(Locale.ENGLISH)).text(),
119121
of("TZH").acceptsLowercase(false).formatFn("ZZ", s -> s.substring(0, 3)).text(),
120122
of("TZM").acceptsLowercase(false).formatFn("ZZ", s -> lastNCharacter(s, 2)).text(),
121123
of("OF").acceptsLowercase(false).formatFn("ZZZZZ", ToCharFormatter::formatOffset).offset()
@@ -128,7 +130,7 @@ class ToCharFormatter {
128130
// also index the lower case version of the patterns if accepted
129131
for (ToCharFormatter formatter : formatters) {
130132
if (formatter.acceptsLowercase) {
131-
formatterMap.putIfAbsent(formatter.pattern.toLowerCase(Locale.ROOT), formatter);
133+
formatterMap.putIfAbsent(formatter.pattern.toLowerCase(Locale.ENGLISH), formatter);
132134
}
133135
}
134136
FORMATTER_MAP = formatterMap;
@@ -275,8 +277,8 @@ private static String appendOrdinalSuffix(String defaultSuffix, String s) {
275277
// the Y,YYY pattern might can cause problems with the parsing, but thankfully the last 3
276278
// characters is enough to calculate the suffix
277279
int i = parseInt(lastNCharacter(s, 3));
278-
final boolean upperCase = defaultSuffix.equals(defaultSuffix.toUpperCase(Locale.ROOT));
279-
return s + (upperCase ? ordinalSuffix(i).toUpperCase(Locale.ROOT) : ordinalSuffix(i));
280+
final boolean upperCase = defaultSuffix.equals(defaultSuffix.toUpperCase(Locale.ENGLISH));
281+
return s + (upperCase ? ordinalSuffix(i).toUpperCase(Locale.ENGLISH) : ordinalSuffix(i));
280282
} catch (NumberFormatException ex) {
281283
return s + defaultSuffix;
282284
}
@@ -313,19 +315,19 @@ private static String removeLeadingZerosFromOffset(String offset) {
313315
private static String absoluteWeekBasedYear(TemporalAccessor t) {
314316
int year = t.get(IsoFields.WEEK_BASED_YEAR);
315317
year = year > 0 ? year : -(year - 1);
316-
return String.format(Locale.ROOT, "%04d", year);
318+
return String.format(Locale.ENGLISH, "%04d", year);
317319
}
318320

319321
private static String firstDigitsOfNanos(String nano, int digits) {
320-
return String.format(Locale.ROOT, "%09d", parseInt(nano)).substring(0, digits);
322+
return String.format(Locale.ENGLISH, "%09d", parseInt(nano)).substring(0, digits);
321323
}
322324

323325
private static String lastNCharacter(String s, int n) {
324326
return s.substring(Math.max(0, s.length() - n));
325327
}
326328

327329
private static String zoneAbbreviationOf(TemporalAccessor temporalAccessor) {
328-
String zone = ZoneId.from(temporalAccessor).getDisplayName(TextStyle.SHORT, Locale.ROOT);
330+
String zone = ZoneId.from(temporalAccessor).getDisplayName(TextStyle.SHORT, Locale.ENGLISH);
329331
return "Z".equals(zone) ? "UTC" : zone;
330332
}
331333

@@ -345,7 +347,7 @@ public Builder formatFn(final String javaPattern) {
345347

346348
public Builder formatFn(final String javaPattern, final Function<String, String> additionalMapper) {
347349
this.formatFn = temporalAccessor -> {
348-
String formatted = DateTimeFormatter.ofPattern(javaPattern != null ? javaPattern : "'" + pattern + "'", Locale.ROOT)
350+
String formatted = DateTimeFormatter.ofPattern(javaPattern != null ? javaPattern : "'" + pattern + "'", Locale.ENGLISH)
349351
.format(temporalAccessor);
350352
return additionalMapper == null ? formatted : additionalMapper.apply(formatted);
351353
};

0 commit comments

Comments
 (0)