All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.andrewoma.kwery.mapper.TimeConverters.kt Maven / Gradle / Ivy

/*
 * Copyright (c) 2015 Andrew O'Malley
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.andrewoma.kwery.mapper

import java.math.BigDecimal
import java.sql.Date
import java.sql.Time
import java.sql.Timestamp
import java.time.*
import java.time.temporal.ChronoUnit.*
import java.time.temporal.TemporalUnit

val offsetDateTimeConverter = OffsetDateTimeConverter()
val zonedDateTimeConverter = ZonedDateTimeConverter()

val timeConverters: Map, Converter<*>> = listOf(
        reifiedConverter(localDateTimeConverter),
        reifiedConverter(localDateConverter),
        reifiedConverter(localTimeConverter),
        reifiedConverter(instantConverter),
        reifiedConverter(durationToBigDecimalConverter),
        reifiedConverter(offsetDateTimeConverter),
        reifiedConverter(zonedDateTimeConverter)
).toMap()

object localDateTimeConverter : SimpleConverter(
        { row, c -> LocalDateTime.ofInstant(row.timestamp(c).toInstant(), ZoneId.systemDefault()) },
        { Timestamp.from(it.atZone(ZoneId.systemDefault()).toInstant()) }
)

object localDateConverter : SimpleConverter(
        { row, c -> row.date(c).toLocalDate() },
        { Date.valueOf(it) }
)

object localTimeConverter : SimpleConverter(
        { row, c -> row.time(c).toLocalTime() },
        {
            @Suppress("DEPRECATED_SYMBOL_WITH_MESSAGE") // No sane alternative
            Time(it.hour, it.minute, it.second)
        }
)

object instantConverter : SimpleConverter(
        { row, c -> row.timestamp(c).toInstant() },
        { Timestamp(it.toEpochMilli()) }
)

/**
 * Note: the offset is not stored in the database and will be lost. When reading the offset will
 * be set based on the zone id given to the converter on construction.
 */
class OffsetDateTimeConverter(val zone: ZoneId = ZoneId.systemDefault()) : SimpleConverter(
        { row, c -> OffsetDateTime.ofInstant(row.timestamp(c).toInstant(), zone)},
        { Timestamp(it.toInstant().toEpochMilli()) }
)

/**
 * Note: the zone is not stored in the database and will be lost. When reading the offset will
 * be set based on the zone id given to the converter on construction.
 */
class ZonedDateTimeConverter(val zone: ZoneId = ZoneId.systemDefault()) : SimpleConverter(
        { row, c -> ZonedDateTime.ofInstant(row.timestamp(c).toInstant(), zone)},
        { Timestamp(it.toInstant().toEpochMilli()) }
)

/**
 * Converts a Duration to a BigDecimal of seconds for storage.
 * A duration's max value is 9,223,372,036,854,775,807.999999999 seconds so requires DECIMAL(28,9) to store all
 * durations without overflow or truncation.
 */
object durationToBigDecimalConverter : SimpleConverter(
        { row, c -> row.bigDecimal(c).toDuration() },
        { it.toBigDecimal() }
)

/**
 * Converts a duration to a given unit when storing in the database as an integer.
 * Only NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS and DAYS are supported.
 * Ensure that if converting between units for storage that the conversion is a whole number
 * of the unit specified and does not overflow a Long.
 */
class DurationConverter(unit: TemporalUnit) : SimpleConverter(
        { row, c -> Duration.of(row.long(c), unit) },
        { duration ->
            val converted = when (unit) {
                NANOS -> duration.toNanos()
                MICROS -> duration.toNanos() / 1000
                MILLIS -> duration.toMillis()
                SECONDS -> duration.seconds
                MINUTES -> duration.toMinutes()
                HOURS -> duration.toHours()
                HALF_DAYS -> duration.toHours() / 12
                DAYS -> duration.toDays()
                else -> throw UnsupportedOperationException("") // Not possible
            }
            require(duration == Duration.of(converted, unit)) { "$duration must be a whole number of $unit and not overflow a Long" }
            converted
        }
) {
    companion object {
        private val supported = setOf(NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS, DAYS)
    }

    init {
        require(unit in supported) { "Only ${supported.joinToString(", ")} are supported" }
    }
}

private val nanosInSecond: BigDecimal = BigDecimal.valueOf(1000000000L)

fun BigDecimal.toDuration(): Duration {
    val seconds = this.longValue()
    val nanoseconds = this.remainder(BigDecimal.ONE).multiply(nanosInSecond).longValue()
    return Duration.ofSeconds(seconds, nanoseconds);
}

fun Duration.toBigDecimal(): BigDecimal {
    return BigDecimal.valueOf(this.seconds).add(BigDecimal.valueOf(this.nano.toLong()).divide(nanosInSecond))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy