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

com.blr19c.falowp.bot.system.scheduling.cron.CronExpression.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC2
Show newest version
package com.blr19c.falowp.bot.system.scheduling.cron

import java.time.temporal.ChronoUnit
import java.time.temporal.Temporal

@Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "UNUSED")
class CronExpression private constructor(
    seconds: CronField,
    minutes: CronField,
    hours: CronField,
    daysOfMonth: CronField,
    months: CronField,
    daysOfWeek: CronField,
    private val expression: String
) {
    private val fields: Array

    init {

        // reverse order, to make big changes first
        // to make sure we end up at 0 nanos, we add an extra field
        fields = arrayOf(daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos())
    }

    /**
     * Calculate the next [Temporal] that matches this expression.
     *
     * @param temporal the seed value
     * @param       the type of temporal
     * @return the next temporal that matches this expression, or `null`
     * if no such temporal can be found
     */

    fun  next(temporal: T): T? where T : Temporal? {
        return nextOrSame(ChronoUnit.NANOS.addTo(temporal, 1))
    }


    private fun  nextOrSame(temp: T): T? where T : Temporal? {
        var temporal = temp
        for (i in 0 until MAX_ATTEMPTS) {
            val result = nextOrSameInternal(temporal) as Temporal
            if (result == temporal) {
                return result as T?
            }
            temporal = result as T
        }
        return null
    }


    private fun  nextOrSameInternal(temp: T): T? where T : Temporal? {
        var temporal: T? = temp
        for (field in fields) {
            temporal = field.nextOrSame(temporal)
            if (temporal == null) {
                return null
            }
        }
        return temporal
    }

    override fun hashCode(): Int {
        return fields.contentHashCode()
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) {
            return true
        }
        return if (other is CronExpression) {
            fields.contentEquals(other.fields)
        } else {
            false
        }
    }

    /**
     * Return the expression string used to create this `CronExpression`.
     *
     * @return the expression string
     */
    override fun toString(): String {
        return expression
    }

    companion object {
        const val MAX_ATTEMPTS = 366
        private val MACROS = arrayOf(
            "@yearly", "0 0 0 1 1 *",
            "@annually", "0 0 0 1 1 *",
            "@monthly", "0 0 0 1 * *",
            "@weekly", "0 0 0 * * 0",
            "@daily", "0 0 0 * * *",
            "@midnight", "0 0 0 * * *",
            "@hourly", "0 0 * * * *"
        )

        /**
         * Parse the given
         * [crontab expression](https://www.manpagez.com/man/5/crontab/)
         * string into a `CronExpression`.
         * The string has six single space-separated time and date fields:
         * 
         * ┌───────────── second (0-59)
         * │ ┌───────────── minute (0 - 59)
         * │ │ ┌───────────── hour (0 - 23)
         * │ │ │ ┌───────────── day of the month (1 - 31)
         * │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
         * │ │ │ │ │ ┌───────────── day of the week (0 - 7)
         * │ │ │ │ │ │          (0 or 7 is Sunday, or MON-SUN)
         * │ │ │ │ │ │
         * * * * * * *
        
* * * * The following rules apply: * * * * A field may be an asterisk (`*`), which always stands for * "first-last". For the "day of the month" or "day of the week" fields, a * question mark (`?`) may be used instead of an asterisk. * * * * Ranges of numbers are expressed by two numbers separated with a hyphen * (`-`). The specified range is inclusive. * * * Following a range (or `*`) with `/n` specifies * the interval of the number's value through the range. * * * * English names can also be used for the "month" and "day of week" fields. * Use the first three letters of the particular day or month (case does not * matter). * * * * The "day of month" and "day of week" fields can contain a * `L`-character, which stands for "last", and has a different meaning * in each field: * * * * In the "day of month" field, `L` stands for "the last day of the * month". If followed by an negative offset (i.e. `L-n`), it means * "`n`th-to-last day of the month". If followed by `W` (i.e. * `LW`), it means "the last weekday of the month". * * * * * * The "day of month" field can be `nW`, which stands for "the nearest * weekday to day of the month `n`". * If `n` falls on Saturday, this yields the Friday before it. * If `n` falls on Sunday, this yields the Monday after, * which also happens if `n` is `1` and falls on a Saturday * (i.e. `1W` stands for "the first weekday of the month"). * * * * The "day of week" field can be `d#n` (or `DDD#n`), which * stands for "the `n`-th day of week `d` (or `DDD`) in * the month". * * * * * Example expressions: * * * `"0 0 * * * *"` = the top of every hour of every day. * * `"*/10 * * * * *"` = every ten seconds. * * `"0 0 8-10 * * *"` = 8, 9 and 10 o'clock of every day. * * `"0 0 6,19 * * *"` = 6:00 AM and 7:00 PM every day. * * `"0 0/30 8-10 * * *"` = 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day. * * `"0 0 9-17 * * MON-FRI"` = on the hour nine-to-five weekdays * * `"0 0 0 25 12 ?"` = every Christmas Day at midnight * * `"0 0 0 L * *"` = last day of the month at midnight * * `"0 0 0 L-3 * *"` = third-to-last day of the month at midnight * * `"0 0 0 1W * *"` = first weekday of the month at midnight * * `"0 0 0 LW * *"` = last weekday of the month at midnight * * `"0 0 0 * * 5L"` = last Friday of the month at midnight * * `"0 0 0 * * TH UL"` = last Thursday of the month at midnight * * `"0 0 0 ? * 5#2"` = the second Friday in the month at midnight * * `"0 0 0 ? * MON#1"` = the first Monday in the month at midnight * * * * The following macros are also supported: * * * `"@yearly"` (or `"@annually"`) to run un once a year, i.e. `"0 0 0 1 1 *"`, * * `"@monthly"` to run once a month, i.e. `"0 0 0 1 * *"`, * * `"@weekly"` to run once a week, i.e. `"0 0 0 * * 0"`, * * `"@daily"` (or `"@midnight"`) to run once a day, i.e. `"0 0 0 * * *"`, * * `"@hourly"` to run once an hour, i.e. `"0 0 * * * *"`. * * * @param exp the expression string to parse * @return the parsed `CronExpression` object * @throws IllegalArgumentException in the expression does not conform to * the cron format */ fun parse(exp: String): CronExpression { var expression = exp expression = resolveMacros(expression) val fields: Array = expression.split(" ").toTypedArray() require(fields.size == 6) { String.format( "Cron expression must consist of 6 fields (found %d in \"%s\")", fields.size, expression ) } return try { val seconds = CronField.parseSeconds(fields[0]) val minutes = CronField.parseMinutes(fields[1]) val hours = CronField.parseHours(fields[2]) val daysOfMonth = CronField.parseDaysOfMonth(fields[3]) val months = CronField.parseMonth(fields[4]) val daysOfWeek = CronField.parseDaysOfWeek(fields[5]) CronExpression(seconds, minutes, hours, daysOfMonth, months, daysOfWeek, expression) } catch (ex: IllegalArgumentException) { val msg = ex.message + " in cron expression \"" + expression + "\"" throw IllegalArgumentException(msg, ex) } } /** * Determine whether the given string represents a valid cron expression. * * @param expression the expression to evaluate * @return `true` if the given expression is a valid cron expression * @since 5.3.8 */ fun isValidExpression(expression: String?): Boolean { return if (expression == null) { false } else try { parse(expression) true } catch (ex: IllegalArgumentException) { false } } private fun resolveMacros(exp: String): String { var expression = exp expression = expression.trim { it <= ' ' } var i = 0 while (i < MACROS.size) { if (MACROS[i].equals(expression, ignoreCase = true)) { return MACROS[i + 1] } i += 2 } return expression } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy