-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add UNIX epoch <-> TIMESTAMP conversion functions (#330)
- Loading branch information
Showing
7 changed files
with
300 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
lang/src/org/partiql/lang/eval/builtins/FromUnixTimeFunction.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package org.partiql.lang.eval.builtins | ||
|
||
import com.amazon.ion.Timestamp | ||
import org.partiql.lang.eval.Environment | ||
import org.partiql.lang.eval.ExprValue | ||
import org.partiql.lang.eval.ExprValueFactory | ||
import org.partiql.lang.eval.NullPropagatingExprFunction | ||
import org.partiql.lang.eval.bigDecimalValue | ||
import java.math.BigDecimal | ||
|
||
/** | ||
* Builtin function to convert the given unix epoch into a PartiQL `TIMESTAMP` [ExprValue]. A unix epoch represents | ||
* the seconds since '1970-01-01 00:00:00' UTC. Largely based off MySQL's FROM_UNIXTIME. | ||
* | ||
* Syntax: `FROM_UNIXTIME(unix_timestamp)` | ||
* Where unix_timestamp is a (potentially decimal) numeric value. If unix_timestamp is a decimal, the returned | ||
* `TIMESTAMP` will have fractional seconds. If unix_timestamp is an integer, the returned `TIMESTAMP` will not have | ||
* fractional seconds. | ||
* | ||
* When given a negative numeric value, this function returns a PartiQL `TIMESTAMP` [ExprValue] before the last epoch. | ||
* When given a non-negative numeric value, this function returns a PartiQL `TIMESTAMP` [ExprValue] after the last | ||
* epoch. | ||
*/ | ||
internal class FromUnixTimeFunction(valueFactory: ExprValueFactory) : NullPropagatingExprFunction("from_unixtime", 1, valueFactory) { | ||
private val millisPerSecond = BigDecimal(1000) | ||
|
||
override fun eval(env: Environment, args: List<ExprValue>): ExprValue { | ||
val unixTimestamp = args[0].bigDecimalValue() | ||
|
||
val numMillis = unixTimestamp.times(millisPerSecond).stripTrailingZeros() | ||
|
||
val timestamp = Timestamp.forMillis(numMillis, null) | ||
return valueFactory.newTimestamp(timestamp) | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
lang/src/org/partiql/lang/eval/builtins/UnixTimestampFunction.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package org.partiql.lang.eval.builtins | ||
|
||
import org.partiql.lang.eval.Environment | ||
import org.partiql.lang.eval.ExprValue | ||
import org.partiql.lang.eval.ExprValueFactory | ||
import org.partiql.lang.eval.NullPropagatingExprFunction | ||
import org.partiql.lang.eval.timestampValue | ||
import java.math.BigDecimal | ||
|
||
/** | ||
* Builtin function to convert the given PartiQL `TIMESTAMP` [ExprValue] into a unix epoch, where a unix epoch | ||
* represents the seconds since '1970-01-01 00:00:00' UTC. Largely based off MySQL's UNIX_TIMESTAMP. | ||
* | ||
* Syntax: `UNIX_TIMESTAMP([timestamp])` | ||
* | ||
* If UNIX_TIMESTAMP() is called with no [timestamp] argument, it returns the number of whole seconds since | ||
* '1970-01-01 00:00:00' UTC as a PartiQL `INT` [ExprValue] | ||
* | ||
* If UNIX_TIMESTAMP() is called with a [timestamp] argument, it returns the number of seconds from | ||
* '1970-01-01 00:00:00' UTC to the given [timestamp] argument. If given a [timestamp] before the last epoch, will | ||
* return the number of seconds before the last epoch as a negative number. The return value will be a decimal if and | ||
* only if the given [timestamp] has a fractional seconds part. | ||
* | ||
* The valid range of argument values is the range of PartiQL's `TIMESTAMP` value. | ||
*/ | ||
internal class UnixTimestampFunction(valueFactory: ExprValueFactory) : NullPropagatingExprFunction("unix_timestamp", 0..1, valueFactory) { | ||
private val millisPerSecond = BigDecimal(1000) | ||
|
||
override fun eval(env: Environment, args: List<ExprValue>): ExprValue { | ||
val timestamp = if (args.isEmpty()) { | ||
env.session.now | ||
} else { | ||
args[0].timestampValue() | ||
} | ||
|
||
val numMillis = timestamp.decimalMillis | ||
val epochTime = numMillis.divide(millisPerSecond) | ||
|
||
if (timestamp.decimalSecond.scale() == 0 || args.isEmpty()) { | ||
return valueFactory.newInt(epochTime.toLong()) | ||
} | ||
|
||
return valueFactory.newDecimal(epochTime) | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
lang/test/org/partiql/lang/eval/builtins/FromUnixTimeFunctionTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package org.partiql.lang.eval.builtins | ||
|
||
import org.junit.Test | ||
import org.junit.jupiter.params.ParameterizedTest | ||
import org.junit.jupiter.params.provider.ArgumentsSource | ||
import org.partiql.lang.errors.ErrorCode | ||
import org.partiql.lang.errors.Property | ||
import org.partiql.lang.eval.EvaluatorTestBase | ||
import org.partiql.lang.util.ArgumentsProviderBase | ||
|
||
data class FromUnixTimeTestCase(val unixTimestamp: String, val expected: String) | ||
|
||
class FromUnixTimeFunctionTest : EvaluatorTestBase() { | ||
private val testUnixTime = 1234567890 | ||
|
||
@Test | ||
fun `from_unixtime 0 args`() = | ||
checkInputThrowingEvaluationException( | ||
"from_unixtime()", | ||
ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, | ||
mapOf(Property.LINE_NUMBER to 1L, | ||
Property.COLUMN_NUMBER to 1L, | ||
Property.EXPECTED_ARITY_MIN to 1, | ||
Property.EXPECTED_ARITY_MAX to 1)) | ||
|
||
@Test | ||
fun `from_unixtime 2 args`() = | ||
checkInputThrowingEvaluationException( | ||
"from_unixtime($testUnixTime, $testUnixTime)", | ||
ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, | ||
mapOf(Property.LINE_NUMBER to 1L, | ||
Property.COLUMN_NUMBER to 1L, | ||
Property.EXPECTED_ARITY_MIN to 1, | ||
Property.EXPECTED_ARITY_MAX to 1)) | ||
|
||
@Test | ||
fun `from_unixtime 3 args`() = | ||
checkInputThrowingEvaluationException( | ||
"from_unixtime($testUnixTime, $testUnixTime, $testUnixTime)", | ||
ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, | ||
mapOf(Property.LINE_NUMBER to 1L, | ||
Property.COLUMN_NUMBER to 1L, | ||
Property.EXPECTED_ARITY_MIN to 1, | ||
Property.EXPECTED_ARITY_MAX to 1)) | ||
|
||
|
||
class FromUnixTimeTests : ArgumentsProviderBase() { | ||
override fun getParameters(): List<Any> = listOf( | ||
// negative unix epochs output timestamp before last epoch | ||
FromUnixTimeTestCase("from_unixtime(-1)", "1969-12-31T23:59:59-00:00"), | ||
FromUnixTimeTestCase("from_unixtime(-0.1)", "1969-12-31T23:59:59.9-00:00"), | ||
// non-negative cases outputting a timestamp after last epoch | ||
FromUnixTimeTestCase("from_unixtime(0)", "1970-01-01T00:00:00.000-00:00"), | ||
FromUnixTimeTestCase("from_unixtime(0.001)", "1970-01-01T00:00:00.001-00:00"), | ||
FromUnixTimeTestCase("from_unixtime(0.01)", "1970-01-01T00:00:00.01-00:00"), | ||
FromUnixTimeTestCase("from_unixtime(0.1)", "1970-01-01T00:00:00.1-00:00"), | ||
FromUnixTimeTestCase("from_unixtime(1)", "1970-01-01T00:00:01-00:00"), | ||
FromUnixTimeTestCase("from_unixtime(1577836800)", "2020-01-01T00:00:00-00:00") | ||
) | ||
} | ||
@ParameterizedTest | ||
@ArgumentsSource(FromUnixTimeTests::class) | ||
fun runNoArgTests(tc: FromUnixTimeTestCase) = assertEval(tc.unixTimestamp, tc.expected) | ||
} |
90 changes: 90 additions & 0 deletions
90
lang/test/org/partiql/lang/eval/builtins/UnixTimestampFunctionTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package org.partiql.lang.eval.builtins | ||
|
||
import com.amazon.ion.Timestamp | ||
import org.junit.Test | ||
import org.junit.jupiter.params.ParameterizedTest | ||
import org.junit.jupiter.params.provider.ArgumentsSource | ||
import org.partiql.lang.errors.ErrorCode | ||
import org.partiql.lang.errors.Property | ||
import org.partiql.lang.eval.EvaluationSession | ||
import org.partiql.lang.eval.EvaluatorTestBase | ||
import org.partiql.lang.util.ArgumentsProviderBase | ||
|
||
data class UnixTimestampNoArgTestCase(val numMillis: Long, val expected: String) | ||
data class UnixTimestampOneArgTestCase(val timestamp: String, val expected: String) | ||
|
||
class UnixTimestampFunctionTest : EvaluatorTestBase() { | ||
private val testTimestamp = "`2007-02-23T12:14Z`" | ||
|
||
@Test | ||
fun `unix_timestamp 2 args`() = | ||
checkInputThrowingEvaluationException( | ||
"unix_timestamp($testTimestamp, $testTimestamp)", | ||
ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, | ||
mapOf(Property.LINE_NUMBER to 1L, | ||
Property.COLUMN_NUMBER to 1L, | ||
Property.EXPECTED_ARITY_MIN to 0, | ||
Property.EXPECTED_ARITY_MAX to 1)) | ||
|
||
@Test | ||
fun `unix_timestamp 3 args`() = | ||
checkInputThrowingEvaluationException( | ||
"unix_timestamp($testTimestamp, $testTimestamp, $testTimestamp)", | ||
ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, | ||
mapOf(Property.LINE_NUMBER to 1L, | ||
Property.COLUMN_NUMBER to 1L, | ||
Property.EXPECTED_ARITY_MIN to 0, | ||
Property.EXPECTED_ARITY_MAX to 1)) | ||
|
||
|
||
class NoArgsTests : ArgumentsProviderBase() { | ||
override fun getParameters(): List<Any> = listOf( | ||
// unix_timestamp no args, now = 0 | ||
UnixTimestampNoArgTestCase(numMillis = 0, expected = "0"), | ||
// nix_timestamp no args, now = 1ms | ||
UnixTimestampNoArgTestCase(numMillis = 1, expected = "0"), | ||
// unix_timestamp no args, now = 999ms | ||
UnixTimestampNoArgTestCase(numMillis = 999, expected = "0"), | ||
// unix_timestamp no args, now = 1s | ||
UnixTimestampNoArgTestCase(numMillis = 1000, expected = "1"), | ||
// unix_timestamp no args, now = 1001ms | ||
UnixTimestampNoArgTestCase(numMillis = 1001, expected = "1") | ||
) | ||
} | ||
@ParameterizedTest | ||
@ArgumentsSource(NoArgsTests::class) | ||
fun runNoArgTests(tc: UnixTimestampNoArgTestCase) = | ||
assertEval( | ||
"unix_timestamp()", | ||
tc.expected, | ||
session = EvaluationSession.build { now(Timestamp.forMillis(tc.numMillis, 0)) }) | ||
|
||
|
||
class OneArgTests : ArgumentsProviderBase() { | ||
private val epoch2020 = "1577836800" | ||
private val epoch2020Decimal = "1577836800." | ||
|
||
override fun getParameters(): List<Any> = listOf( | ||
// time before the last epoch | ||
UnixTimestampOneArgTestCase("unix_timestamp(`1969T`)", "-31536000"), | ||
UnixTimestampOneArgTestCase("unix_timestamp(`1969-12-31T23:59:59.999Z`)", "-0.001"), | ||
// exactly the last epoch | ||
UnixTimestampOneArgTestCase("unix_timestamp(`1970T`)", "0"), | ||
UnixTimestampOneArgTestCase("unix_timestamp(`1970-01-01T00:00:00.000Z`)", "0."), | ||
// whole number unix epoch | ||
UnixTimestampOneArgTestCase("unix_timestamp(`2020T`)", epoch2020), | ||
UnixTimestampOneArgTestCase("unix_timestamp(`2020-01T`)", epoch2020), | ||
UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T`)", epoch2020), | ||
UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00Z`)", epoch2020), | ||
UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00Z`)", epoch2020), | ||
// decimal unix epoch | ||
UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00.0Z`)", epoch2020Decimal), | ||
UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00.00Z`)", epoch2020Decimal), | ||
UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00.000Z`)", epoch2020Decimal), | ||
UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00.100Z`)", "1577836800.1") | ||
) | ||
} | ||
@ParameterizedTest | ||
@ArgumentsSource(OneArgTests::class) | ||
fun runOneArgTests(tc: UnixTimestampOneArgTestCase) = assertEval(tc.timestamp, tc.expected) | ||
} |