Skip to content

Commit

Permalink
Add human_readable timestamp function
Browse files Browse the repository at this point in the history
  • Loading branch information
haldes authored and martint committed Aug 25, 2020
1 parent 82cdb14 commit d5577c6
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 0 deletions.
10 changes: 10 additions & 0 deletions presto-docs/src/main/sphinx/functions/datetime.rst
Expand Up @@ -287,6 +287,16 @@ Unit Description
SELECT parse_duration('5m');
-- 0 00:05:00.000

.. function:: human_readable_seconds(double) -> varchar

Returns ``seconds`` expressed in terms of ``human readable interval``::

SELECT human_readable_seconds(56363463);
-- 93 weeks, 1 day, 8 hours, 31 minutes, 3 seconds

SELECT human_readable_seconds(61);
-- 1 minute, 1 second

MySQL Date Functions
--------------------

Expand Down
Expand Up @@ -166,6 +166,7 @@
import io.prestosql.operator.scalar.timestamp.ExtractYear;
import io.prestosql.operator.scalar.timestamp.ExtractYearOfWeek;
import io.prestosql.operator.scalar.timestamp.FormatDateTime;
import io.prestosql.operator.scalar.timestamp.HumanReadableSeconds;
import io.prestosql.operator.scalar.timestamp.LastDayOfMonth;
import io.prestosql.operator.scalar.timestamp.LocalTimestamp;
import io.prestosql.operator.scalar.timestamp.SequenceIntervalDayToSecond;
Expand Down Expand Up @@ -674,6 +675,7 @@ public FunctionRegistry(Supplier<BlockEncodingSerde> blockEncodingSerdeSupplier,
.scalar(LocalTimestamp.class)
.scalar(DateTrunc.class)
.scalar(ToUnixTime.class)
.scalar(HumanReadableSeconds.class)
.scalar(ToIso8601.class)
.scalar(WithTimeZone.class)
.scalar(FormatDateTime.class)
Expand Down
@@ -0,0 +1,106 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.prestosql.operator.scalar.timestamp;

import com.google.common.base.Strings;
import io.airlift.slice.Slice;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.function.ScalarFunction;
import io.prestosql.spi.function.SqlNullable;
import io.prestosql.spi.function.SqlType;
import io.prestosql.spi.type.StandardTypes;

import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;

import static io.airlift.slice.Slices.utf8Slice;
import static io.prestosql.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT;
import static java.lang.String.format;
import static org.joda.time.DateTimeConstants.SECONDS_PER_DAY;
import static org.joda.time.DateTimeConstants.SECONDS_PER_HOUR;
import static org.joda.time.DateTimeConstants.SECONDS_PER_MINUTE;

@ScalarFunction("human_readable_seconds")
public final class HumanReadableSeconds
{
private static final int DAYS_IN_WEEK = 7;
private static final int SECONDS_IN_WEEK = SECONDS_PER_DAY * DAYS_IN_WEEK;
private static final String WEEK = "week";
private static final String DAY = "day";
private static final String HOUR = "hour";
private static final String MINUTE = "minute";
private static final String SECOND = "second";

private HumanReadableSeconds() {}

@SqlNullable
@SqlType(StandardTypes.VARCHAR)
public static Slice humanReadableSeconds(@SqlType(StandardTypes.DOUBLE) double inputSeconds)
{
if (Double.isNaN(inputSeconds) || Double.isInfinite(inputSeconds)) {
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, format("Invalid argument found: %s", inputSeconds));
}

long seconds = Math.round(Math.abs(inputSeconds));

long weeks = TimeUnit.SECONDS.toDays(seconds) / DAYS_IN_WEEK;
seconds = seconds % SECONDS_IN_WEEK;

long days = TimeUnit.SECONDS.toDays(seconds);
seconds = seconds % SECONDS_PER_DAY;

long hours = TimeUnit.SECONDS.toHours(seconds);
seconds = seconds % SECONDS_PER_HOUR;

long minutes = TimeUnit.SECONDS.toMinutes(seconds);
seconds = seconds % SECONDS_PER_MINUTE;

return getTimePeriods(weeks, days, hours, minutes, seconds);
}

private static Slice getTimePeriods(long weeks, long days, long hours, long minutes, long seconds)
{
StringJoiner stringJoiner = new StringJoiner(", ");

if (weeks > 0) {
stringJoiner.add(renderPeriodType(weeks, WEEK));
}
if (days > 0) {
stringJoiner.add(renderPeriodType(days, DAY));
}
if (hours > 0) {
stringJoiner.add(renderPeriodType(hours, HOUR));
}
if (minutes > 0) {
stringJoiner.add(renderPeriodType(minutes, MINUTE));
}
if (seconds > 0) {
stringJoiner.add(renderPeriodType(seconds, SECOND));
}

String timePeriod = stringJoiner.toString();
if (Strings.isNullOrEmpty(timePeriod)) {
return utf8Slice(renderPeriodType(0, SECOND));
}
return utf8Slice(timePeriod);
}

private static String renderPeriodType(long value, String periodType)
{
if (value == 1) {
return value + " " + periodType;
}
return value + " " + periodType + "s";
}
}
@@ -0,0 +1,45 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.prestosql.operator.scalar.timestamp;

import io.prestosql.operator.scalar.AbstractTestFunctions;
import org.testng.annotations.Test;

import static io.prestosql.spi.type.VarcharType.VARCHAR;

public class TestHumanReadableSeconds
extends AbstractTestFunctions
{
@Test
public void testToHumanRedableSecondsFormat()
{
assertFunction("human_readable_seconds(0)", VARCHAR, "0 seconds");
assertFunction("human_readable_seconds(1)", VARCHAR, "1 second");
assertFunction("human_readable_seconds(60)", VARCHAR, "1 minute");
assertFunction("human_readable_seconds(-60)", VARCHAR, "1 minute");
assertFunction("human_readable_seconds(61)", VARCHAR, "1 minute, 1 second");
assertFunction("human_readable_seconds(-61)", VARCHAR, "1 minute, 1 second");
assertFunction("human_readable_seconds(535333.9513888889)", VARCHAR, "6 days, 4 hours, 42 minutes, 14 seconds");
assertFunction("human_readable_seconds(535333.2513888889)", VARCHAR, "6 days, 4 hours, 42 minutes, 13 seconds");
assertFunction("human_readable_seconds(56363463)", VARCHAR, "93 weeks, 1 day, 8 hours, 31 minutes, 3 seconds");
assertFunction("human_readable_seconds(3660)", VARCHAR, "1 hour, 1 minute");
assertFunction("human_readable_seconds(3601)", VARCHAR, "1 hour, 1 second");
assertFunction("human_readable_seconds(8003)", VARCHAR, "2 hours, 13 minutes, 23 seconds");
assertFunction("human_readable_seconds(NULL)", VARCHAR, null);
// check for NaN
assertInvalidFunction("human_readable_seconds(0.0E0 / 0.0E0)", "Invalid argument found: NaN");
// check for infinity
assertInvalidFunction("human_readable_seconds(1.0E0 / 0.0E0)", "Invalid argument found: Infinity");
}
}

0 comments on commit d5577c6

Please sign in to comment.