-
Notifications
You must be signed in to change notification settings - Fork 188
DATE_FORMAT function #764
DATE_FORMAT function #764
Changes from 103 commits
fb2ed91
254f2e0
33c6d3e
09132da
893cd18
923c96d
4b33a2f
af74293
baac103
35e37a3
0ed6594
e4981e3
c11125d
34b979e
5ab1bc8
8735723
332ee9c
2939081
9e82a91
89c6e05
db82e29
e46da70
88a878b
63ea788
7551dc8
a673fe2
7c70c33
d87f8f4
242403c
be7051d
6834c10
ee02527
8ab2f63
458c34a
e42aac1
960783b
4d355b2
0881f24
d199d54
c88a6dd
4dd0db0
988d177
9c1d96b
693211a
36449f7
048d5d6
c0593d9
7ee0dbc
7764a0d
18a6a84
7054a4b
7e4c726
f0a16ce
b757335
a41aaad
1a44c36
20acde3
a51f2f7
42c7222
0f3894a
a73a8e9
ea9b190
7f57158
498bdf0
61ac4e5
365b000
7840b12
0814004
8567662
9d51b9c
c869ff0
6d19513
5ab8631
ce4473b
829fa06
6dc155c
dc70cd6
d93f02b
c001615
104e6b9
0f8d8bc
6a8434f
84a111e
126890b
38e875d
c73e430
4d3eff0
e573d32
9e68366
e6942e4
a57014e
18820c6
e6bdab5
c72d100
568667f
cf69bbc
8d41f33
76cd45b
6267a24
e9793f3
c9b68db
8ee52f7
1656d75
21c5fc5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| package com.amazon.opendistroforelasticsearch.sql.expression.datetime; | ||
|
|
||
| import com.amazon.opendistroforelasticsearch.sql.data.model.ExprStringValue; | ||
| import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue; | ||
| import com.google.common.collect.ImmutableMap; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.Locale; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * This class converts a SQL style DATE_FORMAT format specifier and converts it to a | ||
| * Java SimpleDateTime format. | ||
| */ | ||
| class DateTimeFormatterUtil { | ||
| private static final int SUFFIX_SPECIAL_START_TH = 11; | ||
| private static final int SUFFIX_SPECIAL_END_TH = 13; | ||
| private static final String SUFFIX_SPECIAL_TH = "th"; | ||
| private static final Map<Integer, String> SUFFIX_CONVERTER = | ||
| ImmutableMap.<Integer, String>builder() | ||
| .put(1, "st").put(2, "nd").put(3, "rd").build(); | ||
|
|
||
| // The following have special cases that need handling outside of the format options provided | ||
| // by the DateTimeFormatter class. | ||
| interface DateTimeFormatHandler { | ||
| String getFormat(LocalDateTime date); | ||
| } | ||
|
|
||
| private static final Map<String, DateTimeFormatHandler> HANDLERS = | ||
| ImmutableMap.<String, DateTimeFormatHandler>builder() | ||
| .put("%a", (date) -> "EEE") // %a => EEE - Abbreviated weekday name (Sun..Sat) | ||
| .put("%b", (date) -> "LLL") // %b => LLL - Abbreviated month name (Jan..Dec) | ||
| .put("%c", (date) -> "MM") // %c => MM - Month, numeric (0..12) | ||
| .put("%d", (date) -> "dd") // %d => dd - Day of the month, numeric (00..31) | ||
| .put("%e", (date) -> "d") // %e => d - Day of the month, numeric (0..31) | ||
| .put("%H", (date) -> "HH") // %H => HH - (00..23) | ||
| .put("%h", (date) -> "hh") // %h => hh - (01..12) | ||
| .put("%I", (date) -> "hh") // %I => hh - (01..12) | ||
| .put("%i", (date) -> "mm") // %i => mm - Minutes, numeric (00..59) | ||
| .put("%j", (date) -> "DDD") // %j => DDD - (001..366) | ||
| .put("%k", (date) -> "H") // %k => H - (0..23) | ||
| .put("%l", (date) -> "h") // %l => h - (1..12) | ||
| .put("%p", (date) -> "a") // %p => a - AM or PM | ||
| .put("%M", (date) -> "LLLL") // %M => LLLL - Month name (January..December) | ||
| .put("%m", (date) -> "MM") // %m => MM - Month, numeric (00..12) | ||
| .put("%r", (date) -> "hh:mm:ss a") // %r => hh:mm:ss a - hh:mm:ss followed by AM or PM | ||
| .put("%S", (date) -> "ss") // %S => ss - Seconds (00..59) | ||
| .put("%s", (date) -> "ss") // %s => ss - Seconds (00..59) | ||
| .put("%T", (date) -> "HH:mm:ss") // %T => HH:mm:ss | ||
| .put("%W", (date) -> "EEEE") // %W => EEEE - Weekday name (Sunday..Saturday) | ||
| .put("%Y", (date) -> "yyyy") // %Y => yyyy - Year, numeric, 4 digits | ||
| .put("%y", (date) -> "yy") // %y => yy - Year, numeric, 2 digits | ||
| // The following are not directly supported by DateTimeFormatter. | ||
| .put("%D", (date) -> // %w - Day of month with English suffix | ||
| String.format("'%d%s'", date.getDayOfMonth(), getSuffix(date.getDayOfMonth()))) | ||
| .put("%f", (date) -> // %f - Microseconds | ||
| String.format("'%d'", (date.getNano() / 1000))) | ||
| .put("%w", (date) -> // %w - Day of week (0 indexed) | ||
| String.format("'%d'", date.getDayOfWeek().getValue())) | ||
| .put("%U", (date) -> // %U Week where Sunday is the first day - WEEK() mode 0 | ||
| String.format("'%d'", CalendarLookup.getWeekNumber(0, date.toLocalDate()))) | ||
| .put("%u", (date) -> // %u Week where Monday is the first day - WEEK() mode 1 | ||
| String.format("'%d'", CalendarLookup.getWeekNumber(1, date.toLocalDate()))) | ||
| .put("%V", (date) -> // %V Week where Sunday is the first day - WEEK() mode 2 used with %X | ||
| String.format("'%d'", CalendarLookup.getWeekNumber(2, date.toLocalDate()))) | ||
| .put("%v", (date) -> // %v Week where Monday is the first day - WEEK() mode 3 used with %x | ||
| String.format("'%d'", CalendarLookup.getWeekNumber(3, date.toLocalDate()))) | ||
| .put("%X", (date) -> // %X Year for week where Sunday is the first day, 4 digits used with %V | ||
| String.format("'%d'", CalendarLookup.getYearNumber(2, date.toLocalDate()))) | ||
| .put("%x", (date) -> // %x Year for week where Monday is the first day, 4 digits used with %v | ||
| String.format("'%d'", CalendarLookup.getYearNumber(3, date.toLocalDate()))) | ||
| .build(); | ||
|
|
||
| private static final String MOD = "%"; | ||
| private static final String QUOTE_LITERAL = "'"; | ||
|
|
||
| private DateTimeFormatterUtil() { | ||
| } | ||
|
|
||
| /** | ||
| * Format the date using the date format String. | ||
| * @param dateExpr the date ExprValue of Date/Datetime/Timestamp/String type. | ||
| * @param formatExpr the format ExprValue of String type. | ||
| * @return Date formatted using format and returned as a String. | ||
| */ | ||
| static ExprValue getFormattedDate(ExprValue dateExpr, ExprValue formatExpr) { | ||
| String format = formatExpr.stringValue(); | ||
| final LocalDateTime date = dateExpr.datetimeValue(); | ||
| for (Map.Entry<String, DateTimeFormatHandler> handler: HANDLERS.entrySet()) { | ||
| if (format.contains(handler.getKey())) { | ||
| format = format.replace(handler.getKey(), handler.getValue().getFormat(date)); | ||
| } | ||
| } | ||
| format = literalReplace(format); | ||
|
|
||
| // English Locale matches SQL requirements. | ||
| // 'AM'/'PM' instead of 'a.m.'/'p.m.' | ||
| // 'Sat' instead of 'Sat.' etc | ||
| return new ExprStringValue(date.format(DateTimeFormatter.ofPattern(format, Locale.ENGLISH))); | ||
| } | ||
|
|
||
| /** | ||
| * Returns English suffix of incoming value. | ||
| * @param val Incoming value. | ||
| * @return English suffix as String (st, nd, rd, th) | ||
| */ | ||
| private static String getSuffix(int val) { | ||
| // The numbers 11, 12, and 13 do not follow general suffix rules. | ||
| if ((SUFFIX_SPECIAL_START_TH <= val) && (val <= SUFFIX_SPECIAL_END_TH)) { | ||
| return SUFFIX_SPECIAL_TH; | ||
| } | ||
| return SUFFIX_CONVERTER.getOrDefault(val % 10, SUFFIX_SPECIAL_TH); | ||
| } | ||
|
|
||
| /** | ||
| * Goes through format String and replaces any %x with 'x' where x is unmapped. | ||
| * @param format Incoming format String without mapping completed. | ||
| * @return Outgoing format String with mapping completed. | ||
| */ | ||
| private static String literalReplace(String format) { | ||
|
||
| // Need to do %x=>'x' for any % not listed | ||
| int index = format.indexOf(MOD); | ||
| while ((index != -1) && ((index + 2) <= format.length())) { | ||
| String substr = format.substring(index, index + 2); | ||
| format = format.replace(substr, substr.replaceFirst(MOD, QUOTE_LITERAL) + QUOTE_LITERAL); | ||
| index = format.indexOf(MOD, index + 2); | ||
| } | ||
| return format; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure the use case. Do you try to do something like this format.replace("%\W", () -> replace).
If so, i recommended to consider the https://docs.oracle.com/javase/7/docs/api/java/util/regex/Matcher.html#appendReplacement(java.lang.StringBuffer,%20java.lang.String)
which provide the more clean code structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't realize I could do this, tahnks for the suggestion! I have updated with this change.