Skip to content

Commit 48b4904

Browse files
committed
fix(Cron Parser): handle aws, next executions and TZ
Handle AWS Cron syntax and distinguishe from standard syntax (fix CorentinTh#855) Add show crontab next 5 execution times (taken from CorentinTh#1283) Add Timezone handling: fix CorentinTh#261
1 parent cb5b462 commit 48b4904

File tree

9 files changed

+339
-18
lines changed

9 files changed

+339
-18
lines changed

components.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ declare module '@vue/runtime-core' {
132132
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
133133
NDivider: typeof import('naive-ui')['NDivider']
134134
NEllipsis: typeof import('naive-ui')['NEllipsis']
135+
NForm: typeof import('naive-ui')['NForm']
135136
NFormItem: typeof import('naive-ui')['NFormItem']
136137
NGi: typeof import('naive-ui')['NGi']
137138
NGrid: typeof import('naive-ui')['NGrid']
@@ -143,8 +144,12 @@ declare module '@vue/runtime-core' {
143144
NLayout: typeof import('naive-ui')['NLayout']
144145
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
145146
NMenu: typeof import('naive-ui')['NMenu']
147+
NRadio: typeof import('naive-ui')['NRadio']
148+
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
146149
NScrollbar: typeof import('naive-ui')['NScrollbar']
150+
NSpace: typeof import('naive-ui')['NSpace']
147151
NSpin: typeof import('naive-ui')['NSpin']
152+
NSwitch: typeof import('naive-ui')['NSwitch']
148153
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
149154
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
150155
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
@@ -159,6 +164,7 @@ declare module '@vue/runtime-core' {
159164
RouterLink: typeof import('vue-router')['RouterLink']
160165
RouterView: typeof import('vue-router')['RouterView']
161166
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default']
167+
SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default']
162168
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
163169
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
164170
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,20 @@
5151
"change-case": "^4.1.2",
5252
"colord": "^2.9.3",
5353
"composerize-ts": "^0.6.2",
54+
"countries-and-timezones": "^3.6.0",
5455
"country-code-lookup": "^0.1.0",
56+
"cron-parser": "^4.9.0",
5557
"cron-validator": "^1.3.1",
5658
"cronstrue": "^2.26.0",
5759
"crypto-js": "^4.1.1",
5860
"date-fns": "^2.29.3",
5961
"dompurify": "^3.0.6",
6062
"emojilib": "^3.0.10",
63+
"event-cron-parser": "^1.0.34",
6164
"figlet": "^1.7.0",
6265
"figue": "^1.2.0",
6366
"fuse.js": "^6.6.2",
67+
"get-timezone-offset": "^1.0.5",
6468
"highlight.js": "^11.7.0",
6569
"iarna-toml-esm": "^3.0.5",
6670
"ibantools": "^4.3.3",

pnpm-lock.yaml

Lines changed: 53 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/composable/queryParams.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { useRouteQuery } from '@vueuse/router';
22
import { computed } from 'vue';
3+
import { useStorage } from '@vueuse/core';
34

4-
export { useQueryParam };
5+
export { useQueryParam, useQueryParamOrStorage };
56

67
const transformers = {
78
number: {
@@ -16,6 +17,12 @@ const transformers = {
1617
fromQuery: (value: string) => value.toLowerCase() === 'true',
1718
toQuery: (value: boolean) => (value ? 'true' : 'false'),
1819
},
20+
object: {
21+
fromQuery: (value: string) => {
22+
return JSON.parse(value);
23+
},
24+
toQuery: (value: object) => JSON.stringify(value),
25+
},
1926
};
2027

2128
function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: T }) {
@@ -33,3 +40,27 @@ function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue:
3340
},
3441
});
3542
}
43+
44+
function useQueryParamOrStorage<T>({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) {
45+
const type = typeof defaultValue;
46+
const transformer = transformers[type as keyof typeof transformers] ?? transformers.string;
47+
48+
const storageRef = useStorage(storageName, defaultValue);
49+
const proxyDefaultValue = transformer.toQuery(defaultValue as never);
50+
const proxy = useRouteQuery(name, proxyDefaultValue);
51+
52+
const r = ref(defaultValue);
53+
54+
watch(r,
55+
(value) => {
56+
proxy.value = transformer.toQuery(value as never);
57+
storageRef.value = value as never;
58+
},
59+
{ deep: true });
60+
61+
r.value = (proxy.value && proxy.value !== proxyDefaultValue
62+
? transformer.fromQuery(proxy.value) as unknown as T
63+
: storageRef.value as T) as never;
64+
65+
return r;
66+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { getCronType, getLastExecutionTimes, isCronValid } from './crontab-generator.service';
3+
4+
describe('crontab-generator', () => {
5+
describe('isCronValid', () => {
6+
it('should return true for all valid formats', () => {
7+
expect(isCronValid('0 0 * * 1-5')).toBe(true);
8+
expect(isCronValid('23 0-20/2 * * *')).toBe(true);
9+
10+
// AWS formats
11+
expect(isCronValid('0 11-22 ? * MON-FRI *')).toBe(true);
12+
expect(isCronValid('0 0 ? * 1 *')).toBe(true);
13+
});
14+
it('should return false for all invalid formats', () => {
15+
expect(isCronValid('aert')).toBe(false);
16+
expect(isCronValid('40 *')).toBe(false);
17+
});
18+
});
19+
20+
describe('getCronType', () => {
21+
it('should return right type', () => {
22+
expect(getCronType('0 0 * * 1-5')).toBe('standard');
23+
expect(getCronType('23 0-20/2 * * *')).toBe('standard');
24+
25+
// AWS formats
26+
expect(getCronType('0 11-22 ? * MON-FRI *')).toBe('aws');
27+
expect(getCronType('0 0 ? * 1 *')).toBe('aws');
28+
29+
expect(getCronType('aert')).toBe(false);
30+
expect(getCronType('40 *')).toBe(false);
31+
});
32+
});
33+
34+
describe('getLastExecutionTimes', () => {
35+
it('should return next valid datetimes', () => {
36+
expect(getLastExecutionTimes('0 0 * * 1-5')).toHaveLength(5);
37+
expect(getLastExecutionTimes('23 0-20/2 * * *')).toHaveLength(5);
38+
39+
// AWS formats
40+
expect(getLastExecutionTimes('0 11-22 ? * MON-FRI *')).toHaveLength(5);
41+
expect(getLastExecutionTimes('0 0 ? * 1 *')).toHaveLength(5);
42+
});
43+
});
44+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { parseExpression } from 'cron-parser';
2+
import EventCronParser from 'event-cron-parser';
3+
4+
export function getLastExecutionTimes(cronExpression: string, tz: string | undefined = undefined, count: number = 5) {
5+
if (getCronType(cronExpression) === 'standard') {
6+
const interval = parseExpression(cronExpression, { tz });
7+
const times = [];
8+
for (let i = 0; i < count; i++) {
9+
times.push(interval.next().toJSON());
10+
}
11+
return times;
12+
}
13+
if (getCronType(cronExpression) === 'aws') {
14+
const parsed = new EventCronParser(cronExpression);
15+
const times = [];
16+
for (let i = 0; i < count; i++) {
17+
times.push(JSON.stringify(parsed.next()));
18+
}
19+
return times;
20+
}
21+
22+
return [];
23+
}
24+
25+
export function isCronValid(v: string) {
26+
return !!getCronType(v);
27+
}
28+
29+
export function getCronType(v: string) {
30+
try {
31+
parseExpression(v);
32+
return 'standard';
33+
}
34+
catch (_) {
35+
try {
36+
const parsed = new EventCronParser(v);
37+
parsed.validate();
38+
return 'aws';
39+
}
40+
catch (_) {
41+
}
42+
}
43+
return false;
44+
}

0 commit comments

Comments
 (0)