Skip to content

Commit ab39c60

Browse files
committed
feat: Support Steps with Ranges #76
BREAKING CHANGE: Rename`CronType` to `FieldPattern` - Rename `CronType.Empty` to `FieldPattern.Any`. - Rename `CronType.EveryX` to `FieldPattern.Step`. - Due to this change, the structure of existing localization objects need adjustments.
1 parent 2eada15 commit ab39c60

File tree

21 files changed

+210
-205
lines changed

21 files changed

+210
-205
lines changed

core/src/__tests__/cron.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ describe('segments', () => {
4141

4242
expect(arrayToCron([1, 10], r(1, 10))).toEqual('1,10')
4343
expect(arrayToCron([1, 2, 3], r(1, 10))).toEqual('1-3')
44-
expect(arrayToCron([2, 4, 6], r(1, 10))).toEqual('2,4,6')
44+
expect(arrayToCron([2, 4, 7], r(1, 10))).toEqual('2,4,7')
4545
expect(arrayToCron([], r(1, 3))).toEqual('*')
4646
expect(arrayToCron([1, 2, 3], r(1, 3))).toEqual('*')
4747
expect(arrayToCron([0, 5, 10], r(0, 10))).toEqual('*/5')
4848
expect(arrayToCron([5, 12, 19, 26], r(5, 30))).toEqual('*/7')
49-
expect(arrayToCron([0, 5, 10], r(0, 20))).toEqual('0,5,10')
49+
expect(arrayToCron([0, 5, 10], r(0, 20))).toEqual('0-10/5')
5050
expect(arrayToCron([1, 2, 5, 8, 9, 10], r(1, 10))).toEqual('1-2,5,8-10')
5151
})
5252
})

core/src/__tests__/locale.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getLocale } from '@/locale'
2-
import { CronType, TextPosition } from '@/types'
2+
import { FieldPattern, TextPosition } from '@/types'
33
import { describe, expect, it } from 'vitest'
44

