cronli5 0.1.4 → 0.1.6

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/en.cjs CHANGED
@@ -24,6 +24,26 @@ __export(index_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
26
 
27
+ // src/core/util.ts
28
+ function arithmeticStep(values) {
29
+ if (values.length < 5) {
30
+ return null;
31
+ }
32
+ const interval = values[1] - values[0];
33
+ if (interval < 2) {
34
+ return null;
35
+ }
36
+ for (let i = 2; i < values.length; i += 1) {
37
+ if (values[i] - values[i - 1] !== interval) {
38
+ return null;
39
+ }
40
+ }
41
+ return { start: values[0], interval, last: values[values.length - 1] };
42
+ }
43
+
44
+ // src/core/specs.ts
45
+ var maxClockTimes = 6;
46
+
27
47
  // src/core/format.ts
28
48
  function pad(n) {
29
49
  n = "" + n;
@@ -179,18 +199,34 @@ function renderSecondsWithinMinute(ir, plan, opts) {
179
199
  }
180
200
  return secondsLeadClause(ir, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(ir, opts);
181
201
  }
202
+ function composeHourCadence(ir, plan, opts) {
203
+ const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
204
+ if (!clockRest || ir.shapes.minute !== "single") {
205
+ return null;
206
+ }
207
+ const minute = +ir.pattern.minute;
208
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
209
+ }
210
+ function clockTimesConfinement(ir, rest, opts) {
211
+ if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
212
+ return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, rest, opts);
213
+ }
214
+ return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, rest, opts);
215
+ }
182
216
  function renderComposeSeconds(ir, plan, opts) {
217
+ const cadence = composeHourCadence(ir, plan, opts);
218
+ if (cadence !== null) {
219
+ return cadence;
220
+ }
183
221
  if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
184
- const minute = plan.rest.times[0].minute;
185
- if (+minute === 0) {
186
- return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, plan.rest, opts);
187
- }
188
- return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, plan.rest, opts);
222
+ return clockTimesConfinement(ir, plan.rest, opts);
189
223
  }
190
224
  if (ir.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && ir.pattern.minute === "*/2") {
191
225
  return "every second of every other minute" + trailingQualifier(ir, opts);
192
226
  }
193
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
227
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
228
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
229
+ return lead + render(ir, plan.rest, opts);
194
230
  }
