const MONTH_VAL = 'JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC'
const WEEKDAY_VAL = 'SUN|MON|TUE|WED|THU|FRI|SAT'
const WILDCARD_VAL = '[*]([/][0-9]+)?'
const NUMBER_VAL = '[0-9]+(-[0-9]+([/][0-9]+)?)?'
const GROUP_VAL = `(${WILDCARD_VAL}|${NUMBER_VAL})(,(${WILDCARD_VAL}|${NUMBER_VAL}))*`
const CORNTAB_CHARS = '[*,-/0-9]+'
const CRONTAB_GLOBAL_REG = new RegExp(
`^(${CORNTAB_CHARS}) (${CORNTAB_CHARS}) (${CORNTAB_CHARS}) (${CORNTAB_CHARS}|${MONTH_VAL}) (${CORNTAB_CHARS}|${WEEKDAY_VAL})$`
)
const CRONTAB_KEY = {
minute: 1,
hour: 2,
monthDay: 3,
month: 4,
weekDay: 5
} as const
type KEYOF_CRONTAB_KEY = keyof typeof CRONTAB_KEY
interface CorntabValOption {
min: number
max: number
itemReg: RegExp
groupReg: RegExp
}
interface CorntabStandard {
minute: CorntabValOption
hour: CorntabValOption
monthDay: CorntabValOption
month: CorntabValOption
weekDay: CorntabValOption
}
const STANDARD: CorntabStandard = {
minute: {
min: 0,
max: 59,
itemReg: new RegExp(`^${WILDCARD_VAL}$`),
groupReg: new RegExp(`^${GROUP_VAL}$`)
},
hour: {
min: 0,
max: 23,
itemReg: new RegExp(`^${WILDCARD_VAL}$`),
groupReg: new RegExp(`^${GROUP_VAL}$`)
},
monthDay: {
min: 1,
max: 31,
itemReg: new RegExp(`^${WILDCARD_VAL}$`),
groupReg: new RegExp(`^${GROUP_VAL}$`)
},
month: {
min: 1,
max: 12,
itemReg: new RegExp(`^(${MONTH_VAL}|${WILDCARD_VAL})$`),
groupReg: new RegExp(`^(${MONTH_VAL}|${GROUP_VAL})$`)
},
weekDay: {
min: 0,
max: 6,
itemReg: new RegExp(`^(${WEEKDAY_VAL}|${WILDCARD_VAL})$`),
groupReg: new RegExp(`^(${WEEKDAY_VAL}|${GROUP_VAL})$`)
}
}
const checkNumerators = (
numerators: string[],
key: KEYOF_CRONTAB_KEY
): string => {
const { min, max } = STANDARD[key]
const nums = numerators.map(n => Number(n))
let prev = min
for (let i = 0; i < nums.length; i++) {
if (nums[i] > max) {
return `${key}: ${nums[i]} 不能大于 ${max}`
} else if (nums[i] < prev) {
return `${key}: ${nums[i]} 不能小于 ${prev}`
}
prev = Math.max(prev, nums[i])
}
return ''
}
const checkGroup = (key: keyof typeof CRONTAB_KEY, group: string[]): string => {
const { itemReg } = STANDARD[key]
for (const item of group) {
if (new RegExp(`^${NUMBER_VAL}$`).test(item)) {
const numerators = item.split('/')[0].match(/[0-9]+/g)
if (!numerators) {
return `${item} 不是标准的语法`
}
const numeratorError = checkNumerators(numerators, key)
if (numeratorError) {
return numeratorError
}
} else if (itemReg.test(item)) {
} else {
return `${item} 不是标准的语法`
}
}
return ''
}
export const checkCrontab = (value: string): string => {
const fullMatch = value.match(CRONTAB_GLOBAL_REG)
if (!fullMatch || fullMatch.length !== 6) {
return `${value} 不是标准的语法`
}
const keys = Object.keys(CRONTAB_KEY) as KEYOF_CRONTAB_KEY[]
for (const key of keys) {
const index = CRONTAB_KEY[key]
const { groupReg } = STANDARD[key]
const groupStr = fullMatch[index]
if (!groupReg.test(groupStr)) {
return `${groupStr} 不是标准的语法`
}
const errMsg = checkGroup(key, groupStr.split(','))
if (errMsg) {
return errMsg
}
}
return ''
}