Compare commits

..

No commits in common. "master" and "v2.2.5" have entirely different histories.

19 changed files with 79 additions and 407 deletions

View File

@ -14,6 +14,11 @@ jobs:
sed -i -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" gradle.properties sed -i -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" gradle.properties
- name: prebuild - name: prebuild
run: ./gradlew clean build run: ./gradlew clean build
- name: Publish to Gitea
continue-on-error: true
run: ./gradlew publishAllPublicationsToGiteaRepository
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
- name: Publish package - name: Publish package
continue-on-error: true continue-on-error: true
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
.idea .idea
.kotlin
out/* out/*
*.iml *.iml
target target

View File

@ -1,67 +1,5 @@
# Changelog # Changelog
## 2.6.1
* Fixes in parsing of string parts
## 2.6.0
* Fix of `doOnceTz` behaviour: now it will use local time as utc time to get next waking up time
## 2.5.1
* Add cleaning up of incoming template, which must remove all malformed parts from string template (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126))
* Add support of insufficient amount of arguments (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126))
* `Version`:
* `Kotlin`: `2.0.21`
* `AndroidXWork`: `2.10.0`
## 2.5.0
* `Version`:
* `Kotlin`: `2.0.20`
* `Coroutines`: `1.9.0`
* `Serialization`: `1.7.3`
## 2.4.0
* `Version`:
* `Kotlin`: `2.0.10`
* `Coroutines`: `1.8.1`
* `Serialization`: `1.7.1`
* `Klock`: `5.4.0`
* `AndroidXWork`: `2.9.1`
## 2.3.0
* `doWhile` now will guarantee that it will not call `doOnce` more than once for time
## 2.2.9
* `Version`:
* `Kotlin`: `1.9.23`
* `Serialization`: `1.6.3`
* `Klock`: `5.3.2`
* Fixes in build-in schedulers
## 2.2.8
* `Version`:
* `Coroutines`: `1.8.0`
## 2.2.7
* `Version`:
* `Klock`: `5.3.1`
* `AndroidXWork`: `2.9.0`
## 2.2.6
**Since this version klock depdendency replaced with `com.soywiz.korge:korlibs-time`**
* `Version`:
* `Klock`: `5.3.0`
## 2.2.5 ## 2.2.5
* `Versions` * `Versions`

View File

@ -71,7 +71,7 @@ kotlin {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlin_serialization_version" api "org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlin_serialization_version"
api "com.soywiz.korge:korlibs-time:$klockVersion" api "com.soywiz.korlibs.klock:klock:$klockVersion"
} }
} }

View File

@ -18,13 +18,13 @@ if (new File(projectDir, "secret.gradle").exists()) {
githubRelease { githubRelease {
token "${project.property('GITHUB_RELEASE_TOKEN')}" token "${project.property('GITHUB_RELEASE_TOKEN')}"
owner = "InsanusMokrassar" owner "InsanusMokrassar"
repo = "${rootProject.name}" repo "${rootProject.name}"
tagName = "v${project.version}" tagName "v${project.version}"
releaseName = "${project.version}" releaseName "${project.version}"
targetCommitish = "${project.version}" targetCommitish "${project.version}"
body = getCurrentVersionChangelog("${project.version}") body getCurrentVersionChangelog("${project.version}")
} }
} }

View File

@ -9,32 +9,32 @@ android.useAndroidX=true
android.enableJetifier=false android.enableJetifier=false
kotlin_version=2.0.21 kotlin_version=1.9.22
kotlin_coroutines_version=1.9.0 kotlin_coroutines_version=1.7.3
kotlin_serialization_version=1.7.3 kotlin_serialization_version=1.6.2
dokka_version=1.9.20 dokka_version=1.9.10
klockVersion=5.4.0 klockVersion=4.0.10
## Github reease ## Github reease
github_release_plugin_version=2.5.2 github_release_plugin_version=2.4.1
## Android ## Android
android_gradle_version=8.2.2 android_gradle_version=8.1.4
android_minSdkVersion=21 android_minSdkVersion=21
android_compileSdkVersion=35 android_compileSdkVersion=34
android_buildToolsVersion=35.0.0 android_buildToolsVersion=34.0.0
dexcount_version=4.0.0 dexcount_version=4.0.0
junit_version=4.12 junit_version=4.12
test_ext_junit_version=1.1.3 test_ext_junit_version=1.1.3
espresso_core=3.4.0 espresso_core=3.4.0
androidx_work_version=2.10.0 androidx_work_version=2.8.1
## Common ## Common
version=2.6.1 version=2.2.5
android_code_version=45 android_code_version=35

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip

View File

@ -50,14 +50,18 @@ publishing {
} }
} }
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) { if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
maven { maven {
name = "InmoNexus" name = "Gitea"
url = uri("https://nexus.inmo.dev/repository/maven-releases/") url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
credentials { credentials(HttpHeaderCredentials) {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER') name = "Authorization"
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD') value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
}
authentication {
header(HttpHeaderAuthentication)
} }
} }
@ -98,21 +102,4 @@ if (project.hasProperty("signing.gnupg.keyName")) {
def signingTasks = project.getTasks().withType(Sign.class) def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks) mustRunAfter(signingTasks)
} }
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
} }

View File

@ -1 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/krontab/blob/master/LICENSE"}],"mavenConfig":{"name":"Krontab","description":"It is an analog of crontab util for Kotlin Coroutines","url":"https://github.com/InsanusMokrassar/krontab","vcsUrl":"https://github.com/InsanusMokrassar/krontab.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/krontab"},{"name":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}} {"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/krontab/blob/master/LICENSE"}],"mavenConfig":{"name":"Krontab","description":"It is an analog of crontab util for Kotlin Coroutines","url":"https://github.com/InsanusMokrassar/krontab","vcsUrl":"https://github.com/InsanusMokrassar/krontab.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/krontab"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}

View File

@ -3,7 +3,6 @@ package dev.inmo.krontab
import android.content.Context import android.content.Context
import androidx.work.* import androidx.work.*
import korlibs.time.DateTime import korlibs.time.DateTime
import korlibs.time.millisecondsLong
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
@ -139,14 +138,10 @@ abstract class KronSchedulerWork(
} }
throw e throw e
} }
result.checkResults( when (result) {
onFailure = { is Result.Failure -> if (prolongOnFailure) prolong()
if (prolongOnFailure) prolong() is Result.Success -> if (prolongOnSuccess) prolong()
}, }
onSuccess = {
if (prolongOnSuccess) prolong()
}
)
return result return result
} }
} }

View File

@ -1,15 +0,0 @@
package dev.inmo.krontab
import android.annotation.SuppressLint
import androidx.work.ListenableWorker
@SuppressLint("RestrictedApi")
internal inline fun ListenableWorker.Result.checkResults(
onFailure: () -> Unit,
onSuccess: () -> Unit
) {
when (this) {
is ListenableWorker.Result.Failure -> onFailure()
is ListenableWorker.Result.Success -> onSuccess()
}
}

View File

@ -2,7 +2,6 @@ package dev.inmo.krontab
import korlibs.time.DateTime import korlibs.time.DateTime
import korlibs.time.DateTimeTz import korlibs.time.DateTimeTz
import korlibs.time.millisecondsLong
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
@ -35,8 +34,6 @@ suspend inline fun <T> KronScheduler.doOnceLocal(block: (DateTime) -> T): T = do
/** /**
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation. * Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation.
* *
* WARNING!!! This method will use local time instead of utc
*
* WARNING!!! If you want to launch it in parallel, you must do this explicitly. * WARNING!!! If you want to launch it in parallel, you must do this explicitly.
* *
* WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediatelly * WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediatelly
@ -44,7 +41,7 @@ suspend inline fun <T> KronScheduler.doOnceLocal(block: (DateTime) -> T): T = do
suspend inline fun <T> KronScheduler.doOnceTz(noinline block: suspend (DateTimeTz) -> T): T { suspend inline fun <T> KronScheduler.doOnceTz(noinline block: suspend (DateTimeTz) -> T): T {
val time = when (this) { val time = when (this) {
is KronSchedulerTz -> nextOrNowWithOffset() is KronSchedulerTz -> nextOrNowWithOffset()
else -> nextTimeZoned() ?: DateTimeTz.nowLocal() else -> nextOrNow().local
} }
delay((time - DateTimeTz.nowLocal()).millisecondsLong) delay((time - DateTimeTz.nowLocal()).millisecondsLong)
return block(time) return block(time)
@ -73,18 +70,9 @@ suspend inline fun <T> doOnceTz(
* Will execute [block] while it will return true as a result of its calculation * Will execute [block] while it will return true as a result of its calculation
*/ */
suspend inline fun KronScheduler.doWhile(block: (DateTime) -> Boolean) { suspend inline fun KronScheduler.doWhile(block: (DateTime) -> Boolean) {
var latest: DateTime? = null
do { do {
delay(1L) delay(1L)
val result = doOnce { } while (doOnce(block))
if (latest != it) {
latest = it
block(it)
} else {
null
}
}
} while (result == null || result)
} }
/** /**
* Will execute [block] while it will return true as a result of its calculation * Will execute [block] while it will return true as a result of its calculation
@ -96,18 +84,9 @@ suspend inline fun KronScheduler.doWhileLocal(block: (DateTime) -> Boolean) = do
* Will execute [block] while it will return true as a result of its calculation * Will execute [block] while it will return true as a result of its calculation
*/ */
suspend inline fun KronScheduler.doWhileTz(noinline block: suspend (DateTimeTz) -> Boolean) { suspend inline fun KronScheduler.doWhileTz(noinline block: suspend (DateTimeTz) -> Boolean) {
var latest: DateTimeTz? = null
do { do {
delay(1L) delay(1L)
val result = doOnceTz { } while (doOnceTz(block))
if (latest != it) {
latest = it
block(it)
} else {
null
}
}
} while (result == null || result)
} }
/** /**

View File

@ -1,6 +1,5 @@
package dev.inmo.krontab package dev.inmo.krontab
import dev.inmo.krontab.internal.*
import dev.inmo.krontab.internal.CronDateTimeScheduler import dev.inmo.krontab.internal.CronDateTimeScheduler
import dev.inmo.krontab.internal.CronDateTimeSchedulerTz import dev.inmo.krontab.internal.CronDateTimeSchedulerTz
import dev.inmo.krontab.internal.createKronScheduler import dev.inmo.krontab.internal.createKronScheduler
@ -95,37 +94,27 @@ value class KrontabConfig(
var dayOfWeekParsed: Array<Byte>? = null var dayOfWeekParsed: Array<Byte>? = null
var yearParsed: Array<Int>? = null var yearParsed: Array<Int>? = null
var millisecondsParsed: Array<Short>? = null var millisecondsParsed: Array<Short>? = null
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template.split(" ").also {
.split(" ") listOfNotNull(
.filter { checkIncomingPart(it) } // filter garbage from string it.getOrNull(5),
.let { it.getOrNull(6),
if (it.size < 5) { // reconstruction in case of insufficient arguments; 5 is amount of required arguments out of latest also code it.getOrNull(7),
it + (it.size until 5).map { "*" } it.getOrNull(8)
} else { ).forEach {
it val offsetFromString = parseOffset(it)
} val dayOfWeekFromString = parseWeekDay(it)
} val millisecondsFromString = parseMilliseconds(it)
.also { offsetParsed = offsetParsed ?: offsetFromString
listOfNotNull( dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString
it.getOrNull(5), millisecondsParsed = millisecondsParsed ?: millisecondsFromString
it.getOrNull(6), when {
it.getOrNull(7), dayOfWeekFromString != null || offsetFromString != null || millisecondsFromString != null -> return@forEach
it.getOrNull(8) yearParsed == null -> {
).forEach { yearParsed = parseYears(it)
val offsetFromString = parseOffset(it)
val dayOfWeekFromString = parseWeekDay(it)
val millisecondsFromString = parseMilliseconds(it)
offsetParsed = offsetParsed ?: offsetFromString
dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString
millisecondsParsed = millisecondsParsed ?: millisecondsFromString
when {
dayOfWeekFromString != null || offsetFromString != null || millisecondsFromString != null -> return@forEach
yearParsed == null -> {
yearParsed = parseYears(it)
}
} }
} }
} }
}
val secondsParsed = parseSeconds(secondsSource) val secondsParsed = parseSeconds(secondsSource)
val minutesParsed = parseMinutes(minutesSource) val minutesParsed = parseMinutes(minutesSource)

View File

@ -34,60 +34,35 @@ val EverySecondScheduler: KronScheduler by lazy {
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one minute * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one minute
*/ */
val EveryMinuteScheduler: KronScheduler by lazy { val EveryMinuteScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { minutes { 0 every 1 } }
seconds { at(0) }
minutes { 0 every 1 }
}
} }
/** /**
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one hour * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one hour
*/ */
val EveryHourScheduler: KronScheduler by lazy { val EveryHourScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { hours { 0 every 1 } }
seconds { at(0) }
minutes { at(0) }
hours { 0 every 1 }
}
} }
/** /**
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one day * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one day
*/ */
val EveryDayOfMonthScheduler: KronScheduler by lazy { val EveryDayOfMonthScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { dayOfMonth { 0 every 1 } }
seconds { at(0) }
minutes { at(0) }
hours { at(0) }
dayOfMonth { 0 every 1 }
}
} }
/** /**
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one month * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one month
*/ */
val EveryMonthScheduler: KronScheduler by lazy { val EveryMonthScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { months { 0 every 1 } }
seconds { at(0) }
minutes { at(0) }
hours { at(0) }
dayOfMonth { at(0) }
months { 0 every 1 }
}
} }
/** /**
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one year * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one year
*/ */
val EveryYearScheduler: KronScheduler by lazy { val EveryYearScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { years { 0 every 1 } }
seconds { at(0) }
minutes { at(0) }
hours { at(0) }
dayOfMonth { at(0) }
months { at(0) }
years { 0 every 1 }
}
} }
/** /**

View File

@ -182,24 +182,15 @@ class SchedulerBuilder(
*/ */
fun build(): KronScheduler = offset ?.let { fun build(): KronScheduler = offset ?.let {
createKronSchedulerWithOffset( createKronSchedulerWithOffset(
seconds = seconds, seconds,
minutes = minutes, minutes,
hours = hours, hours,
dayOfMonth = dayOfMonth, dayOfMonth,
month = month, month,
years = year, year,
weekDays = dayOfWeek, dayOfWeek,
offset = TimezoneOffset(it.minutes), TimezoneOffset(it.minutes),
milliseconds = milliseconds ?: millisecondsArrayDefault milliseconds ?: millisecondsArrayDefault
) )
} ?: createKronScheduler( } ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek, milliseconds ?: millisecondsArrayDefault)
seconds = seconds,
minutes = minutes,
hours = hours,
dayOfMonth = dayOfMonth,
month = month,
years = year,
weekDays = dayOfWeek,
milliseconds = milliseconds ?: millisecondsArrayDefault
)
} }