55
describe('locale', () => {
@@ -60,7 +60,7 @@ describe('locale', () => {
6060
})
6161

6262
expect(
63-
l.render('period', 'field', CronType.Value, TextPosition.Text, {
63+
l.render('period', 'field', FieldPattern.Value, TextPosition.Text, {
6464
start: {
6565
text: 'foo',
6666
},

core/src/components/__tests__/cron-core.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ describe('useCron', () => {
5757
period: 'week',
5858
expected: `Every Week on Sun at 23 : 59`,
5959
},
60+
{
61+
format: 'crontab',
62+
value: '10,20,30 23 * * 0-4/2',
63+
period: 'week',
64+
expected: `Every Week on Sun-Thu/2 at 23 : 10-30/10`,
65+
},
6066
{
6167
format: 'quartz',
6268
value: '* * * * * *',

core/src/components/cron-core.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AnySegment, EverySegment, NoSpecificSegment, RangeSegment, ValueSegment } from '@/cron'
1+
import { AnySegment, NoSpecificSegment, RangeSegment, StepSegment, ValueSegment } from '@/cron'
22
import type { Localization } from '@/locale/types'
33
import {
44
computed,
@@ -75,7 +75,7 @@ export class DefaultCronOptions {
7575
? [
7676
AnySegment.fromString,
7777
NoSpecificSegment.fromString,
78-
EverySegment.fromString,
78+
StepSegment.fromString,
7979
RangeSegment.fromString,
8080
ValueSegment.fromString,
8181
]
@@ -90,7 +90,7 @@ export class DefaultCronOptions {
9090
? [
9191
AnySegment.fromString,
9292
NoSpecificSegment.fromString,
93-
EverySegment.fromString,
93+
StepSegment.fromString,
9494
RangeSegment.fromString,
9595
ValueSegment.fromString,
9696
]

core/src/cron.ts

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { CronType, FieldWrapper, type CronSegment, type SegmentFromString } from './types'
1+
import { FieldPattern, FieldWrapper, type CronSegment, type SegmentFromString } from './types'
22
import { isSquence, range, unimplemented } from './util'
33

44
class NoSpecificSegment implements CronSegment {
55
field: FieldWrapper
6-
type: CronType = CronType.NoSpecific
6+
type: FieldPattern = FieldPattern.NoSpecific
77

88
constructor(field: FieldWrapper) {
99
this.field = field
@@ -31,7 +31,7 @@ class NoSpecificSegment implements CronSegment {
3131

3232
class AnySegment implements CronSegment {
3333
field: FieldWrapper
34-
type: CronType = CronType.Empty
34+
type: FieldPattern = FieldPattern.Any
3535

3636
constructor(field: FieldWrapper) {
3737
this.field = field
@@ -82,7 +82,7 @@ class RangeSegment implements CronSegment {
8282
static re = /^\d+-\d+$/
8383

8484
field: FieldWrapper
85-
type: CronType = CronType.Range
85+
type: FieldPattern = FieldPattern.Range
8686
start: number
8787
end: number
8888

@@ -128,75 +128,79 @@ class RangeSegment implements CronSegment {
128128
}
129129
}
130130

131-
const _every = (n: number, min: number, max: number) => {
131+
function _rangeWithStep(n: number, min: number, max: number) {
132132
const res = []
133133
for (let i = min; i <= max; i += n) {
134134
res.push(i)
135135
}
136136
return res
137137
}
138-
139-
class EverySegment implements CronSegment {
138+
class StepSegment implements CronSegment {
140139
static re = /^(\*|\d+-\d+)\/\d+$/
141140

142141
field: FieldWrapper
143-
type: CronType = CronType.EveryX
144-
every: number
142+
step: number
145143
start: number
146144
end: number
147145

148-
constructor(field: FieldWrapper, every: number, start?: number, end?: number) {
146+
constructor(field: FieldWrapper, step: number, start?: number, end?: number) {
149147
this.field = field
150-
this.every = every
148+
this.step = step
151149
this.start = start ?? field.min
152150
this.end = end ?? field.max
153151
}
154152

153+
get type() {
154+
const { min, max } = this.field
155+
if (this.start !== min || max - this.end >= this.step) {
156+
return FieldPattern.RangeStep
157+
}
158+
return FieldPattern.Step
159+
}
160+
155161
toCron() {
156-
if (this.start == this.field.min && this.end == this.field.max) {
157-
return `*/${this.every}`
162+
if (this.type == FieldPattern.RangeStep) {
163+
return `${this.start}-${this.end}/${this.step}`
158164
}
159-
return `${this.start}-${this.end}/${this.every}`
165+
return `*/${this.step}`
160166
}
161167

162168
toArray() {
163-
return _every(this.every, this.start, this.end)
169+
return _rangeWithStep(this.step, this.start, this.end)
164170
}
165171

166172
get items() {
167173
return {
168-
every: this.field.itemMap[this.every],
174+
step: this.field.itemMap[this.step],
169175
start: this.field.itemMap[this.start],
170176
end: this.field.itemMap[this.end],
171177
}
172178
}
173179

174180
static fromString(str: string, field: FieldWrapper) {
175-
if (!EverySegment.re.test(str)) {
181+
if (!StepSegment.re.test(str)) {
176182
return null
177183
}
178184

179-
const [rangeStr, everyStr] = str.split('/')
180-
const every = parseInt(everyStr)
185+
const [rangeStr, stepStr] = str.split('/')
186+
const step = parseInt(stepStr)
181187

182-
if (every > field.items.length) {
188+
if (step > field.items.length) {
183189
return null
184190
}
185191

186192
const range = str.split('-').map((s) => parseInt(s))
187193
const min = rangeStr == '*' ? field.min : range[0]
188194
const max = rangeStr == '*' ? field.max : range[1]
189195

190-
if (_every(every, min, max).length == 0) {
196+
if (_rangeWithStep(step, min, max).length == 0) {
191197
return null
192198
}
193199

194-
return new EverySegment(field, every, min, max)
200+
return new StepSegment(field, step, min, max)
195201
}
196202

197203
static fromArray(arr: number[], field: FieldWrapper) {
198-
const { min, max } = field
199-
200204
if (arr.length < 3) {
201205
return null
202206
}
@@ -206,28 +210,19 @@ class EverySegment implements CronSegment {
206210
return null
207211
}
208212

209-
// prevent a-b/x segments until localization is ready
210-
if (arr[0] != min) {
211-
return null
212-
}
213-
const end = arr[arr.length - 1]
214-
if (max - end >= step) {
215-
return null
216-
}
217-
218213
for (let i = 2; i < arr.length; i++) {
219214
if (arr[i] - arr[i - 1] != step) {
220215
return null
221216
}
222217
}
223218

224-
return new EverySegment(field, step, min, max)
219+
return new StepSegment(field, step, arr[0], arr[arr.length - 1])
225220
}
226221
}
227222

228223
class ValueSegment implements CronSegment {
229224
field: FieldWrapper
230-
type: CronType = CronType.Value
225+
type: FieldPattern = FieldPattern.Value
231226
value: number
232227

233228
constructor(field: FieldWrapper, value: number) {
@@ -276,7 +271,7 @@ class ValueSegment implements CronSegment {
276271
class CombinedSegment implements CronSegment {
277272
static segmentFactories: SegmentFromString[] = [
278273
AnySegment.fromString,
279-
EverySegment.fromString,
274+
StepSegment.fromString,
280275
RangeSegment.fromString,
281276
ValueSegment.fromString,
282277
]
@@ -293,7 +288,7 @@ class CombinedSegment implements CronSegment {
293288
if (this.segments.length == 1) {
294289
return this.segments[0].type
295290
}
296-
return CronType.Range
291+
return FieldPattern.Range
297292
}
298293

299294
addSegment(segment: CronSegment) {
@@ -377,7 +372,7 @@ function cronToSegment(cron: string, field: FieldWrapper) {
377372
function arrayToSegment(arr: number[], field: FieldWrapper) {
378373
for (const fromArray of [
379374
AnySegment.fromArray,
380-
EverySegment.fromArray,
375+
StepSegment.fromArray,
381376
CombinedSegment.fromArray,
382377
]) {
383378
const seg = fromArray(arr, field)
@@ -390,11 +385,11 @@ function arrayToSegment(arr: number[], field: FieldWrapper) {
390385

391386
export {
392387
AnySegment,
388+
arrayToSegment,
393389
CombinedSegment,
394-
EverySegment,
390+
cronToSegment,
395391
NoSpecificSegment,
396392
RangeSegment,
393+
StepSegment,
397394
ValueSegment,
398-
arrayToSegment,
399-
cronToSegment,
400395
}

core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export {
2222
export { Locale, getLocale } from './locale'
2323
export type * from './locale/types'
2424
export type * from './types'
25-
export { CronType, FieldWrapper, TextPosition } from './types'
25+
export { FieldPattern, FieldWrapper, TextPosition } from './types'
2626
export { defaultItems, genItems, pad, splitArray, type toText } from './util'
2727

2828
export const CronCorePlugin = {

core/src/locale/cn.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ const locale: Localization = {
66
suffix: '',
77
text: '未知',
88
'*': {
9-
empty: { text: '每 {{field.id}}' },
9+
any: { text: '每 {{field.id}}' },
1010
value: { text: '{{value.text}}' },
1111
range: { text: '{{start.text}}-{{end.text}}' },
12-
everyX: { text: '每 {{every.value}}' },
12+
step: { text: '每 {{step.value}}' },
1313
},
1414
month: {
1515
'*': { prefix: '的' },
16-
empty: { text: '每月' },
16+
any: { text: '每月' },
1717
value: { text: '{{value.alt}}' },
1818
range: { text: '{{start.alt}}-{{end.alt}}' },
1919
},
2020
day: {
2121
'*': { prefix: '的' },
22-
empty: { text: '每日' },
22+
any: { text: '每日' },
2323
value: { text: '{{value.alt}}号' },
2424
range: { text: '{{start.alt}}号-{{end.alt}}号' },
2525
noSpecific: {
@@ -28,7 +28,7 @@ const locale: Localization = {
2828
},
2929
dayOfWeek: {
3030
'*': { prefix: '的' },
31-
empty: { text: '一周的每一天' },
31+
any: { text: '一周的每一天' },
3232
value: { text: '{{value.alt}}' },
3333
range: { text: '{{start.alt}}-{{end.alt}}' },
3434
noSpecific: {
@@ -37,15 +37,15 @@ const locale: Localization = {
3737
},
3838
hour: {
3939
'*': { prefix: '的' },
40-
empty: { text: '每小时' },
40+
any: { text: '每小时' },
4141
},
4242
minute: {
4343
'*': { prefix: ':' },
44-
empty: { text: '每分钟' },
44+
any: { text: '每分钟' },
4545
},
4646
second: {
4747
'*': { prefix: ':' },
48-
empty: { text: '每秒' },
48+
any: { text: '每秒' },
4949
},
5050
},
5151
minute: {
@@ -58,7 +58,7 @@ const locale: Localization = {
5858
prefix: ':',
5959
suffix: '分钟',
6060
},
61-
empty: { text: '每' },
61+
any: { text: '每' },
6262
},
6363
},
6464
day: {
@@ -68,7 +68,7 @@ const locale: Localization = {
6868
text: '周',
6969
dayOfWeek: {
7070
'*': { prefix: '的' },
71-
empty: { text: '每天' },
71+
any: { text: '每天' },
7272
value: { text: '{{value.alt}}' },
7373
range: { text: '{{start.alt}}-{{end.alt}}' },
7474
},
@@ -100,7 +100,7 @@ const locale: Localization = {
100100
prefix: ':',
101101
suffix: '秒',
102102
},
103-
empty: { text: '每' },
103+
any: { text: '每' },
104104
},
105105
},
106106
'q-hour': {

0 commit comments

Comments
 (0)