-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparse.ts
134 lines (124 loc) · 3.26 KB
/
parse.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* @packageDocumentation
* @ignore
*/
/** @name Schedule
* @description Any field which is undefined is assumed to be unconstrained.
*/
export type Schedule = [
second: number[] | undefined,
minute: number[] | undefined,
hour: number[] | undefined,
dayOfMonth: number[] | undefined,
month: number[] | undefined,
dayOfWeek: number[] | undefined,
year: number[] | undefined
];
export enum Field {
second = 0,
minute = 1,
hour = 2,
dayOfMonth = 3,
month = 4,
dayOfWeek = 5,
year = 6,
}
type FieldConfig = {
name: string;
min: number;
max: number;
adjustment: number;
names: Record<string, number>;
};
const monthMap = {
JAN: 0,
FEB: 1,
MAR: 2,
APR: 3,
MAY: 4,
JUN: 5,
JUL: 6,
AUG: 7,
SEP: 8,
OCT: 9,
NOV: 10,
DEC: 11,
};
const dayOfWeekMap = {
SUN: 0,
MON: 1,
TUE: 2,
WED: 3,
THU: 4,
FRI: 5,
SAT: 6,
};
const fieldsConfig: FieldConfig[] = [
{ name: "Second", min: 0, max: 59, adjustment: 0, names: {} },
{ name: "Minute", min: 0, max: 59, adjustment: 0, names: {} },
{ name: "Hour", min: 0, max: 23, adjustment: 0, names: {} },
{ name: "Day of month", min: 1, max: 31, adjustment: 0, names: {} },
{ name: "Month", min: 1, max: 12, adjustment: -1, names: monthMap },
{ name: "Day of week", min: 0, max: 6, adjustment: 0, names: dayOfWeekMap },
{ name: "Year", min: 1970, max: 3000, adjustment: 0, names: {} },
];
function uniq<T>(source: T[]): T[] {
const target = [];
for (const item of source) {
if (target.indexOf(item) < 0) {
target.push(item);
}
}
return target;
}
function parseField(input: string, field: Field): number[] | undefined {
if (input === "*") return undefined;
const config = fieldsConfig[field];
function parseValue(value: string) {
if (value in config.names) {
return config.names[value];
}
const asNumber = Number.parseInt(value, 10);
if (Number.isNaN(asNumber)) {
throw new Error(`${config.name} invalid value: ${value}`);
}
if (asNumber < config.min || asNumber > config.max) {
throw new Error(
`${config.name} is out of bounds (${config.min}-${config.max}): ${value}`
);
}
return asNumber + config.adjustment;
}
const asRange = /^(.+)\-(.+)$/g.exec(input);
if (asRange) {
const start = parseValue(asRange[1]);
const end = parseValue(asRange[2]);
if (start > end) {
throw new Error(
`${config.name} invalid range: start cannot be after end`
);
}
const range = new Array(end - start + 1);
for (let index = 0; index < range.length; index++) {
range[index] = start + index;
}
return range;
}
return uniq(input.split(",").map(parseValue)).sort((a, b) => a - b);
}
/**
* Internal method to parse a string to an internal Schedule representation.
*
* Avoid depending on this method directly as it might change without notice.
* @internal
*/
export function parse(input: string): Schedule {
const fields = input.split(" ");
if (fields.length > 7) throw new Error("Too many fields");
if (fields.length < 5) throw new Error("Too few fields");
const hasYear = fields.length >= 6;
const hasSeconds = fields.length === 7;
if (!hasSeconds) fields.unshift("0");
if (!hasYear) fields.push("*");
return fields.map(parseField) as Schedule;
}