View File

@ -37,103 +37,6 @@ private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataCon
return results.map(dataConverter) return results.map(dataConverter)
} }
/**
* FSM for parsing of incoming data. If at the end of parsing it have non-null state and string is not empty, data passed check
*
* 1.
* * "\\d" -> 2
* * "\\*" -> 4
* * "f" -> 7
* * "l" -> 7
* * "/" -> 6
* 2.
* * "\\d" -> 2
* * "/" -> 6
* * "," -> 1
* * "-" -> 3
* * "m" -> 9
* * "o" -> 10
* * "w" -> 10
* 3.
* * "l" -> 7
* * "\\d" -> 8
* 4.
* * "/" -> 6
* * "," -> 1
* 5.
* * "/" -> 6
* 6.
* * "\\d" -> 8
* * "\\*" -> 7
* 7.
* * "," -> 1
* 8.
* * "\\d" -> 8
* * "," -> 1
* 9.
* * "s" -> 10 // end of ms
* 10. Empty, end of parse
*/
private val checkIncomingPartTransitionsMap = listOf(
listOf( // 0
Regex("\\d") to 1,
Regex("\\*") to 3,
Regex("f") to 6,
Regex("l") to 6,
Regex("/") to 5,
),
listOf( // 1
Regex("\\d") to 1,
Regex("/") to 5,
Regex(",") to 0,
Regex("-") to 2,
Regex("m") to 8,
Regex("o") to 9,
Regex("w") to 9,
),
listOf( // 2
Regex("l") to 6,
Regex("\\d") to 7,
),
listOf( // 3
Regex("/") to 5,
Regex(",") to 0,
),
listOf( // 4
Regex("/") to 5,
),
listOf( // 5
Regex("\\d") to 7,
Regex("\\*") to 6,
),
listOf( // 6
Regex(",") to 0,
),
listOf( // 7
Regex("\\d") to 7,
Regex(",") to 0,
),
listOf( // 8
Regex("s") to 9, // end of ms
),
listOf(), // empty state, end of parsing
)
internal fun checkIncomingPart(part: String): Boolean {
var i = 0
var state = checkIncomingPartTransitionsMap[0]
while (i < part.length) {
val char = part[i]
val nextState = state.firstNotNullOfOrNull {
it.second.takeIf { _ -> it.first.matches("$char") }
}
if (nextState == null) return false
state = checkIncomingPartTransitionsMap[nextState]
i++
}
return part.isNotEmpty()
}
internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null } internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null }
internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null } internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null }
internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() } internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() }

