Skip to content

Commit

Permalink
Backup the spi.datetime package
Browse files Browse the repository at this point in the history
  • Loading branch information
RCHowell committed Nov 25, 2024
1 parent 6a4b3e2 commit 1fd7894
Show file tree
Hide file tree
Showing 19 changed files with 2,487 additions and 0 deletions.
98 changes: 98 additions & 0 deletions partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Date.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.partiql.spi.datetime

import org.partiql.spi.datetime.util.DatetimeComparisons
import java.math.BigDecimal

/**
* Superclass for all implementations representing date value.
* Date represents a calendar system, (i.e., 2023-06-01).
* It does not include information on time or timezone, instead, it is meant to represent a specific date on calendar.
* For example, 2022-11-25 (black friday in 2022).
* The valid range are from 0001-01-01 to 9999-12-31
* The [day] must be valid for the year and month, otherwise an exception will be thrown.
*/
public sealed interface Date : Datetime, Comparable<Date> {

public override val year: Int
public override val month: Int
public override val day: Int

/**
* Hour field for [Date] value is always null.
*/
public override val hour: Int?
get() = null

/**
* Minute field for [Date] value is always null.
*/
public override val minute: Int?
get() = null

/**
* Second field for [Date] value is always null.
*/
public override val decimalSecond: BigDecimal?
get() = null

/**
* Timezone field for [Date] value is always null.
*/
public override val timeZone: Timezone?
get() = null

// Operation
/**
* Construct a [Timestamp] value by appending [time] to this [Date] value.
*/
public fun atTime(time: Time): Timestamp

/**
* Returns a [Date] value with the specified number of days added.
* The month and year fields may be changed as necessary to ensure the result remains valid.
* [days] can be negative.
*/
public fun plusDays(days: Long): Date

/**
* Returns a [Date] value with the specified number of months added.
* The month and year fields may be changed as necessary to ensure the result remains valid.
* [months] can be negative.
*/
public fun plusMonths(months: Long): Date

/**
* Returns a [Date] value with the specified number of months added.
* [years] can be negative.
*/
public fun plusYears(years: Long): Date

/**
* Comparison method for [Date] value.
*
* Since [Date] value has no concept of time zone, they are compared as calendar date.
*/
public override fun compareTo(other: Date): Int =
DatetimeComparisons.compareTo(this, other)
}

/**
* Superclass for all implementation representing date value
*/
public abstract class DateImpl : Date, Comparable<Date> {
public final override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as DateImpl
if (this.year != other.year) return false
if (this.month != other.month) return false
if (this.day != other.day) return false
return true
}

public final override fun hashCode(): Int =
year.hashCode() + month.hashCode() + day.hashCode()

public final override fun toString(): String =
"${this.javaClass.simpleName}(year=$year, month=$month, day=$day)"
}
292 changes: 292 additions & 0 deletions partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Datetime.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file 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 org.partiql.spi.datetime

import java.math.BigDecimal

