cronli5 0.7.2 → 0.8.2

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.
package/dist/lang/fr.js CHANGED
@@ -193,6 +193,7 @@ function normalizeOptions(options) {
193
193
  // satisfied without the 12-hour machinery the es donor carried.
194
194
  ampm: false,
195
195
  lenient: !!options.lenient,
196
+ quartz: !!options.quartz,
196
197
  seconds: !!options.seconds,
197
198
  short: !!options.short,
198
199
  style,
@@ -338,6 +339,9 @@ function minutesList(schedule, opts) {
338
339
  opts
339
340
  ) ?? "aux minutes " + joinList(segmentWords(segmentsOf(schedule, "minute"))) + " de chaque heure";
340
341
  }
342
+ function withoutHourAnchor(lead) {
343
+ return lead.replace(/ de chaque heure$/, "");
344
+ }
341
345
  function minuteRangeLead(minuteField) {
342
346
  const bounds = minuteField.split("-");
343
347
  return "chaque minute de " + bounds[0] + " \xE0 " + bounds[1];
@@ -375,7 +379,7 @@ function renderMinuteFrequency(schedule, plan, opts) {
375
379
  } else if (plan.hours.kind === "window") {
376
380
  phrase += " " + hourWindow(plan.hours, opts);
377
381
  } else if (plan.hours.kind === "step") {
378
- phrase += ", " + stepHourSpan(stepSegment(schedule, "hour"), opts);
382
+ phrase = withoutHourAnchor(phrase) + ", " + stepHourSpan(stepSegment(schedule, "hour"), opts);
379
383
  }
380
384
  return phrase + trailingQualifier(schedule, opts);
381
385
  }
@@ -412,7 +416,7 @@ function renderMinuteSpanAcrossHourStep(schedule, plan, opts) {
412
416
  if (plan.form === "wildcard") {
413
417
  return "chaque minute, " + stepHourSpan(segment, opts) + trailingQualifier(schedule, opts);
414
418
  }
415
- const lead = plan.form === "list" ? minutesList(schedule, opts) : minuteRangeLead(schedule.pattern.minute);
419
+ const lead = withoutHourAnchor(plan.form === "list" ? minutesList(schedule, opts) : minuteRangeLead(schedule.pattern.minute));
416
420
  return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(schedule, opts);
417
421
  }
418
422
  function renderEveryHour(schedule, plan, opts) {
@@ -585,7 +589,7 @@ function renderCompactClockTimes(schedule, plan, opts) {
585
589
  );
586
590
  }
587
591
  const cadence = unevenHourCadence(schedule, opts);
588
- const phrase = cadence ? minutesList(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts) : minutesList(schedule, opts) + ", " + hourContextTimes(schedule, opts) + trailingQualifier(schedule, opts);
592
+ const phrase = cadence ? withoutHourAnchor(minutesList(schedule, opts)) + ", " + cadence + trailingQualifier(schedule, opts) : minutesList(schedule, opts) + ", " + hourContextTimes(schedule, opts) + trailingQualifier(schedule, opts);
589
593
  return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase : phrase;
590
594
  }
