material-ui-cron
Version:
A React cron editor using material ui
361 lines (335 loc) • 11.7 kB
text/typescript
import { DEFAULT_DAY_OF_MONTH_OPTS, DEFAULT_MINUTE_OPTS } from './constants'
import { CronValidation, SelectOptions } from './types'
export const getIndex = (obj: SelectOptions, arr: Array<SelectOptions>) => {
return arr.findIndex((x) => x.value === obj.value)
}
export const getTimeIndex = (obj: SelectOptions) => {
return getIndex(obj, getTimesOfTheDay())
}
export const getMinutesIndex = (obj: SelectOptions) => {
return getIndex(obj, DEFAULT_MINUTE_OPTS)
}
export const getPeriodIndex = (obj: SelectOptions) => {
if (obj.value === 'hour') {
return 0
} else if (obj.value === 'day') {
return 1
} else if (obj.value === 'week') {
return 2
} else if (obj.value === 'month') {
return 3
}
return 4
}
export const getDayOfMonthIndex = (obj: SelectOptions) => {
return getIndex(obj, DEFAULT_DAY_OF_MONTH_OPTS)
}
export const getSortedOptions = (options: SelectOptions[]) => {
return options.sort((a, b) => Number(a.value) - Number(b.value))
}
export const isIncreasingSequence = (options: SelectOptions[]) =>
options.every(
(option, i) =>
i === 0 || Number(options[i - 1].value) + 1 === Number(option.value)
)
export function isAscending(arr: string[]) {
return arr.every(function (x, i) {
return i === 0 || Number(x) >= Number(arr[i - 1])
})
}
export function getTimesOfTheDayList(): Array<string> {
const hours = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
const periods = ['AM', 'PM']
return periods.flatMap((period) =>
hours.map((hour) =>
hour > 9 ? `${hour}:00 ${period}` : `0${hour}:00 ${period}`
)
)
}
export function getTimesOfTheDay(): Array<SelectOptions> {
return getTimesOfTheDayList().map((time) => ({
value: time,
label: time,
}))
}
export const hasValidCronParts = (cronExp: string) =>
cronExp.split(' ').length === 5
export const getNumbersInCronPart = (part: string) => {
let numbers = []
let tmpNumber = ''
for (let i = 0; i < part.length; i++) {
let num = Number(part[i])
if (num >= 0 && num <= 9) {
tmpNumber += num
} else {
if (tmpNumber.length > 0) {
numbers.push(tmpNumber)
}
tmpNumber = ''
}
}
if (tmpNumber.length > 0) {
numbers.push(tmpNumber)
}
return numbers.map((part) => Number(part))
}
export const doesNumberStartWithZero = (part: string) => {
let tmpNumber = ''
for (let i = 0; i < part.length; i++) {
let num = Number(part[i])
if (num >= 0 && num <= 9) {
tmpNumber += num
} else {
if (tmpNumber.startsWith('0') && tmpNumber.length > 1) {
return true
}
tmpNumber = ''
}
}
if (tmpNumber.startsWith('0') && tmpNumber.length > 1) {
return true
}
return false
}
export const hasValidNumbersInCronPart = (
part: string,
condition: (n: number) => boolean
) => {
return getNumbersInCronPart(part).every((num) => condition(num))
}
export function hasNoDuplicates(part: string) {
const subparts = part.split('/')
return subparts.every((subpart) => {
let numArr = getNumbersInCronPart(subpart)
return new Set(numArr).size === numArr.length
})
}
export const REGEX_ALL = /^([*])\/([1-9]{1})([0-9]{0,1})$/
export const REGEX_EVERY = /^([0-9]{1,4})\/([1-9]{1,2})$/
export const REGEX_EVERY_HYPEN = /^([0-9]{1,2}-[0-9]{1,2})\/([1-9]{1})?([0-9]{1})$/
export const REGEX_COMMA = /^[0-9]{1,2}(,[0-9]{1,2})+$/
export const REGEX_HYPHEN = /^([0-9]{1,2}-[0-9]{1,2})$/
export const REGEX_SINGLE_DIGIT = /^([0-9]{1,2})$/
export const REGEX_SINGLE_ALL = /^([*]{1})$/
export const REGEX_SINGLE_SPL = /^([L]{1})$/
export const CRON_VALIDATION = (
isValid: boolean,
message: string
): CronValidation => {
return {
isValid,
message,
}
}
export const isValidMinutePart = (cronExp: string) => {
const part = cronExp.split(' ')[0]
if (doesNumberStartWithZero(part)) {
return CRON_VALIDATION(false, 'Number starts with zero')
} else if (
!hasValidNumbersInCronPart(part, (num: number) => num >= 0 && num <= 59)
) {
return CRON_VALIDATION(false, 'Number should be between 0 and 59')
} else if (!hasNoDuplicates(part)) {
return CRON_VALIDATION(false, 'Duplicate numbers not allowed')
} else if (part.indexOf('/') > 0) {
if (
part.split('/')[0] !== '*' &&
isAscending(part.split('/')[0].split('-'))
) {
return CRON_VALIDATION(
REGEX_EVERY_HYPEN.test(part),
'Incorrect syntax hypen'
)
} else if (part.indexOf('-') > 0) {
return CRON_VALIDATION(false, 'Incorrect range')
} else {
return CRON_VALIDATION(REGEX_ALL.test(part), 'Incorrect syntax')
}
} else if (part.indexOf(',') > 0) {
return CRON_VALIDATION(REGEX_COMMA.test(part), 'Invalid syntax')
} else if (part.indexOf('-') > 0) {
if (isAscending(part.split('-'))) {
return CRON_VALIDATION(REGEX_HYPHEN.test(part), 'Invalid range')
} else {
return CRON_VALIDATION(false, 'Invalid range')
}
} else if (REGEX_SINGLE_DIGIT.test(part)) {
return CRON_VALIDATION(true, 'Invalid single digit')
}
return CRON_VALIDATION(REGEX_SINGLE_ALL.test(part), 'Invalid single all')
}
export const isValidHourPart = (cronExp: string) => {
const part = cronExp.split(' ')[1]
if (doesNumberStartWithZero(part)) {
return CRON_VALIDATION(false, 'Number starts with zero')
} else if (
!hasValidNumbersInCronPart(part, (num: number) => num >= 0 && num <= 23)
) {
return CRON_VALIDATION(false, 'Number should be between 0 and 23')
} else if (!hasNoDuplicates(part)) {
return CRON_VALIDATION(false, 'Duplicate numbers not allowed')
} else if (part.indexOf('/') > 0) {
if (
part.split('/')[0] !== '*' &&
isAscending(part.split('/')[0].split('-'))
) {
return CRON_VALIDATION(
REGEX_EVERY_HYPEN.test(part),
'Incorrect syntax hypen'
)
} else if (part.indexOf('-') > 0) {
return CRON_VALIDATION(false, 'Incorrect range')
} else {
return CRON_VALIDATION(REGEX_ALL.test(part), 'Incorrect syntax')
}
} else if (part.indexOf(',') > 0) {
return CRON_VALIDATION(REGEX_COMMA.test(part), 'Invalid syntax')
} else if (part.indexOf('-') > 0) {
if (isAscending(part.split('-'))) {
return CRON_VALIDATION(REGEX_HYPHEN.test(part), 'Invalid range')
} else {
return CRON_VALIDATION(false, 'Invalid range')
}
} else if (REGEX_SINGLE_DIGIT.test(part)) {
return CRON_VALIDATION(true, 'Invalid single digit')
}
return CRON_VALIDATION(REGEX_SINGLE_ALL.test(part), 'Invalid single all')
}
export const isValidDayOfMonthPart = (cronExp: string) => {
const part = cronExp.split(' ')[2]
if (doesNumberStartWithZero(part)) {
return CRON_VALIDATION(false, 'Number starts with zero')
} else if (
!hasValidNumbersInCronPart(part, (num: number) => num >= 1 && num <= 31)
) {
return CRON_VALIDATION(false, 'Number should be between 1 and 31')
} else if (!hasNoDuplicates(part)) {
return CRON_VALIDATION(false, 'Duplicate numbers not allowed')
} else if (part.indexOf('/') > 0) {
if (
part.split('/')[0] !== '*' &&
isAscending(part.split('/')[0].split('-'))
) {
return CRON_VALIDATION(
REGEX_EVERY_HYPEN.test(part),
'Incorrect syntax hypen'
)
} else if (part.indexOf('-') > 0) {
return CRON_VALIDATION(false, 'Incorrect range')
} else {
return CRON_VALIDATION(REGEX_ALL.test(part), 'Incorrect syntax')
}
} else if (part.indexOf(',') > 0) {
return CRON_VALIDATION(REGEX_COMMA.test(part), 'Invalid syntax')
} else if (part.indexOf('-') > 0) {
if (isAscending(part.split('-'))) {
return CRON_VALIDATION(REGEX_HYPHEN.test(part), 'Invalid range')
} else {
return CRON_VALIDATION(false, 'Invalid range')
}
} else if (REGEX_SINGLE_DIGIT.test(part)) {
return CRON_VALIDATION(true, '')
} else if (REGEX_SINGLE_SPL.test(part)) {
return CRON_VALIDATION(true, '')
}
return CRON_VALIDATION(REGEX_SINGLE_ALL.test(part), 'Invalid single all')
}
export const isValidDayOfWeekPart = (cronExp: string) => {
const part = cronExp.split(' ')[4]
if (doesNumberStartWithZero(part)) {
return CRON_VALIDATION(false, 'Number starts with zero')
} else if (
!hasValidNumbersInCronPart(part, (num: number) => num >= 0 && num <= 6)
) {
return CRON_VALIDATION(false, 'Number should be between 0 and 6')
} else if (!hasNoDuplicates(part)) {
return CRON_VALIDATION(false, 'Duplicate numbers not allowed')
} else if (part.indexOf(',') > 0) {
return CRON_VALIDATION(REGEX_COMMA.test(part), 'Invalid syntax')
} else if (part.indexOf('-') > 0) {
if (isAscending(part.split('-'))) {
return CRON_VALIDATION(REGEX_HYPHEN.test(part), 'Incorrect syntax hypen')
} else {
return CRON_VALIDATION(false, 'Range should be low to high')
}
} else if (REGEX_SINGLE_DIGIT.test(part)) {
return CRON_VALIDATION(true, 'Invalid single digit')
}
return CRON_VALIDATION(REGEX_SINGLE_ALL.test(part), 'Invalid single all')
}
export const isValidMonthPart = (cronExp: string) => {
const part = cronExp.split(' ')[3]
if (doesNumberStartWithZero(part)) {
return CRON_VALIDATION(false, 'Number starts with zero')
} else if (
!hasValidNumbersInCronPart(part, (num: number) => num >= 1 && num <= 12)
) {
return CRON_VALIDATION(false, 'Number should be between 0 and 6')
} else if (!hasNoDuplicates(part)) {
return CRON_VALIDATION(false, 'Duplicate numbers not allowed')
} else if (part.indexOf(',') > 0) {
return CRON_VALIDATION(REGEX_COMMA.test(part), 'Invalid syntax')
} else if (part.indexOf('-') > 0) {
if (isAscending(part.split('-'))) {
return CRON_VALIDATION(REGEX_HYPHEN.test(part), 'Incorrect syntax hypen')
} else {
return CRON_VALIDATION(false, 'Range should be low to high')
}
} else if (REGEX_SINGLE_DIGIT.test(part)) {
return CRON_VALIDATION(true, 'Invalid single digit')
}
return CRON_VALIDATION(REGEX_SINGLE_ALL.test(part), 'Invalid single all')
}
export const getCronStatus = (msg: string, hasError: boolean) => ({
hasError: hasError,
message: msg,
})
export const validateCronExp = (cronExp: string) => {
if (!hasValidCronParts(cronExp)) {
return getCronStatus('Cron should have 5 parts', true)
}
const minuteValidation = isValidMinutePart(cronExp)
if (!minuteValidation.isValid) {
return getCronStatus(
`Invalid minute cron part: ${minuteValidation.message}`,
true
)
}
const hourValidation = isValidHourPart(cronExp)
if (!hourValidation.isValid) {
return getCronStatus(
`Invalid hour cron part: ${hourValidation.message}`,
true
)
}
const dayOfMonthValidation = isValidDayOfMonthPart(cronExp)
if (!dayOfMonthValidation.isValid) {
return getCronStatus(
`Invalid day of month cron part: ${dayOfMonthValidation.message}`,
true
)
}
const monthValidation = isValidMonthPart(cronExp)
if (!monthValidation.isValid) {
return getCronStatus(
`Invalid month cron part: ${monthValidation.message}`,
true
)
}
const dayOfWeekValidation = isValidDayOfWeekPart(cronExp)
if (!dayOfWeekValidation.isValid) {
return getCronStatus(
`Invalid day of week cron part: ${dayOfWeekValidation.message}`,
true
)
}
return getCronStatus('', false)
}
export const countOccurrences = (arr: string[], val: string) =>
arr.reduce((a, v) => (v === val ? a + 1 : a), 0)
export function range(start: number, end: number, step = 1): Array<string> {
const len = Math.floor((end - start) / step) + 1
return Array(len)
.fill('00')
.map((_, idx) => `${start + idx * step}`)
}