Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/en/sql-reference/functions/date-time-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
4 changes: 4 additions & 0 deletions src/Common/Concepts.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&...)
{
Expand Down
3 changes: 0 additions & 3 deletions src/Common/typeid_cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/Core/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,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) \
Expand Down
1 change: 1 addition & 0 deletions src/Core/SettingsChangesHistory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
1 change: 1 addition & 0 deletions src/Functions/FunctionsConversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
729 changes: 523 additions & 206 deletions src/Functions/formatDateTime.cpp

Large diffs are not rendered by default.

82 changes: 63 additions & 19 deletions src/Functions/parseDateTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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}},
Expand Down Expand Up @@ -103,16 +103,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.
Expand All @@ -139,7 +139,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;
Expand Down Expand Up @@ -277,23 +277,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;
Expand All @@ -308,16 +308,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_)
Expand Down Expand Up @@ -466,8 +466,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; }

Expand Down Expand Up @@ -788,6 +795,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;
Expand Down Expand Up @@ -920,7 +959,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);
Expand All @@ -934,7 +973,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)
Expand Down Expand Up @@ -1532,9 +1571,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
Expand Down
1 change: 1 addition & 0 deletions src/Functions/widthBucket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
1 change: 1 addition & 0 deletions tests/queries/0_stateless/00718_format_datetime.reference
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Jan Jan
366 366
00 00
01 01
January January
33 00
\n \n
AM AM
Expand Down
3 changes: 2 additions & 1 deletion tests/queries/0_stateless/00718_format_datetime.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 %"
Expand Down
2 changes: 1 addition & 1 deletion tests/queries/0_stateless/01411_from_unixtime.reference
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Jan Jan
366 366
00 00
01 01
33 00
January January
\n \n
AM AM
AM
Expand Down
2 changes: 1 addition & 1 deletion tests/queries/0_stateless/02564_date_format.reference
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Jan Jan
366 366
00 00
01 01
33 00
January January
\n \n
AM AM
AM
Expand Down
19 changes: 19 additions & 0 deletions tests/queries/0_stateless/02668_parse_datetime.reference
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions tests/queries/0_stateless/02668_parse_datetime.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down