diff --git a/core/src/core.vue b/core/src/core.vue index bbc5f0cd..9ff4bd41 100644 --- a/core/src/core.vue +++ b/core/src/core.vue @@ -1,5 +1,5 @@ \ No newline at end of file + diff --git a/core/src/fields/every.js b/core/src/fields/every.js index fc4d672c..629c5f86 100644 --- a/core/src/fields/every.js +++ b/core/src/fields/every.js @@ -8,7 +8,7 @@ function strToArray(str, {min, max}){ if(!re.test(str)){ return null } - + let [, everyStr] = str.split('/') let every = parseInt(everyStr) diff --git a/core/src/fields/everyAt.js b/core/src/fields/everyAt.js new file mode 100644 index 00000000..a0c5e58f --- /dev/null +++ b/core/src/fields/everyAt.js @@ -0,0 +1,59 @@ +// x/y +import types from '../types' +const { EveryAtColumn } = types + +let re = /^\d+\/\d+$/ + +function buildArray(min, max, at, every) { + let res = [] + for(let i = at; i <= max; i+=every){ + if(i >= min){ + res.push(i) + } + } + return res.length > 0 ? res : null +} + +function strToArray(str, {min, max}){ + if(!re.test(str)){ + return null + } + + let [atStr, everyStr] = str.split('/') + let at = parseInt(atStr) + let every = parseInt(everyStr) + + return buildArray(min, max, at, every) +} + +function arrayToStr(arr, field){ + let {min, max} = field + + if(arr.length < 3){ + return null + } + + let at = arr[0] + let step = arr[1] - arr[0] + + if(step <= 1){ + return null + } + + let computedArray = buildArray(min, max, at, step) + if (arr.length !== computedArray.length) { + return null + } + + let missing = arr.filter((i) => !computedArray.includes(i)) + if (missing.length > 0) { + return null + } + + return new EveryAtColumn(field, step, at) +} + +export default { + strToArray, + arrayToStr +} diff --git a/core/src/fields/multiple.js b/core/src/fields/multiple.js index ad3d992b..5e43062c 100644 --- a/core/src/fields/multiple.js +++ b/core/src/fields/multiple.js @@ -2,46 +2,70 @@ import any from './any' import every from './every' +import everyAt from './everyAt' import range from './range' import value from './value' -let fieldTypes = [any, every, range, value] -function strToArray(str, field){ - let fields = str.split(',') - let res = [] - for(let f of fields){ - if(f == '*'){ - return [] + +// let fieldTypes = [any, every, everyAt, range, value] + +export class MultipleColumns { + + constructor(enableEveryAt){ + this._enableEveryAt = null + this.enableEveryAt = enableEveryAt + } + + get enableEveryAt(){ + return this._enableEveryAt + } + + set enableEveryAt(value) { + this._enableEveryAt = !!value + } + + get fieldTypes() { + if (this.enableEveryAt) { + return [any, every, everyAt, range, value] + } else { + return [any, every, range, value] } + } - let values = null - for(let fieldType of fieldTypes){ - values = fieldType.strToArray(f, field) - if(values !== null){ - break + strToArray(str, field){ + let fields = str.split(',') + let res = [] + for(let f of fields){ + if(f == '*'){ + return [] } + + let values = null + for(let fieldType of this.fieldTypes){ + values = fieldType.strToArray(f, field) + if(values !== null){ + break + } + } + if(values === null){ + return null + } + res.push(...values) } - if(values === null){ - return null - } - res.push(...values) + return Array.from(new Set(res)) } - return Array.from(new Set(res)) -} -function arrayToStr(arr, field){ - for(let fieldType of fieldTypes){ - let value = fieldType.arrayToStr(arr, field) - if(value){ - return value + arrayToStr(arr, field){ + for(let fieldType of this.fieldTypes){ + let value = fieldType.arrayToStr(arr, field) + if(value){ + return value + } } + return null } - return null } -export default { - strToArray, - arrayToStr -} +// export MultipleColumns diff --git a/core/src/locale/en.js b/core/src/locale/en.js index f557526a..b78bf2f3 100644 --- a/core/src/locale/en.js +++ b/core/src/locale/en.js @@ -1,24 +1,29 @@ export default { - eachPeriod: { + eachPeriod: { eachField: { empty: 'every {{field.id}}', value: '{{value.text}}', range: '{{start.text}}-{{end.text}}', - everyX: 'every {{every.value}}' + everyX: 'every {{every.value}} {{field.id}}(s)', + everyAt: 'every {{every.value}} {{field.id}}(s), starting at {{at.value}} {{field.id}}(s)' }, monthField: { prefix: 'in', value: '{{value.alt}}', range: '{{start.alt}}-{{end.alt}}', + everyAt: 'every {{every.value}} {{field.id}}(s), starting in {{at.alt}}' }, dayField: { - prefix: 'on' + prefix: 'on', + everyAt: 'every {{every.value}} {{field.id}}(s), starting on the {{at.alt}} of the month' }, dayOfWeekField: { prefix: 'on', empty: 'every day of the week', value: '{{value.alt}}', range: '{{start.alt}}-{{end.alt}}', + everyX: 'every {{every.value}} day(s) of the week', + everyAt: 'every {{every.value}} day(s) of the week, starting on {{at.alt}}' }, hourField: { prefix: 'at' @@ -29,9 +34,7 @@ export default { }, hourPeriod: { minuteField: { - prefix: 'at', - suffix: 'minute(s)', - empty: 'every' + prefix: 'at' } }, monthPeriod: { @@ -46,4 +49,4 @@ export default { }, periodPrefix: 'Every', periodSuffix: '' -} \ No newline at end of file +} diff --git a/core/src/locale/index.js b/core/src/locale/index.js index 55ec0957..1a7824bc 100644 --- a/core/src/locale/index.js +++ b/core/src/locale/index.js @@ -7,9 +7,9 @@ const locales = { } /** - * - * @param {string} locale=en - * @returns {object} object with all strings in the requested language + * + * @param {string} locale=en + * @returns {object} object with all strings in the requested language */ function getLocale(locale){ if(locales.hasOwnProperty(locale)){ @@ -20,16 +20,36 @@ function getLocale(locale){ } } +/** Solution provided by https://stackoverflow.com/a/57518703 + * + * + */ +function getLocaleOrdinalSuffix(locale, value) { + const ordRules = new Intl.PluralRules(locale, {type: "ordinal"}); + const suffixes = { + one: "st", + two: "nd", + few: "rd", + other: "th" + }; + const category = ordRules.select(value); + const suffix = suffixes[category]; + return (value + suffix); +} + /** - * - * @param {string} locale + * + * @param {string} locale * @returns {object} items for minute, hour, day, month and day of week */ function defaultItems(locale){ return { minuteItems: genItems(0, 59, (value) => pad(value, 2)), hourItems: genItems(0, 23, (value) => pad(value, 2)), - dayItems: genItems(1, 31), + dayItems: genItems(1, 31, + (value) => { return value+''}, + (value) => getLocaleOrdinalSuffix(locale, value) + ), monthItems: genItems(1, 12, (value) => { return new Date(2021, value-1, 1).toLocaleDateString(locale, {month: 'long'}) }, (value) => { @@ -57,7 +77,7 @@ export default { getSuffix: (locale, periodId, fieldId) => { return traverse(locale, [periodId+'Period', 'eachPeriod'], [fieldId+'Field', 'eachField'], ['suffix']) || '' }, - + defaultItems, getLocale -} \ No newline at end of file +} diff --git a/core/src/types.js b/core/src/types.js index 6f53fef6..17807d66 100644 --- a/core/src/types.js +++ b/core/src/types.js @@ -6,9 +6,9 @@ const {getLocaleStr} = locale class Field { /** - * - * @param {String} name - * @param {Array} items + * + * @param {String} name + * @param {Array} items */ constructor(id, items){ this.id = id @@ -37,8 +37,8 @@ class Field { class CronColumn{ /** - * - * @param {Field} field + * + * @param {Field} field */ constructor(field){ this.field = field @@ -76,11 +76,11 @@ class CronColumn{ } class AnyColumn extends CronColumn { - + get localeKey(){ return 'empty' } - + get value(){ return '*' } @@ -88,7 +88,7 @@ class AnyColumn extends CronColumn { } class RangeColumn extends CronColumn { - + constructor(field, start, end){ super(field) this.start = start @@ -105,7 +105,7 @@ class RangeColumn extends CronColumn { end: this.end } } - + get value(){ return `${this.start}-${this.end}` } @@ -135,6 +135,31 @@ class EveryColumn extends CronColumn { } +class EveryAtColumn extends CronColumn { + + constructor(field, every, at){ + super(field) + this.every = every + this.at = at + } + + get localeKey(){ + return 'everyAt' + } + + get localeParams(){ + return { + every: this.every, + at: this.at, + } + } + + get value(){ + return `${this.at}/${this.every}` + } + +} + class ValueColumn extends CronColumn { constructor(field, value){ @@ -185,5 +210,6 @@ export default { RangeColumn, ValueColumn, EveryColumn, + EveryAtColumn, CombinedColumn, -} \ No newline at end of file +} diff --git a/core/test/multiple.test.js b/core/test/multiple.test.js index d8bf2faf..51a83256 100644 --- a/core/test/multiple.test.js +++ b/core/test/multiple.test.js @@ -1,7 +1,6 @@ -import multiple from '../src/fields/multiple' +import {MultipleColumns} from '../src/fields/multiple' import util from '../src/util' import types from '../src/types' -const {strToArray, arrayToStr} = multiple const {Range,genItems} = util const {Field} = types @@ -10,30 +9,46 @@ const r = (min, max) => { } test('test strToArray', () => { - expect(strToArray('*', r(1, 3))).toEqual([]); - expect(strToArray('1,3,5', r(0, 24))).toEqual([1,3,5]); - expect(strToArray('*/5', r(0, 11))).toEqual([0,5,10]); - expect(strToArray('*/5', r(1, 11))).toEqual([5,10]); - expect(strToArray('10-15', r(0, 59))).toEqual([10,11,12,13,14,15]); - expect(strToArray('10-11,20-22,30-33', r(0, 59))).toEqual([10,11,20,21,22,30,31,32,33]); - expect(strToArray('5,7-8', r(0, 59))).toEqual([5,7,8]); + let multiple = new MultipleColumns(false) + expect(multiple.strToArray('*', r(1, 3))).toEqual([]); + expect(multiple.strToArray('1,3,5', r(0, 24))).toEqual([1,3,5]); + expect(multiple.strToArray('*/5', r(0, 11))).toEqual([0,5,10]); + expect(multiple.strToArray('*/5', r(1, 11))).toEqual([5,10]); + expect(multiple.strToArray('10-15', r(0, 59))).toEqual([10,11,12,13,14,15]); + expect(multiple.strToArray('10-11,20-22,30-33', r(0, 59))).toEqual([10,11,20,21,22,30,31,32,33]); + expect(multiple.strToArray('5,7-8', r(0, 59))).toEqual([5,7,8]); - expect(strToArray('x', r(0, 59))).toBe(null); - expect(strToArray('1-100', r(0, 59))).toBe(null); - expect(strToArray('0-10', r(1, 59))).toBe(null); - expect(strToArray('100', r(0, 59))).toBe(null); - expect(strToArray('0', r(1, 10))).toBe(null); - expect(strToArray('*/90', r(1, 10))).toBe(null); + expect(multiple.strToArray('x', r(0, 59))).toBe(null); + expect(multiple.strToArray('1-100', r(0, 59))).toBe(null); + expect(multiple.strToArray('0-10', r(1, 59))).toBe(null); + expect(multiple.strToArray('100', r(0, 59))).toBe(null); + expect(multiple.strToArray('0', r(1, 10))).toBe(null); + expect(multiple.strToArray('*/90', r(1, 10))).toBe(null); }); test('test arrayToStr', () => { - expect(arrayToStr([1,10], r(1, 10)).value).toEqual('1,10'); - expect(arrayToStr([1,2,3], r(1, 10)).value).toEqual('1-3'); - expect(arrayToStr([2,4,6], r(1, 10)).value).toEqual('2,4,6'); - expect(arrayToStr([], r(1, 3)).value).toEqual('*'); - expect(arrayToStr([1,2,3], r(1, 3)).value).toEqual('*'); - expect(arrayToStr([0,5,10], r(0, 10)).value).toEqual('*/5'); - expect(arrayToStr([7,14,21,28], r(5, 30)).value).toEqual('*/7'); - expect(arrayToStr([0,5,10], r(0, 20)).value).toEqual('0,5,10'); - expect(arrayToStr([1,2,5,8,9,10], r(1, 10)).value).toEqual('1-2,5,8-10'); -}); \ No newline at end of file + let multiple = new MultipleColumns(false) + expect(multiple.arrayToStr([1,10], r(1, 10)).value).toEqual('1,10'); + expect(multiple.arrayToStr([1,2,3], r(1, 10)).value).toEqual('1-3'); + expect(multiple.arrayToStr([2,4,6], r(1, 10)).value).toEqual('2,4,6'); + expect(multiple.arrayToStr([], r(1, 3)).value).toEqual('*'); + expect(multiple.arrayToStr([1,2,3], r(1, 3)).value).toEqual('*'); + expect(multiple.arrayToStr([0,5,10], r(0, 10)).value).toEqual('*/5'); + expect(multiple.arrayToStr([7,14,21,28], r(5, 30)).value).toEqual('*/7'); + expect(multiple.arrayToStr([0,5,10], r(0, 20)).value).toEqual('0,5,10'); + expect(multiple.arrayToStr([1,2,5,8,9,10], r(1, 10)).value).toEqual('1-2,5,8-10'); +}); + +test('test every-at strToArray', () => { + let multiple = new MultipleColumns(true) + expect(multiple.strToArray('5/10', r(0, 59))).toEqual([5, 15, 25, 35, 45, 55]); + expect(multiple.strToArray('5/10', r(8, 59))).toEqual([15, 25, 35, 45, 55]); + expect(multiple.strToArray('3/8', r(7, 59))).toEqual([11, 19, 27, 35, 43, 51, 59]); + +}); + +test('test every-at arrayToStr', () => { + let multiple = new MultipleColumns(true) + expect(multiple.arrayToStr([5, 15, 25, 35, 45, 55], r(0, 59)).value).toEqual('5/10'); + expect(multiple.arrayToStr([15, 25, 35, 45, 55], r(10, 59)).value).toEqual('15/10'); +}); diff --git a/light/dev/example-usage.vue b/light/dev/example-usage.vue index 59d209d7..ab3a7572 100644 --- a/light/dev/example-usage.vue +++ b/light/dev/example-usage.vue @@ -1,7 +1,13 @@