MicroUtils/klock/src/commonMain/kotlin/korlibs/time/_Time.internal.kt

220 lines
7.4 KiB
Kotlin

@file:Suppress("PackageDirectoryMismatch")
package korlibs.time.internal
import korlibs.time.*
import kotlin.jvm.*
import kotlin.math.*
internal inline fun Int.chainComparison(comparer: () -> Int): Int = if (this == 0) comparer() else this
internal inline fun <T> List<T>.fastForEach(callback: (T) -> Unit) {
var n = 0
while (n < size) callback(this[n++])
}
// Original implementation grabbed from Kds to prevent additional dependencies:
// - https://github.com/korlibs/kds/blob/965f6017d7ad82e4bad714acf26cd7189186bdb3/kds/src/commonMain/kotlin/korlibs/datastructure/_Extensions.kt#L48
internal inline fun genericBinarySearch(
fromIndex: Int,
toIndex: Int,
invalid: (from: Int, to: Int, low: Int, high: Int) -> Int = { from, to, low, high -> -low - 1 },
check: (index: Int) -> Int
): Int {
var low = fromIndex
var high = toIndex - 1
while (low <= high) {
val mid = (low + high) / 2
val mval = check(mid)
when {
mval < 0 -> low = mid + 1
mval > 0 -> high = mid - 1
else -> return mid
}
}
return invalid(fromIndex, toIndex, low, high)
}
@JvmInline
internal value class BSearchResult(val raw: Int) {
val found: Boolean get() = raw >= 0
val index: Int get() = if (found) raw else -1
val nearIndex: Int get() = if (found) raw else -raw - 1
}
internal const val MILLIS_PER_SECOND = 1000
internal const val MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60 // 60_000
internal const val MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60 // 3600_000
internal const val MILLIS_PER_DAY = MILLIS_PER_HOUR * 24 // 86400_000
internal const val MILLIS_PER_WEEK = MILLIS_PER_DAY * 7 // 604800_000
internal const val MILLIS_PER_MICROSECOND = 1.0 / 1000.0
internal const val MILLIS_PER_NANOSECOND = MILLIS_PER_MICROSECOND / 1000.0
internal fun Int.padded(count: Int): String {
// @TODO: Handle edge case Int.MIN_VALUE that could not be represented as abs
val res = this.absoluteValue.toString().padStart(count, '0')
return if (this < 0) return "-$res" else res
}
internal fun Double.padded(intCount: Int, decCount: Int): String {
val intPart = floor(this).toInt()
val decPart = round((this - intPart) * 10.0.pow(decCount)).toInt()
return "${intPart.padded(intCount).substr(-intCount, intCount)}.${decPart.toString().padStart(decCount, '0').substr(-decCount)}"
}
internal fun String.substr(start: Int, length: Int = this.length): String {
val low = (if (start >= 0) start else this.length + start).coerceIn(0, this.length)
val high = (if (length >= 0) low + length else this.length + length).coerceIn(0, this.length)
return if (high < low) "" else this.substring(low, high)
}
internal fun String.splitKeep(regex: Regex): List<String> {
val str = this
val out = arrayListOf<String>()
var lastPos = 0
for (part in regex.findAll(this)) {
val prange = part.range
if (lastPos != prange.start) {
out += str.substring(lastPos, prange.start)
}
out += str.substring(prange)
lastPos = prange.endInclusive + 1
}
if (lastPos != str.length) {
out += str.substring(lastPos)
}
return out
}
internal class Moduler(val value: Double) {
private var avalue = abs(value)
private val sign = sign(value)
fun double(count: Double): Double {
val ret = (avalue / count)
avalue %= count
return floor(ret) * sign
}
fun double(count: Int): Double = double(count.toDouble())
fun double(count: Float): Double = double(count.toDouble())
fun int(count: Double): Int = double(count).toInt()
fun int(count: Int): Int = int(count.toDouble())
fun int(count: Float): Int = int(count.toDouble())
}
internal expect object KlockInternal {
val currentTime: Double
val now: TimeSpan
fun localTimezoneOffsetMinutes(time: DateTime): TimeSpan
fun sleep(time: TimeSpan)
}
expect interface Serializable
internal fun <K> MutableMap<K, Int>.increment(key: K) {
this.getOrPut(key) { 0 }
this[key] = this[key]!! + 1
}
internal class MicroStrReader(val str: String, var offset: Int = 0) {
val length get() = str.length
val available get() = str.length - offset
val hasMore get() = offset < str.length
val eof get() = !hasMore
inline fun readChunk(callback: () -> Unit): String {
val start = this.offset
callback()
val end = this.offset
return this.str.substring(start, end)
}
fun peekCharOrZero(): Char = if (hasMore) str[offset] else '\u0000'
fun peekChar(): Char = str[offset]
fun readChar(): Char = str[offset++]
fun tryRead(expected: Char): Boolean {
if (eof || peekChar() != expected) return false
readChar()
return true
}
fun tryReadOrNull(expected: String): String? {
return if (tryRead(expected)) expected else null
}
fun tryRead(expected: String): Boolean {
if (expected.length > available) return false
for (n in expected.indices) if (this.str[offset + n] != expected[n]) return false
offset += expected.length
return true
}
fun read(count: Int): String = this.str.substring(offset, (offset + count).coerceAtMost(length)).also { this.offset += it.length }
fun readRemaining(): String = read(available)
fun readInt(count: Int): Int = read(count).toInt()
fun tryReadInt(count: Int): Int? = read(count).toIntOrNull()
fun tryReadDouble(count: Int): Double? = read(count).replace(',', '.').toDoubleOrNull()
fun tryReadDouble(): Double? {
var numCount = 0
var num = 0
var denCount = 0
var den = 0
var decimals = false
loop@while (hasMore) {
when (val pc = peekChar()) {
',' -> {
if (numCount == 0) {
return null
}
decimals = true
readChar()
}
in '0'..'9' -> {
val c = readChar()
if (decimals) {
denCount++
den *= 10
den += (c - '0')
} else {
numCount++
num *= 10
num += (c - '0')
}
}
else -> {
break@loop
}
}
}
if (numCount == 0) {
return null
}
return num.toDouble() + (den.toDouble() * 10.0.pow(-denCount))
}
}
internal fun spinlock(time: TimeSpan) {
val start = TimeSpan.now()
while (TimeSpan.now() - start < time) Unit
}
internal fun MicroStrReader.readTimeZoneOffset(tzNames: TimezoneNames = TimezoneNames.DEFAULT): TimeSpan? {
val reader = this
for ((name, offset) in tzNames.namesToOffsets) {
if (name == "GMT" || name == "UTC") continue
if (reader.tryRead(name)) return offset
}
if (reader.tryRead('Z')) return 0.minutes
var sign = +1
reader.tryRead("GMT")
reader.tryRead("UTC")
if (reader.tryRead("+")) sign = +1
if (reader.tryRead("-")) sign = -1
val part = reader.readRemaining().replace(":", "")
val hours = part.substr(0, 2).padStart(2, '0').toIntOrNull() ?: return null
val minutes = part.substr(2, 2).padStart(2, '0').toIntOrNull() ?: return null
val roffset = hours.hours + minutes.minutes
return if (sign > 0) +roffset else -roffset
}