mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-23 16:12:01 +00:00
Merge pull request #47246 from ClickHouse/rs/m
Change the behavior of formatter %M in function formatDateTime() from minutes to month name
This commit is contained in:
commit
ce2692ba79
@ -1276,16 +1276,16 @@ Using replacement fields, you can define a pattern for the resulting string. “
|
||||
| %k | hour in 24h format (00-23) | 22 |
|
||||
| %l | hour in 12h format (01-12) | 09 |
|
||||
| %m | month as an integer number (01-12) | 01 |
|
||||
| %M | minute (00-59) | 33 |
|
||||
| %M | full month name (January-December) | January |
|
||||
| %n | new-line character (‘’) | |
|
||||
| %p | AM or PM designation | PM |
|
||||
| %Q | Quarter (1-4) | 1 |
|
||||
| %r | 12-hour HH:MM AM/PM time, equivalent to %H:%M %p | 10:30 PM |
|
||||
| %R | 24-hour HH:MM time, equivalent to %H:%M | 22:33 |
|
||||
| %r | 12-hour HH:MM AM/PM time, equivalent to %H:%i %p | 10:30 PM |
|
||||
| %R | 24-hour HH:MM time, equivalent to %H:%i | 22:33 |
|
||||
| %s | second (00-59) | 44 |
|
||||
| %S | second (00-59) | 44 |
|
||||
| %t | horizontal-tab character (’) | |
|
||||
| %T | ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S | 22:33:44 |
|
||||
| %T | ISO 8601 time format (HH:MM:SS), equivalent to %H:%i:%S | 22:33:44 |
|
||||
| %u | ISO 8601 weekday as number with Monday as 1 (1-7) | 2 |
|
||||
| %V | ISO 8601 week number (01-53) | 01 |
|
||||
| %w | weekday as a integer number with Sunday as 0 (0-6) | 2 |
|
||||
|
@ -5,6 +5,10 @@
|
||||
namespace DB
|
||||
{
|
||||
|
||||
template<typename T, typename ... U>
|
||||
concept is_any_of = (std::same_as<T, U> || ...);
|
||||
|
||||
|
||||
template <typename... T>
|
||||
concept OptionalArgument = requires(T &&...)
|
||||
{
|
||||
|
@ -18,9 +18,6 @@ namespace DB
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename ... U>
|
||||
concept is_any_of = (std::same_as<T, U> || ...);
|
||||
|
||||
|
||||
/** Checks type by comparing typeid.
|
||||
* The exact match of the type is checked. That is, cast to the ancestor will be unsuccessful.
|
||||
|
@ -467,6 +467,7 @@ class IColumn;
|
||||
M(Bool, allow_introspection_functions, false, "Allow functions for introspection of ELF and DWARF for query profiling. These functions are slow and may impose security considerations.", 0) \
|
||||
\
|
||||
M(Bool, allow_execute_multiif_columnar, true, "Allow execute multiIf function columnar", 0) \
|
||||
M(Bool, formatdatetime_parsedatetime_m_is_month_name, true, "Formatter '%M' in function 'formatDateTime' produces the month name instead of minutes.", 0) \
|
||||
\
|
||||
M(UInt64, max_partitions_per_insert_block, 100, "Limit maximum number of partitions in single INSERTed block. Zero means unlimited. Throw exception if the block contains too many partitions. This setting is a safety threshold, because using large number of partitions is a common misconception.", 0) \
|
||||
M(Int64, max_partitions_to_read, -1, "Limit the max number of partitions that can be accessed in one query. <= 0 means unlimited.", 0) \
|
||||
|
@ -101,6 +101,7 @@ static std::map<ClickHouseVersion, SettingsChangesHistory::SettingsChanges> sett
|
||||
{"query_plan_aggregation_in_order", 0, 1, "Enable some refactoring around query plan"},
|
||||
{"format_binary_max_string_size", 0, 1_GiB, "Prevent allocating large amount of memory"}}},
|
||||
{"22.11", {{"use_structure_from_insertion_table_in_table_functions", 0, 2, "Improve using structure from insertion table in table functions"}}},
|
||||
{"23.4", {{"formatdatetime_parsedatetime_m_is_month_name", false, true, "Improved compatibility with MySQL DATE_FORMAT/STR_TO_DATE"}}},
|
||||
{"22.9", {{"force_grouping_standard_compatibility", false, true, "Make GROUPING function output the same as in SQL standard and other DBMS"}}},
|
||||
{"22.7", {{"cross_to_inner_join_rewrite", 1, 2, "Force rewrite comma join to inner"},
|
||||
{"enable_positional_arguments", false, true, "Enable positional arguments feature by default"},
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include <Columns/ColumnsCommon.h>
|
||||
#include <Columns/ColumnStringHelpers.h>
|
||||
#include <Common/assert_cast.h>
|
||||
#include <Common/Concepts.h>
|
||||
#include <Common/quoteString.h>
|
||||
#include <Common/Exception.h>
|
||||
#include <Core/AccurateComparison.h>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -48,7 +48,7 @@ namespace
|
||||
const std::unordered_map<String, std::pair<String, Int32>> monthMap{
|
||||
{"jan", {"uary", 1}},
|
||||
{"feb", {"ruary", 2}},
|
||||
{"mar", {"rch", 3}},
|
||||
{"mar", {"ch", 3}},
|
||||
{"apr", {"il", 4}},
|
||||
{"may", {"", 5}},
|
||||
{"jun", {"e", 6}},
|
||||
@ -101,16 +101,16 @@ namespace
|
||||
bool is_year_of_era = false; /// If true, year is calculated from era and year of era, the latter cannot be zero or negative.
|
||||
bool has_year = false; /// Whether year was explicitly specified.
|
||||
|
||||
/// If is_clock_hour = true, is_hour_of_half_day = true, hour's range is [1, 12]
|
||||
/// If is_clock_hour = true, is_hour_of_half_day = false, hour's range is [1, 24]
|
||||
/// If is_clock_hour = false, is_hour_of_half_day = true, hour's range is [0, 11]
|
||||
/// If is_clock_hour = false, is_hour_of_half_day = false, hour's range is [0, 23]
|
||||
/// If hour_starts_at_1 = true, is_hour_of_half_day = true, hour's range is [1, 12]
|
||||
/// If hour_starts_at_1 = true, is_hour_of_half_day = false, hour's range is [1, 24]
|
||||
/// If hour_starts_at_1 = false, is_hour_of_half_day = true, hour's range is [0, 11]
|
||||
/// If hour_starts_at_1 = false, is_hour_of_half_day = false, hour's range is [0, 23]
|
||||
Int32 hour = 0;
|
||||
Int32 minute = 0; /// range [0, 59]
|
||||
Int32 second = 0; /// range [0, 59]
|
||||
|
||||
bool is_am = true; /// If is_hour_of_half_day = true and is_am = false (i.e. pm) then add 12 hours to the result DateTime
|
||||
bool is_clock_hour = false; /// Whether the hour is clockhour
|
||||
bool hour_starts_at_1 = false; /// Whether the hour is clockhour
|
||||
bool is_hour_of_half_day = false; /// Whether the hour is of half day
|
||||
|
||||
bool has_time_zone_offset = false; /// If true, time zone offset is explicitly specified.
|
||||
@ -137,7 +137,7 @@ namespace
|
||||
second = 0;
|
||||
|
||||
is_am = true;
|
||||
is_clock_hour = false;
|
||||
hour_starts_at_1 = false;
|
||||
is_hour_of_half_day = false;
|
||||
|
||||
has_time_zone_offset = false;
|
||||
@ -275,23 +275,23 @@ namespace
|
||||
throw Exception(ErrorCodes::CANNOT_PARSE_DATETIME, "Unknown half day of day: {}", text);
|
||||
}
|
||||
|
||||
void setHour(Int32 hour_, bool is_hour_of_half_day_ = false, bool is_clock_hour_ = false)
|
||||
void setHour(Int32 hour_, bool is_hour_of_half_day_ = false, bool hour_starts_at_1_ = false)
|
||||
{
|
||||
Int32 max_hour;
|
||||
Int32 min_hour;
|
||||
Int32 new_hour = hour_;
|
||||
if (!is_hour_of_half_day_ && !is_clock_hour_)
|
||||
if (!is_hour_of_half_day_ && !hour_starts_at_1_)
|
||||
{
|
||||
max_hour = 23;
|
||||
min_hour = 0;
|
||||
}
|
||||
else if (!is_hour_of_half_day_ && is_clock_hour_)
|
||||
else if (!is_hour_of_half_day_ && hour_starts_at_1_)
|
||||
{
|
||||
max_hour = 24;
|
||||
min_hour = 1;
|
||||
new_hour = hour_ % 24;
|
||||
}
|
||||
else if (is_hour_of_half_day_ && !is_clock_hour_)
|
||||
else if (is_hour_of_half_day_ && !hour_starts_at_1_)
|
||||
{
|
||||
max_hour = 11;
|
||||
min_hour = 0;
|
||||
@ -306,16 +306,16 @@ namespace
|
||||
if (hour_ < min_hour || hour_ > max_hour)
|
||||
throw Exception(
|
||||
ErrorCodes::CANNOT_PARSE_DATETIME,
|
||||
"Value {} for hour must be in the range [{}, {}] if_hour_of_half_day={} and is_clock_hour={}",
|
||||
"Value {} for hour must be in the range [{}, {}] if_hour_of_half_day={} and hour_starts_at_1={}",
|
||||
hour,
|
||||
max_hour,
|
||||
min_hour,
|
||||
is_hour_of_half_day_,
|
||||
is_clock_hour_);
|
||||
hour_starts_at_1_);
|
||||
|
||||
hour = new_hour;
|
||||
is_hour_of_half_day = is_hour_of_half_day_;
|
||||
is_clock_hour = is_clock_hour_;
|
||||
hour_starts_at_1 = hour_starts_at_1_;
|
||||
}
|
||||
|
||||
void setMinute(Int32 minute_)
|
||||
@ -464,8 +464,15 @@ namespace
|
||||
class FunctionParseDateTimeImpl : public IFunction
|
||||
{
|
||||
public:
|
||||
const bool mysql_M_is_month_name;
|
||||
|
||||
static constexpr auto name = Name::name;
|
||||
static FunctionPtr create(ContextPtr) { return std::make_shared<FunctionParseDateTimeImpl>(); }
|
||||
static FunctionPtr create(ContextPtr context) { return std::make_shared<FunctionParseDateTimeImpl>(context); }
|
||||
|
||||
explicit FunctionParseDateTimeImpl(ContextPtr context)
|
||||
: mysql_M_is_month_name(context->getSettings().formatdatetime_parsedatetime_m_is_month_name)
|
||||
{
|
||||
}
|
||||
|
||||
String getName() const override { return name; }
|
||||
|
||||
@ -768,6 +775,38 @@ namespace
|
||||
return cur;
|
||||
}
|
||||
|
||||
static Pos mysqlMonthOfYearTextLong(Pos cur, Pos end, const String & fragment, DateTime & date)
|
||||
{
|
||||
checkSpace(cur, end, 3, "mysqlMonthOfYearTextLong requires size >= 3", fragment);
|
||||
String text1(cur, 3);
|
||||
boost::to_lower(text1);
|
||||
auto it = monthMap.find(text1);
|
||||
if (it == monthMap.end())
|
||||
throw Exception(
|
||||
ErrorCodes::CANNOT_PARSE_DATETIME,
|
||||
"Unable to parse first part of fragment {} from {} because of unknown month of year text: {}",
|
||||
fragment,
|
||||
std::string_view(cur, end - cur),
|
||||
text1);
|
||||
cur += 3;
|
||||
|
||||
size_t expected_remaining_size = it->second.first.size();
|
||||
checkSpace(cur, end, expected_remaining_size, "mysqlMonthOfYearTextLong requires the second parg size >= " + std::to_string(expected_remaining_size), fragment);
|
||||
String text2(cur, expected_remaining_size);
|
||||
boost::to_lower(text2);
|
||||
if (text2 != it->second.first)
|
||||
throw Exception(
|
||||
ErrorCodes::CANNOT_PARSE_DATETIME,
|
||||
"Unable to parse second part of fragment {} from {} because of unknown month of year text: {}",
|
||||
fragment,
|
||||
std::string_view(cur, end - cur),
|
||||
text1 + text2);
|
||||
cur += expected_remaining_size;
|
||||
|
||||
date.setMonth(it->second.second);
|
||||
return cur;
|
||||
}
|
||||
|
||||
static Pos mysqlMonth(Pos cur, Pos end, const String & fragment, DateTime & date)
|
||||
{
|
||||
Int32 month;
|
||||
@ -900,7 +939,7 @@ namespace
|
||||
|
||||
static Pos mysqlDayOfWeekTextLong(Pos cur, Pos end, const String & fragment, DateTime & date)
|
||||
{
|
||||
checkSpace(cur, end, 6, "jodaDayOfWeekText requires size >= 6", fragment);
|
||||
checkSpace(cur, end, 6, "mysqlDayOfWeekTextLong requires size >= 6", fragment);
|
||||
String text1(cur, 3);
|
||||
boost::to_lower(text1);
|
||||
auto it = dayOfWeekMap.find(text1);
|
||||
@ -914,7 +953,7 @@ namespace
|
||||
cur += 3;
|
||||
|
||||
size_t expected_remaining_size = it->second.first.size();
|
||||
checkSpace(cur, end, expected_remaining_size, "jodaDayOfWeekText requires the second parg size >= " + std::to_string(expected_remaining_size), fragment);
|
||||
checkSpace(cur, end, expected_remaining_size, "mysqlDayOfWeekTextLong requires the second parg size >= " + std::to_string(expected_remaining_size), fragment);
|
||||
String text2(cur, expected_remaining_size);
|
||||
boost::to_lower(text2);
|
||||
if (text2 != it->second.first)
|
||||
@ -1512,9 +1551,14 @@ namespace
|
||||
instructions.emplace_back(ACTION_ARGS(Instruction::mysqlTimezoneOffset));
|
||||
break;
|
||||
|
||||
// Minute (00-59)
|
||||
// Depending on a setting
|
||||
// - Full month [January...December]
|
||||
// - Minute (00-59) OR
|
||||
case 'M':
|
||||
instructions.emplace_back(ACTION_ARGS(Instruction::mysqlMinute));
|
||||
if (mysql_M_is_month_name)
|
||||
instructions.emplace_back(ACTION_ARGS(Instruction::mysqlMonthOfYearTextLong));
|
||||
else
|
||||
instructions.emplace_back(ACTION_ARGS(Instruction::mysqlMinute));
|
||||
break;
|
||||
|
||||
// AM or PM
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <Functions/IFunction.h>
|
||||
#include <Interpreters/Context.h>
|
||||
#include <Interpreters/castColumn.h>
|
||||
#include <Common/Concepts.h>
|
||||
#include <Common/Exception.h>
|
||||
#include <Common/NaNUtils.h>
|
||||
#include <Common/register_objects.h>
|
||||
|
@ -17,6 +17,7 @@ Jan Jan
|
||||
366 366
|
||||
00 00
|
||||
01 01
|
||||
January January
|
||||
33 00
|
||||
\n \n
|
||||
AM AM
|
||||
|
@ -29,7 +29,8 @@ SELECT formatDateTime(toDateTime('2018-01-01 00:33:44'), '%j'), formatDateTime(t
|
||||
SELECT formatDateTime(toDateTime('2000-12-31 00:33:44'), '%j'), formatDateTime(toDate32('2000-12-31'), '%j');
|
||||
SELECT formatDateTime(toDateTime('2000-12-31 00:33:44'), '%k'), formatDateTime(toDate32('2000-12-31'), '%k');
|
||||
SELECT formatDateTime(toDateTime('2018-01-02 22:33:44'), '%m'), formatDateTime(toDate32('2018-01-02'), '%m');
|
||||
SELECT formatDateTime(toDateTime('2018-01-02 22:33:44'), '%M'), formatDateTime(toDate32('2018-01-02'), '%M');
|
||||
SELECT formatDateTime(toDateTime('2018-01-02 22:33:44'), '%M'), formatDateTime(toDate32('2018-01-02'), '%M') SETTINGS formatdatetime_parsedatetime_m_is_month_name = 1;
|
||||
SELECT formatDateTime(toDateTime('2018-01-02 22:33:44'), '%M'), formatDateTime(toDate32('2018-01-02'), '%M') SETTINGS formatdatetime_parsedatetime_m_is_month_name = 0;
|
||||
SELECT formatDateTime(toDateTime('2018-01-02 22:33:44'), '%n'), formatDateTime(toDate32('2018-01-02'), '%n');
|
||||
SELECT formatDateTime(toDateTime('2018-01-02 00:33:44'), '%p'), formatDateTime(toDateTime('2018-01-02'), '%p');
|
||||
SELECT formatDateTime(toDateTime('2018-01-02 11:33:44'), '%p');
|
||||
|
@ -83,7 +83,7 @@ CAST(N as DateTime64(9, 'Europe/Minsk'))
|
||||
# CAST(N as DateTime64(12, 'Asia/Istanbul'))
|
||||
# DateTime64(18) will always fail due to zero precision, but it is Ok to test here:
|
||||
# CAST(N as DateTime64(18, 'Asia/Istanbul'))
|
||||
formatDateTime(N, '%C %d %D %e %F %H %I %j %m %M %p %R %S %T %u %V %w %y %Y %%', 'Asia/Istanbul')
|
||||
formatDateTime(N, '%C %d %D %e %F %H %I %j %m %i %p %R %S %T %u %V %w %y %Y %%', 'Asia/Istanbul')
|
||||
""".splitlines()
|
||||
|
||||
# Expanded later to cartesian product of all arguments, using format string.
|
||||
|
@ -353,7 +353,7 @@ SELECT CAST(N as DateTime64(9, \'Europe/Minsk\'))
|
||||
"DateTime64(9, 'Europe/Minsk')","2019-09-16 19:20:11.000000000"
|
||||
"DateTime64(9, 'Europe/Minsk')","2019-09-16 19:20:11.234000000"
|
||||
------------------------------------------
|
||||
SELECT formatDateTime(N, \'%C %d %D %e %F %H %I %j %m %M %p %R %S %T %u %V %w %y %Y %%\', \'Asia/Istanbul\')
|
||||
SELECT formatDateTime(N, \'%C %d %D %e %F %H %I %j %m %i %p %R %S %T %u %V %w %y %Y %%\', \'Asia/Istanbul\')
|
||||
"String","20 16 09/16/19 16 2019-09-16 00 12 259 09 00 AM 00:00 00 00:00:00 1 38 1 19 2019 %"
|
||||
"String","20 16 09/16/19 16 2019-09-16 19 07 259 09 20 PM 19:20 11 19:20:11 1 38 1 19 2019 %"
|
||||
"String","20 16 09/16/19 16 2019-09-16 19 07 259 09 20 PM 19:20 11 19:20:11 1 38 1 19 2019 %"
|
||||
|
@ -24,7 +24,7 @@ Jan Jan
|
||||
366 366
|
||||
00 00
|
||||
01 01
|
||||
33 00
|
||||
January January
|
||||
\n \n
|
||||
AM AM
|
||||
AM
|
||||
|
@ -17,7 +17,7 @@ Jan Jan
|
||||
366 366
|
||||
00 00
|
||||
01 01
|
||||
33 00
|
||||
January January
|
||||
\n \n
|
||||
AM AM
|
||||
AM
|
||||
|
@ -26,6 +26,25 @@ select parseDateTime('jun', '%b', 'UTC') = toDateTime('2000-06-01', 'UTC');
|
||||
select parseDateTime('JUN', '%b', 'UTC') = toDateTime('2000-06-01', 'UTC');
|
||||
1
|
||||
select parseDateTime('abc', '%b'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
set formatdatetime_parsedatetime_m_is_month_name = 1;
|
||||
select parseDateTime('may', '%M', 'UTC') = toDateTime('2000-05-01', 'UTC');
|
||||
1
|
||||
select parseDateTime('MAY', '%M', 'UTC') = toDateTime('2000-05-01', 'UTC');
|
||||
1
|
||||
select parseDateTime('september', '%M', 'UTC') = toDateTime('2000-09-01', 'UTC');
|
||||
1
|
||||
select parseDateTime('summer', '%M'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
set formatdatetime_parsedatetime_m_is_month_name = 0;
|
||||
select parseDateTime('08', '%M', 'UTC') = toDateTime('1970-01-01 00:08:00', 'UTC');
|
||||
1
|
||||
select parseDateTime('59', '%M', 'UTC') = toDateTime('1970-01-01 00:59:00', 'UTC');
|
||||
1
|
||||
select parseDateTime('00/', '%M/', 'UTC') = toDateTime('1970-01-01 00:00:00', 'UTC');
|
||||
1
|
||||
select parseDateTime('60', '%M', 'UTC'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
select parseDateTime('-1', '%M', 'UTC'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
select parseDateTime('123456789', '%M', 'UTC'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
set formatdatetime_parsedatetime_m_is_month_name = 1;
|
||||
-- day of month
|
||||
select parseDateTime('07', '%d', 'UTC') = toDateTime('2000-01-07', 'UTC');
|
||||
1
|
||||
|
@ -18,6 +18,19 @@ select parseDateTime('12345', '%c'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
select parseDateTime('jun', '%b', 'UTC') = toDateTime('2000-06-01', 'UTC');
|
||||
select parseDateTime('JUN', '%b', 'UTC') = toDateTime('2000-06-01', 'UTC');
|
||||
select parseDateTime('abc', '%b'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
set formatdatetime_parsedatetime_m_is_month_name = 1;
|
||||
select parseDateTime('may', '%M', 'UTC') = toDateTime('2000-05-01', 'UTC');
|
||||
select parseDateTime('MAY', '%M', 'UTC') = toDateTime('2000-05-01', 'UTC');
|
||||
select parseDateTime('september', '%M', 'UTC') = toDateTime('2000-09-01', 'UTC');
|
||||
select parseDateTime('summer', '%M'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
set formatdatetime_parsedatetime_m_is_month_name = 0;
|
||||
select parseDateTime('08', '%M', 'UTC') = toDateTime('1970-01-01 00:08:00', 'UTC');
|
||||
select parseDateTime('59', '%M', 'UTC') = toDateTime('1970-01-01 00:59:00', 'UTC');
|
||||
select parseDateTime('00/', '%M/', 'UTC') = toDateTime('1970-01-01 00:00:00', 'UTC');
|
||||
select parseDateTime('60', '%M', 'UTC'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
select parseDateTime('-1', '%M', 'UTC'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
select parseDateTime('123456789', '%M', 'UTC'); -- { serverError CANNOT_PARSE_DATETIME }
|
||||
set formatdatetime_parsedatetime_m_is_month_name = 1;
|
||||
|
||||
-- day of month
|
||||
select parseDateTime('07', '%d', 'UTC') = toDateTime('2000-01-07', 'UTC');
|
||||
|
Loading…
Reference in New Issue
Block a user