diff --git a/dbms/src/Common/MyTime.cpp b/dbms/src/Common/MyTime.cpp index 9727c163741..deb90252135 100644 --- a/dbms/src/Common/MyTime.cpp +++ b/dbms/src/Common/MyTime.cpp @@ -1267,21 +1267,15 @@ static std::tuple parseNDigits(const StringRef & view, const si static std::tuple parseYearNDigits(const StringRef & view, const size_t pos, const size_t limit) { - int32_t effective_count = 0; - int32_t effective_value = 0; - while (static_cast(effective_count + 1) <= limit) - { - auto [step, num] = parseNDigits(view, pos, effective_count + 1); - if (step == 0) - break; - effective_count++; - effective_value = num; - } - if (effective_count == 0) - return std::make_tuple(effective_count, 0); - else if (effective_count <= 2) - effective_value = adjustYear(effective_value); - return std::make_tuple(effective_count, effective_value); + // Try to parse a "year" within `limit` digits + size_t step = 0; + int32_t year = 0; + std::tie(step, year) = parseNDigits(view, pos, limit); + if (step == 0) + return std::make_tuple(step, 0); + else if (step <= 2) + year = adjustYear(year); + return std::make_tuple(step, year); } enum class ParseState diff --git a/dbms/src/Common/tests/gtest_mytime.cpp b/dbms/src/Common/tests/gtest_mytime.cpp index 3a363111f99..31647d0787c 100644 --- a/dbms/src/Common/tests/gtest_mytime.cpp +++ b/dbms/src/Common/tests/gtest_mytime.cpp @@ -230,7 +230,7 @@ try {" 2/Jun", "%d/%b/%Y", MyDateTime{0, 6, 2, 0, 0, 0, 0}}, // More patterns than input string {" liter", "lit era l", MyDateTime{0, 0, 0, 0, 0, 0, 0}}, // More patterns than input string // Test case for empty input - {" ", " ", MyDateTime{0, 0, 0, 0, 0, 0, 0}}, + {" ", " ", MyDateTime{0, 0, 0, 0, 0, 0, 0}}, // {" ", "%d/%b/%Y", MyDateTime{0, 0, 0, 0, 0, 0, 0}}, // Prefix white spaces should be ignored {" 2/Jun/2019 ", "%d/%b/%Y", MyDateTime{2019, 6, 2, 0, 0, 0, 0}}, @@ -248,8 +248,8 @@ try MyDateTime{2016, 2, 30, 12, 34, 56, 123400}}, // Feb 30th (not exist in actual) is valid for parsing (in mariadb) {"31/April/2016 12:34:56.", "%d/%M/%Y %H:%i:%s.%f", MyDateTime{2016, 4, 31, 12, 34, 56, 0}}, // April 31th (not exist in actual) {"01,5,2013 9", "%d,%c,%Y %f", MyDateTime{2013, 5, 1, 0, 0, 0, 900000}}, - {"01,52013", "%d,%c%Y", std::nullopt}, // %c will try to parse '52' as month and fail - {"01,5,2013", "%d,%c,%Y", MyDateTime{2013, 5, 1, 0, 0, 0, 0}}, + {"01,52013", "%d,%c%Y", std::nullopt}, // %c will try to parse '52' as month and fail + {"01,5,2013", "%d,%c,%Y", MyDateTime{2013, 5, 1, 0, 0, 0, 0}}, // {"01,5,2013 ", "%d,%c,%Y %f", MyDateTime{2013, 5, 1, 0, 0, 0, 0}}, /// Test cases for AM/PM set @@ -280,9 +280,9 @@ try {"12: 13:56 AM 13/05/2019", "%r%d/%c/%Y", MyDateTime{2019, 5, 13, 0, 13, 56, 0}}, {"12:13 :56 pm 13/05/2019", "%r %d/%c/%Y", MyDateTime{2019, 5, 13, 12, 13, 56, 0}}, {"11:13: 56pm 13/05/2019", "%r %d/%c/%Y", MyDateTime{2019, 5, 13, 23, 13, 56, 0}}, - {"11:13:56a", "%r", std::nullopt}, // EOF while parsing "AM"/"PM" - {"11:13", "%r", MyDateTime{0, 0, 0, 11, 13, 0, 0}}, - {"11:", "%r", MyDateTime{0, 0, 0, 11, 0, 0, 0}}, + {"11:13:56a", "%r", std::nullopt}, // EOF while parsing "AM"/"PM" + {"11:13", "%r", MyDateTime{0, 0, 0, 11, 13, 0, 0}}, // + {"11:", "%r", MyDateTime{0, 0, 0, 11, 0, 0, 0}}, // {"12", "%r", MyDateTime{0, 0, 0, 0, 0, 0, 0}}, /// Test cases for %T @@ -290,7 +290,7 @@ try {"23: 13:56 13/05/2019", "%T%d/%c/%Y", MyDateTime{2019, 5, 13, 23, 13, 56, 0}}, {"12:13 :56 13/05/2019", "%T %d/%c/%Y", MyDateTime{2019, 5, 13, 12, 13, 56, 0}}, {"19:13: 56 13/05/2019", "%T %d/%c/%Y", MyDateTime{2019, 5, 13, 19, 13, 56, 0}}, - {"21:13", "%T", MyDateTime{0, 0, 0, 21, 13, 0, 0}}, + {"21:13", "%T", MyDateTime{0, 0, 0, 21, 13, 0, 0}}, // {"21:", "%T", MyDateTime{0, 0, 0, 21, 0, 0, 0}}, // mutiple chars between pattern @@ -301,16 +301,15 @@ try {"01/Feb /2016 ab cdefg 23: 45:54", "%d /%b/%Y abc defg %H:%i :%S", MyDateTime{2016, 2, 1, 23, 45, 54, 0}}, /// Cases collect from MySQL 8.0 document - {"01,5,2013", "%d,%m,%Y", MyDateTime{2013, 5, 1, 0, 0, 0, 0}}, - {"May 1, 2013", "%M %d,%Y", MyDateTime{2013, 5, 1, 0, 0, 0, 0}}, - {"a09:30:17", "a%h:%i:%s", MyDateTime{0, 0, 0, 9, 30, 17, 0}}, - {"a09:30:17", "%h:%i:%s", std::nullopt}, - {"09:30:17a", "%h:%i:%s", MyDateTime{0, 0, 0, 9, 30, 17, 0}}, - {"abc", "abc", MyDateTime{0, 0, 0, 0, 0, 0, 0}}, - {"9", "%m", MyDateTime{0, 9, 0, 0, 0, 0, 0}}, - {"9", "%s", MyDateTime{0, 0, 0, 0, 0, 9, 0}}, - // Range checking on the parts of date values is as described in Section 11.2.2, “The DATE, DATETIME, and TIMESTAMP Types”. This means, for example, that “zero” dates or dates with part values of 0 are permitted unless the SQL mode is set to disallow such values. - {"00/00/0000", "%m/%d/%Y", MyDateTime{0, 0, 0, 0, 0, 0, 0}}, + {"01,5,2013", "%d,%m,%Y", MyDateTime{2013, 5, 1, 0, 0, 0, 0}}, // + {"May 1, 2013", "%M %d,%Y", MyDateTime{2013, 5, 1, 0, 0, 0, 0}}, // + {"a09:30:17", "a%h:%i:%s", MyDateTime{0, 0, 0, 9, 30, 17, 0}}, // + {"a09:30:17", "%h:%i:%s", std::nullopt}, // + {"09:30:17a", "%h:%i:%s", MyDateTime{0, 0, 0, 9, 30, 17, 0}}, // + {"abc", "abc", MyDateTime{0, 0, 0, 0, 0, 0, 0}}, // + {"9", "%m", MyDateTime{0, 9, 0, 0, 0, 0, 0}}, // + {"9", "%s", MyDateTime{0, 0, 0, 0, 0, 9, 0}}, // + {"00/00/0000", "%m/%d/%Y", MyDateTime{0, 0, 0, 0, 0, 0, 0}}, // {"04/31/2004", "%m/%d/%Y", MyDateTime{2004, 4, 31, 0, 0, 0, 0}}, /// Below cases are ported from TiDB @@ -337,6 +336,63 @@ try {"abcd-2020-10-10.1", "%@-%Y-%m-%d%.%#%@", MyDateTime(2020, 10, 10, 0, 0, 0, 0)}, {"2020-10-10", "%Y-%m-%d%@", MyDateTime(2020, 10, 10, 0, 0, 0, 0)}, {"2020-10-10abcde123abcdef", "%Y-%m-%d%@%#", MyDateTime(2020, 10, 10, 0, 0, 0, 0)}, + + /// Cases ported from mysql testing by executing following SQL in mysql + /// create table t1 (date char(30) COLLATE latin1_bin, format char(30) COLLATE latin1_bin not null); + /// insert into t1 values (...),... + /// select if( str_to_date is not null, concat( '{"', date, '", "', format, '", ', concat( "MyDateTime{", year(str_to_date), date_format(str_to_date, ",%c,%e,"), hour(str_to_date), ",", minute(str_to_date), ",", second(str_to_date), ",", MICROSECOND(str_to_date), "}" ), '}, //' ), concat( '{"', date, '", "', format, '", std::nullopt}, //' )) as s from ( select date, format, str_to_date(date, format) as str_to_date from t1 ) a group by date, format, str_to_date order by date; + {"0003-01-02 8:11:2.123456", "%Y-%m-%d %H:%i:%S.%#", MyDateTime{3, 1, 2, 8, 11, 2, 0}}, // + {"03-01-02 8:11:2.123456", "%Y-%m-%d %H:%i:%S.%#", MyDateTime{2003, 1, 2, 8, 11, 2, 0}}, // + {"03-01-02 8:11:2.123456", "%y-%m-%d %H:%i:%S.%#", MyDateTime{2003, 1, 2, 8, 11, 2, 0}}, // + {"10:20:10", "%H:%i:%s", MyDateTime{0, 0, 0, 10, 20, 10, 0}}, // + {"10:20:10", "%T", MyDateTime{0, 0, 0, 10, 20, 10, 0}}, // + {"10:20:10", "%h:%i:%s.%f", MyDateTime{0, 0, 0, 10, 20, 10, 0}}, // + {"10:20:10.44AM", "%h:%i:%s.%f%p", MyDateTime{0, 0, 0, 10, 20, 10, 440000}}, // + {"10:20:10AM", "%h:%i:%s%p", MyDateTime{0, 0, 0, 10, 20, 10, 0}}, // + {"10:20:10AM", "%r", MyDateTime{0, 0, 0, 10, 20, 10, 0}}, // + {"15 MAY 2001", "%d %b %Y", MyDateTime{2001, 5, 15, 0, 0, 0, 0}}, // + // {"15 SEPTEMB 2001", "%d %M %Y", MyDateTime{2001, 9, 15, 0, 0, 0, 0}}, // The SEPTEMB is a broken string of 'SEPTEMBER', ignore this case + {"15 September 2001", "%d %M %Y", MyDateTime{2001, 9, 15, 0, 0, 0, 0}}, // + {"15-01-20", "%d-%m-%y", MyDateTime{2020, 1, 15, 0, 0, 0, 0}}, // + {"15-01-2001", "%d-%m-%Y %H:%i:%S", MyDateTime{2001, 1, 15, 0, 0, 0, 0}}, // + {"15-01-2001 12:59:58", "%d-%m-%Y %H:%i:%S", MyDateTime{2001, 1, 15, 12, 59, 58, 0}}, // + {"15-2001-1", "%d-%Y-%c", MyDateTime{2001, 1, 15, 0, 0, 0, 0}}, // + {"2003-01-02 01:11:12.12345AM", "%Y-%m-%d %h:%i:%S.%f%p", MyDateTime{2003, 1, 2, 1, 11, 12, 123450}}, // + {"2003-01-02 02:11:12.12345AM", "%Y-%m-%d %h:%i:%S.%f %p", MyDateTime{2003, 1, 2, 2, 11, 12, 123450}}, // + {"2003-01-02 10:11:12", "%Y-%m-%d %H:%i:%S", MyDateTime{2003, 1, 2, 10, 11, 12, 0}}, // + {"2003-01-02 10:11:12 PM", "%Y-%m-%d %h:%i:%S %p", MyDateTime{2003, 1, 2, 22, 11, 12, 0}}, // + {"2003-01-02 11:11:12Pm", "%Y-%m-%d %h:%i:%S%p", MyDateTime{2003, 1, 2, 23, 11, 12, 0}}, // + {"2003-01-02 12:11:12.12345 am", "%Y-%m-%d %h:%i:%S.%f%p", MyDateTime{2003, 1, 2, 0, 11, 12, 123450}}, // + // some cases that are not implemented + // {"060 2004", "%j %Y", MyDateTime{2004, 2, 29, 0, 0, 0, 0}}, // + // {"15th May 2001", "%D %b %Y", MyDateTime{2001, 5, 15, 0, 0, 0, 0}}, // + // {"4 53 1998", "%w %u %Y", MyDateTime{1998, 12, 31, 0, 0, 0, 0}}, // + // {"Sund 15 MAY 2001", "%W %d %b %Y", MyDateTime{2001, 5, 15, 0, 0, 0, 0}}, // + // {"Sunday 01 2001", "%W %v %x", MyDateTime{2001, 1, 7, 0, 0, 0, 0}}, // + // {"Sunday 15 MAY 2001", "%W %d %b %Y", MyDateTime{2001, 5, 15, 0, 0, 0, 0}}, // + // {"Thursday 53 1998", "%W %u %Y", MyDateTime{1998, 12, 31, 0, 0, 0, 0}}, // + // {"Tuesday 00 2002", "%W %U %Y", MyDateTime{2002, 1, 1, 0, 0, 0, 0}}, // + // {"Tuesday 52 2001", "%W %V %X", MyDateTime{2002, 1, 1, 0, 0, 0, 0}}, // + // Test 'maybe' date formats and 'strange but correct' results + {"03-01-02 10:11:12 PM", "%Y-%m-%d %h:%i:%S %p", MyDateTime{2003, 1, 2, 22, 11, 12, 0}}, // + {"10:20:10AM", "%h:%i:%s", MyDateTime{0, 0, 0, 10, 20, 10, 0}}, // + {"2003-01-02 10:11:12", "%Y-%m-%d %h:%i:%S", MyDateTime{2003, 1, 2, 10, 11, 12, 0}}, // + // Test wrong dates or converion specifiers + {"10:20:10AM", "%H:%i:%s%p", std::nullopt}, // + {"15 Ju 2001", "%d %M %Y", std::nullopt}, // + {"15 Septembei 2001", "%d %M %Y", std::nullopt}, // + {"2003-01-02 10:11:12 PM", "%Y-%m-%d %H:%i:%S %p", std::nullopt}, // + {"2003-01-02 10:11:12 PM", "%y-%m-%d %H:%i:%S %p", std::nullopt}, // + {"2003-01-02 10:11:12.123456", "%Y-%m-%d %h:%i:%S %p", std::nullopt}, // + {"2003-01-02 10:11:12AM", "%Y-%m-%d %h:%i:%S.%f %p", std::nullopt}, // + {"2003-01-02 10:11:12AN", "%Y-%m-%d %h:%i:%S%p", std::nullopt}, // + // {"7 53 1998", "%w %u %Y", std::nullopt}, // + // {"Sund 15 MA", "%W %d %b %Y", std::nullopt}, // + // {"Sunday 01 2001", "%W %v %X", std::nullopt}, // + // {"Thursdai 12 1998", "%W %u %Y", std::nullopt}, // + // {"Tuesday 52 2001", "%W %V %Y", std::nullopt}, // + // {"Tuesday 52 2001", "%W %V %x", std::nullopt}, // + // {"Tuesday 52 2001", "%W %u %x", std::nullopt}, // }; auto result_formatter = MyDateTimeFormatter("%Y/%m/%d %T.%f"); size_t idx = 0;