/**
* Superclass for all classes representing datetime values.
*/
public sealed interface Datetime {
/**
* Year field of date time object
*/
public val year: Int?

/**
* Month field of date time object
*/
public val month: Int?

/**
* Day field of date time object
*/
public val day: Int?

/**
* Hour field of date time object
*/
public val hour: Int?

/**
* Minute field of date time object
*/
public val minute: Int?

/**
* Second field of date time object.
* This field includes second fraction.
*/
public val decimalSecond: BigDecimal?

/**
* Time zone field of date time object. See [Timezone]
*/
public val timeZone: Timezone?

/**
* Equals method.
* Two [Datetime] values are considered equals if and only if all the fields are equals.
*/
public override fun equals(other: Any?): Boolean

public override fun hashCode(): Int

public override fun toString(): String

public companion object {

/**
* Create a timestamp value.
*
* If time zone is null, then the value created is timestamp without timezone.
* Otherwise, a timestamp with timezone is created.
*
* @param year Proleptic Year
* @param month Month of Year
* @param day Day Of Month
* @param hour Hour of Day
* @param minute Minute of Hour
* @param second Second, include any fraction second.
* @param timeZone TimeZone offset, see [Timezone]
*/
@JvmStatic
@JvmOverloads
public fun timestamp(
year: Int,
month: Int = 1,
day: Int = 1,
hour: Int = 0,
minute: Int = 0,
second: BigDecimal = BigDecimal.ZERO,
timeZone: Timezone? = null,
): Timestamp = when (timeZone) {
Timezone.UnknownTimeZone -> {
if (second.scale() <= 9) {
OffsetTimestampLowPrecision.of(year, month, day, hour, minute, second, timeZone)
} else {
OffsetTimestampHighPrecision.of(year, month, day, hour, minute, second, timeZone)
}
}
is Timezone.UtcOffset -> {
if (timeZone.totalOffsetMinutes.absoluteValue > JAVA_MAX_OFFSET) {
OffsetTimestampHighPrecision.of(year, month, day, hour, minute, second, timeZone)
} else if (second.scale() <= 9) {
OffsetTimestampLowPrecision.of(year, month, day, hour, minute, second, timeZone)
} else {
OffsetTimestampHighPrecision.of(year, month, day, hour, minute, second, timeZone)
}
}

null -> {
if (second.scale() <= 9) LocalTimestampLowPrecision.of(year, month, day, hour, minute, second)
else LocalTimestampHighPrecision.of(year, month, day, hour, minute, second)
}
}

/**
* Create a timestamp value.
* The timestamp created will have precision 0 (no fractional second).
*
* @param year Proleptic Year
* @param month Month of Year
* @param day Day Of Month
* @param hour Hour of Day
* @param minute Minute of Hour
* @param second whole Second.
* @param timeZone TimeZone offset, see [Timezone]
*/
@JvmStatic
@JvmOverloads
public fun timestamp(
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int,
timeZone: Timezone? = null,
): Timestamp = timestamp(year, month, day, hour, minute, second.toBigDecimal(), timeZone)

/**
* Create a timestamp value.
* If time is an instance of [TimeWithTimeZone], then the timestamp created will be [TimestampWithTimeZone],
* Otherwise it will be a [TimestampWithoutTimeZone].
*
* @param date: Date. See [Date]
* @param time: Time. See [Time]
*/
@JvmStatic
public fun timestamp(date: Date, time: Time): Timestamp = when (time) {
is TimeWithTimeZone -> timestamp(
date.year, date.month, date.day, time.hour, time.minute, time.decimalSecond, time.timeZone
)

is TimeWithoutTimeZone -> timestamp(
date.year, date.month, date.day, time.hour, time.minute, time.decimalSecond
)
}

/**
* Create a timestamp value based on [com.amazon.ion.Timestamp]
* The created timestamp will always be an instance of [TimestampWithTimeZone]
*/
@JvmStatic
public fun timestamp(ionTimestamp: com.amazon.ion.Timestamp): TimestampWithTimeZone =
if (ionTimestamp.localOffset != null && ionTimestamp.localOffset.absoluteValue > JAVA_MAX_OFFSET) {
OffsetTimestampHighPrecision.forIonTimestamp(ionTimestamp)
} else if (ionTimestamp.decimalSecond.scale() <= 9) {
OffsetTimestampLowPrecision.forIonTimestamp(ionTimestamp)
} else {
OffsetTimestampHighPrecision.forIonTimestamp(ionTimestamp)
}

/**
* Create a timestamp value based on displacement of Unix Epoch, at given time zone.
* The created timestamp will always be an instance of [TimestampWithTimeZone]
*/
@JvmStatic
public fun timestamp(epochSeconds: BigDecimal, timeZone: Timezone): TimestampWithTimeZone = when (timeZone) {
Timezone.UnknownTimeZone -> {
if (epochSeconds.scale() <= 9) {
OffsetTimestampLowPrecision.forEpochSeconds(epochSeconds, timeZone)
} else {
OffsetTimestampHighPrecision.forEpochSeconds(epochSeconds, timeZone)
}
}
is Timezone.UtcOffset -> {
if (timeZone.totalOffsetMinutes > JAVA_MAX_OFFSET) {
OffsetTimestampHighPrecision.forEpochSeconds(epochSeconds, timeZone)
} else if (epochSeconds.scale() <= 9) {
OffsetTimestampLowPrecision.forEpochSeconds(epochSeconds, timeZone)
} else {
OffsetTimestampHighPrecision.forEpochSeconds(epochSeconds, timeZone)
}
}
}

/**
* Create a time value.
*
* If time zone is null, then the value created is time without timezone.
* Otherwise, a time with timezone is created.
*
* @param hour Hour of Day
* @param minute Minute of Hour
* @param second Second, include any fraction second.
* @param timeZone TimeZone offset, see [Timezone]
*/
@JvmStatic
@JvmOverloads
public fun time(
hour: Int,
minute: Int,
second: BigDecimal,
timeZone: Timezone? = null,
): Time = when (timeZone) {
Timezone.UnknownTimeZone -> {
if (second.scale() <= 9) {
OffsetTimeLowPrecision.of(hour, minute, second, timeZone)
} else {
OffsetTimeHighPrecision.of(hour, minute, second, timeZone)
}
}
is Timezone.UtcOffset -> {
if (timeZone.totalOffsetMinutes.absoluteValue > JAVA_MAX_OFFSET) {
OffsetTimeHighPrecision.of(hour, minute, second, timeZone)
} else if (second.scale() <= 9) {
OffsetTimeLowPrecision.of(hour, minute, second, timeZone)
} else {
OffsetTimeHighPrecision.of(hour, minute, second, timeZone)
}
}

null -> {
if (second.scale() <= 9) LocalTimeLowPrecision.of(hour, minute, second)
else LocalTimeHighPrecision.of(hour, minute, second)
}
}

/**
* Create a time value.
* The time created will have precision 0 (no fractional second).
*
* @param hour Hour of Day
* @param minute Minute of Hour
* @param second whole Second.
* @param timeZone TimeZone offset, see [Timezone]
*/
@JvmStatic
@JvmOverloads
public fun time(
hour: Int,
minute: Int,
second: Int,
timeZone: Timezone? = null,
): Time = time(hour, minute, second.toBigDecimal(), timeZone)

/**
* Create a time value.
* The time created will have precision 9 (nanosecond precision).
*
* @param hour Hour of Day
* @param minute Minute of Hour
* @param second whole Second.
* @param nano Nano offset.
* @param timeZone TimeZone offset, see [Timezone]
*/
@JvmStatic
@JvmOverloads
public fun time(
hour: Int,
minute: Int,
second: Int,
nano: Int,
timeZone: Timezone? = null,
): Time {
val decimalSecond = second.toBigDecimal().plus(nano.toBigDecimal().movePointLeft(9))
return time(hour, minute, decimalSecond, timeZone)
}

@JvmStatic
public fun date(
year: Int,
month: Int,
day: Int,
): Date = SqlDate.of(year, month, day)
}
}

Loading

0 comments on commit 1fd7894

Please sign in to comment.