mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-10 09:32:06 +00:00
add an SQL function formatReadableTimeDelta to format time delta
This commit is contained in:
parent
9808d0be81
commit
077ee81177
@ -538,6 +538,26 @@ SELECT
|
||||
└────────────────┴───────────────────┘
|
||||
```
|
||||
|
||||
## formatReadableTimeDelta(x) {#formatreadabletimedeltax}
|
||||
|
||||
Accepts the time delta in seconds. Returns a time delta with (year, month, day, hour, minute, second) as a string.
|
||||
|
||||
Example:
|
||||
|
||||
``` sql
|
||||
SELECT
|
||||
arrayJoin([100, 12345, 432546534]) AS number,
|
||||
formatReadableTimeDelta(number) AS time_delta
|
||||
```
|
||||
|
||||
``` text
|
||||
┌─────number─┬─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 │
|
||||
└────────────┴─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## least(a, b) {#leasta-b}
|
||||
|
||||
Returns the smallest value from a and b.
|
||||
|
@ -10,6 +10,7 @@ void registerFunctionsFormatting(FunctionFactory & factory)
|
||||
factory.registerFunction<FunctionBitmaskToList>();
|
||||
factory.registerFunction<FunctionFormatReadableSize>();
|
||||
factory.registerFunction<FunctionFormatReadableQuantity>();
|
||||
factory.registerFunction<FunctionFormatReadableTimeDelta>();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -278,4 +278,155 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class FunctionFormatReadableTimeDelta : public IFunction
|
||||
{
|
||||
public:
|
||||
static constexpr auto name = "formatReadableTimeDelta";
|
||||
static FunctionPtr create(const Context &) { return std::make_shared<FunctionFormatReadableTimeDelta>(); }
|
||||
|
||||
String getName() const override
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
size_t getNumberOfArguments() const override { return 1; }
|
||||
|
||||
DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
|
||||
{
|
||||
const IDataType & type = *arguments[0];
|
||||
|
||||
if (!isNativeNumber(type))
|
||||
throw Exception("Cannot format " + type.getName() + " as time delta", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
||||
|
||||
return std::make_shared<DataTypeString>();
|
||||
}
|
||||
|
||||
bool useDefaultImplementationForConstants() const override { return true; }
|
||||
|
||||
void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t /*input_rows_count*/) const override
|
||||
{
|
||||
if (!(executeType<UInt8>(block, arguments, result)
|
||||
|| executeType<UInt16>(block, arguments, result)
|
||||
|| executeType<UInt32>(block, arguments, result)
|
||||
|| executeType<UInt64>(block, arguments, result)
|
||||
|| executeType<Int8>(block, arguments, result)
|
||||
|| executeType<Int16>(block, arguments, result)
|
||||
|| executeType<Int32>(block, arguments, result)
|
||||
|| executeType<Int64>(block, arguments, result)
|
||||
|| executeType<Float32>(block, arguments, result)
|
||||
|| executeType<Float64>(block, arguments, result)))
|
||||
throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName()
|
||||
+ " of argument of function " + getName(),
|
||||
ErrorCodes::ILLEGAL_COLUMN);
|
||||
}
|
||||
|
||||
private:
|
||||
void formatReadableTimeDelta(double value, 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
|
||||
|
||||
char sig = value<0?-1:1;
|
||||
value *= sig;
|
||||
|
||||
long long int date = value / 86400;
|
||||
double time = value - date * 86400;
|
||||
|
||||
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;
|
||||
|
||||
std::vector<String> parts;
|
||||
|
||||
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"));
|
||||
}
|
||||
// Test overflow 2^64
|
||||
// If overflow happens then hide the time
|
||||
if (time < 9223372036854775808.0)
|
||||
{
|
||||
if (hours)
|
||||
{
|
||||
parts.push_back(std::to_string(hours) + (hours==1?" hour":" hours"));
|
||||
}
|
||||
if (minutes)
|
||||
{
|
||||
parts.push_back(std::to_string(minutes) + (minutes==1?" minute":" minutes"));
|
||||
}
|
||||
if (seconds)
|
||||
{
|
||||
std::string seconds_str = std::to_string(seconds);
|
||||
seconds_str.erase(seconds_str.find_last_not_of('0') + 1, std::string::npos);
|
||||
seconds_str.erase(seconds_str.find_last_not_of('.') + 1, std::string::npos);
|
||||
parts.push_back(seconds_str + (seconds==1?" second":" seconds"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String str_value;
|
||||
for(size_t i=0; i<parts.size(); i++)
|
||||
{
|
||||
if(!str_value.empty())
|
||||
{
|
||||
if (i == parts.size()-1)
|
||||
str_value += " and ";
|
||||
else
|
||||
str_value += ", ";
|
||||
}
|
||||
str_value += parts[i];
|
||||
}
|
||||
if (sig < 0)
|
||||
str_value = "- " + str_value;
|
||||
writeCString(str_value.c_str(), out);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool executeType(Block & block, const ColumnNumbers & arguments, size_t result) const
|
||||
{
|
||||
if (const ColumnVector<T> * col_from = checkAndGetColumn<ColumnVector<T>>(block.getByPosition(arguments[0]).column.get()))
|
||||
{
|
||||
auto col_to = ColumnString::create();
|
||||
|
||||
const typename ColumnVector<T>::Container & vec_from = col_from->getData();
|
||||
ColumnString::Chars & data_to = col_to->getChars();
|
||||
ColumnString::Offsets & offsets_to = col_to->getOffsets();
|
||||
size_t size = vec_from.size();
|
||||
data_to.resize(size * 2);
|
||||
offsets_to.resize(size);
|
||||
|
||||
WriteBufferFromVector<ColumnString::Chars> buf_to(data_to);
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
formatReadableTimeDelta(static_cast<double>(vec_from[i]), buf_to);
|
||||
writeChar(0, buf_to);
|
||||
offsets_to[i] = buf_to.count();
|
||||
}
|
||||
|
||||
buf_to.finalize();
|
||||
block.getByPosition(result).column = std::move(col_to);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -175,6 +175,7 @@ SELECT protocol(NULL);
|
||||
SELECT toInt16OrZero(NULL);
|
||||
SELECT formatReadableSize(NULL);
|
||||
SELECT formatReadableQuantity(NULL);
|
||||
SELECT formatReadableTimeDelta(NULL);
|
||||
SELECT concatAssumeInjective(NULL);
|
||||
SELECT toString(NULL);
|
||||
SELECT MACStringToNum(NULL);
|
||||
|
30
tests/queries/0_stateless/01511_format_readable_timedelta.reference
Executable file
30
tests/queries/0_stateless/01511_format_readable_timedelta.reference
Executable file
@ -0,0 +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
|
6
tests/queries/0_stateless/01511_format_readable_timedelta.sql
Executable file
6
tests/queries/0_stateless/01511_format_readable_timedelta.sql
Executable file
@ -0,0 +1,6 @@
|
||||
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;
|
Loading…
Reference in New Issue
Block a user