591
595
  var renderers = {
package/dist/lang/pt.cjs CHANGED
@@ -305,6 +305,7 @@ function normalizeOptions(options) {
305
305
  // `{ampm}` option overrides it.
306
306
  ampm: typeof options.ampm === "boolean" ? options.ampm : style.ampm,
307
307
  lenient: !!options.lenient,
308
+ quartz: !!options.quartz,
308
309
  seconds: !!options.seconds,
309
310
  short: !!options.short,
310
311
  style,
@@ -452,6 +453,9 @@ function minutesList(schedule, opts) {
452
453
  opts
453
454
  ) ?? "nos minutos " + joinList(segmentWords(segmentsOf(schedule, "minute"))) + " de cada hora";
454
455
  }
456
+ function withoutHourAnchor(lead) {
457
+ return lead.replace(/ de cada hora$/, "");
458
+ }
455
459
  function minuteRangeLead(minuteField) {
456
460
  const bounds = minuteField.split("-");
457
461
  return "a cada minuto do " + bounds[0] + " ao " + bounds[1];
@@ -522,7 +526,7 @@ function renderMinuteFrequency(schedule, plan, opts) {
522
526
  } else if (plan.hours.kind === "window") {
523
527
  phrase += " " + hourWindow(plan.hours, opts);
524
528
  } else if (plan.hours.kind === "step") {
525
- phrase += ", " + stepHourSpan(stepSegment(schedule, "hour"), opts);
529
+ phrase = withoutHourAnchor(phrase) + ", " + stepHourSpan(stepSegment(schedule, "hour"), opts);
526
530
  }
527
531
  return phrase + trailingQualifier(schedule, opts);
528
532
  }
@@ -559,7 +563,7 @@ function renderMinuteSpanAcrossHourStep(schedule, plan, opts) {
559
563
  if (plan.form === "wildcard") {
560
564
  return "a cada minuto, " + stepHourSpan(segment, opts) + trailingQualifier(schedule, opts);
561
565
  }
562
- const lead = plan.form === "list" ? minutesList(schedule, opts) : minuteRangeLead(schedule.pattern.minute);
566
+ const lead = withoutHourAnchor(plan.form === "list" ? minutesList(schedule, opts) : minuteRangeLead(schedule.pattern.minute));
563
567
  return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(schedule, opts);
564
568
  }
565
569
  function renderEveryHour(schedule, plan, opts) {
@@ -864,7 +868,7 @@ function renderCompactClockTimes(schedule, plan, opts) {
864
868
  );
865
869
  }
866
870
  const cadence = unevenHourCadence(schedule, opts);
867
- const phrase = cadence ? minutesList(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts) : minutesList(schedule, opts) + ", " + hourContextTimes(schedule, opts) + trailingQualifier(schedule, opts);
871
+ const phrase = cadence ? withoutHourAnchor(minutesList(schedule, opts)) + ", " + cadence + trailingQualifier(schedule, opts) : minutesList(schedule, opts) + ", " + hourContextTimes(schedule, opts) + trailingQualifier(schedule, opts);
868
872
  return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase : phrase;
869
873
  }
870
874
  var renderers = {
package/dist/lang/pt.js CHANGED
@@ -279,6 +279,7 @@ function normalizeOptions(options) {
279
279
  // `{ampm}` option overrides it.
280
280
  ampm: typeof options.ampm === "boolean" ? options.ampm : style.ampm,
281
281
  lenient: !!options.lenient,
282
+ quartz: !!options.quartz,
282
283
  seconds: !!options.seconds,
283
284
  short: !!options.short,
284
285
  style,
@@ -426,6 +427,9 @@ function minutesList(schedule, opts) {
426
427
  opts
427
428
  ) ?? "nos minutos " + joinList(segmentWords(segmentsOf(schedule, "minute"))) + " de cada hora";
428
429
  }
430
+ function withoutHourAnchor(lead) {
431
+ return lead.replace(/ de cada hora$/, "");
432
+ }
429
433
  function minuteRangeLead(minuteField) {
430
434
  const bounds = minuteField.split("-");
431
435
  return "a cada minuto do " + bounds[0] + " ao " + bounds[1];
@@ -496,7 +500,7 @@ function renderMinuteFrequency(schedule, plan, opts) {
496
500
  } else if (plan.hours.kind === "window") {
497
501
  phrase += " " + hourWindow(plan.hours, opts);
498
502
  } else if (plan.hours.kind === "step") {
499
- phrase += ", " + stepHourSpan(stepSegment(schedule, "hour"), opts);
503
+ phrase = withoutHourAnchor(phrase) + ", " + stepHourSpan(stepSegment(schedule, "hour"), opts);
500
504
  }
501
505
  return phrase + trailingQualifier(schedule, opts);
502
506
  }
@@ -533,7 +537,7 @@ function renderMinuteSpanAcrossHourStep(schedule, plan, opts) {
533
537
  if (plan.form === "wildcard") {
534
538
  return "a cada minuto, " + stepHourSpan(segment, opts) + trailingQualifier(schedule, opts);
535
539
  }
536
- const lead = plan.form === "list" ? minutesList(schedule, opts) : minuteRangeLead(schedule.pattern.minute);
540
+ const lead = withoutHourAnchor(plan.form === "list" ? minutesList(schedule, opts) : minuteRangeLead(schedule.pattern.minute));
537
541
  return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(schedule, opts);
538
542
  }
539
543
  function renderEveryHour(schedule, plan, opts) {
@@ -838,7 +842,7 @@ function renderCompactClockTimes(schedule, plan, opts) {
838
842
  );
839
843
  }
840
844
  const cadence = unevenHourCadence(schedule, opts);
841
- const phrase = cadence ? minutesList(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts) : minutesList(schedule, opts) + ", " + hourContextTimes(schedule, opts) + trailingQualifier(schedule, opts);
845
+ const phrase = cadence ? withoutHourAnchor(minutesList(schedule, opts)) + ", " + cadence + trailingQualifier(schedule, opts) : minutesList(schedule, opts) + ", " + hourContextTimes(schedule, opts) + trailingQualifier(schedule, opts);
842
846
  return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase : phrase;
843
847
  }