View File

@ -5,7 +5,6 @@ import korlibs.time.DateTimeTz
import korlibs.time.milliseconds import korlibs.time.milliseconds
import dev.inmo.krontab.KronScheduler import dev.inmo.krontab.KronScheduler
import dev.inmo.krontab.next import dev.inmo.krontab.next
import korlibs.time.millisecondsLong
import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow

View File

@ -1,38 +0,0 @@
package dev.inmo.krontab.utils
import dev.inmo.krontab.*
import korlibs.time.*
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
class BuildersTest {
@Test
fun presetsWorksCorrectly() {
val data = mapOf(
EverySecondScheduler to { it: DateTime -> if (it.milliseconds > 0 ) it + 1.seconds - it.milliseconds.milliseconds else it },
EveryMinuteScheduler to { it: DateTime -> if (it.seconds > 0 || it.milliseconds > 0 ) it + 1.minutes - it.seconds.seconds - it.milliseconds.milliseconds else it },
EveryHourScheduler to { it: DateTime -> if (it.minutes > 0 || it.seconds > 0 || it.milliseconds > 0 ) it + 1.hours - it.minutes.minutes - it.seconds.seconds - it.milliseconds.milliseconds else it },
EveryDayOfMonthScheduler to { it: DateTime -> if (it.hours > 0 || it.minutes > 0 || it.seconds > 0 || it.milliseconds > 0 ) it + 1.days - it.hours.hours - it.minutes.minutes - it.seconds.seconds - it.milliseconds.milliseconds else it },
EveryMonthScheduler to { it: DateTime -> if (it.dayOfMonth > 1 || it.hours > 0 || it.minutes > 0 || it.seconds > 0 || it.milliseconds > 0 ) (it + 1.months).copy(dayOfMonth = 1, hour = 0, minute = 0, second = 0, milliseconds = 0) else it },
)
val samples = 10000
runTest {
var now = DateTime.now()
for (i in 0 until samples) {
data.forEach { (scheduler, expectCalculator) ->
val expectValue = expectCalculator(now)
val newNow = scheduler.nextOrRelative(now)
assertEquals(expectValue, newNow, "For time ${now.toStringDefault()} calculated wrong value: ${newNow.toStringDefault()} is not equal to ${expectValue.toStringDefault()}")
now = newNow
}
}
}
}
}

