@djangocfg/ui-tools 2.1.130 → 2.1.132

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +57 -1
  2. package/dist/CronScheduler.client-REXEMXZN.mjs +67 -0
  3. package/dist/CronScheduler.client-REXEMXZN.mjs.map +1 -0
  4. package/dist/CronScheduler.client-YJ2SHYNH.cjs +72 -0
  5. package/dist/CronScheduler.client-YJ2SHYNH.cjs.map +1 -0
  6. package/dist/chunk-6G72N466.mjs +995 -0
  7. package/dist/chunk-6G72N466.mjs.map +1 -0
  8. package/dist/chunk-74JT4UIM.cjs +1015 -0
  9. package/dist/chunk-74JT4UIM.cjs.map +1 -0
  10. package/dist/index.cjs +109 -0
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +280 -1
  13. package/dist/index.d.ts +280 -1
  14. package/dist/index.mjs +32 -1
  15. package/dist/index.mjs.map +1 -1
  16. package/package.json +6 -6
  17. package/src/index.ts +5 -0
  18. package/src/tools/CronScheduler/CronScheduler.client.tsx +140 -0
  19. package/src/tools/CronScheduler/CronScheduler.story.tsx +220 -0
  20. package/src/tools/CronScheduler/components/CronCheatsheet.tsx +101 -0
  21. package/src/tools/CronScheduler/components/CustomInput.tsx +67 -0
  22. package/src/tools/CronScheduler/components/DayChips.tsx +130 -0
  23. package/src/tools/CronScheduler/components/MonthDayGrid.tsx +143 -0
  24. package/src/tools/CronScheduler/components/SchedulePreview.tsx +103 -0
  25. package/src/tools/CronScheduler/components/ScheduleTypeSelector.tsx +57 -0
  26. package/src/tools/CronScheduler/components/TimeSelector.tsx +132 -0
  27. package/src/tools/CronScheduler/components/index.ts +24 -0
  28. package/src/tools/CronScheduler/context/CronSchedulerContext.tsx +242 -0
  29. package/src/tools/CronScheduler/context/hooks.ts +86 -0
  30. package/src/tools/CronScheduler/context/index.ts +18 -0
  31. package/src/tools/CronScheduler/index.tsx +91 -0
  32. package/src/tools/CronScheduler/lazy.tsx +67 -0
  33. package/src/tools/CronScheduler/types/index.ts +112 -0
  34. package/src/tools/CronScheduler/utils/cron-builder.ts +100 -0
  35. package/src/tools/CronScheduler/utils/cron-humanize.ts +218 -0
  36. package/src/tools/CronScheduler/utils/cron-parser.ts +188 -0
  37. package/src/tools/CronScheduler/utils/index.ts +12 -0
  38. package/src/tools/index.ts +36 -0
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * CronScheduler Hooks
5
+ *
6
+ * Selective subscription hooks for optimal re-renders.
7
+ * Each hook returns only the data it needs, preventing
8
+ * unnecessary re-renders when unrelated state changes.
9
+ */
10
+
11
+ import { useMemo } from 'react';
12
+ import { useCronSchedulerContext } from './CronSchedulerContext';
13
+
14
+ /**
15
+ * Hook for schedule type selection
16
+ * Re-renders only when type changes
17
+ */
18
+ export function useCronType() {
19
+ const { type, setType } = useCronSchedulerContext();
20
+ return useMemo(() => ({ type, setType }), [type, setType]);
21
+ }
22
+
23
+ /**
24
+ * Hook for time selection
25
+ * Re-renders only when hour/minute changes
26
+ */
27
+ export function useCronTime() {
28
+ const { hour, minute, setTime } = useCronSchedulerContext();
29
+ return useMemo(() => ({ hour, minute, setTime }), [hour, minute, setTime]);
30
+ }
31
+
32
+ /**
33
+ * Hook for week days selection
34
+ * Re-renders only when weekDays changes
35
+ */
36
+ export function useCronWeekDays() {
37
+ const { weekDays, toggleWeekDay, setWeekDays } = useCronSchedulerContext();
38
+ return useMemo(
39
+ () => ({ weekDays, toggleWeekDay, setWeekDays }),
40
+ [weekDays, toggleWeekDay, setWeekDays]
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Hook for month days selection
46
+ * Re-renders only when monthDays changes
47
+ */
48
+ export function useCronMonthDays() {
49
+ const { monthDays, toggleMonthDay, setMonthDays } = useCronSchedulerContext();
50
+ return useMemo(
51
+ () => ({ monthDays, toggleMonthDay, setMonthDays }),
52
+ [monthDays, toggleMonthDay, setMonthDays]
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Hook for custom cron input
58
+ * Re-renders only when customCron/isValid changes
59
+ */
60
+ export function useCronCustom() {
61
+ const { customCron, isValid, setCustomCron } = useCronSchedulerContext();
62
+ return useMemo(
63
+ () => ({ customCron, isValid, setCustomCron }),
64
+ [customCron, isValid, setCustomCron]
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Hook for preview display
70
+ * Re-renders only when computed values change
71
+ */
72
+ export function useCronPreview() {
73
+ const { cronExpression, humanDescription, isValid } = useCronSchedulerContext();
74
+ return useMemo(
75
+ () => ({ cronExpression, humanDescription, isValid }),
76
+ [cronExpression, humanDescription, isValid]
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Full context access
82
+ * Use sparingly - causes re-render on any state change
83
+ */
84
+ export function useCronScheduler() {
85
+ return useCronSchedulerContext();
86
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * CronScheduler Context
3
+ */
4
+
5
+ export {
6
+ CronSchedulerProvider,
7
+ useCronSchedulerContext,
8
+ } from './CronSchedulerContext';
9
+
10
+ export {
11
+ useCronType,
12
+ useCronTime,
13
+ useCronWeekDays,
14
+ useCronMonthDays,
15
+ useCronCustom,
16
+ useCronPreview,
17
+ useCronScheduler,
18
+ } from './hooks';
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * CronScheduler
5
+ *
6
+ * Compact cron expression builder following Apple HIG principles.
7
+ * Lazy-loaded for optimal bundle size (~15KB).
8
+ *
9
+ * @example
10
+ * import { CronScheduler } from '@djangocfg/ui-tools';
11
+ *
12
+ * <CronScheduler
13
+ * value={cron}
14
+ * onChange={setCron}
15
+ * showPreview
16
+ * />
17
+ */
18
+
19
+ import React, { lazy, Suspense } from 'react';
20
+ import { LoadingFallback } from '../../components';
21
+ import type { CronSchedulerProps } from './types';
22
+
23
+ // Lazy load the client component
24
+ const CronSchedulerClient = lazy(() => import('./CronScheduler.client'));
25
+
26
+ /**
27
+ * CronScheduler with Suspense wrapper
28
+ */
29
+ export function CronScheduler(props: CronSchedulerProps) {
30
+ return (
31
+ <Suspense fallback={<CronSchedulerFallback />}>
32
+ <CronSchedulerClient {...props} />
33
+ </Suspense>
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Loading fallback for CronScheduler
39
+ */
40
+ function CronSchedulerFallback() {
41
+ return (
42
+ <LoadingFallback
43
+ minHeight={120}
44
+ showText={false}
45
+ className="rounded-lg"
46
+ />
47
+ );
48
+ }
49
+
50
+ // Re-export types
51
+ export type {
52
+ CronSchedulerProps,
53
+ ScheduleType,
54
+ WeekDay,
55
+ MonthDay,
56
+ CronSchedulerState,
57
+ CronSchedulerContextValue,
58
+ } from './types';
59
+
60
+ // Re-export context and hooks for advanced usage
61
+ export {
62
+ CronSchedulerProvider,
63
+ useCronSchedulerContext,
64
+ useCronType,
65
+ useCronTime,
66
+ useCronWeekDays,
67
+ useCronMonthDays,
68
+ useCronCustom,
69
+ useCronPreview,
70
+ useCronScheduler,
71
+ } from './context';
72
+
73
+ // Re-export utilities
74
+ export {
75
+ buildCron,
76
+ parseCron,
77
+ isValidCron,
78
+ humanizeCron,
79
+ } from './utils';
80
+
81
+ // Re-export components for custom compositions
82
+ export {
83
+ ScheduleTypeSelector,
84
+ TimeSelector,
85
+ DayChips,
86
+ MonthDayGrid,
87
+ CustomInput,
88
+ SchedulePreview,
89
+ } from './components';
90
+
91
+ export default CronScheduler;
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Lazy-loaded CronScheduler Component
5
+ *
6
+ * CronScheduler (~15KB) is loaded only when component is rendered.
7
+ * Use this for automatic code-splitting with Suspense fallback.
8
+ *
9
+ * @example
10
+ * import { LazyCronScheduler } from '@djangocfg/ui-tools';
11
+ *
12
+ * <LazyCronScheduler
13
+ * value={cron}
14
+ * onChange={setCron}
15
+ * />
16
+ */
17
+
18
+ import { createLazyComponent, LoadingFallback } from '../../components';
19
+ import type { CronSchedulerProps } from './types';
20
+
21
+ // Re-export types
22
+ export type {
23
+ CronSchedulerProps,
24
+ ScheduleType,
25
+ WeekDay,
26
+ MonthDay,
27
+ CronSchedulerState,
28
+ } from './types';
29
+
30
+ /**
31
+ * LazyCronScheduler - Lazy-loaded cron expression builder
32
+ *
33
+ * Automatically shows loading state while CronScheduler loads (~15KB).
34
+ * Uses createLazyComponent factory for optimal code-splitting.
35
+ *
36
+ * @example
37
+ * // Basic usage
38
+ * <LazyCronScheduler
39
+ * value="0 9 * * *"
40
+ * onChange={handleChange}
41
+ * />
42
+ *
43
+ * @example
44
+ * // With all options
45
+ * <LazyCronScheduler
46
+ * value={cron}
47
+ * onChange={setCron}
48
+ * defaultType="weekly"
49
+ * showPreview
50
+ * showCronExpression
51
+ * allowCopy
52
+ * timeFormat="24h"
53
+ * />
54
+ */
55
+ export const LazyCronScheduler = createLazyComponent<CronSchedulerProps>(
56
+ () => import('./CronScheduler.client'),
57
+ {
58
+ displayName: 'LazyCronScheduler',
59
+ fallback: (
60
+ <LoadingFallback
61
+ minHeight={120}
62
+ showText={false}
63
+ className="rounded-lg"
64
+ />
65
+ ),
66
+ }
67
+ );
@@ -0,0 +1,112 @@
1
+ /**
2
+ * CronScheduler Types
3
+ *
4
+ * Unix 5-field cron format: minute hour day-of-month month day-of-week
5
+ */
6
+
7
+ // ============================================================================
8
+ // Base Types
9
+ // ============================================================================
10
+
11
+ export type ScheduleType = 'daily' | 'weekly' | 'monthly' | 'custom';
12
+
13
+ /** Day of week: 0 = Sunday, 1 = Monday, ..., 6 = Saturday */
14
+ export type WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6;
15
+
16
+ /** Day of month: 1-31 */
17
+ export type MonthDay =
18
+ | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
19
+ | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20
20
+ | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31;
21
+
22
+ // ============================================================================
23
+ // State Types
24
+ // ============================================================================
25
+
26
+ export interface CronSchedulerState {
27
+ /** Current schedule type */
28
+ type: ScheduleType;
29
+ /** Hour (0-23) */
30
+ hour: number;
31
+ /** Minute (0-59) */
32
+ minute: number;
33
+ /** Selected week days (for weekly type) */
34
+ weekDays: WeekDay[];
35
+ /** Selected month days (for monthly type) */
36
+ monthDays: MonthDay[];
37
+ /** Raw cron expression (for custom type) */
38
+ customCron: string;
39
+ /** Whether current state produces valid cron */
40
+ isValid: boolean;
41
+ }
42
+
43
+ export interface CronSchedulerComputed {
44
+ /** Generated cron expression */
45
+ cronExpression: string;
46
+ /** Human-readable description */
47
+ humanDescription: string;
48
+ }
49
+
50
+ // ============================================================================
51
+ // Action Types
52
+ // ============================================================================
53
+
54
+ export interface CronSchedulerActions {
55
+ /** Set schedule type */
56
+ setType: (type: ScheduleType) => void;
57
+ /** Set time (hour and minute) */
58
+ setTime: (hour: number, minute: number) => void;
59
+ /** Toggle a week day selection */
60
+ toggleWeekDay: (day: WeekDay) => void;
61
+ /** Set all week days at once */
62
+ setWeekDays: (days: WeekDay[]) => void;
63
+ /** Toggle a month day selection */
64
+ toggleMonthDay: (day: MonthDay) => void;
65
+ /** Set all month days at once */
66
+ setMonthDays: (days: MonthDay[]) => void;
67
+ /** Set custom cron expression */
68
+ setCustomCron: (cron: string) => void;
69
+ /** Reset to default state */
70
+ reset: () => void;
71
+ }
72
+
73
+ // ============================================================================
74
+ // Context Types
75
+ // ============================================================================
76
+
77
+ export interface CronSchedulerContextValue
78
+ extends CronSchedulerState,
79
+ CronSchedulerComputed,
80
+ CronSchedulerActions {}
81
+
82
+ // ============================================================================
83
+ // Component Props
84
+ // ============================================================================
85
+
86
+ export interface CronSchedulerProps {
87
+ /** Current cron expression (Unix 5-field format) */
88
+ value?: string;
89
+ /** Callback when schedule changes */
90
+ onChange?: (cron: string) => void;
91
+ /** Initial schedule type (default: 'daily') */
92
+ defaultType?: ScheduleType;
93
+ /** Show human-readable preview (default: true) */
94
+ showPreview?: boolean;
95
+ /** Show raw cron expression in preview (default: false) */
96
+ showCronExpression?: boolean;
97
+ /** Allow copying cron expression (default: false) */
98
+ allowCopy?: boolean;
99
+ /** 12h or 24h time format (default: '24h') */
100
+ timeFormat?: '12h' | '24h';
101
+ /** Disable all interactions */
102
+ disabled?: boolean;
103
+ /** Additional CSS classes */
104
+ className?: string;
105
+ }
106
+
107
+ export interface CronSchedulerProviderProps {
108
+ children: React.ReactNode;
109
+ value?: string;
110
+ onChange?: (cron: string) => void;
111
+ defaultType?: ScheduleType;
112
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Cron Builder
3
+ *
4
+ * Converts CronSchedulerState to Unix 5-field cron expression
5
+ * Format: minute hour day-of-month month day-of-week
6
+ */
7
+
8
+ import type { CronSchedulerState } from '../types';
9
+
10
+ /**
11
+ * Builds Unix 5-field cron expression from state
12
+ *
13
+ * @example
14
+ * // Daily at 9:00
15
+ * buildCron({ type: 'daily', hour: 9, minute: 0, ... }) // '0 9 * * *'
16
+ *
17
+ * // Weekly on Mon, Wed, Fri at 14:30
18
+ * buildCron({ type: 'weekly', hour: 14, minute: 30, weekDays: [1, 3, 5], ... }) // '30 14 * * 1,3,5'
19
+ *
20
+ * // Monthly on 1st and 15th at 0:00
21
+ * buildCron({ type: 'monthly', hour: 0, minute: 0, monthDays: [1, 15], ... }) // '0 0 1,15 * *'
22
+ */
23
+ export function buildCron(state: CronSchedulerState): string {
24
+ const { type, hour, minute, weekDays, monthDays, customCron } = state;
25
+
26
+ // Ensure valid hour/minute
27
+ const h = Math.max(0, Math.min(23, hour));
28
+ const m = Math.max(0, Math.min(59, minute));
29
+
30
+ switch (type) {
31
+ case 'daily':
32
+ // Run every day at specified time
33
+ return `${m} ${h} * * *`;
34
+
35
+ case 'weekly': {
36
+ // Run on specified days of week at specified time
37
+ const sortedDays = [...weekDays].sort((a, b) => a - b);
38
+ const daysStr = sortedDays.length > 0 ? formatNumberList(sortedDays) : '*';
39
+ return `${m} ${h} * * ${daysStr}`;
40
+ }
41
+
42
+ case 'monthly': {
43
+ // Run on specified days of month at specified time
44
+ const sortedDays = [...monthDays].sort((a, b) => a - b);
45
+ const daysStr = sortedDays.length > 0 ? formatNumberList(sortedDays) : '1';
46
+ return `${m} ${h} ${daysStr} * *`;
47
+ }
48
+
49
+ case 'custom':
50
+ // Return user-provided expression
51
+ return customCron.trim() || '* * * * *';
52
+
53
+ default:
54
+ return '0 0 * * *';
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Formats a list of numbers as ranges where possible
60
+ * [1,2,3,5,7,8,9] -> "1-3,5,7-9"
61
+ */
62
+ function formatNumberList(nums: number[]): string {
63
+ if (nums.length === 0) return '';
64
+ if (nums.length === 1) return nums[0].toString();
65
+
66
+ const sorted = [...nums].sort((a, b) => a - b);
67
+ const ranges: string[] = [];
68
+ let start = sorted[0];
69
+ let end = sorted[0];
70
+
71
+ for (let i = 1; i < sorted.length; i++) {
72
+ if (sorted[i] === end + 1) {
73
+ // Continue the range
74
+ end = sorted[i];
75
+ } else {
76
+ // End current range, start new one
77
+ ranges.push(start === end ? `${start}` : `${start}-${end}`);
78
+ start = sorted[i];
79
+ end = sorted[i];
80
+ }
81
+ }
82
+
83
+ // Don't forget the last range
84
+ ranges.push(start === end ? `${start}` : `${start}-${end}`);
85
+
86
+ return ranges.join(',');
87
+ }
88
+
89
+ /**
90
+ * Builds cron for a specific schedule type with defaults
91
+ */
92
+ export function buildDefaultCron(type: CronSchedulerState['type']): string {
93
+ const defaults: Record<typeof type, string> = {
94
+ daily: '0 9 * * *', // Every day at 9:00
95
+ weekly: '0 9 * * 1-5', // Weekdays at 9:00
96
+ monthly: '0 9 1 * *', // 1st of month at 9:00
97
+ custom: '* * * * *', // Every minute
98
+ };
99
+ return defaults[type];
100
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Cron Humanize
3
+ *
4
+ * Converts cron expressions to human-readable descriptions
5
+ */
6
+
7
+ const WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
8
+ const WEEKDAY_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
9
+
10
+ /**
11
+ * Converts cron expression to human-readable description
12
+ *
13
+ * Examples:
14
+ * - humanizeCron('0 9 * * *') returns "Every day at 09:00"
15
+ * - humanizeCron('30 14 * * 1,3,5') returns "Mon, Wed, Fri at 14:30"
16
+ * - humanizeCron('0 0 1 * *') returns "1st of every month at 00:00"
17
+ */
18
+ export function humanizeCron(cron: string): string {
19
+ if (!cron || typeof cron !== 'string') return 'Invalid schedule';
20
+
21
+ const parts = cron.trim().split(/\s+/);
22
+ if (parts.length !== 5) return 'Invalid schedule';
23
+
24
+ const [minutePart, hourPart, dayOfMonthPart, monthPart, dayOfWeekPart] = parts;
25
+
26
+ // Parse time
27
+ const minute = parseInt(minutePart, 10);
28
+ const hour = parseInt(hourPart, 10);
29
+ const timeStr = formatTime(hour, minute);
30
+
31
+ // Every minute
32
+ if (minutePart === '*' && hourPart === '*' && dayOfMonthPart === '*' && monthPart === '*' && dayOfWeekPart === '*') {
33
+ return 'Every minute';
34
+ }
35
+
36
+ // Every N minutes
37
+ if (minutePart.startsWith('*/')) {
38
+ const interval = parseInt(minutePart.slice(2), 10);
39
+ if (hourPart === '*') {
40
+ return `Every ${interval} minute${interval > 1 ? 's' : ''}`;
41
+ }
42
+ return `Every ${interval} minute${interval > 1 ? 's' : ''} at hour ${hour}`;
43
+ }
44
+
45
+ // Hourly
46
+ if (!isNaN(minute) && hourPart === '*' && dayOfMonthPart === '*' && monthPart === '*' && dayOfWeekPart === '*') {
47
+ return `Every hour at minute ${minute}`;
48
+ }
49
+
50
+ // Daily
51
+ if (dayOfMonthPart === '*' && monthPart === '*' && dayOfWeekPart === '*') {
52
+ return `Every day at ${timeStr}`;
53
+ }
54
+
55
+ // Weekly
56
+ if (dayOfMonthPart === '*' && monthPart === '*' && dayOfWeekPart !== '*') {
57
+ const days = parseWeekDays(dayOfWeekPart);
58
+
59
+ if (days.length === 7) {
60
+ return `Every day at ${timeStr}`;
61
+ }
62
+
63
+ if (days.length === 5 && isWeekdays(days)) {
64
+ return `Weekdays at ${timeStr}`;
65
+ }
66
+
67
+ if (days.length === 2 && isWeekend(days)) {
68
+ return `Weekends at ${timeStr}`;
69
+ }
70
+
71
+ const dayNames = days.map(d => WEEKDAY_SHORT[d]).join(', ');
72
+ return `${dayNames} at ${timeStr}`;
73
+ }
74
+
75
+ // Monthly
76
+ if (dayOfMonthPart !== '*' && monthPart === '*' && dayOfWeekPart === '*') {
77
+ const days = parseMonthDays(dayOfMonthPart);
78
+
79
+ if (days.length === 1) {
80
+ return `${ordinal(days[0])} of every month at ${timeStr}`;
81
+ }
82
+
83
+ if (days.length <= 3) {
84
+ const dayStr = days.map(d => ordinal(d)).join(', ');
85
+ return `${dayStr} of every month at ${timeStr}`;
86
+ }
87
+
88
+ return `${days.length} days per month at ${timeStr}`;
89
+ }
90
+
91
+ // Complex expression - show as-is
92
+ return `Custom schedule`;
93
+ }
94
+
95
+ /**
96
+ * Formats time as HH:MM
97
+ */
98
+ function formatTime(hour: number, minute: number): string {
99
+ const h = isNaN(hour) ? 0 : Math.max(0, Math.min(23, hour));
100
+ const m = isNaN(minute) ? 0 : Math.max(0, Math.min(59, minute));
101
+ return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
102
+ }
103
+
104
+ /**
105
+ * Formats time in 12-hour format with AM/PM
106
+ */
107
+ export function formatTime12h(hour: number, minute: number): string {
108
+ const h = isNaN(hour) ? 0 : Math.max(0, Math.min(23, hour));
109
+ const m = isNaN(minute) ? 0 : Math.max(0, Math.min(59, minute));
110
+ const period = h >= 12 ? 'PM' : 'AM';
111
+ const h12 = h % 12 || 12;
112
+ return `${h12}:${m.toString().padStart(2, '0')} ${period}`;
113
+ }
114
+
115
+ /**
116
+ * Parses week days from cron field
117
+ */
118
+ function parseWeekDays(part: string): number[] {
119
+ const result: number[] = [];
120
+
121
+ // Handle ranges like 1-5
122
+ if (part.includes('-') && !part.includes(',')) {
123
+ const [start, end] = part.split('-').map(s => parseInt(s, 10));
124
+ if (!isNaN(start) && !isNaN(end)) {
125
+ for (let i = start; i <= end && i <= 6; i++) {
126
+ if (i >= 0) result.push(i);
127
+ }
128
+ }
129
+ return result;
130
+ }
131
+
132
+ // Handle comma-separated list
133
+ for (const segment of part.split(',')) {
134
+ // Handle nested ranges
135
+ if (segment.includes('-')) {
136
+ const [start, end] = segment.split('-').map(s => parseInt(s, 10));
137
+ if (!isNaN(start) && !isNaN(end)) {
138
+ for (let i = start; i <= end && i <= 6; i++) {
139
+ if (i >= 0) result.push(i);
140
+ }
141
+ }
142
+ } else {
143
+ const num = parseInt(segment.trim(), 10);
144
+ if (!isNaN(num) && num >= 0 && num <= 6) {
145
+ result.push(num);
146
+ }
147
+ }
148
+ }
149
+
150
+ return result.sort((a, b) => a - b);
151
+ }
152
+
153
+ /**
154
+ * Parses month days from cron field
155
+ */
156
+ function parseMonthDays(part: string): number[] {
157
+ const result: number[] = [];
158
+
159
+ // Handle ranges
160
+ if (part.includes('-') && !part.includes(',')) {
161
+ const [start, end] = part.split('-').map(s => parseInt(s, 10));
162
+ if (!isNaN(start) && !isNaN(end)) {
163
+ for (let i = start; i <= end && i <= 31; i++) {
164
+ if (i >= 1) result.push(i);
165
+ }
166
+ }
167
+ return result;
168
+ }
169
+
170
+ // Handle comma-separated list
171
+ for (const segment of part.split(',')) {
172
+ const num = parseInt(segment.trim(), 10);
173
+ if (!isNaN(num) && num >= 1 && num <= 31) {
174
+ result.push(num);
175
+ }
176
+ }
177
+
178
+ return result.sort((a, b) => a - b);
179
+ }
180
+
181
+ /**
182
+ * Checks if days array represents weekdays (Mon-Fri)
183
+ */
184
+ function isWeekdays(days: number[]): boolean {
185
+ const sorted = [...days].sort((a, b) => a - b);
186
+ return sorted.join(',') === '1,2,3,4,5';
187
+ }
188
+
189
+ /**
190
+ * Checks if days array represents weekend (Sat-Sun)
191
+ */
192
+ function isWeekend(days: number[]): boolean {
193
+ const sorted = [...days].sort((a, b) => a - b);
194
+ return sorted.join(',') === '0,6';
195
+ }
196
+
197
+ /**
198
+ * Returns ordinal suffix for a number (1st, 2nd, 3rd, etc.)
199
+ */
200
+ function ordinal(n: number): string {
201
+ const s = ['th', 'st', 'nd', 'rd'];
202
+ const v = n % 100;
203
+ return n + (s[(v - 20) % 10] || s[v] || s[0]);
204
+ }
205
+
206
+ /**
207
+ * Gets full weekday name
208
+ */
209
+ export function getWeekdayName(day: number): string {
210
+ return WEEKDAY_NAMES[day] || 'Unknown';
211
+ }
212
+
213
+ /**
214
+ * Gets short weekday name
215
+ */
216
+ export function getWeekdayShort(day: number): string {
217
+ return WEEKDAY_SHORT[day] || '?';
218
+ }