diff --git a/docs/en/sql-reference/functions/other-functions.md b/docs/en/sql-reference/functions/other-functions.md index b69a0693ed5..f70f0841421 100644 --- a/docs/en/sql-reference/functions/other-functions.md +++ b/docs/en/sql-reference/functions/other-functions.md @@ -538,26 +538,51 @@ SELECT └────────────────┴───────────────────┘ ``` -## formatReadableTimeDelta(x) {#formatreadabletimedeltax} +## formatReadableTimeDelta {#formatreadabletimedelta} Accepts the time delta in seconds. Returns a time delta with (year, month, day, hour, minute, second) as a string. +**Syntax** + +``` sql +formatReadableTimeDelta(column[, maximum_unit]) +``` + +**Parameters** + +- `column` — A column with numeric time delta. +- `maximum_unit` — Optional. Maximum unit to show. Acceptable values seconds, minutes, hours, days, months, years. + Example: ``` sql SELECT - arrayJoin([100, 12345, 432546534]) AS number, - formatReadableTimeDelta(number) AS time_delta + arrayJoin([100, 12345, 432546534]) AS elapsed, + formatReadableTimeDelta(elapsed) AS time_delta ``` ``` text -┌─────number─┬─time_delta ─────────────────────────────────────────────────────┐ +┌────elapsed─┬─time_delta ─────────────────────────────────────────────────────┐ │ 100 │ 1 minute and 40 seconds │ │ 12345 │ 3 hours, 25 minutes and 45 seconds │ │ 432546534 │ 13 years, 8 months, 17 days, 7 hours, 48 minutes and 54 seconds │ └────────────┴─────────────────────────────────────────────────────────────────┘ ``` +``` sql +SELECT + arrayJoin([100, 12345, 432546534]) AS elapsed, + formatReadableTimeDelta(elapsed, 'minutes') AS time_delta +``` + +``` text +┌────elapsed─┬─time_delta ─────────────────────────────────────────────────────┐ +│ 100 │ 1 minute and 40 seconds │ +│ 12345 │ 205 minutes and 45 seconds │ +│ 432546534 │ 7209108 minutes and 54 seconds │ +└────────────┴─────────────────────────────────────────────────────────────────┘ +``` + ## least(a, b) {#leasta-b} Returns the smallest value from a and b. diff --git a/src/Functions/FunctionsFormatting.h b/src/Functions/FunctionsFormatting.h index 3c48772d840..ab5166bac46 100644 --- a/src/Functions/FunctionsFormatting.h +++ b/src/Functions/FunctionsFormatting.h @@ -18,6 +18,7 @@ namespace DB namespace ErrorCodes { extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int ILLEGAL_COLUMN; } @@ -285,23 +286,44 @@ public: static constexpr auto name = "formatReadableTimeDelta"; static FunctionPtr create(const Context &) { return std::make_shared(); } - String getName() const override - { - return name; - } + String getName() const override { return name; } - size_t getNumberOfArguments() const override { return 1; } + bool isVariadic() const override { return true; } + + size_t getNumberOfArguments() const override { return 0; } DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override { + if (arguments.size() < 1) + throw Exception( + "Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size()) + + ", should be at least 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + if (arguments.size() > 2) + throw Exception( + "Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size()) + + ", should be at most 2.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + const IDataType & type = *arguments[0]; if (!isNativeNumber(type)) throw Exception("Cannot format " + type.getName() + " as time delta", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + if (arguments.size() == 2) + { + const auto * maximum_unit_arg = arguments[1].get(); + if (!isStringOrFixedString(maximum_unit_arg)) + throw Exception("Illegal type " + maximum_unit_arg->getName() + " of argument maximum_unit of function " + + getName(), ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + return std::make_shared(); } + ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {1}; } + bool useDefaultImplementationForConstants() const override { return true; } void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t /*input_rows_count*/) const override @@ -322,46 +344,73 @@ public: } private: - void formatReadableTimeDelta(double value, DB::WriteBuffer & out) const + + void formatReadableTimeDelta(double value, String maximum_unit, DB::WriteBuffer & out) const { - // 60 SECONDS = 1 MINUTE - // 3600 SECONDS = 1 HOUR - // 86400 SECONDS = 1 DAY - // 30.5 DAYS = 1 MONTH - // 365 DAYS = 1 YEAR + // 60 SECONDS = 1 MINUTE + // 3600 SECONDS = 1 HOUR + // 86400 SECONDS = 1 DAY + // 2635200 SECONDS = 30.5 DAYS = 1 MONTH + // 31536000 SECONDS = 365 DAYS = 1 YEAR char sig = value<0?-1:1; + int maximum_unit_int = 6; + + if (maximum_unit == "seconds") + maximum_unit_int = 1; + else if (maximum_unit == "minutes") + maximum_unit_int = 2; + else if (maximum_unit == "hours") + maximum_unit_int = 3; + else if (maximum_unit == "days") + maximum_unit_int = 4; + else if (maximum_unit == "months") + maximum_unit_int = 5; + else if (maximum_unit == "years") + maximum_unit_int = 6; + value *= sig; - long long int date = value / 86400; - double time = value - date * 86400; + double aux = 0; - long long int hours = time / 3600; - long long int minutes = (time - hours * 3600) / 60; - double seconds = time - hours * 3600 - minutes * 60; - - long long int years = date / 365; - long long int months = (date - years * 365) / 30.5; - long long int days = date - years * 365 - months * 30.5; + long long int years = maximum_unit_int < 6 ? 0 : value / 31536000; + aux += years * 31536000; + long long int months = maximum_unit_int < 5 ? 0 : (value - aux) / 2635200; + aux += months * 2635200; + long long int days = maximum_unit_int < 4 ? 0 : (value - aux) / 86400; + aux += days * 86400; + long long int hours = maximum_unit_int < 3 ? 0 : (value - aux) / 3600; + aux += hours * 3600; + long long int minutes = maximum_unit_int < 2 ? 0 : (value - aux) / 60; + aux += minutes * 60; + double seconds = maximum_unit_int < 1 ? 0 : value - aux; std::vector parts; - if (years) + /* If value is bigger than 2**64 (292471208677 years) overflow happens + To prevent wrong results the function shows only the year + and maximum_unit is ignored + */ + if (value > 9223372036854775808.0) { - parts.push_back(std::to_string(years) + (years==1?" year":" years")); + std::string years_str = std::to_string(value/31536000.0); + years_str.erase(years_str.find('.'), std::string::npos); + parts.push_back(years_str + " years"); } - if (months) - { - parts.push_back(std::to_string(months) + (months==1?"month":" months")); - } - if (days) - { - parts.push_back(std::to_string(days) + (days==1?" day":" days")); - } - // Test overflow 2^64 - // If overflow happens then hide the time - if (time < 9223372036854775808.0) + else { + if (years) + { + parts.push_back(std::to_string(years) + (years==1?" year":" years")); + } + if (months) + { + parts.push_back(std::to_string(months) + (months==1?"month":" months")); + } + if (days) + { + parts.push_back(std::to_string(days) + (days==1?" day":" days")); + } if (hours) { parts.push_back(std::to_string(hours) + (hours==1?" hour":" hours")); @@ -379,7 +428,6 @@ private: } } - String str_value; for(size_t i=0; i bool executeType(Block & block, const ColumnNumbers & arguments, size_t result) const { + String maximum_unit = ""; + if (arguments.size() == 2) + { + const ColumnPtr & maximum_unit_column = block.getByPosition(arguments[1]).column; + if (const ColumnConst * maximum_unit_const_col = checkAndGetColumnConstStringOrFixedString(maximum_unit_column.get())) + maximum_unit = maximum_unit_const_col->getValue(); + else + throw Exception( + "Illegal column " + maximum_unit_const_col->getName() + " of argument of function " + getName(), ErrorCodes::ILLEGAL_COLUMN); + } + if (const ColumnVector * col_from = checkAndGetColumn>(block.getByPosition(arguments[0]).column.get())) { auto col_to = ColumnString::create(); @@ -415,7 +474,7 @@ private: for (size_t i = 0; i < size; ++i) { - formatReadableTimeDelta(static_cast(vec_from[i]), buf_to); + formatReadableTimeDelta(static_cast(vec_from[i]), maximum_unit, buf_to); writeChar(0, buf_to); offsets_to[i] = buf_to.count(); } diff --git a/tests/queries/0_stateless/01511_format_readable_timedelta.reference b/tests/queries/0_stateless/01511_format_readable_timedelta.reference index 04a41c8e08a..5e67a51a190 100755 --- a/tests/queries/0_stateless/01511_format_readable_timedelta.reference +++ b/tests/queries/0_stateless/01511_format_readable_timedelta.reference @@ -1,30 +1,30 @@ -1 second 1 second -2 seconds 2 seconds -7 seconds 7 seconds -20 seconds 20 seconds -54 seconds 54 seconds -2 minutes and 28 seconds 2 minutes and 28 seconds -6 minutes and 43 seconds 6 minutes and 43 seconds -18 minutes and 16 seconds 18 minutes and 16 seconds -49 minutes and 40 seconds 49 minutes and 40 seconds -2 hours, 15 minutes and 3 seconds 2 hours, 15 minutes and 3 seconds -6 hours, 7 minutes and 6 seconds 6 hours, 7 minutes and 6 seconds -16 hours, 37 minutes and 54 seconds 16 hours, 37 minutes and 54 seconds -1 day, 21 hours, 12 minutes and 34 seconds 1 day, 21 hours, 12 minutes and 34 seconds -5 days, 2 hours, 53 minutes and 33 seconds 5 days, 2 hours, 53 minutes and 33 seconds -13 days, 22 hours, 3 minutes and 24 seconds 13 days, 22 hours, 3 minutes and 24 seconds -1month, 6 days, 20 hours, 3 minutes and 37 seconds 1month, 6 days, 20 hours, 3 minutes and 37 seconds -3 months, 10 days, 20 hours, 21 minutes and 50 seconds 3 months, 10 days, 20 hours, 21 minutes and 50 seconds -9 months, 4 days, 13 hours, 42 minutes and 32 seconds 9 months, 4 days, 13 hours, 42 minutes and 32 seconds -2 years, 29 days, 22 hours, 52 minutes and 49 seconds 2 years, 29 days, 22 hours, 52 minutes and 49 seconds -5 years, 7 months, 26 days, 18 hours and 25 minutes 5 years, 7 months, 26 days, 18 hours and 25 minutes -15 years, 4 months, 18 days, 8 hours, 6 minutes and 35 seconds 15 years, 4 months, 18 days, 8 hours, 6 minutes and 35 seconds -41 years, 9 months, 24 days, 1 hour, 42 minutes and 14 seconds 41 years, 9 months, 24 days, 1 hour, 42 minutes and 14 seconds -113 years, 8 months, 3 days, 1 hour, 7 minutes and 26 seconds - 68 years, 1month, 4 days, 3 hours, 14 minutes and 8 seconds -309 years, 2 days, 1 hour, 50 minutes and 46 seconds - 68 years, 1month, 4 days, 3 hours, 14 minutes and 8 seconds -839 years, 11 months, 16 days, 1 hour, 28 minutes and 49 seconds - 68 years, 1month, 4 days, 3 hours, 14 minutes and 8 seconds -2283 years, 3 months, 3 days, 55 minutes and 37 seconds - 68 years, 1month, 4 days, 3 hours, 14 minutes and 8 seconds -6206 years, 6 months, 15 days, 23 hours, 57 minutes and 8 seconds - 68 years, 1month, 4 days, 3 hours, 14 minutes and 8 seconds -16871 years, 1month, 19 days, 17 hours, 56 minutes and 42 seconds - 68 years, 1month, 4 days, 3 hours, 14 minutes and 8 seconds -45860 years, 6 months, 3 days, 9 hours, 24 minutes and 52 seconds - 68 years, 1month, 4 days, 3 hours, 14 minutes and 8 seconds -124661 years, 9 months, 14 days, 8 hours, 45 minutes and 42 seconds - 68 years, 1month, 4 days, 3 hours, 14 minutes and 8 seconds +1 5.5 seconds +60 5 minutes and 30 seconds +3600 5 hours and 30 minutes +86400 5 days and 12 hours +2592000 5 months, 12 days and 12 hours +31536000 5 years, 5 months and 30 days +minutes 1 5.5 seconds +minutes 60 5 minutes and 30 seconds +minutes 3600 330 minutes +minutes 86400 7920 minutes +minutes 2592000 237600 minutes +minutes 31536000 2890800 minutes +hours 1 5.5 seconds +hours 60 5 minutes and 30 seconds +hours 3600 5 hours and 30 minutes +hours 86400 132 hours +hours 2592000 3960 hours +hours 31536000 48180 hours +days 1 5.5 seconds +days 60 5 minutes and 30 seconds +days 3600 5 hours and 30 minutes +days 86400 5 days and 12 hours +days 2592000 165 days +days 31536000 2007 days and 12 hours +months 1 5.5 seconds +months 60 5 minutes and 30 seconds +months 3600 5 hours and 30 minutes +months 86400 5 days and 12 hours +months 2592000 5 months, 12 days and 12 hours +months 31536000 65 months and 25 days diff --git a/tests/queries/0_stateless/01511_format_readable_timedelta.sql b/tests/queries/0_stateless/01511_format_readable_timedelta.sql index a71742d540c..cf5781456ac 100755 --- a/tests/queries/0_stateless/01511_format_readable_timedelta.sql +++ b/tests/queries/0_stateless/01511_format_readable_timedelta.sql @@ -1,6 +1,19 @@ -WITH round(exp(number), 6) AS x, toUInt64(x) AS y, toInt32(x) AS z -SELECT - formatReadableTimeDelta(y), - formatReadableTimeDelta(z) -FROM system.numbers -LIMIT 30; +SELECT + arrayJoin([1, 60, 60*60, 60*60*24, 60*60*24*30, 60*60*24*365]) AS elapsed, + formatReadableTimeDelta(elapsed*5.5) AS time_delta; +SELECT + 'minutes' AS maximum_unit, + arrayJoin([1, 60, 60*60, 60*60*24, 60*60*24*30, 60*60*24*365]) AS elapsed, + formatReadableTimeDelta(elapsed*5.5, maximum_unit) AS time_delta; +SELECT + 'hours' AS maximum_unit, + arrayJoin([1, 60, 60*60, 60*60*24, 60*60*24*30, 60*60*24*365]) AS elapsed, + formatReadableTimeDelta(elapsed*5.5, maximum_unit) AS time_delta; +SELECT + 'days' AS maximum_unit, + arrayJoin([1, 60, 60*60, 60*60*24, 60*60*24*30, 60*60*24*365]) AS elapsed, + formatReadableTimeDelta(elapsed*5.5, maximum_unit) AS time_delta; +SELECT + 'months' AS maximum_unit, + arrayJoin([1, 60, 60*60, 60*60*24, 60*60*24*30, 60*60*24*365]) AS elapsed, + formatReadableTimeDelta(elapsed*5.5, maximum_unit) AS time_delta;