View File

@ -2,7 +2,6 @@ package dev.inmo.krontab.utils
import korlibs.time.* import korlibs.time.*
import dev.inmo.krontab.KronSchedulerTz import dev.inmo.krontab.KronSchedulerTz
import dev.inmo.krontab.KrontabTemplate
import dev.inmo.krontab.buildSchedule import dev.inmo.krontab.buildSchedule
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.takeWhile
@ -12,52 +11,23 @@ import kotlin.test.*
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
@FlowPreview @FlowPreview
class StringParseTest { class StringParseTest {
private fun makeSimpleEverySecondTest(template: KrontabTemplate) { @Test
val kronScheduler = buildSchedule(template) fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() {
val kronScheduler = buildSchedule("*/1 * * * *")
val flow = kronScheduler.asFlowWithoutDelays() val flow = kronScheduler.asFlowWithoutDelays()
runTest { runTest {
val mustBeCollected = 10 val mustBeCollected = 10
var collected = 0 var collected = 0
var previousTime: DateTime? = null
flow.takeWhile { flow.takeWhile {
collected < mustBeCollected collected < mustBeCollected
}.collect { }.collect {
previousTime ?.let { previousTime ->
assertEquals(previousTime + 1.seconds, it)
}
previousTime = it
collected++ collected++
} }
assertEquals(mustBeCollected, collected) assertEquals(mustBeCollected, collected)
} }
} }
@Test
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() {
makeSimpleEverySecondTest("*/1 * * * *")
}
@Test
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithWrongAmountOfSpaces() {
val templatesFirstReplacers = listOf(
"*/1",
"*",
"/1",
"f,/1",
)
templatesFirstReplacers.forEach { replacer ->
makeSimpleEverySecondTest("$replacer * * * * ")
}
}
@Test
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithGarbageInTemplate() {
makeSimpleEverySecondTest(" sdf */1 * * * oo * ")
}
@Test
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithInsufficientArgsInTemplate() {
makeSimpleEverySecondTest(" sdf */1 ")
}
@Test @Test
fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() { fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() {
val kronScheduler = buildSchedule("*/1 * * * * 500ms") val kronScheduler = buildSchedule("*/1 * * * * 500ms")
@ -67,14 +37,9 @@ class StringParseTest {
runTest { runTest {
val mustBeCollected = 10 val mustBeCollected = 10
var collected = 0 var collected = 0
var previousTime: DateTime? = null
flow.takeWhile { flow.takeWhile {
collected < mustBeCollected collected < mustBeCollected
}.collect { }.collect {
previousTime ?.let { previousTime ->
assertEquals(previousTime.copy(milliseconds = 500) + 1.seconds, it)
}
previousTime = it
collected++ collected++
} }
assertEquals(mustBeCollected, collected) assertEquals(mustBeCollected, collected)