diff --git a/core/src/main/java/org/opensearch/sql/data/model/ExprTimeValue.java b/core/src/main/java/org/opensearch/sql/data/model/ExprTimeValue.java index df5b5498e9..9c5f5c5d55 100644 --- a/core/src/main/java/org/opensearch/sql/data/model/ExprTimeValue.java +++ b/core/src/main/java/org/opensearch/sql/data/model/ExprTimeValue.java @@ -6,11 +6,11 @@ package org.opensearch.sql.data.model; +import static org.opensearch.sql.utils.DateTimeFormatters.TIME_FORMATTER_VARIABLE_NANOS; + import java.time.LocalTime; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoField; import java.util.Objects; import lombok.RequiredArgsConstructor; import org.opensearch.sql.data.type.ExprCoreType; @@ -24,27 +24,12 @@ public class ExprTimeValue extends AbstractExprValue { private final LocalTime time; - private static final DateTimeFormatter FORMATTER_VARIABLE_NANOS; - private static final int MIN_FRACTION_SECONDS = 0; - private static final int MAX_FRACTION_SECONDS = 9; - - static { - FORMATTER_VARIABLE_NANOS = new DateTimeFormatterBuilder() - .appendPattern("HH:mm:ss") - .appendFraction( - ChronoField.NANO_OF_SECOND, - MIN_FRACTION_SECONDS, - MAX_FRACTION_SECONDS, - true) - .toFormatter(); - } - /** * Constructor. */ public ExprTimeValue(String time) { try { - this.time = LocalTime.parse(time, FORMATTER_VARIABLE_NANOS); + this.time = LocalTime.parse(time, TIME_FORMATTER_VARIABLE_NANOS); } catch (DateTimeParseException e) { throw new SemanticCheckException(String.format("time:%s in unsupported format, please use " + "HH:mm:ss[.SSSSSSSSS]", time)); diff --git a/core/src/main/java/org/opensearch/sql/data/model/ExprTimestampValue.java b/core/src/main/java/org/opensearch/sql/data/model/ExprTimestampValue.java index 46903d877e..219a4c2663 100644 --- a/core/src/main/java/org/opensearch/sql/data/model/ExprTimestampValue.java +++ b/core/src/main/java/org/opensearch/sql/data/model/ExprTimestampValue.java @@ -6,15 +6,15 @@ package org.opensearch.sql.data.model; +import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_VARIABLE_NANOS; +import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_WITHOUT_NANO; + import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.util.Objects; import lombok.RequiredArgsConstructor; @@ -31,34 +31,15 @@ public class ExprTimestampValue extends AbstractExprValue { * todo. only support UTC now. */ private static final ZoneId ZONE = ZoneId.of("UTC"); - /** - * todo. only support timestamp in format yyyy-MM-dd HH:mm:ss. - */ - private static final DateTimeFormatter FORMATTER_WITHOUT_NANO = DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss"); - private final Instant timestamp; - private static final DateTimeFormatter FORMATTER_VARIABLE_NANOS; - private static final int MIN_FRACTION_SECONDS = 0; - private static final int MAX_FRACTION_SECONDS = 9; - - static { - FORMATTER_VARIABLE_NANOS = new DateTimeFormatterBuilder() - .appendPattern("yyyy-MM-dd HH:mm:ss") - .appendFraction( - ChronoField.NANO_OF_SECOND, - MIN_FRACTION_SECONDS, - MAX_FRACTION_SECONDS, - true) - .toFormatter(); - } + private final Instant timestamp; /** * Constructor. */ public ExprTimestampValue(String timestamp) { try { - this.timestamp = LocalDateTime.parse(timestamp, FORMATTER_VARIABLE_NANOS) + this.timestamp = LocalDateTime.parse(timestamp, DATE_TIME_FORMATTER_VARIABLE_NANOS) .atZone(ZONE) .toInstant(); } catch (DateTimeParseException e) { @@ -70,9 +51,9 @@ public ExprTimestampValue(String timestamp) { @Override public String value() { - return timestamp.getNano() == 0 ? FORMATTER_WITHOUT_NANO.withZone(ZONE) + return timestamp.getNano() == 0 ? DATE_TIME_FORMATTER_WITHOUT_NANO.withZone(ZONE) .format(timestamp.truncatedTo(ChronoUnit.SECONDS)) - : FORMATTER_VARIABLE_NANOS.withZone(ZONE).format(timestamp); + : DATE_TIME_FORMATTER_VARIABLE_NANOS.withZone(ZONE).format(timestamp); } @Override diff --git a/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java b/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java index d8dc7fc85f..732585d99a 100644 --- a/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java +++ b/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java @@ -18,13 +18,22 @@ import static org.opensearch.sql.expression.function.FunctionDSL.define; import static org.opensearch.sql.expression.function.FunctionDSL.impl; import static org.opensearch.sql.expression.function.FunctionDSL.nullMissingHandling; +import static org.opensearch.sql.utils.DateTimeFormatters.DATE_FORMATTER_LONG_YEAR; +import static org.opensearch.sql.utils.DateTimeFormatters.DATE_FORMATTER_SHORT_YEAR; +import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_LONG_YEAR; +import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_SHORT_YEAR; import java.math.BigDecimal; import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.time.format.TextStyle; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -32,6 +41,7 @@ import lombok.experimental.UtilityClass; import org.opensearch.sql.data.model.ExprDateValue; import org.opensearch.sql.data.model.ExprDatetimeValue; +import org.opensearch.sql.data.model.ExprDoubleValue; import org.opensearch.sql.data.model.ExprIntegerValue; import org.opensearch.sql.data.model.ExprLongValue; import org.opensearch.sql.data.model.ExprNullValue; @@ -39,6 +49,7 @@ import org.opensearch.sql.data.model.ExprTimeValue; import org.opensearch.sql.data.model.ExprTimestampValue; import org.opensearch.sql.data.model.ExprValue; +import org.opensearch.sql.data.type.ExprCoreType; import org.opensearch.sql.expression.function.BuiltinFunctionName; import org.opensearch.sql.expression.function.BuiltinFunctionRepository; import org.opensearch.sql.expression.function.DefaultFunctionResolver; @@ -56,6 +67,10 @@ public class DateTimeFunction { // The number of days from year zero to year 1970. private static final Long DAYS_0000_TO_1970 = (146097 * 5L) - (30L * 365L + 7L); + // MySQL doesn't process any datetime/timestamp values which are greater than + // 32536771199.999999, or equivalent '3001-01-18 23:59:59.999999' UTC + private static final Double MYSQL_MAX_TIMESTAMP = 32536771200d; + /** * Register Date and Time Functions. * @@ -72,6 +87,7 @@ public void register(BuiltinFunctionRepository repository) { repository.register(dayOfWeek()); repository.register(dayOfYear()); repository.register(from_days()); + repository.register(from_unixtime()); repository.register(hour()); repository.register(makedate()); repository.register(maketime()); @@ -87,6 +103,7 @@ public void register(BuiltinFunctionRepository repository) { repository.register(timestamp()); repository.register(date_format()); repository.register(to_days()); + repository.register(unix_timestamp()); repository.register(week()); repository.register(year()); @@ -313,6 +330,13 @@ private DefaultFunctionResolver from_days() { impl(nullMissingHandling(DateTimeFunction::exprFromDays), DATE, LONG)); } + private FunctionResolver from_unixtime() { + return define(BuiltinFunctionName.FROM_UNIXTIME.getName(), + impl(nullMissingHandling(DateTimeFunction::exprFromUnixTime), DATETIME, DOUBLE), + impl(nullMissingHandling(DateTimeFunction::exprFromUnixTimeFormat), + STRING, DOUBLE, STRING)); + } + /** * HOUR(STRING/TIME/DATETIME/TIMESTAMP). return the hour value for time. */ @@ -461,6 +485,16 @@ private DefaultFunctionResolver to_days() { impl(nullMissingHandling(DateTimeFunction::exprToDays), LONG, DATETIME)); } + private FunctionResolver unix_timestamp() { + return define(BuiltinFunctionName.UNIX_TIMESTAMP.getName(), + impl(DateTimeFunction::unixTimeStamp, LONG), + impl(nullMissingHandling(DateTimeFunction::unixTimeStampOf), DOUBLE, DATE), + impl(nullMissingHandling(DateTimeFunction::unixTimeStampOf), DOUBLE, DATETIME), + impl(nullMissingHandling(DateTimeFunction::unixTimeStampOf), DOUBLE, TIMESTAMP), + impl(nullMissingHandling(DateTimeFunction::unixTimeStampOf), DOUBLE, DOUBLE) + ); + } + /** * WEEK(DATE[,mode]). return the week number for date. */ @@ -601,6 +635,35 @@ private ExprValue exprFromDays(ExprValue exprValue) { return new ExprDateValue(LocalDate.ofEpochDay(exprValue.longValue() - DAYS_0000_TO_1970)); } + private ExprValue exprFromUnixTime(ExprValue time) { + if (0 > time.doubleValue()) { + return ExprNullValue.of(); + } + // According to MySQL documentation: + // effective maximum is 32536771199.999999, which returns '3001-01-18 23:59:59.999999' UTC. + // Regardless of platform or version, a greater value for first argument than the effective + // maximum returns 0. + if (MYSQL_MAX_TIMESTAMP <= time.doubleValue()) { + return ExprNullValue.of(); + } + return new ExprDatetimeValue(exprFromUnixTimeImpl(time)); + } + + private LocalDateTime exprFromUnixTimeImpl(ExprValue time) { + return LocalDateTime.ofInstant( + Instant.ofEpochSecond((long)Math.floor(time.doubleValue())), + ZoneId.of("UTC")) + .withNano((int)((time.doubleValue() % 1) * 1E9)); + } + + private ExprValue exprFromUnixTimeFormat(ExprValue time, ExprValue format) { + var value = exprFromUnixTime(time); + if (value.equals(ExprNullValue.of())) { + return ExprNullValue.of(); + } + return DateTimeFormatterUtil.getFormattedDate(value, format); + } + /** * Hour implementation for ExprValue. * @@ -803,6 +866,79 @@ private ExprValue exprWeek(ExprValue date, ExprValue mode) { CalendarLookup.getWeekNumber(mode.integerValue(), date.dateValue())); } + private ExprValue unixTimeStamp() { + return new ExprLongValue(Instant.now().getEpochSecond()); + } + + private ExprValue unixTimeStampOf(ExprValue value) { + var res = unixTimeStampOfImpl(value); + if (res == null) { + return ExprNullValue.of(); + } + if (res < 0) { + // According to MySQL returns 0 if year < 1970, don't return negative values as java does. + return new ExprDoubleValue(0); + } + if (res >= MYSQL_MAX_TIMESTAMP) { + // Return 0 also for dates > '3001-01-19 03:14:07.999999' UTC (32536771199.999999 sec) + return new ExprDoubleValue(0); + } + return new ExprDoubleValue(res); + } + + private Double unixTimeStampOfImpl(ExprValue value) { + // Also, according to MySQL documentation: + // The date argument may be a DATE, DATETIME, or TIMESTAMP ... + switch ((ExprCoreType)value.type()) { + case DATE: return value.dateValue().toEpochSecond(LocalTime.MIN, ZoneOffset.UTC) + 0d; + case DATETIME: return value.datetimeValue().toEpochSecond(ZoneOffset.UTC) + + value.datetimeValue().getNano() / 1E9; + case TIMESTAMP: return value.timestampValue().getEpochSecond() + + value.timestampValue().getNano() / 1E9; + default: + // ... or a number in YYMMDD, YYMMDDhhmmss, YYYYMMDD, or YYYYMMDDhhmmss format. + // If the argument includes a time part, it may optionally include a fractional + // seconds part. + + var format = new DecimalFormat("0.#"); + format.setMinimumFractionDigits(0); + format.setMaximumFractionDigits(6); + String input = format.format(value.doubleValue()); + double fraction = 0; + if (input.contains(".")) { + // Keeping fraction second part and adding it to the result, don't parse it + // Because `toEpochSecond` returns only `long` + // input = 12345.6789 becomes input = 12345 and fraction = 0.6789 + fraction = value.doubleValue() - Math.round(Math.ceil(value.doubleValue())); + input = input.substring(0, input.indexOf('.')); + } + try { + var res = LocalDateTime.parse(input, DATE_TIME_FORMATTER_SHORT_YEAR); + return res.toEpochSecond(ZoneOffset.UTC) + fraction; + } catch (DateTimeParseException ignored) { + // nothing to do, try another format + } + try { + var res = LocalDateTime.parse(input, DATE_TIME_FORMATTER_LONG_YEAR); + return res.toEpochSecond(ZoneOffset.UTC) + fraction; + } catch (DateTimeParseException ignored) { + // nothing to do, try another format + } + try { + var res = LocalDate.parse(input, DATE_FORMATTER_SHORT_YEAR); + return res.toEpochSecond(LocalTime.MIN, ZoneOffset.UTC) + 0d; + } catch (DateTimeParseException ignored) { + // nothing to do, try another format + } + try { + var res = LocalDate.parse(input, DATE_FORMATTER_LONG_YEAR); + return res.toEpochSecond(LocalTime.MIN, ZoneOffset.UTC) + 0d; + } catch (DateTimeParseException ignored) { + return null; + } + } + } + /** * Week for date implementation for ExprValue. * When mode is not specified default value mode 0 is used for default_week_format. diff --git a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java index 3a3db4201e..88de359bf2 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java @@ -67,6 +67,7 @@ public enum BuiltinFunctionName { DAYOFWEEK(FunctionName.of("dayofweek")), DAYOFYEAR(FunctionName.of("dayofyear")), FROM_DAYS(FunctionName.of("from_days")), + FROM_UNIXTIME(FunctionName.of("from_unixtime")), HOUR(FunctionName.of("hour")), MAKEDATE(FunctionName.of("makedate")), MAKETIME(FunctionName.of("maketime")), @@ -82,6 +83,7 @@ public enum BuiltinFunctionName { TIMESTAMP(FunctionName.of("timestamp")), DATE_FORMAT(FunctionName.of("date_format")), TO_DAYS(FunctionName.of("to_days")), + UNIX_TIMESTAMP(FunctionName.of("unix_timestamp")), WEEK(FunctionName.of("week")), YEAR(FunctionName.of("year")), // `now`-like functions diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchDateFormatters.java b/core/src/main/java/org/opensearch/sql/utils/DateTimeFormatters.java similarity index 57% rename from opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchDateFormatters.java rename to core/src/main/java/org/opensearch/sql/utils/DateTimeFormatters.java index 13d3347055..ecb52f7f98 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchDateFormatters.java +++ b/core/src/main/java/org/opensearch/sql/utils/DateTimeFormatters.java @@ -4,7 +4,7 @@ */ -package org.opensearch.sql.opensearch.data.value; +package org.opensearch.sql.utils; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoField.HOUR_OF_DAY; @@ -12,6 +12,7 @@ import static java.time.temporal.ChronoField.MONTH_OF_YEAR; import static java.time.temporal.ChronoField.NANO_OF_SECOND; import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; @@ -26,7 +27,7 @@ * Reference org.opensearch.common.time.DateFormatters. */ @UtilityClass -public class OpenSearchDateFormatters { +public class DateTimeFormatters { public static final DateTimeFormatter TIME_ZONE_FORMATTER_NO_COLON = new DateTimeFormatterBuilder() @@ -93,4 +94,72 @@ public class OpenSearchDateFormatters { public static final DateTimeFormatter SQL_LITERAL_DATE_TIME_FORMAT = DateTimeFormatter .ofPattern("yyyy-MM-dd HH:mm:ss"); + + public static final DateTimeFormatter DATE_TIME_FORMATTER = + new DateTimeFormatterBuilder() + .appendOptional(SQL_LITERAL_DATE_TIME_FORMAT) + .appendOptional(STRICT_DATE_OPTIONAL_TIME_FORMATTER) + .appendOptional(STRICT_HOUR_MINUTE_SECOND_FORMATTER) + .toFormatter(); + + /** + * todo. only support timestamp in format yyyy-MM-dd HH:mm:ss. + */ + public static final DateTimeFormatter DATE_TIME_FORMATTER_WITHOUT_NANO = + SQL_LITERAL_DATE_TIME_FORMAT; + + private static final int MIN_FRACTION_SECONDS = 0; + private static final int MAX_FRACTION_SECONDS = 9; + + public static final DateTimeFormatter DATE_TIME_FORMATTER_VARIABLE_NANOS = + new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd HH:mm:ss") + .appendFraction( + ChronoField.NANO_OF_SECOND, + MIN_FRACTION_SECONDS, + MAX_FRACTION_SECONDS, + true) + .toFormatter(Locale.ROOT); + + public static final DateTimeFormatter TIME_FORMATTER_VARIABLE_NANOS = + new DateTimeFormatterBuilder() + .appendPattern("HH:mm:ss") + .appendFraction( + ChronoField.NANO_OF_SECOND, + MIN_FRACTION_SECONDS, + MAX_FRACTION_SECONDS, + true) + .toFormatter(); + + // YYMMDD + public static final DateTimeFormatter DATE_FORMATTER_SHORT_YEAR = + new DateTimeFormatterBuilder() + .appendValueReduced(YEAR, 2, 2, 1970) + .appendPattern("MMdd") + .toFormatter() + .withResolverStyle(ResolverStyle.STRICT); + + // YYYYMMDD + public static final DateTimeFormatter DATE_FORMATTER_LONG_YEAR = + new DateTimeFormatterBuilder() + .appendValue(YEAR, 4) + .appendPattern("MMdd") + .toFormatter() + .withResolverStyle(ResolverStyle.STRICT); + + // YYMMDDhhmmss + public static final DateTimeFormatter DATE_TIME_FORMATTER_SHORT_YEAR = + new DateTimeFormatterBuilder() + .appendValueReduced(YEAR, 2, 2, 1970) + .appendPattern("MMddHHmmss") + .toFormatter() + .withResolverStyle(ResolverStyle.STRICT); + + // YYYYMMDDhhmmss + public static final DateTimeFormatter DATE_TIME_FORMATTER_LONG_YEAR = + new DateTimeFormatterBuilder() + .appendValue(YEAR,4) + .appendPattern("MMddHHmmss") + .toFormatter() + .withResolverStyle(ResolverStyle.STRICT); } diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeFunctionTest.java b/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeFunctionTest.java index 89415e0560..79efa2a015 100644 --- a/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeFunctionTest.java +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeFunctionTest.java @@ -16,7 +16,6 @@ import static org.opensearch.sql.data.model.ExprValueUtils.stringValue; import static org.opensearch.sql.data.type.ExprCoreType.DATE; import static org.opensearch.sql.data.type.ExprCoreType.DATETIME; -import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE; import static org.opensearch.sql.data.type.ExprCoreType.INTEGER; import static org.opensearch.sql.data.type.ExprCoreType.INTERVAL; import static org.opensearch.sql.data.type.ExprCoreType.LONG; @@ -25,15 +24,7 @@ import static org.opensearch.sql.data.type.ExprCoreType.TIMESTAMP; import com.google.common.collect.ImmutableList; -import java.time.Duration; -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.Year; -import java.util.HashSet; import java.util.List; -import java.util.Random; -import java.util.Set; -import java.util.stream.IntStream; import lombok.AllArgsConstructor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,10 +43,7 @@ import org.opensearch.sql.expression.Expression; import org.opensearch.sql.expression.ExpressionTestBase; import org.opensearch.sql.expression.FunctionExpression; -import org.opensearch.sql.expression.config.ExpressionConfig; import org.opensearch.sql.expression.env.Environment; -import org.opensearch.sql.expression.function.FunctionName; -import org.opensearch.sql.expression.function.FunctionSignature; @ExtendWith(MockitoExtension.class) class DateTimeFunctionTest extends ExpressionTestBase { diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeTestBase.java b/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeTestBase.java new file mode 100644 index 0000000000..6be1548608 --- /dev/null +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeTestBase.java @@ -0,0 +1,131 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.expression.datetime; + +import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.sql.data.model.ExprDateValue; +import org.opensearch.sql.data.model.ExprDatetimeValue; +import org.opensearch.sql.data.model.ExprTimestampValue; +import org.opensearch.sql.data.model.ExprValue; +import org.opensearch.sql.expression.DSL; +import org.opensearch.sql.expression.Expression; +import org.opensearch.sql.expression.ExpressionTestBase; +import org.opensearch.sql.expression.FunctionExpression; +import org.opensearch.sql.expression.env.Environment; +import org.opensearch.sql.expression.function.BuiltinFunctionRepository; +import org.opensearch.sql.expression.function.FunctionName; +import org.opensearch.sql.expression.function.FunctionSignature; +import org.springframework.beans.factory.annotation.Autowired; + +@ExtendWith(MockitoExtension.class) +public class DateTimeTestBase extends ExpressionTestBase { + + @Mock + protected Environment env; + + @Mock + protected Expression nullRef; + + @Mock + protected Expression missingRef; + + @Autowired + protected BuiltinFunctionRepository functionRepository; + + protected FunctionExpression maketime(Expression hour, Expression minute, Expression second) { + var func = functionRepository.resolve(new FunctionSignature(new FunctionName("maketime"), + List.of(DOUBLE, DOUBLE, DOUBLE))); + return (FunctionExpression)func.apply(List.of(hour, minute, second)); + } + + protected LocalTime maketime(Double hour, Double minute, Double second) { + return maketime(DSL.literal(hour), DSL.literal(minute), DSL.literal(second)) + .valueOf(null).timeValue(); + } + + protected FunctionExpression makedate(Expression year, Expression dayOfYear) { + var func = functionRepository.resolve(new FunctionSignature(new FunctionName("makedate"), + List.of(DOUBLE, DOUBLE))); + return (FunctionExpression)func.apply(List.of(year, dayOfYear)); + } + + protected LocalDate makedate(Double year, Double dayOfYear) { + return makedate(DSL.literal(year), DSL.literal(dayOfYear)).valueOf(null).dateValue(); + } + + protected FunctionExpression unixTimeStampExpr() { + var func = functionRepository.resolve( + new FunctionSignature(new FunctionName("unix_timestamp"), List.of())); + return (FunctionExpression)func.apply(List.of()); + } + + protected Long unixTimeStamp() { + return unixTimeStampExpr().valueOf(null).longValue(); + } + + protected FunctionExpression unixTimeStampOf(Expression value) { + var func = functionRepository.resolve(new FunctionSignature(new FunctionName("unix_timestamp"), + List.of(value.type()))); + return (FunctionExpression)func.apply(List.of(value)); + } + + protected Double unixTimeStampOf(Double value) { + return unixTimeStampOf(DSL.literal(value)).valueOf(null).doubleValue(); + } + + protected Double unixTimeStampOf(LocalDate value) { + return unixTimeStampOf(DSL.literal(new ExprDateValue(value))).valueOf(null).doubleValue(); + } + + protected Double unixTimeStampOf(LocalDateTime value) { + return unixTimeStampOf(DSL.literal(new ExprDatetimeValue(value))).valueOf(null).doubleValue(); + } + + protected Double unixTimeStampOf(Instant value) { + return unixTimeStampOf(DSL.literal(new ExprTimestampValue(value))).valueOf(null).doubleValue(); + } + + protected FunctionExpression fromUnixTime(Expression value) { + var func = functionRepository.resolve(new FunctionSignature(new FunctionName("from_unixtime"), + List.of(value.type()))); + return (FunctionExpression)func.apply(List.of(value)); + } + + protected FunctionExpression fromUnixTime(Expression value, Expression format) { + var func = functionRepository.resolve(new FunctionSignature(new FunctionName("from_unixtime"), + List.of(value.type(), format.type()))); + return (FunctionExpression)func.apply(List.of(value, format)); + } + + protected LocalDateTime fromUnixTime(Long value) { + return fromUnixTime(DSL.literal(value)).valueOf(null).datetimeValue(); + } + + protected LocalDateTime fromUnixTime(Double value) { + return fromUnixTime(DSL.literal(value)).valueOf(null).datetimeValue(); + } + + protected String fromUnixTime(Long value, String format) { + return fromUnixTime(DSL.literal(value), DSL.literal(format)).valueOf(null).stringValue(); + } + + protected String fromUnixTime(Double value, String format) { + return fromUnixTime(DSL.literal(value), DSL.literal(format)).valueOf(null).stringValue(); + } + + protected ExprValue eval(Expression expression) { + return expression.valueOf(env); + } +} diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/FromUnixTimeTest.java b/core/src/test/java/org/opensearch/sql/expression/datetime/FromUnixTimeTest.java new file mode 100644 index 0000000000..b6d04cbc97 --- /dev/null +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/FromUnixTimeTest.java @@ -0,0 +1,185 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.expression.datetime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.text.DecimalFormat; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opensearch.sql.data.model.ExprDoubleValue; +import org.opensearch.sql.data.model.ExprLongValue; +import org.opensearch.sql.data.model.ExprNullValue; +import org.opensearch.sql.data.model.ExprStringValue; +import org.opensearch.sql.expression.DSL; + +public class FromUnixTimeTest extends DateTimeTestBase { + + private static Stream getLongSamples() { + return Stream.of( + Arguments.of(0L), + Arguments.of(1L), + Arguments.of(1447430881L), + Arguments.of(2147483647L), + Arguments.of(1662577241L) + ); + } + + /** + * Test processing different Long values. + * @param value a value + */ + @ParameterizedTest + @MethodSource("getLongSamples") + public void checkOfLong(Long value) { + assertEquals(LocalDateTime.of(1970, 1, 1, 0, 0, 0).plus(value, ChronoUnit.SECONDS), + fromUnixTime(value)); + assertEquals(LocalDateTime.of(1970, 1, 1, 0, 0, 0).plus(value, ChronoUnit.SECONDS), + eval(fromUnixTime(DSL.literal(new ExprLongValue(value)))).datetimeValue()); + } + + private static Stream getDoubleSamples() { + return Stream.of( + Arguments.of(0.123d), + Arguments.of(100500.100500d), + Arguments.of(1447430881.564d), + Arguments.of(2147483647.451232d), + Arguments.of(1662577241.d) + ); + } + + /** + * Test processing different Double values. + * @param value a value + */ + @ParameterizedTest + @MethodSource("getDoubleSamples") + public void checkOfDouble(Double value) { + var intPart = Math.round(Math.floor(value)); + var fracPart = value - intPart; + var valueAsString = new DecimalFormat("0.#").format(value); + + assertEquals( + LocalDateTime.ofEpochSecond(intPart, (int)Math.round(fracPart * 1E9), ZoneOffset.UTC), + fromUnixTime(value), + valueAsString); + assertEquals( + LocalDateTime.ofEpochSecond(intPart, (int)Math.round(fracPart * 1E9), ZoneOffset.UTC), + eval(fromUnixTime(DSL.literal(new ExprDoubleValue(value)))).datetimeValue(), + valueAsString); + } + + // We are testing all different formats and combinations, because they are already tested + + private static Stream getLongSamplesWithFormat() { + return Stream.of( + Arguments.of(0L, "%c", "01"), // 1970-01-01 00:00:00, %c - 2 digit month + Arguments.of(1L, "%Y", "1970"), // 1970-01-01 00:00:01, %Y - 4 digit year + Arguments.of(1447430881L, "%s", "01"), // 2015-11-13 16:08:01, %s - second + Arguments.of(2147483647L, "%T", "03:14:07"), // 2038-01-19 03:14:07, %T - time + Arguments.of(1662577241L, "%d", "07") // 1662577241, %d - day of the month + ); + } + + /** + * Test processing different Long values with format. + * @param value a value + * @param format a format + * @param expected expected result + */ + @ParameterizedTest + @MethodSource("getLongSamplesWithFormat") + public void checkOfLongWithFormat(Long value, String format, String expected) { + assertEquals(expected, fromUnixTime(value, format)); + assertEquals(expected, eval(fromUnixTime(DSL.literal(new ExprLongValue(value)), + DSL.literal(new ExprStringValue(format)))).stringValue()); + } + + private static Stream getDoubleSamplesWithFormat() { + return Stream.of( + Arguments.of(0.123d, "%f", "123000"), // 1970-01-01 00:00:00.123, %f - microseconds + Arguments.of(100500.1005d, "%W", "Friday"), // 1970-01-02 03:55:00.1005, %W - Weekday name + Arguments.of(1447430881.56d, "%M", "November"), // 2015-11-13 16:08:01.56, %M - Month name + Arguments.of(2147483647.42d, "%j", "019"), // 2038-01-19 03:14:07.42, %j - day of the year + Arguments.of(1662577241.d, "%l", "7") // 2022-09-07 19:00:41, %l - 12 hour clock, no 0 pad + ); + } + + /** + * Test processing different Double values with format. + * @param value a value + * @param format a format + * @param expected expected result + */ + @ParameterizedTest + @MethodSource("getDoubleSamplesWithFormat") + public void checkOfDoubleWithFormat(Double value, String format, String expected) { + assertEquals(expected, fromUnixTime(value, format)); + assertEquals(expected, eval(fromUnixTime(DSL.literal(new ExprDoubleValue(value)), + DSL.literal(new ExprStringValue(format)))).stringValue()); + } + + @Test + public void checkInvalidFormat() { + assertEquals(new ExprStringValue("q"), + fromUnixTime(DSL.literal(0L), DSL.literal("%q")).valueOf(null)); + assertEquals(new ExprStringValue(""), + fromUnixTime(DSL.literal(0L), DSL.literal("")).valueOf(null)); + } + + @Test + public void checkValueOutsideOfTheRangeWithoutFormat() { + assertEquals(ExprNullValue.of(), fromUnixTime(DSL.literal(-1L)).valueOf(null)); + assertEquals(ExprNullValue.of(), fromUnixTime(DSL.literal(-1.5d)).valueOf(null)); + assertEquals(ExprNullValue.of(), fromUnixTime(DSL.literal(32536771200L)).valueOf(null)); + assertEquals(ExprNullValue.of(), fromUnixTime(DSL.literal(32536771200d)).valueOf(null)); + } + + @Test + public void checkInsideTheRangeWithoutFormat() { + assertNotEquals(ExprNullValue.of(), fromUnixTime(DSL.literal(32536771199L)).valueOf(null)); + assertNotEquals(ExprNullValue.of(), fromUnixTime(DSL.literal(32536771199d)).valueOf(null)); + } + + @Test + public void checkValueOutsideOfTheRangeWithFormat() { + assertEquals(ExprNullValue.of(), + fromUnixTime(DSL.literal(32536771200L), DSL.literal("%d")).valueOf(null)); + assertEquals(ExprNullValue.of(), + fromUnixTime(DSL.literal(32536771200d), DSL.literal("%d")).valueOf(null)); + } + + @Test + public void checkInsideTheRangeWithFormat() { + assertNotEquals(ExprNullValue.of(), + fromUnixTime(DSL.literal(32536771199L), DSL.literal("%d")).valueOf(null)); + assertNotEquals(ExprNullValue.of(), + fromUnixTime(DSL.literal(32536771199d), DSL.literal("%d")).valueOf(null)); + } + + @Test + public void checkNullOrNegativeValues() { + assertEquals(ExprNullValue.of(), fromUnixTime(DSL.literal(ExprNullValue.of())).valueOf(null)); + assertEquals(ExprNullValue.of(), + fromUnixTime(DSL.literal(-1L), DSL.literal("%d")).valueOf(null)); + assertEquals(ExprNullValue.of(), + fromUnixTime(DSL.literal(-1.5d), DSL.literal("%d")).valueOf(null)); + assertEquals(ExprNullValue.of(), + fromUnixTime(DSL.literal(42L), DSL.literal(ExprNullValue.of())).valueOf(null)); + assertEquals(ExprNullValue.of(), + fromUnixTime(DSL.literal(ExprNullValue.of()), DSL.literal("%d")).valueOf(null)); + assertEquals(ExprNullValue.of(), + fromUnixTime(DSL.literal(ExprNullValue.of()), DSL.literal(ExprNullValue.of())) + .valueOf(null)); + } +} diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/MakeDateTest.java b/core/src/test/java/org/opensearch/sql/expression/datetime/MakeDateTest.java index 497e73ea51..981468f31e 100644 --- a/core/src/test/java/org/opensearch/sql/expression/datetime/MakeDateTest.java +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/MakeDateTest.java @@ -10,51 +10,17 @@ import static org.mockito.Mockito.when; import static org.opensearch.sql.data.model.ExprValueUtils.missingValue; import static org.opensearch.sql.data.model.ExprValueUtils.nullValue; -import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE; import java.time.LocalDate; import java.time.Year; -import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.opensearch.sql.data.model.ExprValue; import org.opensearch.sql.expression.DSL; -import org.opensearch.sql.expression.Expression; -import org.opensearch.sql.expression.ExpressionTestBase; -import org.opensearch.sql.expression.FunctionExpression; -import org.opensearch.sql.expression.config.ExpressionConfig; -import org.opensearch.sql.expression.env.Environment; -import org.opensearch.sql.expression.function.FunctionName; -import org.opensearch.sql.expression.function.FunctionSignature; - -@ExtendWith(MockitoExtension.class) -public class MakeDateTest extends ExpressionTestBase { - - @Mock - Environment env; - - @Mock - Expression nullRef; - - @Mock - Expression missingRef; - - private FunctionExpression makedate(Expression year, Expression dayOfYear) { - var repo = new ExpressionConfig().functionRepository(); - var func = repo.resolve(new FunctionSignature(new FunctionName("makedate"), - List.of(DOUBLE, DOUBLE))); - return (FunctionExpression)func.apply(List.of(year, dayOfYear)); - } - private LocalDate makedate(Double year, Double dayOfYear) { - return makedate(DSL.literal(year), DSL.literal(dayOfYear)).valueOf(null).dateValue(); - } +public class MakeDateTest extends DateTimeTestBase { @Test public void checkEdgeCases() { @@ -167,8 +133,4 @@ private LocalDate getReferenceValue(double year, double dayOfYear) { } return LocalDate.ofYearDay(yearL, dayL); } - - private ExprValue eval(Expression expression) { - return expression.valueOf(env); - } } diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/MakeTimeTest.java b/core/src/test/java/org/opensearch/sql/expression/datetime/MakeTimeTest.java index 8269f74ccc..7dd78ff845 100644 --- a/core/src/test/java/org/opensearch/sql/expression/datetime/MakeTimeTest.java +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/MakeTimeTest.java @@ -11,53 +11,18 @@ import static org.mockito.Mockito.when; import static org.opensearch.sql.data.model.ExprValueUtils.missingValue; import static org.opensearch.sql.data.model.ExprValueUtils.nullValue; -import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE; import java.time.Duration; import java.time.LocalTime; import java.time.format.DateTimeParseException; -import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.opensearch.sql.data.model.ExprValue; import org.opensearch.sql.expression.DSL; -import org.opensearch.sql.expression.Expression; -import org.opensearch.sql.expression.ExpressionTestBase; -import org.opensearch.sql.expression.FunctionExpression; -import org.opensearch.sql.expression.config.ExpressionConfig; -import org.opensearch.sql.expression.env.Environment; -import org.opensearch.sql.expression.function.FunctionName; -import org.opensearch.sql.expression.function.FunctionSignature; - -@ExtendWith(MockitoExtension.class) -public class MakeTimeTest extends ExpressionTestBase { - - @Mock - Environment env; - - @Mock - Expression nullRef; - - @Mock - Expression missingRef; - - private FunctionExpression maketime(Expression hour, Expression minute, Expression second) { - var repo = new ExpressionConfig().functionRepository(); - var func = repo.resolve(new FunctionSignature(new FunctionName("maketime"), - List.of(DOUBLE, DOUBLE, DOUBLE))); - return (FunctionExpression)func.apply(List.of(hour, minute, second)); - } - private LocalTime maketime(Double hour, Double minute, Double second) { - return maketime(DSL.literal(hour), DSL.literal(minute), DSL.literal(second)) - .valueOf(null).timeValue(); - } +public class MakeTimeTest extends DateTimeTestBase { @Test public void checkEdgeCases() { @@ -165,8 +130,4 @@ public void checkRandomValues(double hour, double minute, double second) { assertEquals(0, delta, 1, String.format("hour = %f, minute = %f, second = %f", hour, minute, second)); } - - private ExprValue eval(Expression expression) { - return expression.valueOf(env); - } } diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/UnixTimeStampTest.java b/core/src/test/java/org/opensearch/sql/expression/datetime/UnixTimeStampTest.java new file mode 100644 index 0000000000..437e195f3e --- /dev/null +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/UnixTimeStampTest.java @@ -0,0 +1,235 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.expression.datetime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.text.DecimalFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoField; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opensearch.sql.data.model.ExprDateValue; +import org.opensearch.sql.data.model.ExprDatetimeValue; +import org.opensearch.sql.data.model.ExprDoubleValue; +import org.opensearch.sql.data.model.ExprNullValue; +import org.opensearch.sql.data.model.ExprTimestampValue; +import org.opensearch.sql.expression.DSL; + +public class UnixTimeStampTest extends DateTimeTestBase { + + @Test + public void checkNoArgs() { + assertEquals(System.currentTimeMillis() / 1000L, unixTimeStamp()); + assertEquals(System.currentTimeMillis() / 1000L, eval(unixTimeStampExpr()).longValue()); + } + + private static Stream getDateSamples() { + return Stream.of( + Arguments.of(LocalDate.of(1984, 1, 1)), + Arguments.of(LocalDate.of(2000, 2, 29)), + Arguments.of(LocalDate.of(1999, 12, 31)), + Arguments.of(LocalDate.of(2004, 2, 29)), + Arguments.of(LocalDate.of(2100, 2, 28)), + Arguments.of(LocalDate.of(2012, 2, 21)) + ); + } + + /** + * Check processing valid values of type LocalDate. + * @param value a value + */ + @ParameterizedTest + @MethodSource("getDateSamples") + public void checkOfDate(LocalDate value) { + assertEquals(value.getLong(ChronoField.EPOCH_DAY) * 24 * 3600, unixTimeStampOf(value)); + assertEquals(value.getLong(ChronoField.EPOCH_DAY) * 24 * 3600, + eval(unixTimeStampOf(DSL.literal(new ExprDateValue(value)))).longValue()); + } + + private static Stream getDateTimeSamples() { + return Stream.of( + Arguments.of(LocalDateTime.of(1984, 1, 1, 1, 1)), + Arguments.of(LocalDateTime.of(2000, 2, 29, 22, 54)), + Arguments.of(LocalDateTime.of(1999, 12, 31, 23, 59)), + Arguments.of(LocalDateTime.of(2004, 2, 29, 7, 40)), + Arguments.of(LocalDateTime.of(2100, 2, 28, 13, 14)), + Arguments.of(LocalDateTime.of(2012, 2, 21, 0, 0)) + ); + } + + /** + * Check processing valid values of type LocalDateTime. + * @param value a value + */ + @ParameterizedTest + @MethodSource("getDateTimeSamples") + public void checkOfDateTime(LocalDateTime value) { + assertEquals(value.toEpochSecond(ZoneOffset.UTC), unixTimeStampOf(value)); + assertEquals(value.toEpochSecond(ZoneOffset.UTC), + eval(unixTimeStampOf(DSL.literal(new ExprDatetimeValue(value)))).longValue()); + } + + private static Stream getInstantSamples() { + return getDateTimeSamples() + .map(v -> Arguments.of(((LocalDateTime)v.get()[0]).toInstant(ZoneOffset.UTC))); + } + + /** + * Check processing valid values of type Instant. + * @param value a value + */ + @ParameterizedTest + @MethodSource("getInstantSamples") + public void checkOfInstant(Instant value) { + assertEquals(value.getEpochSecond(), unixTimeStampOf(value)); + assertEquals(value.getEpochSecond(), + eval(unixTimeStampOf(DSL.literal(new ExprTimestampValue(value)))).longValue()); + } + + // formats: YYMMDD, YYMMDDhhmmss[.uuuuuu], YYYYMMDD, or YYYYMMDDhhmmss[.uuuuuu] + // use BigDecimal, because double can't fit such big values + private static Stream getDoubleSamples() { + return Stream.of( + Arguments.of(840101d, LocalDateTime.of(1984, 1, 1, 0, 0, 0)), + Arguments.of(840101112233d, LocalDateTime.of(1984, 1, 1, 11,22,33)), + Arguments.of(840101112233.123456, LocalDateTime.of(1984, 1, 1, 11, 22, 33, 123456000)), + Arguments.of(19840101d, LocalDateTime.of(1984, 1, 1, 0, 0, 0)), + Arguments.of(19840101000000d, LocalDateTime.of(1984, 1, 1, 0, 0, 0)), + Arguments.of(19840101112233d, LocalDateTime.of(1984, 1, 1, 11,22,33)), + Arguments.of(19840101112233.123456, LocalDateTime.of(1984, 1, 1, 11, 22, 33, 123456000)) + ); + } + + /** + * Check processing valid Double values. + * @param valueAsDouble a value + * @param valueAsLDT the value as LocalDateTime + */ + @ParameterizedTest + @MethodSource("getDoubleSamples") + public void checkOfDoubleFormats(Double valueAsDouble, LocalDateTime valueAsLDT) { + var valueAsStr = new DecimalFormat("0.#").format(valueAsDouble); + assertEquals(valueAsLDT.toEpochSecond(ZoneOffset.UTC), + unixTimeStampOf(valueAsDouble), 1d, valueAsStr); + assertEquals(valueAsLDT.toEpochSecond(ZoneOffset.UTC), + eval(unixTimeStampOf(DSL.literal(new ExprDoubleValue(valueAsDouble)))).longValue(), + 1d, valueAsStr); + } + + @Test + public void checkOfDouble() { + // 19991231235959.99 passed ok, but 19991231235959.999999 rounded to ...60.0 which is incorrect + // It is a double type limitation + var valueDt = LocalDateTime.of(1999, 12, 31, 23, 59, 59, 999_999_999); + assertEquals(valueDt.toEpochSecond(ZoneOffset.UTC), unixTimeStampOf(19991231235959.99d), 1d); + } + + @Test + public void checkYearLessThan1970() { + assertNotEquals(0, unixTimeStamp()); + assertEquals(0, unixTimeStampOf(LocalDate.of(1961, 4, 12))); + assertEquals(0, unixTimeStampOf(LocalDateTime.of(1961, 4, 12, 9, 7, 0))); + assertEquals(0, unixTimeStampOf(Instant.ofEpochMilli(-1))); + assertEquals(0, unixTimeStampOf(LocalDateTime.of(1970, 1, 1, 0, 0, 0))); + assertEquals(1, unixTimeStampOf(LocalDateTime.of(1970, 1, 1, 0, 0, 1))); + assertEquals(0, unixTimeStampOf(19610412d)); + assertEquals(0, unixTimeStampOf(19610412090700d)); + } + + @Test + public void checkMaxValue() { + // MySQL returns 0 for values above + // '3001-01-19 03:14:07.999999' UTC (corresponding to 32536771199.999999 seconds). + assertEquals(0, unixTimeStampOf(LocalDate.of(3001, 1, 20))); + assertNotEquals(0d, unixTimeStampOf(LocalDate.of(3001, 1, 18))); + assertEquals(0, unixTimeStampOf(LocalDateTime.of(3001, 1, 20, 3, 14, 8))); + assertNotEquals(0d, unixTimeStampOf(LocalDateTime.of(3001, 1, 18, 3, 14, 7))); + assertEquals(0, unixTimeStampOf(Instant.ofEpochSecond(32536771199L + 1))); + assertNotEquals(0d, unixTimeStampOf(Instant.ofEpochSecond(32536771199L))); + assertEquals(0, unixTimeStampOf(30010120d)); + assertNotEquals(0d, unixTimeStampOf(30010118d)); + } + + private static Stream getInvalidDoubleSamples() { + return Stream.of( + //invalid dates + Arguments.of(19990231.d), + Arguments.of(19991320.d), + Arguments.of(19991232.d), + Arguments.of(19990013.d), + Arguments.of(19990931.d), + Arguments.of(990231.d), + Arguments.of(991320.d), + Arguments.of(991232.d), + Arguments.of(990013.d), + Arguments.of(990931.d), + Arguments.of(9990102.d), + Arguments.of(99102.d), + Arguments.of(9912.d), + Arguments.of(199912.d), + Arguments.of(1999102.d), + //same as above, but with valid time + Arguments.of(19990231112233.d), + Arguments.of(19991320112233.d), + Arguments.of(19991232112233.d), + Arguments.of(19990013112233.d), + Arguments.of(19990931112233.d), + Arguments.of(990231112233.d), + Arguments.of(991320112233.d), + Arguments.of(991232112233.d), + Arguments.of(990013112233.d), + Arguments.of(990931112233.d), + Arguments.of(9990102112233.d), + Arguments.of(99102112233.d), + Arguments.of(9912112233.d), + Arguments.of(199912112233.d), + Arguments.of(1999102112233.d), + //invalid time + Arguments.of(19840101242233.d), + Arguments.of(19840101116033.d), + Arguments.of(19840101112260.d), + Arguments.of(1984010111223.d), + Arguments.of(198401011123.d), + Arguments.of(19840101123.d), + Arguments.of(1984010113.d), + Arguments.of(198401011.d), + //same, but with short date + Arguments.of(840101242233.d), + Arguments.of(840101116033.d), + Arguments.of(840101112260.d), + Arguments.of(84010111223.d), + Arguments.of(8401011123.d), + Arguments.of(840101123.d), + Arguments.of(8401011.d), + //misc + Arguments.of(0d), + Arguments.of(-1d), + Arguments.of(42d), + //too many digits + Arguments.of(199902201122330d) + ); + } + + /** + * Check processing invalid Double values. + * @param value a value + */ + @ParameterizedTest + @MethodSource("getInvalidDoubleSamples") + public void checkInvalidDoubleCausesNull(Double value) { + assertEquals(ExprNullValue.of(), + unixTimeStampOf(DSL.literal(new ExprDoubleValue(value))).valueOf(null), + new DecimalFormat("0.#").format(value)); + } +} diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/UnixTwoWayConversionTest.java b/core/src/test/java/org/opensearch/sql/expression/datetime/UnixTwoWayConversionTest.java new file mode 100644 index 0000000000..dc509d175b --- /dev/null +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/UnixTwoWayConversionTest.java @@ -0,0 +1,95 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + + +package org.opensearch.sql.expression.datetime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opensearch.sql.data.model.ExprDateValue; +import org.opensearch.sql.data.model.ExprDatetimeValue; +import org.opensearch.sql.data.model.ExprDoubleValue; +import org.opensearch.sql.data.model.ExprLongValue; +import org.opensearch.sql.data.model.ExprTimestampValue; +import org.opensearch.sql.data.model.ExprValue; +import org.opensearch.sql.expression.DSL; +import org.opensearch.sql.expression.Expression; +import org.opensearch.sql.expression.ExpressionTestBase; +import org.opensearch.sql.expression.FunctionExpression; +import org.opensearch.sql.expression.config.ExpressionConfig; +import org.opensearch.sql.expression.env.Environment; +import org.opensearch.sql.expression.function.FunctionName; +import org.opensearch.sql.expression.function.FunctionSignature; + +public class UnixTwoWayConversionTest extends DateTimeTestBase { + + @Test + public void checkConvertNow() { + assertEquals(LocalDateTime.now(ZoneId.of("UTC")).withNano(0), fromUnixTime(unixTimeStamp())); + assertEquals(LocalDateTime.now(ZoneId.of("UTC")).withNano(0), + eval(fromUnixTime(unixTimeStampExpr())).datetimeValue()); + } + + private static Stream getDoubleSamples() { + return Stream.of( + Arguments.of(0.123d), + Arguments.of(100500.100500d), + Arguments.of(1447430881.564d), + Arguments.of(2147483647.451232d), + Arguments.of(1662577241.d) + ); + } + + /** + * Test converting valid Double values EpochTime -> DateTime -> EpochTime. + * @param value a value + */ + @ParameterizedTest + @MethodSource("getDoubleSamples") + public void convertEpoch2DateTime2Epoch(Double value) { + assertEquals(value, unixTimeStampOf(fromUnixTime(value))); + assertEquals(value, + eval(unixTimeStampOf(fromUnixTime(DSL.literal(new ExprDoubleValue(value))))).doubleValue()); + + assertEquals(Math.round(value) + 0d, unixTimeStampOf(fromUnixTime(Math.round(value)))); + assertEquals(Math.round(value) + 0d, + eval(unixTimeStampOf(fromUnixTime(DSL.literal(new ExprLongValue(Math.round(value)))))) + .doubleValue()); + } + + private static Stream getDateTimeSamples() { + return Stream.of( + Arguments.of(LocalDateTime.of(1984, 1, 1, 1, 1)), + Arguments.of(LocalDateTime.of(2000, 2, 29, 22, 54)), + Arguments.of(LocalDateTime.of(1999, 12, 31, 23, 59, 59)), + Arguments.of(LocalDateTime.of(2004, 2, 29, 7, 40)), + Arguments.of(LocalDateTime.of(2100, 2, 28, 13, 14, 15)), + Arguments.of(LocalDateTime.of(2012, 2, 21, 0, 0, 17)) + ); + } + + /** + * Test converting valid values DateTime -> EpochTime -> DateTime. + * @param value a value + */ + @ParameterizedTest + @MethodSource("getDateTimeSamples") + public void convertDateTime2Epoch2DateTime(LocalDateTime value) { + assertEquals(value, fromUnixTime(unixTimeStampOf(value))); + assertEquals(value, + eval(fromUnixTime(unixTimeStampOf(DSL.literal(new ExprDatetimeValue(value))))) + .datetimeValue()); + } +} diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index e914803243..bf791b4925 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1196,6 +1196,43 @@ Example:: +---------------------+ +FROM_UNIXTIME +------------- + +Description +>>>>>>>>>>> + +Usage: Returns a representation of the argument given as a datetime or character string value. Perform reverse conversion for `UNIX_TIMESTAMP`_ function. +If second argument is provided, it is used to format the result in the same way as the format string used for the `DATE_FORMAT`_ function. +If timestamp is outside of range 1970-01-01 00:00:00 - 3001-01-18 23:59:59.999999 (0 to 32536771199.999999 epoch time), function returns NULL. + +Argument type: DOUBLE, STRING + +Return type map: + +DOUBLE -> DATETIME + +DOUBLE, STRING -> STRING + +Examples:: + + os> select FROM_UNIXTIME(1220249547) + fetched rows / total rows = 1/1 + +-----------------------------+ + | FROM_UNIXTIME(1220249547) | + |-----------------------------| + | 2008-09-01 06:12:27 | + +-----------------------------+ + + os> select FROM_UNIXTIME(1220249547, '%T') + fetched rows / total rows = 1/1 + +-----------------------------------+ + | FROM_UNIXTIME(1220249547, '%T') | + |-----------------------------------| + | 06:12:27 | + +-----------------------------------+ + + HOUR ---- @@ -1737,6 +1774,40 @@ Example:: +------------------------------+ +UNIX_TIMESTAMP +-------------- + +Description +>>>>>>>>>>> + +Usage: Converts given argument to Unix time (seconds since January 1st, 1970 at 00:00:00 UTC). If no argument given, it returns current Unix time. +The date argument may be a DATE, DATETIME, or TIMESTAMP string, or a number in YYMMDD, YYMMDDhhmmss, YYYYMMDD, or YYYYMMDDhhmmss format. If the argument includes a time part, it may optionally include a fractional seconds part. +If argument is in invalid format or outside of range 1970-01-01 00:00:00 - 3001-01-18 23:59:59.999999 (0 to 32536771199.999999 epoch time), function returns NULL. +You can use `FROM_UNIXTIME`_ to do reverse conversion. + +Argument type: /DOUBLE/DATE/DATETIME/TIMESTAMP + +Return type: DOUBLE + +Examples:: + + os> select UNIX_TIMESTAMP(20771122143845) + fetched rows / total rows = 1/1 + +----------------------------------+ + | UNIX_TIMESTAMP(20771122143845) | + |----------------------------------| + | 3404817525.0 | + +----------------------------------+ + + os> select UNIX_TIMESTAMP(TIMESTAMP('1996-11-15 17:05:42')) + fetched rows / total rows = 1/1 + +----------------------------------------------------+ + | UNIX_TIMESTAMP(TIMESTAMP('1996-11-15 17:05:42')) | + |----------------------------------------------------| + | 848077542.0 | + +----------------------------------------------------+ + + WEEK ---- diff --git a/docs/user/ppl/functions/datetime.rst b/docs/user/ppl/functions/datetime.rst index dbbc2abe21..3ee4317865 100644 --- a/docs/user/ppl/functions/datetime.rst +++ b/docs/user/ppl/functions/datetime.rst @@ -365,6 +365,42 @@ Example:: +---------------------+ +FROM_UNIXTIME +------------- + +Description +>>>>>>>>>>> + +Usage: Returns a representation of the argument given as a datetime or character string value. Perform reverse conversion for `UNIX_TIMESTAMP`_ function. +If second argument is provided, it is used to format the result in the same way as the format string used for the `DATE_FORMAT`_ function. +If timestamp is outside of range 1970-01-01 00:00:00 - 3001-01-18 23:59:59.999999 (0 to 32536771199.999999 epoch time), function returns NULL. +Argument type: DOUBLE, STRING + +Return type map: + +DOUBLE -> DATETIME + +DOUBLE, STRING -> STRING + +Examples:: + + os> source=people | eval `FROM_UNIXTIME(1220249547)` = FROM_UNIXTIME(1220249547) | fields `FROM_UNIXTIME(1220249547)` + fetched rows / total rows = 1/1 + +-----------------------------+ + | FROM_UNIXTIME(1220249547) | + |-----------------------------| + | 2008-09-01 06:12:27 | + +-----------------------------+ + + os> source=people | eval `FROM_UNIXTIME(1220249547, '%T')` = FROM_UNIXTIME(1220249547, '%T') | fields `FROM_UNIXTIME(1220249547, '%T')` + fetched rows / total rows = 1/1 + +-----------------------------------+ + | FROM_UNIXTIME(1220249547, '%T') | + |-----------------------------------| + | 06:12:27 | + +-----------------------------------+ + + HOUR ---- @@ -906,6 +942,32 @@ Example:: +-------------------------------+ +UNIX_TIMESTAMP +-------------- + +Description +>>>>>>>>>>> + +Usage: Converts given argument to Unix time (seconds since Epoch - very beginning of year 1970). If no argument given, it returns the current Unix time. +The date argument may be a DATE, DATETIME, or TIMESTAMP string, or a number in YYMMDD, YYMMDDhhmmss, YYYYMMDD, or YYYYMMDDhhmmss format. If the argument includes a time part, it may optionally include a fractional seconds part. +If argument is in invalid format or outside of range 1970-01-01 00:00:00 - 3001-01-18 23:59:59.999999 (0 to 32536771199.999999 epoch time), function returns NULL. +You can use `FROM_UNIXTIME`_ to do reverse conversion. + +Argument type: /DOUBLE/DATE/DATETIME/TIMESTAMP + +Return type: DOUBLE + +Example:: + + os> source=people | eval `UNIX_TIMESTAMP(double)` = UNIX_TIMESTAMP(20771122143845), `UNIX_TIMESTAMP(timestamp)` = UNIX_TIMESTAMP(TIMESTAMP('1996-11-15 17:05:42')) | fields `UNIX_TIMESTAMP(double)`, `UNIX_TIMESTAMP(timestamp)` + fetched rows / total rows = 1/1 + +--------------------------+-----------------------------+ + | UNIX_TIMESTAMP(double) | UNIX_TIMESTAMP(timestamp) | + |--------------------------+-----------------------------| + | 3404817525.0 | 848077542.0 | + +--------------------------+-----------------------------+ + + WEEK ---- diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/DateTimeFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/DateTimeFunctionIT.java index a0b0e8673b..b35db2a6e7 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/DateTimeFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/DateTimeFunctionIT.java @@ -662,4 +662,30 @@ public void testNowLikeFunctions() throws IOException { } TimeZone.setDefault(testTz); } + + @Test + public void testFromUnixTime() throws IOException { + var result = executeQuery(String.format( + "source=%s | eval f1 = FROM_UNIXTIME(200300400), f2 = FROM_UNIXTIME(12224.12), " + + "f3 = FROM_UNIXTIME(1662601316, '%%T') | fields f1, f2, f3", TEST_INDEX_DATE)); + verifySchema(result, + schema("f1", null, "datetime"), + schema("f2", null, "datetime"), + schema("f3", null, "string")); + verifySome(result.getJSONArray("datarows"), + rows("1976-05-07 07:00:00", "1970-01-01 03:23:44.12", "01:41:56")); + } + + @Test + public void testUnixTimeStamp() throws IOException { + var result = executeQuery(String.format( + "source=%s | eval f1 = UNIX_TIMESTAMP(MAKEDATE(1984, 1984)), " + + "f2 = UNIX_TIMESTAMP(TIMESTAMP('2003-12-31 12:00:00')), " + + "f3 = UNIX_TIMESTAMP(20771122143845) | fields f1, f2, f3", TEST_INDEX_DATE)); + verifySchema(result, + schema("f1", null, "double"), + schema("f2", null, "double"), + schema("f3", null, "double")); + verifySome(result.getJSONArray("datarows"), rows(613094400d, 1072872000d, 3404817525d)); + } } diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java index db4bec2540..2c9ab69779 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java @@ -648,6 +648,32 @@ public void testNowLikeFunctions() throws IOException { TimeZone.setDefault(testTz); } + @Test + public void testFromUnixTime() throws IOException { + var result = executeQuery( + "select FROM_UNIXTIME(200300400) f1, FROM_UNIXTIME(12224.12) f2, " + + "FROM_UNIXTIME(1662601316, '%T') f3"); + verifySchema(result, + schema("FROM_UNIXTIME(200300400)", "f1", "datetime"), + schema("FROM_UNIXTIME(12224.12)", "f2", "datetime"), + schema("FROM_UNIXTIME(1662601316, '%T')", "f3", "keyword")); + verifySome(result.getJSONArray("datarows"), + rows("1976-05-07 07:00:00", "1970-01-01 03:23:44.12", "01:41:56")); + } + + @Test + public void testUnixTimeStamp() throws IOException { + var result = executeQuery( + "select UNIX_TIMESTAMP(MAKEDATE(1984, 1984)) f1, " + + "UNIX_TIMESTAMP(TIMESTAMP('2003-12-31 12:00:00')) f2, " + + "UNIX_TIMESTAMP(20771122143845) f3"); + verifySchema(result, + schema("UNIX_TIMESTAMP(MAKEDATE(1984, 1984))", "f1", "double"), + schema("UNIX_TIMESTAMP(TIMESTAMP('2003-12-31 12:00:00'))", "f2", "double"), + schema("UNIX_TIMESTAMP(20771122143845)", "f3", "double")); + verifySome(result.getJSONArray("datarows"), rows(613094400d, 1072872000d, 3404817525d)); + } + protected JSONObject executeQuery(String query) throws IOException { Request request = new Request("POST", QUERY_API_ENDPOINT); request.setJsonEntity(String.format(Locale.ROOT, "{\n" + " \"query\": \"%s\"\n" + "}", query)); diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java index 7821a4be3a..008cbb7ec3 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java @@ -25,16 +25,12 @@ import static org.opensearch.sql.opensearch.data.type.OpenSearchDataType.OPENSEARCH_IP; import static org.opensearch.sql.opensearch.data.type.OpenSearchDataType.OPENSEARCH_TEXT; import static org.opensearch.sql.opensearch.data.type.OpenSearchDataType.OPENSEARCH_TEXT_KEYWORD; -import static org.opensearch.sql.opensearch.data.value.OpenSearchDateFormatters.SQL_LITERAL_DATE_TIME_FORMAT; -import static org.opensearch.sql.opensearch.data.value.OpenSearchDateFormatters.STRICT_DATE_OPTIONAL_TIME_FORMATTER; -import static org.opensearch.sql.opensearch.data.value.OpenSearchDateFormatters.STRICT_HOUR_MINUTE_SECOND_FORMATTER; +import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -81,13 +77,6 @@ public class OpenSearchExprValueFactory { @Setter private OpenSearchAggregationResponseParser parser; - private static final DateTimeFormatter DATE_TIME_FORMATTER = - new DateTimeFormatterBuilder() - .appendOptional(SQL_LITERAL_DATE_TIME_FORMAT) - .appendOptional(STRICT_DATE_OPTIONAL_TIME_FORMATTER) - .appendOptional(STRICT_HOUR_MINUTE_SECOND_FORMATTER) - .toFormatter(); - private static final String TOP_PATH = ""; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); diff --git a/ppl/src/main/antlr/OpenSearchPPLLexer.g4 b/ppl/src/main/antlr/OpenSearchPPLLexer.g4 index 4a48f00964..66741fc820 100644 --- a/ppl/src/main/antlr/OpenSearchPPLLexer.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLLexer.g4 @@ -239,6 +239,7 @@ DAYNAME: 'DAYNAME'; FROM_DAYS: 'FROM_DAYS'; LOCALTIME: 'LOCALTIME'; LOCALTIMESTAMP: 'LOCALTIMESTAMP'; +FROM_UNIXTIME: 'FROM_UNIXTIME'; MAKEDATE: 'MAKEDATE'; MAKETIME: 'MAKETIME'; MONTHNAME: 'MONTHNAME'; @@ -252,7 +253,7 @@ TO_DAYS: 'TO_DAYS'; UTC_DATE: 'UTC_DATE'; UTC_TIME: 'UTC_TIME'; UTC_TIMESTAMP: 'UTC_TIMESTAMP'; - +UNIX_TIMESTAMP: 'UNIX_TIMESTAMP'; // TEXT FUNCTIONS SUBSTR: 'SUBSTR'; diff --git a/ppl/src/main/antlr/OpenSearchPPLParser.g4 b/ppl/src/main/antlr/OpenSearchPPLParser.g4 index 1bd1140d8a..68b1d3cd17 100644 --- a/ppl/src/main/antlr/OpenSearchPPLParser.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLParser.g4 @@ -379,10 +379,12 @@ trigonometricFunctionName dateAndTimeFunctionBase : ADDDATE | DATE | DATE_ADD | DATE_FORMAT | DATE_SUB | DAY | DAYNAME | DAYOFMONTH | DAYOFWEEK - | DAYOFYEAR | FROM_DAYS | HOUR | MAKEDATE | MAKETIME | MICROSECOND | MINUTE | MONTH | MONTHNAME - | QUARTER | SECOND | SUBDATE | SYSDATE | TIME | TIME_TO_SEC| TIMESTAMP | TO_DAYS | WEEK | YEAR + | DAYOFYEAR | FROM_DAYS | FROM_UNIXTIME | HOUR | MAKEDATE | MAKETIME | MICROSECOND | MINUTE + | MONTH | MONTHNAME | QUARTER | SECOND | SUBDATE | SYSDATE | TIME | TIME_TO_SEC | TIMESTAMP + | TO_DAYS | UNIX_TIMESTAMP | WEEK | YEAR ; +// Functions which value could be cached in scope of a single query constantFunctionName : datetimeConstantLiteral | CURDATE | CURTIME | NOW diff --git a/sql/src/main/antlr/OpenSearchSQLLexer.g4 b/sql/src/main/antlr/OpenSearchSQLLexer.g4 index 0eaec2a5ee..de53bfffe5 100644 --- a/sql/src/main/antlr/OpenSearchSQLLexer.g4 +++ b/sql/src/main/antlr/OpenSearchSQLLexer.g4 @@ -209,6 +209,7 @@ EXP: 'EXP'; EXPM1: 'EXPM1'; FLOOR: 'FLOOR'; FROM_DAYS: 'FROM_DAYS'; +FROM_UNIXTIME: 'FROM_UNIXTIME'; IF: 'IF'; IFNULL: 'IFNULL'; ISNULL: 'ISNULL'; @@ -253,6 +254,7 @@ TIMESTAMP: 'TIMESTAMP'; TRUNCATE: 'TRUNCATE'; TO_DAYS: 'TO_DAYS'; UTC_DATE: 'UTC_DATE'; +UNIX_TIMESTAMP: 'UNIX_TIMESTAMP'; UPPER: 'UPPER'; UTC_TIME: 'UTC_TIME'; UTC_TIMESTAMP: 'UTC_TIMESTAMP'; diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index a0507c98ef..1100c07642 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -395,8 +395,9 @@ trigonometricFunctionName dateTimeFunctionName : ADDDATE | DATE | DATE_ADD | DATE_FORMAT | DATE_SUB | DAY | DAYNAME | DAYOFMONTH | DAYOFWEEK - | DAYOFYEAR | FROM_DAYS | HOUR | MAKEDATE | MAKETIME | MICROSECOND | MINUTE | MONTH | MONTHNAME - | QUARTER | SECOND | SUBDATE | SYSDATE | TIME | TIME_TO_SEC| TIMESTAMP | TO_DAYS | WEEK | YEAR + | DAYOFYEAR | FROM_DAYS | FROM_UNIXTIME | HOUR | MAKEDATE | MAKETIME | MICROSECOND | MINUTE + | MONTH | MONTHNAME | QUARTER | SECOND | SUBDATE | SYSDATE | TIME | TIME_TO_SEC | TIMESTAMP + | TO_DAYS | UNIX_TIMESTAMP | WEEK | YEAR ; // Functions which value could be cached in scope of a single query