195
231
  function durationHours(ir, plan, opts) {
196
232
  const hours = plan.times.map(function clock(time) {
@@ -212,6 +248,9 @@ function clockTimesOf(ir, plan, opts) {
212
248
  return joinList(times, opts) + (trail && ", " + trail);
213
249
  }
214
250
  function secondsLeadClause(ir, opts) {
251
+ return secondsClause(ir, "minute", opts);
252
+ }
253
+ function secondsClause(ir, anchor, opts) {
215
254
  const secondField = ir.pattern.second;
216
255
  const shape = ir.shapes.second;
217
256
  if (secondField === "*") {
@@ -221,22 +260,27 @@ function secondsLeadClause(ir, opts) {
221
260
  return stepCycle60(
222
261
  ir.analyses.segments.second[0],
223
262
  "second",
224
- "minute",
263
+ anchor,
225
264
  opts
226
265
  );
227
266
  }
228
267
  if (shape === "range") {
229
268
  const bounds = secondField.split("-");
230
269
  const num = seriesNumber(bounds, opts);
231
- return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the minute";
270
+ return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
232
271
  }
233
272
  if (shape === "single") {
234
- return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute";
273
+ return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
235
274
  }
236
- return listPastThe(
275
+ return strideFromSegments(
276
+ ir.analyses.segments.second,
277
+ "second",
278
+ anchor,
279
+ opts
280
+ ) ?? listPastThe(
237
281
  segmentWords(ir.analyses.segments.second, opts),
238
282
  "second",
239
- "minute",
283
+ anchor,
240
284
  opts
241
285
  );
242
286
  }
@@ -251,12 +295,11 @@ function renderRangeOfMinutes(ir, plan, opts) {
251
295
  return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
252
296
  }
253
297
  function renderMultipleMinutes(ir, plan, opts) {
254
- return listPastThe(
255
- segmentWords(ir.analyses.segments.minute, opts),
256
- "minute",
257
- "hour",
298
+ const stride = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts);
299
+ return (stride ?? listPastThe(segmentWords(
300
+ ir.analyses.segments.minute,
258
301
  opts
259
- ) + trailingQualifier(ir, opts);
302
+ ), "minute", "hour", opts)) + trailingQualifier(ir, opts);
260
303
  }
261
304
  function renderMinuteFrequency(ir, plan, opts) {
262
305
  let phrase = stepCycle60(
@@ -266,7 +309,8 @@ function renderMinuteFrequency(ir, plan, opts) {
266
309
  opts
267
310
  );
268
311
  if (plan.hours.kind === "during") {
269
- phrase += " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
312
+ const cadence = unevenHourCadence(ir, opts);
313
+ phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
270
314
  } else if (plan.hours.kind === "window") {
271
315
  phrase += " " + hourWindow(plan.hours, opts);
272
316
  } else if (plan.hours.kind === "step") {
@@ -281,19 +325,27 @@ function renderMinuteSpanInHour(ir, plan, opts) {
281
325
  return "every minute from " + getTime({ hour: plan.hour, minute: plan.span[0] }, opts) + through(opts) + getTime({ hour: plan.hour, minute: plan.span[1] }, opts) + trailingQualifier(ir, opts);
282
326
  }
283
327
  function renderMinutesAcrossHours(ir, plan, opts) {
328
+ const cadence = unevenHourCadence(ir, opts);
284
329
  if (plan.form === "wildcard") {
330
+ if (cadence !== null) {
331
+ return "every minute, " + cadence + trailingQualifier(ir, opts);
332
+ }
285
333
  return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
286
334
  }
287
- const times = hourTimesFromPlan(ir, plan.times, true, opts);
288
335
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
289
- // The 'list' form is a minute list, which has segments.
290
- listPastThe(
336
+ // The 'list' form is a minute list, which has segments; an offset/uneven
337
+ // step enumerated to that list reads as a stride.
338
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
291
339
  segmentWords(ir.analyses.segments.minute, opts),
292
340
  "minute",
293
341
  "hour",
294
342
  opts
295
343
  )
296
344
  );
345
+ if (cadence !== null) {
346
+ return lead + ", " + cadence + trailingQualifier(ir, opts);
347
+ }
348
+ const times = hourTimesFromPlan(ir, plan.times, true, opts);
297
349
  return lead + ", at " + times + trailingQualifier(ir, opts);
298
350
  }
299
351
  var stepOrdinals = {
@@ -314,7 +366,14 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
314
366
  if (plan.form === "wildcard") {
315
367
  return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
316
368
  }
317
- return minuteRangeLead(ir.pattern.minute, opts) + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
369
+ const lead = plan.form === "list" ? strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
370
+ segmentWords(ir.analyses.segments.minute, opts),
371
+ "minute",
372
+ "hour",
373
+ opts
374
+ ) : minuteRangeLead(ir.pattern.minute, opts);
375
+ const cadence = unevenHourCadence(ir, opts);
376
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
318
377
  }
319
378
  function minuteRangeLead(minuteField, opts) {
320
379
  const bounds = minuteField.split("-");
@@ -338,7 +397,12 @@ function rangeMinuteLead(ir, opts) {
338
397
  if (ir.pattern.minute === "0") {
339
398
  return "every hour";
340
399
  }
341
- return listPastThe(
400
+ return strideFromSegments(
401
+ ir.analyses.segments.minute,
402
+ "minute",
403
+ "hour",
404
+ opts
405
+ ) ?? listPastThe(
342
406
  segmentWords(ir.analyses.segments.minute, opts),
343
407
  "minute",
344
408
  "hour",
@@ -346,6 +410,10 @@ function rangeMinuteLead(ir, opts) {
346
410
  );
347
411
  }
348
412
  function renderHourStep(ir, plan, opts) {
413
+ const cadence = unevenHourCadence(ir, opts);
414
+ if (cadence !== null) {
415
+ return cadence + trailingQualifier(ir, opts);
416
+ }
349
417
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
350
418
  }
351
419
  function boundedWindow(plan) {
@@ -355,6 +423,13 @@ function hourWindow(window, opts) {
355
423
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
356
424
  }
357
425
  function renderClockTimes(ir, plan, opts) {
426
+ if (ir.shapes.minute === "single") {
427
+ const minute = +ir.pattern.minute;
428
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
429
+ if (cadence !== null) {
430
+ return cadence;
431
+ }
432
+ }
358
433
  const plain = mixedTwelve(plan.times);
359
434
  const times = plan.times.map(function clock(time) {
360
435
  return getTime({
@@ -368,6 +443,10 @@ function renderClockTimes(ir, plan, opts) {
368
443
  }
369
444
  function renderCompactClockTimes(ir, plan, opts) {
370
445
  if (plan.fold) {
446
+ const cadence2 = hourCadence(ir, +plan.minute, opts) ?? hourRangeCadence(ir, +plan.minute, opts);
447
+ if (cadence2 !== null) {
448
+ return cadence2;
449
+ }
371
450
  const hasRange = ir.analyses.segments.hour.some(function range(segment) {
372
451
  return segment.kind === "range";
373
452
  });
@@ -377,15 +456,18 @@ function renderCompactClockTimes(ir, plan, opts) {
377
456
  const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
378
457
  return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
379
458
  }
380
- const phrase = (
381
- // The non-fold branch is a minute list, which has segments.
382
- listPastThe(
459
+ const minuteLead = (
460
+ // The non-fold branch is a minute list, which has segments. An
461
+ // offset/uneven step enumerated to that list reads as a stride.
462
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
383
463
  segmentWords(ir.analyses.segments.minute, opts),
384
464
  "minute",
385
465
  "hour",
386
466
  opts
387
- ) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
467
+ )
388
468
  );
469
+ const cadence = unevenHourCadence(ir, opts);
470
+ const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(ir, opts) : minuteLead + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts);
389
471
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
390
472
  }
391
473
  function foldedHourWindows(ir, plan, opts) {
@@ -432,24 +514,50 @@ var renderers = {
432
514
  singleMinute: renderSingleMinute,
433
515
  standaloneSeconds: renderStandaloneSeconds
434
516
  };
517
+ function renderStride(stride, opts) {
518
+ const { interval, start, last, cycle, unit, anchor } = stride;
519
+ const cadence = "every " + getNumber(interval, opts) + " " + unit + "s";
520
+ const tiles = cycle % interval === 0;
521
+ if (start === 0 && tiles) {
522
+ return cadence;
523
+ }
524
+ if (start < interval && tiles) {
525
+ return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
526
+ }
527
+ const num = seriesNumber([start, last], opts);
528
+ return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
529
+ }
530
+ function singleValues(segments) {
531
+ const values = [];
532
+ for (const segment of segments) {
533
+ if (segment.kind !== "single") {
534
+ return null;
535
+ }
536
+ values.push(+segment.value);
537
+ }
538
+ return values;
539
+ }
540
+ function strideFromSegments(segments, unit, anchor, opts) {
541
+ const values = singleValues(segments);
542
+ const step = values && arithmeticStep(values);
543
+ return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
544
+ }
435
545
  function stepCycle60(segment, unit, anchor, opts) {
436
546
  if (segment.startToken.indexOf("-") !== -1) {
437
547
  return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
438
548
  }
439
549
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
440
- const interval = segment.interval;
441
- if (start !== 0) {
442
- if (segment.fires.length <= 3) {
443
- return listPastThe(
444
- numberWords(segment.fires, opts),
445
- unit,
446
- anchor,
447
- opts
448
- );
449
- }
450
- return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
550
+ if (start !== 0 && segment.fires.length <= 3) {
551
+ return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
451
552
  }
452
- return "every " + getNumber(interval, opts) + " " + unit + "s";
553
+ return renderStride({
554
+ interval: segment.interval,
555
+ start,
556
+ last: segment.fires[segment.fires.length - 1],
557
+ cycle: 60,
558
+ unit,
559
+ anchor
560
+ }, opts);
453
561
  }
454
562
  function stepHours(segment, opts) {
455
563
  if (segment.startToken.indexOf("-") !== -1) {
@@ -465,6 +573,141 @@ function stepHours(segment, opts) {
465
573
  }
466
574
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
467
575
  }
576
+ function hourStrideCadence(stride, opts) {
577
+ const { start, interval, last } = stride;
578
+ const cadence = "every " + getNumber(interval, opts) + " hours";
579
+ const tiles = 24 % interval === 0;
580
+ if (start === 0 && tiles) {
581
+ return cadence;
582
+ }
583
+ if (start < interval && tiles) {
584
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts);
585
+ }
586
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
587
+ }
588
+ function offsetCleanStride(stride) {
589
+ return stride.start < stride.interval && 24 % stride.interval === 0;
590
+ }
591
+ function unevenHourCadence(ir, opts) {
592
+ const stride = hourStride(ir);
593
+ if (!stride || offsetCleanStride(stride)) {
594
+ return null;
595
+ }
596
+ return hourStrideCadence(stride, opts);
597
+ }
598
+ function hourListStride(values) {
599
+ if (values.length < 2) {
600
+ return null;
601
+ }
602
+ const interval = values[1] - values[0];
603
+ if (interval < 2) {
604
+ return null;
605
+ }
606
+ for (let i = 2; i < values.length; i += 1) {
607
+ if (values[i] - values[i - 1] !== interval) {
608
+ return null;
609
+ }
610
+ }
611
+ if (values[0] !== 0 && values.length < 5) {
612
+ return null;
613
+ }
614
+ return { interval, last: values[values.length - 1], start: values[0] };
615
+ }
616
+ function hourStride(ir) {
617
+ const segments = ir.analyses.segments.hour;
618
+ if (segments.length === 1 && segments[0].kind === "step") {
619
+ const segment = segments[0];
620
+ if (segment.fires.length < 2) {
621
+ return null;
622
+ }
623
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
624
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
625
+ }
626
+ const values = singleValues(segments);
627
+ return values && hourListStride(values);
628
+ }
629
+ function subMinuteSecond(ir) {
630
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
631
+ }
632
+ function hourCadenceLead(ir, minute, opts) {
633
+ if (minute === 0) {
634
+ if (subMinuteSecond(ir)) {
635
+ return secondsClause(ir, "minute", opts) + " for one minute";
636
+ }
637
+ return secondsClause(ir, "hour", opts);
638
+ }
639
+ const minutePhrase = getNumber(minute, opts) + " " + pluralize(minute, "minute") + " past the hour";
640
+ if (ir.pattern.second === "0") {
641
+ return minutePhrase;
642
+ }
643
+ return secondsClause(ir, "minute", opts) + ", " + minutePhrase;
644
+ }
645
+ function hourCadence(ir, minute, opts) {
646
+ const stride = hourStride(ir);
647
+ if (!stride) {
648
+ return null;
649
+ }
650
+ const fires = (stride.last - stride.start) / stride.interval + 1;
651
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
652
+ return null;
653
+ }
654
+ const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
655
+ if (confinement) {
656
+ return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
657
+ }
658
+ if (minute === 0 && ir.pattern.second === "0") {
659
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
660
+ }
661
+ return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
662
+ }
663
+ function cleanStrideSegment(ir) {
664
+ const segments = ir.analyses.segments.hour;
665
+ const segment = segments.length === 1 && segments[0];
666
+ if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
667
+ return null;
668
+ }
669
+ return segment;
670
+ }
671
+ function hasHourWindow(ir) {
672
+ return ir.analyses.segments.hour.some(function range(segment) {
673
+ return segment.kind === "range";
674
+ });
675
+ }
676
+ function hourRangeWindowTail(ir, opts) {
677
+ const windows = [];
678
+ const singles = [];
679
+ ir.analyses.segments.hour.forEach(function classify(segment) {
680
+ if (segment.kind === "range") {
681
+ windows.push("from " + getTime(
682
+ { hour: +segment.bounds[0], minute: 0 },
683
+ opts
684
+ ) + through(opts) + getTime({ hour: +segment.bounds[1], minute: 0 }, opts));
685
+ } else if (segment.kind === "step") {
686
+ singles.push(...segment.fires);
687
+ } else {
688
+ singles.push(+segment.value);
689
+ }
690
+ });
691
+ let phrase = "every hour " + joinList(windows, opts);
692
+ if (singles.length) {
693
+ phrase += " and at " + joinList(singles.map(function time(hour) {
694
+ return getTime({ hour, minute: 0 }, opts);
695
+ }), opts);
696
+ }
697
+ return phrase;
698
+ }
699
+ function hourRangeCadence(ir, minute, opts) {
700
+ if (minute !== 0 || !hasHourWindow(ir)) {
701
+ return null;
702
+ }
703
+ if (ir.pattern.second === "0") {
704
+ return null;
705
+ }
706
+ if (subMinuteSecond(ir)) {
707
+ return secondsClause(ir, "minute", opts) + " for one minute during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(ir, opts);
708
+ }
709
+ return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
710
+ }
468
711
  function seriesNumber(values, opts) {
469
712
  const anyBig = values.some(function big(v) {
470
713
  return +v > 10;