diff --git a/presto-main/src/main/java/io/prestosql/Session.java b/presto-main/src/main/java/io/prestosql/Session.java index 1edac0043..5647d4cdd 100644 --- a/presto-main/src/main/java/io/prestosql/Session.java +++ b/presto-main/src/main/java/io/prestosql/Session.java @@ -49,6 +49,7 @@ import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static io.prestosql.SystemSessionProperties.SNAPSHOT_ENABLED; +import static io.prestosql.SystemSessionProperties.TIME_ZONE_ID; import static io.prestosql.spi.StandardErrorCode.NOT_FOUND; import static io.prestosql.spi.connector.CatalogName.createInformationSchemaCatalogName; import static io.prestosql.spi.connector.CatalogName.createSystemTablesCatalogName; @@ -197,7 +198,11 @@ public final class Session public TimeZoneKey getTimeZoneKey() { - return timeZoneKey; + String timeZoneId = getSystemProperty(TIME_ZONE_ID, String.class); + if (timeZoneId == null) { + return timeZoneKey; + } + return TimeZoneKey.getTimeZoneKey(timeZoneId); } public Locale getLocale() diff --git a/presto-main/src/main/java/io/prestosql/SystemSessionProperties.java b/presto-main/src/main/java/io/prestosql/SystemSessionProperties.java index 8930b9182..b3035008a 100644 --- a/presto-main/src/main/java/io/prestosql/SystemSessionProperties.java +++ b/presto-main/src/main/java/io/prestosql/SystemSessionProperties.java @@ -22,6 +22,7 @@ import io.prestosql.memory.MemoryManagerConfig; import io.prestosql.snapshot.SnapshotConfig; import io.prestosql.spi.PrestoException; import io.prestosql.spi.session.PropertyMetadata; +import io.prestosql.spi.type.TimeZoneKey; import io.prestosql.sql.analyzer.FeaturesConfig; import io.prestosql.sql.analyzer.FeaturesConfig.DynamicFilterDataType; import io.prestosql.sql.analyzer.FeaturesConfig.JoinDistributionType; @@ -75,6 +76,7 @@ public final class SystemSessionProperties public static final String QUERY_MAX_CPU_TIME = "query_max_cpu_time"; public static final String QUERY_MAX_STAGE_COUNT = "query_max_stage_count"; public static final String REDISTRIBUTE_WRITES = "redistribute_writes"; + public static final String TIME_ZONE_ID = "time_zone_id"; // redistribute writes type session property key public static final String REDISTRIBUTE_WRITES_TYPE = "redistribute_writes_type"; public static final String SCALE_WRITERS = "scale_writers"; @@ -281,6 +283,16 @@ public final class SystemSessionProperties "Force parallel distributed writes", featuresConfig.isRedistributeWrites(), false), + stringProperty( + TIME_ZONE_ID, + "Time Zone Id for the current session", + null, + value -> { + if (value != null) { + TimeZoneKey.getTimeZoneKey(value); + } + }, + true), // redistribute writes type config enumProperty( REDISTRIBUTE_WRITES_TYPE, diff --git a/presto-main/src/main/java/io/prestosql/operator/scalar/DateTimeFunctions.java b/presto-main/src/main/java/io/prestosql/operator/scalar/DateTimeFunctions.java index 4a2d51174..505fcb31a 100644 --- a/presto-main/src/main/java/io/prestosql/operator/scalar/DateTimeFunctions.java +++ b/presto-main/src/main/java/io/prestosql/operator/scalar/DateTimeFunctions.java @@ -53,6 +53,7 @@ import static io.prestosql.spi.util.DateTimeZoneIndex.getDateTimeZone; import static io.prestosql.spi.util.DateTimeZoneIndex.packDateTimeWithZone; import static io.prestosql.spi.util.DateTimeZoneIndex.unpackChronology; import static io.prestosql.type.DateTimeOperators.modulo24Hour; +import static io.prestosql.type.TimestampOperators.castToTimestampWithTimeZone; import static io.prestosql.util.Failures.checkCondition; import static java.lang.Math.toIntExact; import static java.lang.String.format; @@ -175,16 +176,23 @@ public final class DateTimeFunctions @ScalarFunction("to_unixtime") @SqlType(StandardTypes.DOUBLE) - public static double toUnixTime(@SqlType(StandardTypes.TIMESTAMP) long timestamp) + public static double toUnixTime(ConnectorSession session, @SqlType(StandardTypes.TIMESTAMP) long timestamp) { - return timestamp / 1000.0; + // Timestamp should use Session timezone + long timestampWithZone = castToTimestampWithTimeZone(session, timestamp); + return unpackMillisUtc(timestampWithZone) / 1000.0; } @ScalarFunction("to_unixtime") @SqlType(StandardTypes.DOUBLE) - public static double toUnixTimeFromTimestampWithTimeZone(@SqlType(StandardTypes.TIMESTAMP_WITH_TIME_ZONE) long timestampWithTimeZone) + public static double toUnixTimeFromTimestampWithTimeZone(ConnectorSession session, @SqlType(StandardTypes.TIMESTAMP_WITH_TIME_ZONE) long timestampWithTimeZone) { - return unpackMillisUtc(timestampWithTimeZone) / 1000.0; + ISOChronology localChronology = getChronology(session.getTimeZoneKey()); + long localMillis = localChronology.getZone().convertUTCToLocal(unpackMillisUtc(timestampWithTimeZone)); + + ISOChronology zoneChronology = unpackChronology(timestampWithTimeZone); + long zoneMillis = zoneChronology.getZone().convertLocalToUTC(localMillis, false); + return zoneMillis / 1000.0; } @ScalarFunction("to_iso8601") diff --git a/presto-main/src/main/java/io/prestosql/type/TimestampOperators.java b/presto-main/src/main/java/io/prestosql/type/TimestampOperators.java index ce42e7697..f6e5a6c5b 100644 --- a/presto-main/src/main/java/io/prestosql/type/TimestampOperators.java +++ b/presto-main/src/main/java/io/prestosql/type/TimestampOperators.java @@ -28,6 +28,7 @@ import io.prestosql.spi.function.SqlNullable; import io.prestosql.spi.function.SqlType; import io.prestosql.spi.type.AbstractLongType; import io.prestosql.spi.type.StandardTypes; +import io.prestosql.spi.type.TimeZoneKey; import org.joda.time.chrono.ISOChronology; import java.util.concurrent.TimeUnit; @@ -149,11 +150,12 @@ public final class TimestampOperators @SqlType(StandardTypes.TIMESTAMP_WITH_TIME_ZONE) public static long castToTimestampWithTimeZone(ConnectorSession session, @SqlType(StandardTypes.TIMESTAMP) long value) { - ISOChronology localChronology = getChronology(session.getTimeZoneKey()); + TimeZoneKey timeZoneKey = session.getTimeZoneKey(); + ISOChronology localChronology = getChronology(timeZoneKey); // This cast does treat TIMESTAMP as wall time in session TZ. This means that in order to get // its UTC representation we need to shift the value by the offset of TZ. - return packDateTimeWithZone(localChronology.getZone().convertLocalToUTC(value, false), session.getTimeZoneKey()); + return packDateTimeWithZone(localChronology.getZone().convertLocalToUTC(value, false), timeZoneKey); } @ScalarOperator(CAST) diff --git a/presto-main/src/test/java/io/prestosql/operator/scalar/TestDateTimeFunctions.java b/presto-main/src/test/java/io/prestosql/operator/scalar/TestDateTimeFunctions.java index 028c80d66..d5ba46fc4 100644 --- a/presto-main/src/test/java/io/prestosql/operator/scalar/TestDateTimeFunctions.java +++ b/presto-main/src/test/java/io/prestosql/operator/scalar/TestDateTimeFunctions.java @@ -29,6 +29,8 @@ import java.time.LocalTime; import java.time.OffsetTime; import java.time.ZoneOffset; +import static io.prestosql.SystemSessionProperties.TIME_ZONE_ID; +import static io.prestosql.spi.type.DoubleType.DOUBLE; import static io.prestosql.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; import static io.prestosql.spi.type.TimeZoneKey.UTC_KEY; import static io.prestosql.spi.type.TimeZoneKey.getTimeZoneKey; @@ -139,4 +141,37 @@ public class TestDateTimeFunctions localAssertion.assertFunctionString("NOW()", TIMESTAMP_WITH_TIME_ZONE, "2017-03-01 14:30:00.000 " + DATE_TIME_ZONE.getID()); } } + + @Test + public void testToUnixtime() + { + assertFunction("to_unixtime(TIMESTAMP'2021-07-25 17:10:00')", DOUBLE, 1.6271862E9); + + String timeZoneId = "Asia/Shanghai"; + Session localSession = Session.builder(session) + .setSystemProperty(TIME_ZONE_ID, timeZoneId) + .build(); + try (FunctionAssertions localAssertion = new FunctionAssertions(localSession)) { + localAssertion.assertFunction("to_unixtime(TIMESTAMP'2021-07-25 17:10:00')", DOUBLE, 1.6272042E9); + } + assertFunction(String.format("to_unixtime(at_timezone(timestamp'2021-07-25 17:10:00', '%s'))", timeZoneId), DOUBLE, 1.6272042E9); + + timeZoneId = "Europe/London"; + localSession = Session.builder(session) + .setSystemProperty(TIME_ZONE_ID, timeZoneId) + .build(); + try (FunctionAssertions localAssertion = new FunctionAssertions(localSession)) { + localAssertion.assertFunction("to_unixtime(TIMESTAMP'2021-07-25 17:10:00')", DOUBLE, 1.6272294E9); + } + assertFunction(String.format("to_unixtime(at_timezone(timestamp'2021-07-25 17:10:00', '%s'))", timeZoneId), DOUBLE, 1.6272294E9); + + timeZoneId = "America/New_York"; + localSession = Session.builder(session) + .setSystemProperty(TIME_ZONE_ID, timeZoneId) + .build(); + try (FunctionAssertions localAssertion = new FunctionAssertions(localSession)) { + localAssertion.assertFunction("to_unixtime(TIMESTAMP'2021-07-25 17:10:00')", DOUBLE, 1.6272474E9); + } + assertFunction(String.format("to_unixtime(at_timezone(timestamp'2021-07-25 17:10:00', '%s'))", timeZoneId), DOUBLE, 1.6272474E9); + } } diff --git a/presto-spi/src/main/java/io/prestosql/spi/session/PropertyMetadata.java b/presto-spi/src/main/java/io/prestosql/spi/session/PropertyMetadata.java index bbff1967f..a4af0cb5a 100644 --- a/presto-spi/src/main/java/io/prestosql/spi/session/PropertyMetadata.java +++ b/presto-spi/src/main/java/io/prestosql/spi/session/PropertyMetadata.java @@ -18,6 +18,7 @@ import io.airlift.units.Duration; import io.prestosql.spi.type.Type; import java.util.EnumSet; +import java.util.function.Consumer; import java.util.function.Function; import static io.prestosql.spi.type.BigintType.BIGINT; @@ -193,6 +194,11 @@ public final class PropertyMetadata } public static PropertyMetadata stringProperty(String name, String description, String defaultValue, boolean hidden) + { + return stringProperty(name, description, defaultValue, value -> {}, hidden); + } + + public static PropertyMetadata stringProperty(String name, String description, String defaultValue, Consumer validation, boolean hidden) { return new PropertyMetadata<>( name, @@ -201,7 +207,11 @@ public final class PropertyMetadata String.class, defaultValue, hidden, - String.class::cast, + object -> { + String value = (String) object; + validation.accept(value); + return value; + }, object -> object); }