844
848
  var renderers = {
package/dist/lang/zh.cjs CHANGED
@@ -346,6 +346,9 @@ function minuteHourClause(schedule) {
346
346
  function renderMinutePast(schedule) {
347
347
  return minuteHourClause(schedule);
348
348
  }
349
+ function withoutHourAnchor(clause) {
350
+ return clause.replace(/^每小时/, "");
351
+ }
349
352
  function hourSegmentWords(segment) {
350
353
  if (segment.kind === "range") {
351
354
  return [hourWord(+segment.bounds[0]) + "\u81F3" + hourWord(+segment.bounds[1])];
@@ -373,7 +376,8 @@ function renderMinuteFrequency(schedule, plan) {
373
376
  if (hours.kind === "step" || hours.kind === "during") {
374
377
  const hourCad = unevenHourCadence(schedule);
375
378
  if (hourCad !== null) {
376
- return hourCad + (hourCad.indexOf("\u81F3") === -1 ? "" : "\uFF0C") + base;
379
+ const minuteBase = hours.kind === "step" ? withoutHourAnchor(base) : base;
380
+ return hourCad + (hourCad.indexOf("\u81F3") === -1 ? "" : "\uFF0C") + minuteBase;
377
381
  }
378
382
  }
379
383
  if (hours.kind === "window" && hours.from === hours.to) {
@@ -406,9 +410,9 @@ function renderMinuteSpanAcrossHourStep(schedule, plan) {
406
410
  const hourStep = stepSegment(schedule, "hour");
407
411
  const { form } = plan;
408
412
  if (form === "list") {
409
- return hourCadencePhrase(schedule) + "\uFF0C" + renderMinutePast(schedule);
413
+ return hourCadencePhrase(schedule) + "\uFF0C" + withoutHourAnchor(renderMinutePast(schedule));
410
414
  }
411
- const minuteTail = form === "wildcard" ? "\u6BCF\u5206\u949F" : minuteHourClause(schedule) + "\uFF0C\u6BCF\u5206\u949F";
415
+ const minuteTail = form === "wildcard" ? "\u6BCF\u5206\u949F" : withoutHourAnchor(minuteHourClause(schedule)) + "\uFF0C\u6BCF\u5206\u949F";
412
416
  if (hourStep.startToken !== "*") {
413
417
  return hourCadencePhrase(schedule) + "\uFF0C" + minuteTail;
414
418
  }
@@ -436,7 +440,7 @@ function renderCompactClockTimes(schedule, plan) {
436
440
  const tail = secs.length && schedule.pattern.second !== "0" ? "\uFF0C\u7B2C" + valueText(secs) + "\u79D2" : "";
437
441
  if (!compact.fold) {
438
442
  const hourCad = unevenHourCadence(schedule);
439
- return hourCad === null ? minuteHourClause(schedule) + "\uFF0C\u5728" + hourList(schedule) + tail : hourCad + "\uFF0C" + minuteHourClause(schedule) + tail;
443
+ return hourCad === null ? minuteHourClause(schedule) + "\uFF0C\u5728" + hourList(schedule) + tail : hourCad + "\uFF0C" + withoutHourAnchor(minuteHourClause(schedule)) + tail;
440
444
  }
441
445
  if (compact.minute > 0) {
442
446
  return minuteHourClause(schedule) + "\uFF0C\u5728" + hourList(schedule) + tail;
@@ -599,6 +603,10 @@ function composeSecondsOnHour(schedule, plan, opts) {
599
603
  if (composedClock && schedule.pattern.minute === "0") {
600
604
  return composeMinuteZeroClocks(schedule, sec);
601
605
  }
606
+ const fusedSingleMinute = composeSingleMinuteClocks(schedule, rest, sec, opts);
607
+ if (fusedSingleMinute !== null) {
608
+ return fusedSingleMinute;
609
+ }
602
610
  const restText = render(schedule, rest, opts);
603
611
  const secTail = clockRestCarriesSecond(rest) ? "" : sec;
604
612
  if (composedClock && isDaily(schedule)) {
@@ -609,6 +617,13 @@ function composeSecondsOnHour(schedule, plan, opts) {
609
617
  }
610
618
  return restText + secTail;
611
619
  }
620
+ function composeSingleMinuteClocks(schedule, rest, sec, opts) {
621
+ if (rest.kind !== "clockTimes" || schedule.shapes.minute !== "single" || clockRestCarriesSecond(rest)) {
622
+ return null;
623
+ }
624
+ const core = render(schedule, rest, opts) + minuteZeroSecondTail(schedule, sec);
625
+ return isDaily(schedule) ? "\u6BCF\u5929" + core : core;
626
+ }
612
627
  function composeMinuteZeroClocks(schedule, sec) {
613
628
  if (hasHourWindow(schedule)) {
614
629
  return isDaily(schedule) ? "\u6BCF\u5929" + hourRangeWindow(schedule, sec) : hourRangeWindow(schedule, sec);
@@ -616,11 +631,16 @@ function composeMinuteZeroClocks(schedule, sec) {
616
631
  const clocks = hourFires(schedule).map(function clock(hour) {
617
632
  return hour === 12 ? "\u6B63\u5348" : hourWord(hour) + "0\u5206";
618
633
  });
619
- const nested = strideFromSegments(segmentsOf(schedule, "second"), "\u79D2", "\u79D2", "");
620
- const tail = sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + (nested ?? sec);
621
- const core = joinAnd(clocks) + tail;
634
+ const core = joinAnd(clocks) + minuteZeroSecondTail(schedule, sec);
622
635
  return isDaily(schedule) ? "\u6BCF\u5929" + core : core;
623
636
  }
637
+ function minuteZeroSecondTail(schedule, sec) {
638
+ if (sec === "\u6BCF\u79D2") {
639
+ return "\u7684\u6BCF\u4E00\u79D2";
640
+ }
641
+ const nested = strideFromSegments(segmentsOf(schedule, "second"), "\u79D2", "\u79D2", "");
642
+ return "\u7684" + (nested ?? sec);
643
+ }
624
644
  function hasHourWindow(schedule) {
625
645
  return segmentsOf(schedule, "hour").some(function range(segment) {
626
646
  return segment.kind === "range";
@@ -667,7 +687,7 @@ function composeSecondsListed(schedule) {
667
687
  }
668
688
  const hourCad = unevenHourCadence(schedule);
669
689
  if (hourCad !== null) {
670
- return hourCad + "\uFF0C" + minutes + "\uFF0C" + sec;
690
+ return hourCad + "\uFF0C" + withoutHourAnchor(minutes) + "\uFF0C" + sec;
671
691
  }
672
692
  return hourFrame(schedule) + minutes + "\uFF0C" + sec;
673
693
  }
@@ -945,6 +965,7 @@ function normalizeOptions(options) {
945
965
  return {
946
966
  ampm: typeof options.ampm === "boolean" ? options.ampm : false,
947
967
  lenient: !!options.lenient,
968
+ quartz: !!options.quartz,
948
969
  seconds: !!options.seconds,
949
970
  short: !!options.short,
950
971
  style,
package/dist/lang/zh.js CHANGED
@@ -320,6 +320,9 @@ function minuteHourClause(schedule) {
320
320
  function renderMinutePast(schedule) {
321
321
  return minuteHourClause(schedule);
322
322
  }
323
+ function withoutHourAnchor(clause) {
324
+ return clause.replace(/^每小时/, "");
325
+ }
323
326
  function hourSegmentWords(segment) {
324
327
  if (segment.kind === "range") {
325
328
  return [hourWord(+segment.bounds[0]) + "\u81F3" + hourWord(+segment.bounds[1])];
@@ -347,7 +350,8 @@ function renderMinuteFrequency(schedule, plan) {
347
350
  if (hours.kind === "step" || hours.kind === "during") {
348
351
  const hourCad = unevenHourCadence(schedule);
349
352
  if (hourCad !== null) {
350
- return hourCad + (hourCad.indexOf("\u81F3") === -1 ? "" : "\uFF0C") + base;
353
+ const minuteBase = hours.kind === "step" ? withoutHourAnchor(base) : base;
354
+ return hourCad + (hourCad.indexOf("\u81F3") === -1 ? "" : "\uFF0C") + minuteBase;
351
355
  }
352
356
  }
353
357
  if (hours.kind === "window" && hours.from === hours.to) {
@@ -380,9 +384,9 @@ function renderMinuteSpanAcrossHourStep(schedule, plan) {
380
384
  const hourStep = stepSegment(schedule, "hour");
381
385
  const { form } = plan;
382
386
  if (form === "list") {
383
- return hourCadencePhrase(schedule) + "\uFF0C" + renderMinutePast(schedule);
387
+ return hourCadencePhrase(schedule) + "\uFF0C" + withoutHourAnchor(renderMinutePast(schedule));
384
388
  }
385
- const minuteTail = form === "wildcard" ? "\u6BCF\u5206\u949F" : minuteHourClause(schedule) + "\uFF0C\u6BCF\u5206\u949F";
389
+ const minuteTail = form === "wildcard" ? "\u6BCF\u5206\u949F" : withoutHourAnchor(minuteHourClause(schedule)) + "\uFF0C\u6BCF\u5206\u949F";
386
390
  if (hourStep.startToken !== "*") {
387
391
  return hourCadencePhrase(schedule) + "\uFF0C" + minuteTail;
388
392
  }
@@ -410,7 +414,7 @@ function renderCompactClockTimes(schedule, plan) {
410
414
  const tail = secs.length && schedule.pattern.second !== "0" ? "\uFF0C\u7B2C" + valueText(secs) + "\u79D2" : "";
411
415
  if (!compact.fold) {
412
416
  const hourCad = unevenHourCadence(schedule);
413
- return hourCad === null ? minuteHourClause(schedule) + "\uFF0C\u5728" + hourList(schedule) + tail : hourCad + "\uFF0C" + minuteHourClause(schedule) + tail;
417
+ return hourCad === null ? minuteHourClause(schedule) + "\uFF0C\u5728" + hourList(schedule) + tail : hourCad + "\uFF0C" + withoutHourAnchor(minuteHourClause(schedule)) + tail;
414
418
  }
415
419
  if (compact.minute > 0) {
416
420
  return minuteHourClause(schedule) + "\uFF0C\u5728" + hourList(schedule) + tail;
@@ -573,6 +577,10 @@ function composeSecondsOnHour(schedule, plan, opts) {
573
577
  if (composedClock && schedule.pattern.minute === "0") {
574
578
  return composeMinuteZeroClocks(schedule, sec);
575
579
  }
580
+ const fusedSingleMinute = composeSingleMinuteClocks(schedule, rest, sec, opts);
581
+ if (fusedSingleMinute !== null) {
582
+ return fusedSingleMinute;
583
+ }
576
584
  const restText = render(schedule, rest, opts);
577
585
  const secTail = clockRestCarriesSecond(rest) ? "" : sec;
578
586
  if (composedClock && isDaily(schedule)) {
@@ -583,6 +591,13 @@ function composeSecondsOnHour(schedule, plan, opts) {
583
591
  }
584
592
  return restText + secTail;
585
593
  }
594
+ function composeSingleMinuteClocks(schedule, rest, sec, opts) {
595
+ if (rest.kind !== "clockTimes" || schedule.shapes.minute !== "single" || clockRestCarriesSecond(rest)) {
596
+ return null;
597
+ }
598
+ const core = render(schedule, rest, opts) + minuteZeroSecondTail(schedule, sec);
599
+ return isDaily(schedule) ? "\u6BCF\u5929" + core : core;
600
+ }
586
601
  function composeMinuteZeroClocks(schedule, sec) {
587
602
  if (hasHourWindow(schedule)) {
588
603
  return isDaily(schedule) ? "\u6BCF\u5929" + hourRangeWindow(schedule, sec) : hourRangeWindow(schedule, sec);
@@ -590,11 +605,16 @@ function composeMinuteZeroClocks(schedule, sec) {
590
605
  const clocks = hourFires(schedule).map(function clock(hour) {
591
606
  return hour === 12 ? "\u6B63\u5348" : hourWord(hour) + "0\u5206";
592
607
  });
593
- const nested = strideFromSegments(segmentsOf(schedule, "second"), "\u79D2", "\u79D2", "");
594
- const tail = sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + (nested ?? sec);
595
- const core = joinAnd(clocks) + tail;
608
+ const core = joinAnd(clocks) + minuteZeroSecondTail(schedule, sec);
596
609
  return isDaily(schedule) ? "\u6BCF\u5929" + core : core;
597
610
  }
611
+ function minuteZeroSecondTail(schedule, sec) {
612
+ if (sec === "\u6BCF\u79D2") {
613
+ return "\u7684\u6BCF\u4E00\u79D2";
614
+ }
615
+ const nested = strideFromSegments(segmentsOf(schedule, "second"), "\u79D2", "\u79D2", "");
616
+ return "\u7684" + (nested ?? sec);
617
+ }
598
618
  function hasHourWindow(schedule) {
599
619
  return segmentsOf(schedule, "hour").some(function range(segment) {
600
620
  return segment.kind === "range";
@@ -641,7 +661,7 @@ function composeSecondsListed(schedule) {
641
661
  }
642
662
  const hourCad = unevenHourCadence(schedule);
643
663
  if (hourCad !== null) {
644
- return hourCad + "\uFF0C" + minutes + "\uFF0C" + sec;
664
+ return hourCad + "\uFF0C" + withoutHourAnchor(minutes) + "\uFF0C" + sec;
645
665
  }
646
666
  return hourFrame(schedule) + minutes + "\uFF0C" + sec;
647
667
  }
@@ -919,6 +939,7 @@ function normalizeOptions(options) {
919
939
  return {
920
940
  ampm: typeof options.ampm === "boolean" ? options.ampm : false,
921
941
  lenient: !!options.lenient,
942
+ quartz: !!options.quartz,
922
943
  seconds: !!options.seconds,
923
944
  short: !!options.short,
924
945
  style,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cronli5",
3
- "version": "0.7.2",
3
+ "version": "0.8.2",
4
4
  "description": "Cron Like I'm Five: A Cron to English Utility",
5
5
  "repository": {
6
6
  "type": "git",
package/src/core/index.ts CHANGED
@@ -5,16 +5,20 @@
5
5
 
6
6
  import {applyQuartzAliases, normalizeCronPattern} from './normalize.js';
7
7
  import type {NormalizedOptions, Pattern} from './schedule.js';
8
+ import {applyQuartz} from './quartz.js';
8
9
  import {parseCronPattern} from './parse.js';
9
10
  import type {CronPattern} from '../types.js';
10
11
  import {validateCronPattern} from './validate.js';
11
12
 
12
- // Parse, alias, validate, and normalize cron input into a canonical
13
- // cron-like object of string fields, ready for semantic analysis and
14
- // rendering.
13
+ // Parse, apply Quartz semantics, alias, validate, and normalize cron input
14
+ // into a canonical cron-like object of string fields, ready for semantic
15
+ // analysis and rendering. Quartz handling runs first: it gates the `?` token
16
+ // (rejected unless `quartz`) and re-indexes the Quartz day-of-week to the
17
+ // canonical cron numbering the rest of the core expects.
15
18
  function prepare(cronPattern: CronPattern, opts: NormalizedOptions): Pattern {
16
19
  const pattern = parseCronPattern(cronPattern, opts);
17
20
 
21
+ applyQuartz(pattern, opts.quartz);
18
22
  applyQuartzAliases(pattern);
19
23
  validateCronPattern(pattern);
20
24
 
@@ -0,0 +1,97 @@
1
+ // Quartz input semantics. cronli5 accepts Quartz tokens (`?`, `L`, `W`, `#`),
2
+ // but Quartz numbers the day-of-week differently from standard cron: Quartz is
3
+ // 1 = Sunday … 7 = Saturday, while cron is 0/7 = Sunday, 1 = Monday. Reading a
4
+ // Quartz pattern with cron indexing silently shifts every weekday by one, so a
5
+ // Quartz `2` (Monday) would read as Tuesday. This module gates and re-indexes
6
+ // the input so the rest of the core keeps facing canonical cron values.
7
+ //
8
+ // The `?` token (Quartz's "no specific value", mandatory in Quartz, absent in
9
+ // standard cron) is the unambiguous mark of a Quartz pattern. Outside Quartz
10
+ // mode it is rejected outright rather than aliased to `*`, so a real Quartz
11
+ // cron errors loudly instead of being mis-read; inside Quartz mode it is the
12
+ // equivalent of `*`.
13
+
14
+ import type {CronLike} from './specs.js';
15
+ import {isNonNegativeInteger} from './util.js';
16
+
17
+ // The error a `?` raises outside Quartz mode: a clear pointer at the option
18
+ // that makes Quartz semantics (and `?`) available.
19
+ const quartzTokenMessage =
20
+ '`?` is a Quartz token — pass { quartz: true } to enable Quartz semantics.';
21
+
22
+ // In standard (non-Quartz) mode, `?` is not a valid value: reject it with a
23
+ // pointer at the `quartz` option. In Quartz mode, accept `?` as `*` and
24
+ // re-index the day-of-week from Quartz numbering (1 = Sunday) to the canonical
25
+ // cron numbering (0 = Sunday) the rest of the core expects. Operates in place
26
+ // on the raw cron-like object, before aliasing and validation.
27
+ function applyQuartz(cronPattern: CronLike, quartz: boolean): void {
28
+ if (!quartz) {
29
+ rejectQuartzToken(cronPattern.date);
30
+ rejectQuartzToken(cronPattern.weekday);
31
+
32
+ return;
33
+ }
34
+
35
+ if ('' + cronPattern.date === '?') {
36
+ cronPattern.date = '*';
37
+ }
38
+
39
+ if ('' + cronPattern.weekday === '?') {
40
+ cronPattern.weekday = '*';
41
+
42
+ return;
43
+ }
44
+
45
+ cronPattern.weekday = reindexWeekday('' + cronPattern.weekday);
46
+ }
47
+
48
+ // Throw the Quartz-token error if a field is exactly `?`.
49
+ function rejectQuartzToken(value: string | number): void {
50
+ if ('' + value === '?') {
51
+ throw new Error(quartzTokenMessage);
52
+ }
53
+ }
54
+
55
+ // Re-index a Quartz day-of-week field to canonical cron numbering. Every
56
+ // numeric weekday position maps n -> n-1 (Quartz 1 = Sunday becomes cron 0),
57
+ // across singles, ranges, and step bounds, and inside the DOW operators `nL`
58
+ // (last weekday) and `n#k` (kth weekday). Day NAMES (`MON`) are unambiguous and
59
+ // left untouched, as is the bare `L` alias (Saturday in both numberings) and
60
+ // `*`. Quartz has no weekday 0; it is rejected here.
61
+ function reindexWeekday(value: string): string {
62
+ if (value === '*') {
63
+ return value;
64
+ }
65
+
66
+ return value.split(',').map(reindexSegment).join(',');
67
+ }
68
+
69
+ // Re-index one comma-separated weekday segment: a single, a range, or either
70
+ // of those followed by a `#k` or `L` operator (or a `/step`).
71
+ function reindexSegment(segment: string): string {
72
+ const operator = (/(#\d+|L)$/).exec(segment);
73
+ const suffix = operator ? operator[0] : '';
74
+ const core = suffix ? segment.slice(0, -suffix.length) : segment;
75
+ const step = core.split('/');
76
+ const range = step[0].split('-').map(reindexNumber).join('-');
77
+ const head = step.length === 2 ? range + '/' + step[1] : range;
78
+
79
+ return head + suffix;
80
+ }
81
+
82
+ // Re-index a single weekday token: a number maps n -> n-1 (rejecting 0, which
83
+ // Quartz does not use); a name passes through unchanged.
84
+ function reindexNumber(token: string): string {
85
+ if (!isNonNegativeInteger(token)) {
86
+ return token;
87
+ }
88
+
89
+ if (token === '0') {
90
+ throw new Error('`cronli5` was passed an invalid Quartz day-of-week ' +
91
+ 'value "0"; Quartz numbers weekdays 1 (Sunday) through 7 (Saturday).');
92
+ }
93
+
94
+ return '' + (+token - 1);
95
+ }
96
+
97
+ export {applyQuartz, quartzTokenMessage};
@@ -147,6 +147,7 @@ export interface DialectStyle {
147
147
  export interface NormalizedOptions<Style = DialectStyle> {
148
148
  ampm: boolean;
149
149
  lenient: boolean;
150
+ quartz: boolean;
150
151
  seconds: boolean;
151
152
  short: boolean;
152
153
  style: Style;
package/src/core/specs.ts CHANGED
@@ -55,9 +55,9 @@ const fieldSpecs: Record<Field, FieldSpec> = {
55
55
  second: {cyclic: true, max: 59, min: 0, top: 59},
56
56
  minute: {cyclic: true, max: 59, min: 0, top: 59},
57
57
  hour: {cyclic: true, max: 23, min: 0, top: 23},
58
- date: {aliases: {'?': '*'}, cyclic: true, max: 31, min: 1, top: 31},
58
+ date: {cyclic: true, max: 31, min: 1, top: 31},
59
59
  month: {cyclic: true, max: 12, min: 1, numbers: monthNumbers, top: 12},
60
- weekday: {aliases: {'?': '*', L: '6'}, cyclic: true, max: 7, min: 0,
60
+ weekday: {aliases: {L: '6'}, cyclic: true, max: 7, min: 0,
61
61
  numbers: weekdayNumbers, top: 6},
62
62
  year: {max: 9999, min: 1970}
63
63
  };
package/src/cronli5.ts CHANGED
@@ -29,7 +29,7 @@
29
29
 
30
30
  import {analyze, prepare} from './core/index.js';
31
31
  import type {NormalizedOptions} from './core/schedule.js';
32
- import type {CronPattern, Cronli5Language, Cronli5Options}
32
+ import type {Cronli5, CronPattern, Cronli5Language, Cronli5Options}
33
33
  from './types.js';
34
34
  import en from './lang/en/index.js';
35
35
 
@@ -88,8 +88,25 @@ function interpretCronPattern(
88
88
  return lang.describe({...schedule, plan}, opts);
89
89
  }
90
90
 
91
- export default cronli5;
91
+ // Two named convenience methods are attached to the callable export as sugar
92
+ // over the `sentence` option: `.sentence(...)` forces the capitalized
93
+ // standalone, `.fragment(...)` forces the embeddable fragment (the default).
94
+ // The method's own intent wins, so a passed-through `sentence` flag is
95
+ // overridden. There is no `toString` method on purpose — it would shadow
96
+ // `Function.prototype.toString` (called arg-less by `String()`, template
97
+ // literals, and console/debug) and break coercion; named methods sidestep that.
98
+ function sentence(cronPattern: CronPattern, options?: Cronli5Options): string {
99
+ return cronli5(cronPattern, {...options, sentence: true});
100
+ }
101
+
102
+ function fragment(cronPattern: CronPattern, options?: Cronli5Options): string {
103
+ return cronli5(cronPattern, {...options, sentence: false});
104
+ }
105
+
106
+ const callable: Cronli5 = Object.assign(cronli5, {sentence, fragment});
107
+
108
+ export default callable;
92
109
  export type {
93
- Cronli5Dialect, Cronli5Language, Cronli5Options, CronPattern,
110
+ Cronli5, Cronli5Dialect, Cronli5Language, Cronli5Options, CronPattern,
94
111
  CronPatternObject
95
112
  } from './types.js';