diff --git a/be/src/runtime/datetime_value.cpp b/be/src/runtime/datetime_value.cpp index f286a911b81e99..67be08190fb724 100644 --- a/be/src/runtime/datetime_value.cpp +++ b/be/src/runtime/datetime_value.cpp @@ -1073,6 +1073,8 @@ static int check_word(const char* lib[], const char* str, const char* end, const return pos; } +// this method is exaclty same as fromDateFormatStr() in DateLiteral.java in FE +// change this method should also change that. bool DateTimeValue::from_date_format_str( const char* format, int format_len, const char* value, int value_len, @@ -1315,14 +1317,14 @@ bool DateTimeValue::from_date_format_str( date_part_used = true; break; case 'r': - if (from_date_format_str("%I:%i:%S %p", 11, val, val_end - val, &tmp)) { + if (!from_date_format_str("%I:%i:%S %p", 11, val, val_end - val, &tmp)) { return false; } val = tmp; time_part_used = true; break; case 'T': - if (from_date_format_str("%H:%i:%S", 8, val, val_end - val, &tmp)) { + if (!from_date_format_str("%H:%i:%S", 8, val, val_end - val, &tmp)) { return false; } time_part_used = true; @@ -1363,6 +1365,33 @@ bool DateTimeValue::from_date_format_str( } } + // continue to iterate pattern if has + // to find out if it has time part. + while (ptr < end) { + if (*ptr == '%' && ptr + 1 < end) { + ptr++; + switch (*ptr++) { + case 'H': + case 'h': + case 'I': + case 'i': + case 'k': + case 'l': + case 'r': + case 's': + case 'S': + case 'p': + case 'T': + time_part_used = true; + break; + default: + break; + } + } else { + ptr++; + } + } + if (usa_time) { if (_hour > 12 || _hour < 1) { return false; @@ -1371,7 +1400,7 @@ bool DateTimeValue::from_date_format_str( } if (sub_val_end) { *sub_val_end = val; - return 0; + return true; } // Year day if (yearday > 0) { diff --git a/be/test/runtime/datetime_value_test.cpp b/be/test/runtime/datetime_value_test.cpp index cec33e95f9e9d6..4d49cc90bd7124 100644 --- a/be/test/runtime/datetime_value_test.cpp +++ b/be/test/runtime/datetime_value_test.cpp @@ -521,7 +521,7 @@ TEST_F(DateTimeValueTest, from_date_format_str) { value.to_string(str); ASSERT_STREQ("2015-01-05 12:34:56", str); - // hour + // hour format_str = "%Y-%m-%d %H %i %s"; value_str = "88-2-1 03 4 5"; ASSERT_TRUE(value.from_date_format_str( diff --git a/docs/en/sql-reference/sql-functions/date-time-functions/str_to_date.md b/docs/en/sql-reference/sql-functions/date-time-functions/str_to_date.md index 059d463e0d7c59..af3fb235f078ce 100644 --- a/docs/en/sql-reference/sql-functions/date-time-functions/str_to_date.md +++ b/docs/en/sql-reference/sql-functions/date-time-functions/str_to_date.md @@ -58,6 +58,14 @@ mysql> select str_to_date('200442 Monday', '%X%V %W'); +-----------------------------------------+ | 2004-10-18 | +-----------------------------------------+ + +mysql> select str_to_date("2020-09-01", "%Y-%m-%d %H:%i:%s"); ++------------------------------------------------+ +| str_to_date('2020-09-01', '%Y-%m-%d %H:%i:%s') | ++------------------------------------------------+ +| 2020-09-01 00:00:00 | ++------------------------------------------------+ +1 row in set (0.01 sec) ``` ## keyword diff --git a/docs/zh-CN/sql-reference/sql-functions/date-time-functions/str_to_date.md b/docs/zh-CN/sql-reference/sql-functions/date-time-functions/str_to_date.md index f5fc145813539c..3102542d019bde 100644 --- a/docs/zh-CN/sql-reference/sql-functions/date-time-functions/str_to_date.md +++ b/docs/zh-CN/sql-reference/sql-functions/date-time-functions/str_to_date.md @@ -30,7 +30,6 @@ under the License. `DATETIME STR_TO_DATE(VARCHAR str, VARCHAR format)` - 通过format指定的方式将str转化为DATE类型,如果转化结果不对返回NULL 支持的format格式与date_format一致 @@ -58,6 +57,14 @@ mysql> select str_to_date('200442 Monday', '%X%V %W'); +-----------------------------------------+ | 2004-10-18 | +-----------------------------------------+ + +mysql> select str_to_date("2020-09-01", "%Y-%m-%d %H:%i:%s"); ++------------------------------------------------+ +| str_to_date('2020-09-01', '%Y-%m-%d %H:%i:%s') | ++------------------------------------------------+ +| 2020-09-01 00:00:00 | ++------------------------------------------------+ +1 row in set (0.01 sec) ``` ## keyword diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/DateLiteral.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/DateLiteral.java index b2d2f495075a25..effaead1325a50 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/DateLiteral.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/DateLiteral.java @@ -22,12 +22,15 @@ import org.apache.doris.catalog.Type; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.FeMetaVersion; +import org.apache.doris.common.InvalidFormatException; import org.apache.doris.common.util.TimeUtils; import org.apache.doris.thrift.TDateLiteral; import org.apache.doris.thrift.TExprNode; import org.apache.doris.thrift.TExprNodeType; import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,7 +44,10 @@ import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; +import java.time.Year; import java.util.Date; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.TimeZone; import java.util.regex.Pattern; @@ -66,6 +72,12 @@ public class DateLiteral extends LiteralExpr { private static DateTimeFormatter DATE_TIME_FORMATTER_TWO_DIGIT = null; private static DateTimeFormatter DATE_FORMATTER_TWO_DIGIT = null; + private static Map MONTH_NAME_DICT = Maps.newHashMap(); + private static Map MONTH_ABBR_NAME_DICT = Maps.newHashMap(); + private static Map WEEK_DAY_NAME_DICT = Maps.newHashMap(); + private static Map WEEK_DAY_ABBR_NAME_DICT = Maps.newHashMap(); + private static List DAYS_IN_MONTH = Lists.newArrayList(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); + static { try { DATE_TIME_FORMATTER = formatBuilder("%Y-%m-%d %H:%i:%s").toFormatter(); @@ -76,10 +88,52 @@ public class DateLiteral extends LiteralExpr { LOG.error("invalid date format", e); System.exit(-1); } + + MONTH_NAME_DICT.put("january", 1); + MONTH_NAME_DICT.put("february", 2); + MONTH_NAME_DICT.put("march", 3); + MONTH_NAME_DICT.put("april", 4); + MONTH_NAME_DICT.put("may", 5); + MONTH_NAME_DICT.put("june", 6); + MONTH_NAME_DICT.put("july", 7); + MONTH_NAME_DICT.put("august", 8); + MONTH_NAME_DICT.put("september", 9); + MONTH_NAME_DICT.put("october", 10); + MONTH_NAME_DICT.put("november", 11); + MONTH_NAME_DICT.put("december", 12); + + MONTH_ABBR_NAME_DICT.put("jan", 1); + MONTH_ABBR_NAME_DICT.put("feb", 2); + MONTH_ABBR_NAME_DICT.put("mar", 3); + MONTH_ABBR_NAME_DICT.put("apr", 4); + MONTH_ABBR_NAME_DICT.put("may", 5); + MONTH_ABBR_NAME_DICT.put("jun", 6); + MONTH_ABBR_NAME_DICT.put("jul", 7); + MONTH_ABBR_NAME_DICT.put("aug", 8); + MONTH_ABBR_NAME_DICT.put("sep", 9); + MONTH_ABBR_NAME_DICT.put("oct", 10); + MONTH_ABBR_NAME_DICT.put("nov", 11); + MONTH_ABBR_NAME_DICT.put("dec", 12); + + WEEK_DAY_NAME_DICT.put("monday", 0); + WEEK_DAY_NAME_DICT.put("tuesday", 1); + WEEK_DAY_NAME_DICT.put("wednesday", 2); + WEEK_DAY_NAME_DICT.put("thursday", 3); + WEEK_DAY_NAME_DICT.put("friday", 4); + WEEK_DAY_NAME_DICT.put("saturday", 5); + WEEK_DAY_NAME_DICT.put("sunday", 6); + + MONTH_ABBR_NAME_DICT.put("mon", 0); + MONTH_ABBR_NAME_DICT.put("tue", 1); + MONTH_ABBR_NAME_DICT.put("wed", 2); + MONTH_ABBR_NAME_DICT.put("thu", 3); + MONTH_ABBR_NAME_DICT.put("fri", 4); + MONTH_ABBR_NAME_DICT.put("sat", 5); + MONTH_ABBR_NAME_DICT.put("sun", 6); } //Regex used to determine if the TIME field exists int date_format - private static final Pattern HAS_TIME_PART = Pattern.compile("^.*[HhIiklrSsT]+.*$"); + private static final Pattern HAS_TIME_PART = Pattern.compile("^.*[HhIiklrSsTp]+.*$"); //Date Literal persist type in meta private enum DateLiteralType { DATETIME(0), @@ -93,9 +147,9 @@ private DateLiteralType(int value) { public int value() { return value; } - } + } - private DateLiteral() { + public DateLiteral() { super(); } @@ -423,7 +477,8 @@ public long unixTimestamp(TimeZone timeZone) { } public static DateLiteral dateParser(String date, String pattern) throws AnalysisException { - LocalDateTime dateTime = formatBuilder(pattern).toFormatter().parseLocalDateTime(date); + DateTimeFormatter formatter = formatBuilder(pattern).toFormatter(); + LocalDateTime dateTime = formatter.parseLocalDateTime(date); DateLiteral dateLiteral = new DateLiteral( dateTime.getYear(), dateTime.getMonthOfYear(), @@ -632,4 +687,435 @@ public long getSecond() { public int hashCode() { return 31 * super.hashCode() + Objects.hashCode(unixTimestamp(TimeZone.getDefault())); } + + // parset the date string value in 'value' by 'format' pattern. + // return the next position to parse if hasSubVal is true. + // throw InvalidFormatException if encounter errors. + // this method is exaclty same as from_date_format_str() in be/src/runtime/datetime_value.cpp + // change this method should also change that. + public int fromDateFormatStr(String format, String value, boolean hasSubVal) throws InvalidFormatException { + int fp = 0; // pointer to the current format string + int fend = format.length(); // end of format string + int vp = 0; // pointer to the date string value + int vend = value.length(); // end of date string value + + boolean datePartUsed = false; + boolean timePartUsed = false; + + int dayPart = 0; + long weekday = -1; + long yearday = -1; + long weekNum = -1; + + boolean strictWeekNumber = false; + boolean sundayFirst = false; + boolean strictWeekNumberYearType = false; + long strictWeekNumberYear = -1; + boolean usaTime = false; + + char f; + while (fp < fend && vp < vend) { + // Skip space character + while (vp < vend && Character.isSpaceChar(value.charAt(vp))) { + vp++; + } + if (vp >= vend) { + break; + } + + // Check switch + f = format.charAt(fp); + if (f == '%' && fp + 1 < fend) { + int tmp = 0; + long intValue = 0; + fp++; + f = format.charAt(fp); + fp++; + switch (f) { + // Year + case 'y': + // Year, numeric (two digits) + tmp = vp + Math.min(2, vend - vp); + intValue = strToLong(value.substring(vp, tmp)); + intValue += intValue >= 70 ? 1900 : 2000; + this.year = intValue; + vp = tmp; + datePartUsed = true; + break; + case 'Y': + // Year, numeric, four digits + tmp = vp + Math.min(4, vend - vp); + intValue = strToLong(value.substring(vp, tmp)); + if (tmp - vp <= 2) { + intValue += intValue >= 70 ? 1900 : 2000; + } + this.year = intValue; + vp = tmp; + datePartUsed = true; + break; + // Month + case 'm': + case 'c': + tmp = vp + Math.min(2, vend - vp); + intValue = strToLong(value.substring(vp, tmp)); + this.month = intValue; + vp = tmp; + datePartUsed = true; + break; + case 'M': { + int nextPos = findWord(value, vp); + intValue = checkWord(MONTH_NAME_DICT, value.substring(vp, nextPos)); + this.month = intValue; + vp = nextPos; + break; + } + case 'b': { + int nextPos = findWord(value, vp); + intValue = checkWord(MONTH_ABBR_NAME_DICT, value.substring(vp, nextPos)); + this.month = intValue; + vp = nextPos; + break; + } + // Day + case 'd': + case 'e': + tmp = vp + Math.min(2, vend - vp); + intValue = strToLong(value.substring(vp, tmp)); + this.day = intValue; + vp = tmp; + datePartUsed = true; + break; + case 'D': + tmp = vp + Math.min(2, vend - vp); + intValue = strToLong(value.substring(vp, tmp)); + this.day = intValue; + vp = tmp + Math.min(2, vend - tmp); + datePartUsed = true; + break; + // Hour + case 'h': + case 'I': + case 'l': + usaTime = true; + // Fall through + case 'k': + case 'H': + tmp = findNumber(value, vp, 2); + intValue = strToLong(value.substring(vp, tmp)); + this.hour = intValue; + vp = tmp; + timePartUsed = true; + break; + // Minute + case 'i': + tmp = vp + Math.min(2, vend - vp); + intValue = strToLong(value.substring(vp, tmp)); + this.minute = intValue; + vp = tmp; + timePartUsed = true; + break; + // Second + case 's': + case 'S': + tmp = vp + Math.min(2, vend - vp); + intValue = strToLong(value.substring(vp, tmp)); + this.second = intValue; + vp = tmp; + timePartUsed = true; + break; + // Micro second + case 'f': + // micro second is not supported, so just eat it and go one. + tmp = vp + Math.min(6, vend - vp); + vp = tmp; + break; + // AM/PM + case 'p': + if ((vend - vp) < 2 || Character.toUpperCase(value.charAt(vp + 1)) != 'M' || !usaTime) { + throw new InvalidFormatException("Invalid %p format"); + } + if (Character.toUpperCase(value.charAt(vp)) == 'P') { + // PM + dayPart = 12; + } + timePartUsed = true; + vp += 2; + break; + // Weekday + case 'W': { + int nextPos = findWord(value, vp); + intValue = checkWord(WEEK_DAY_NAME_DICT, value.substring(vp, nextPos)); + intValue++; + weekday = intValue; + datePartUsed = true; + break; + } + case 'a': { + int nextPos = findWord(value, vp); + intValue = checkWord(WEEK_DAY_NAME_DICT, value.substring(vp, nextPos)); + intValue++; + weekday = intValue; + datePartUsed = true; + break; + } + case 'w': + tmp = vp + Math.min(1, vend - vp); + intValue = strToLong(value.substring(vp, tmp)); + if (intValue >= 7) { + throw new InvalidFormatException("invalid day of week: " + intValue); + } + if (intValue == 0) { + intValue = 7; + } + weekday = intValue; + vp = tmp; + datePartUsed = true; + break; + case 'j': + tmp = vp + Math.min(3, vend - vp); + intValue = strToLong(value.substring(vp, tmp)); + yearday = intValue; + vp = tmp; + datePartUsed = true; + break; + case 'u': + case 'v': + case 'U': + case 'V': + sundayFirst = (format.charAt(fp - 1) == 'U' || format.charAt(fp - 1) == 'V'); + // Used to check if there is %x or %X + strictWeekNumber = (format.charAt(fp - 1) == 'V' || format.charAt(fp - 1) == 'v'); + tmp = vp + Math.min(2, vend - vp); + intValue = Long.valueOf(value.substring(vp, tmp)); + weekNum = intValue; + if (weekNum > 53 || (strictWeekNumber && weekNum == 0)) { + throw new InvalidFormatException("invalid num of week: " + weekNum); + } + vp = tmp; + datePartUsed = true; + break; + // strict week number, must be used with %V or %v + case 'x': + case 'X': + strictWeekNumberYearType = (format.charAt(fp - 1) == 'X'); + tmp = vp + Math.min(4, vend - vp); + intValue = Long.valueOf(value.substring(vp, tmp)); + strictWeekNumberYear = intValue; + vp = tmp; + datePartUsed = true; + break; + case 'r': + tmp = fromDateFormatStr("%I:%i:%S %p", value.substring(vp, vend), true); + vp = tmp; + timePartUsed = true; + break; + case 'T': + tmp = fromDateFormatStr("%H:%i:%S", value.substring(vp, vend), true); + vp = tmp; + timePartUsed = true; + break; + case '.': + while (vp < vend && Character.toString(value.charAt(vp)).matches("\\p{Punct}")) { + vp++; + } + break; + case '@': + while (vp < vend && Character.isLetter(value.charAt(vp))) { + vp++; + } + break; + case '#': + while (vp < vend && Character.isDigit(value.charAt(vp))) { + vp++; + } + break; + case '%': // %%, escape the % + if ('%' != value.charAt(vp)) { + throw new InvalidFormatException("invalid char after %: " + value.charAt(vp)); + } + vp++; + break; + default: + throw new InvalidFormatException("Invalid format pattern: " + f); + } + } else if (format.charAt(fp) != ' ') { + if (format.charAt(fp) != value.charAt(vp)) { + throw new InvalidFormatException("Invalid char: " + value.charAt(vp) + ", expected: " + format.charAt(fp)); + } + fp++; + vp++; + } else { + fp++; + } + } + + // continue to iterate pattern if has + // to find out if it has time part. + while (fp < fend) { + f = format.charAt(fp); + if (f == '%' && fp + 1 < fend) { + fp++; + f = format.charAt(fp); + fp++; + switch (f) { + case 'H': + case 'h': + case 'I': + case 'i': + case 'k': + case 'l': + case 'r': + case 's': + case 'S': + case 'p': + case 'T': + timePartUsed = true; + break; + default: + break; + } + } else { + fp++; + } + } + + if (usaTime) { + if (this.hour > 12 || this.hour < 1) { + throw new InvalidFormatException("Invalid hour: " + hour); + } + this.hour = (this.hour % 12) + dayPart; + } + + if (hasSubVal) { + return vp; + } + + // Year day + if (yearday > 0) { + long days = calcDaynr(this.year, 1, 1) + yearday - 1; + getDateFromDaynr(days); + } + + // weekday + if (weekNum >= 0 && weekday > 0) { + // Check + if ((strictWeekNumber && (strictWeekNumberYear < 0 + || strictWeekNumberYearType != sundayFirst)) + || (!strictWeekNumber && strictWeekNumberYear >= 0)) { + throw new InvalidFormatException("invalid week number"); + } + long days = calcDaynr(strictWeekNumber ? strictWeekNumberYear : this.year, 1, 1); + + long weekday_b = calcWeekday(days, sundayFirst); + + if (sundayFirst) { + days += ((weekday_b == 0) ? 0 : 7) - weekday_b + (weekNum - 1) * 7 + weekday % 7; + } else { + days += ((weekday_b <= 3) ? 0 : 7) - weekday_b + (weekNum - 1) * 7 + weekday - 1; + } + getDateFromDaynr(days); + } + + // Compute timestamp type + if (datePartUsed) { + if (timePartUsed) { + this.type = Type.DATETIME; + } else { + this.type = Type.DATE; + } + } + return 0; + } + + private long strToLong(String l) throws InvalidFormatException { + try { + return Long.valueOf(l); + } catch (NumberFormatException e) { + throw new InvalidFormatException(e.getMessage()); + } + } + + // calculate the number of days from year 0000-00-00 to year-month-day + private long calcDaynr(long year, long month, long day) { + long delsum = 0; + long y = year; + + if (year == 0 && month == 0) { + return 0; + } + + /* Cast to int to be able to handle month == 0 */ + delsum = 365 * y + 31 * (month - 1) + day; + if (month <= 2) { + // No leap year + y--; + } else { + // This is great!!! + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 + // 0, 0, 3, 3, 4, 4, 5, 5, 5, 6, 7, 8 + delsum -= (month * 4 + 23) / 10; + } + // Every 400 year has 97 leap year, 100, 200, 300 are not leap year. + return delsum + y / 4 - y / 100 + y / 400; + } + + private long calcWeekday(long dayNr, boolean isSundayFirstDay) { + return (dayNr + 5L + (isSundayFirstDay ? 1L : 0L)) % 7; + } + + private void getDateFromDaynr(long daynr) throws InvalidFormatException { + if (daynr <= 0 || daynr > 3652424) { + throw new InvalidFormatException("Invalid days to year: " + daynr); + } + this.year = daynr / 365; + long daysBeforeYear = 0; + while (daynr < (daysBeforeYear = calcDaynr(this.year, 1, 1))) { + this.year--; + } + long daysOfYear = daynr - daysBeforeYear + 1; + int leapDay = 0; + if (Year.isLeap(this.year)) { + if (daysOfYear > 31 + 28) { + daysOfYear--; + if (daysOfYear == 31 + 28) { + leapDay = 1; + } + } + } + this.month = 1; + while (daysOfYear > DAYS_IN_MONTH.get((int) this.month)) { + daysOfYear -= DAYS_IN_MONTH.get((int) this.month); + this.month++; + } + this.day = daysOfYear + leapDay; + } + + // find a word start from 'start' from value. + private int findWord(String value, int start) { + int p = start; + while (p < value.length() && Character.isLetter(value.charAt(p))) { + p++; + } + return p; + } + + // find a number start from 'start' from value. + private int findNumber(String value, int start, int maxLen) { + int p = start; + int left = maxLen; + while (p < value.length() && Character.isDigit(value.charAt(p)) && left > 0) { + p++; + left--; + } + return p; + } + + // check if the given value exist in dict, return dict value. + private int checkWord(Map dict, String value) throws InvalidFormatException { + Integer i = dict.get(value.toLowerCase()); + if (i != null) { + return i; + } + throw new InvalidFormatException("'" + value + "' is invalid"); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/InvalidFormatException.java b/fe/fe-core/src/main/java/org/apache/doris/common/InvalidFormatException.java new file mode 100644 index 00000000000000..a6a6e5f23ac01c --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/common/InvalidFormatException.java @@ -0,0 +1,24 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF 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.apache.doris.common; + +public class InvalidFormatException extends Exception { + public InvalidFormatException(String msg) { + super(msg); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/rewrite/FEFunctions.java b/fe/fe-core/src/main/java/org/apache/doris/rewrite/FEFunctions.java index 5660a8e5d7ef28..1780a6a98c0fe6 100755 --- a/fe/fe-core/src/main/java/org/apache/doris/rewrite/FEFunctions.java +++ b/fe/fe-core/src/main/java/org/apache/doris/rewrite/FEFunctions.java @@ -27,6 +27,7 @@ import org.apache.doris.analysis.StringLiteral; import org.apache.doris.catalog.Type; import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.InvalidFormatException; import org.apache.doris.common.util.TimeUtils; import com.google.common.base.Preconditions; @@ -121,7 +122,14 @@ public static StringLiteral dateFormat(LiteralExpr date, StringLiteral fmtLitera @FEFunction(name = "str_to_date", argTypes = { "VARCHAR", "VARCHAR" }, returnType = "DATETIME") public static DateLiteral dateParse(StringLiteral date, StringLiteral fmtLiteral) throws AnalysisException { - return DateLiteral.dateParser(date.getStringValue(), fmtLiteral.getStringValue()); + DateLiteral dateLiteral = new DateLiteral(); + try { + dateLiteral.fromDateFormatStr(fmtLiteral.getStringValue(), date.getStringValue(), false); + return dateLiteral; + } catch (InvalidFormatException e) { + e.printStackTrace(); + throw new AnalysisException(e.getMessage()); + } } @FEFunction(name = "date_sub", argTypes = { "DATETIME", "INT" }, returnType = "DATETIME") diff --git a/fe/fe-core/src/test/java/org/apache/doris/rewrite/FEFunctionsTest.java b/fe/fe-core/src/test/java/org/apache/doris/rewrite/FEFunctionsTest.java index 73c3afb8d44bdb..9023a4258764d7 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/rewrite/FEFunctionsTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/rewrite/FEFunctionsTest.java @@ -17,6 +17,9 @@ package org.apache.doris.rewrite; +import mockit.Expectations; +import mockit.Mocked; + import org.apache.doris.analysis.DateLiteral; import org.apache.doris.analysis.DecimalLiteral; import org.apache.doris.analysis.FloatLiteral; @@ -37,8 +40,6 @@ import java.util.Locale; import java.util.TimeZone; -import mockit.Expectations; -import mockit.Mocked; import static org.junit.Assert.fail; public class FEFunctionsTest { @@ -190,6 +191,7 @@ public void dateFormatUtilTest() { @Test public void dateParseTest() { try { + Assert.assertEquals("2019-05-09 00:00:00", FEFunctions.dateParse(new StringLiteral("2019-05-09"), new StringLiteral("%Y-%m-%d %H:%i:%s")).getStringValue()); Assert.assertEquals("2013-05-10", FEFunctions.dateParse(new StringLiteral("2013,05,10"), new StringLiteral("%Y,%m,%d")).getStringValue()); Assert.assertEquals("2013-05-17 00:35:10", FEFunctions.dateParse(new StringLiteral("2013-05-17 12:35:10"), new StringLiteral("%Y-%m-%d %h:%i:%s")).getStringValue()); Assert.assertEquals("2013-05-17 00:35:10", FEFunctions.dateParse(new StringLiteral("2013-05-17 00:35:10"), new StringLiteral("%Y-%m-%d %H:%i:%s")).getStringValue()); @@ -202,47 +204,25 @@ public void dateParseTest() { Assert.assertEquals("2019-05-09", FEFunctions.dateParse(new StringLiteral("2019,19,Thursday"), new StringLiteral("%x,%v,%W")).getStringValue()); Assert.assertEquals("2019-05-09 12:10:45", FEFunctions.dateParse(new StringLiteral("12:10:45-20190509"), new StringLiteral("%T-%Y%m%d")).getStringValue()); Assert.assertEquals("2019-05-09 09:10:45", FEFunctions.dateParse(new StringLiteral("20190509-9:10:45"), new StringLiteral("%Y%m%d-%k:%i:%S")).getStringValue()); + Assert.assertEquals("0000-00-20", FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%D")).getStringValue()); + Assert.assertEquals("0000-00-00", FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%U")).getStringValue()); + Assert.assertEquals("0000-00-00", FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%u")).getStringValue()); + Assert.assertEquals("0000-00-00", FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%V")).getStringValue()); + Assert.assertEquals("0000-00-00", FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%w")).getStringValue()); + Assert.assertEquals("0000-00-00", FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%x")).getStringValue()); + Assert.assertEquals("0000-00-00", FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%X")).getStringValue()); + Assert.assertEquals("2013-05-17 20:07:05", FEFunctions.dateParse(new StringLiteral("2013-05-17 08:07:05 PM"), new StringLiteral("%Y-%m-%d %r")).getStringValue()); + Assert.assertEquals("2013-05-17 08:07:05", FEFunctions.dateParse(new StringLiteral("2013-05-17 08:07:05"), new StringLiteral("%Y-%m-%d %T")).getStringValue()); } catch (AnalysisException e) { - fail("Junit test dateParse fail"); e.printStackTrace(); - } - - try { - FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%D")); fail("Junit test dateParse fail"); - } catch (AnalysisException e) { - Assert.assertEquals(e.getMessage(), - "errCode = 2, detailMessage = %D not supported in date format string"); - } - try { - FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%U")); - fail("Junit test dateParse fail"); - } catch (AnalysisException e) { - Assert.assertEquals(e.getMessage(), "errCode = 2, detailMessage = %U not supported in date format string"); - } - try { - FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%u")); - fail("Junit test dateParse fail"); - } catch (AnalysisException e) { - Assert.assertEquals(e.getMessage(), "errCode = 2, detailMessage = %u not supported in date format string"); - } - try { - FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%V")); - fail("Junit test dateParse fail"); - } catch (AnalysisException e) { - Assert.assertEquals(e.getMessage(), "errCode = 2, detailMessage = %V not supported in date format string"); - } - try { - FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%w")); - fail("Junit test dateParse fail"); - } catch (AnalysisException e) { - Assert.assertEquals(e.getMessage(), "errCode = 2, detailMessage = %w not supported in date format string"); } + try { - FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%X")); + FEFunctions.dateParse(new StringLiteral("2013-05-17"), new StringLiteral("%W")); fail("Junit test dateParse fail"); } catch (AnalysisException e) { - Assert.assertEquals(e.getMessage(), "errCode = 2, detailMessage = %X not supported in date format string"); + Assert.assertEquals(e.getMessage(), "errCode = 2, detailMessage = '' is invalid"); } }