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.js CHANGED
@@ -1,3 +1,23 @@
1
+ // src/core/util.ts
2
+ function arithmeticStep(values) {
3
+ if (values.length < 5) {
4
+ return null;
5
+ }
6
+ const interval = values[1] - values[0];
7
+ if (interval < 2) {
8
+ return null;
9
+ }
10
+ for (let i = 2; i < values.length; i += 1) {
11
+ if (values[i] - values[i - 1] !== interval) {
12
+ return null;
13
+ }
14
+ }
15
+ return { start: values[0], interval, last: values[values.length - 1] };
16
+ }
17
+
18
+ // src/core/specs.ts
19
+ var maxClockTimes = 6;
20
+
1
21
  // src/core/format.ts
2
22
  function pad(n) {
3
23
  n = "" + n;
@@ -153,18 +173,34 @@ function renderSecondsWithinMinute(ir, plan, opts) {
153
173
  }
154
174
  return secondsLeadClause(ir, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(ir, opts);
155
175
  }
176
+ function composeHourCadence(ir, plan, opts) {
177
+ const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
178
+ if (!clockRest || ir.shapes.minute !== "single") {
179
+ return null;
180
+ }
181
+ const minute = +ir.pattern.minute;
182
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
183
+ }
184
+ function clockTimesConfinement(ir, rest, opts) {
185
+ if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
186
+ return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, rest, opts);
187
+ }
188
+ return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, rest, opts);
189
+ }
156
190
  function renderComposeSeconds(ir, plan, opts) {
191
+ const cadence = composeHourCadence(ir, plan, opts);
192
+ if (cadence !== null) {
193
+ return cadence;
194
+ }
157
195
  if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
158
- const minute = plan.rest.times[0].minute;
159
- if (+minute === 0) {
160
- return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, plan.rest, opts);
161
- }
162
- return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, plan.rest, opts);
196
+ return clockTimesConfinement(ir, plan.rest, opts);
163
197
  }
164
198
  if (ir.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && ir.pattern.minute === "*/2") {
165
199
  return "every second of every other minute" + trailingQualifier(ir, opts);
166
200
  }
167
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
201
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
202
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
203
+ return lead + render(ir, plan.rest, opts);
168
204
  }
169
205
  function durationHours(ir, plan, opts) {
170
206
  const hours = plan.times.map(function clock(time) {
@@ -186,6 +222,9 @@ function clockTimesOf(ir, plan, opts) {
186
222
  return joinList(times, opts) + (trail && ", " + trail);
187
223
  }
188
224
  function secondsLeadClause(ir, opts) {
225
+ return secondsClause(ir, "minute", opts);
226
+ }
227
+ function secondsClause(ir, anchor, opts) {
189
228
  const secondField = ir.pattern.second;
190
229
  const shape = ir.shapes.second;
191
230
  if (secondField === "*") {
@@ -195,22 +234,27 @@ function secondsLeadClause(ir, opts) {
195
234
  return stepCycle60(
196
235
  ir.analyses.segments.second[0],
197
236
  "second",
198
- "minute",
237
+ anchor,
199
238
  opts
200
239
  );
201
240
  }
202
241
  if (shape === "range") {
203
242
  const bounds = secondField.split("-");
204
243
  const num = seriesNumber(bounds, opts);
205
- return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the minute";
244
+ return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
206
245
  }
207
246
  if (shape === "single") {
208
- return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute";
247
+ return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
209
248
  }
210
- return listPastThe(
249
+ return strideFromSegments(
250
+ ir.analyses.segments.second,
251
+ "second",
252
+ anchor,
253
+ opts
254
+ ) ?? listPastThe(
211
255
  segmentWords(ir.analyses.segments.second, opts),
212
256
  "second",
213
- "minute",
257
+ anchor,
214
258
  opts
215
259
  );
216
260
  }
@@ -225,12 +269,11 @@ function renderRangeOfMinutes(ir, plan, opts) {
225
269
  return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
226
270
  }
227
271
  function renderMultipleMinutes(ir, plan, opts) {
228
- return listPastThe(
229
- segmentWords(ir.analyses.segments.minute, opts),
230
- "minute",
231
- "hour",
272
+ const stride = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts);
273
+ return (stride ?? listPastThe(segmentWords(
274
+ ir.analyses.segments.minute,
232
275
  opts
233
- ) + trailingQualifier(ir, opts);
276
+ ), "minute", "hour", opts)) + trailingQualifier(ir, opts);
234
277
  }
235
278
  function renderMinuteFrequency(ir, plan, opts) {
236
279
  let phrase = stepCycle60(
@@ -240,7 +283,8 @@ function renderMinuteFrequency(ir, plan, opts) {
240
283
  opts
241
284
  );
242
285
  if (plan.hours.kind === "during") {
243
- phrase += " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
286
+ const cadence = unevenHourCadence(ir, opts);
287
+ phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
244
288
  } else if (plan.hours.kind === "window") {
245
289
  phrase += " " + hourWindow(plan.hours, opts);
246
290
  } else if (plan.hours.kind === "step") {
@@ -255,19 +299,27 @@ function renderMinuteSpanInHour(ir, plan, opts) {
255
299
  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);
256
300
  }
257
301
  function renderMinutesAcrossHours(ir, plan, opts) {
302
+ const cadence = unevenHourCadence(ir, opts);
258
303
  if (plan.form === "wildcard") {
304
+ if (cadence !== null) {
305
+ return "every minute, " + cadence + trailingQualifier(ir, opts);
306
+ }
259
307
  return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
260
308
  }
261
- const times = hourTimesFromPlan(ir, plan.times, true, opts);
262
309
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
263
- // The 'list' form is a minute list, which has segments.
264
- listPastThe(
310
+ // The 'list' form is a minute list, which has segments; an offset/uneven
311
+ // step enumerated to that list reads as a stride.
312
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
265
313
  segmentWords(ir.analyses.segments.minute, opts),
266
314
  "minute",
267
315
  "hour",
268
316
  opts
269
317
  )
270
318
  );
319
+ if (cadence !== null) {
320
+ return lead + ", " + cadence + trailingQualifier(ir, opts);
321
+ }
322
+ const times = hourTimesFromPlan(ir, plan.times, true, opts);
271
323
  return lead + ", at " + times + trailingQualifier(ir, opts);
272
324
  }
273
325
  var stepOrdinals = {
@@ -288,7 +340,14 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
288
340
  if (plan.form === "wildcard") {
289
341
  return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
290
342
  }
291
- return minuteRangeLead(ir.pattern.minute, opts) + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
343
+ const lead = plan.form === "list" ? strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
344
+ segmentWords(ir.analyses.segments.minute, opts),
345
+ "minute",
346
+ "hour",
347
+ opts
348
+ ) : minuteRangeLead(ir.pattern.minute, opts);
349
+ const cadence = unevenHourCadence(ir, opts);
350
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
292
351
  }
293
352
  function minuteRangeLead(minuteField, opts) {
294
353
  const bounds = minuteField.split("-");
@@ -312,7 +371,12 @@ function rangeMinuteLead(ir, opts) {
312
371
  if (ir.pattern.minute === "0") {
313
372
  return "every hour";
314
373
  }
315
- return listPastThe(
374
+ return strideFromSegments(
375
+ ir.analyses.segments.minute,
376
+ "minute",
377
+ "hour",
378
+ opts
379
+ ) ?? listPastThe(
316
380
  segmentWords(ir.analyses.segments.minute, opts),
317
381
  "minute",
318
382
  "hour",
@@ -320,6 +384,10 @@ function rangeMinuteLead(ir, opts) {
320
384
  );
321
385
  }
322
386
  function renderHourStep(ir, plan, opts) {
387
+ const cadence = unevenHourCadence(ir, opts);
388
+ if (cadence !== null) {
389
+ return cadence + trailingQualifier(ir, opts);
390
+ }
323
391
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
324
392
  }
325
393
  function boundedWindow(plan) {
@@ -329,6 +397,13 @@ function hourWindow(window, opts) {
329
397
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
330
398
  }
331
399
  function renderClockTimes(ir, plan, opts) {
400
+ if (ir.shapes.minute === "single") {
401
+ const minute = +ir.pattern.minute;
402
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
403
+ if (cadence !== null) {
404
+ return cadence;
405
+ }
406
+ }
332
407
  const plain = mixedTwelve(plan.times);
333
408
  const times = plan.times.map(function clock(time) {
334
409
  return getTime({
@@ -342,6 +417,10 @@ function renderClockTimes(ir, plan, opts) {
342
417
  }
343
418
  function renderCompactClockTimes(ir, plan, opts) {
344
419
  if (plan.fold) {
420
+ const cadence2 = hourCadence(ir, +plan.minute, opts) ?? hourRangeCadence(ir, +plan.minute, opts);
421
+ if (cadence2 !== null) {
422
+ return cadence2;
423
+ }
345
424
  const hasRange = ir.analyses.segments.hour.some(function range(segment) {
346
425
  return segment.kind === "range";
347
426
  });
@@ -351,15 +430,18 @@ function renderCompactClockTimes(ir, plan, opts) {
351
430
  const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
352
431
  return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
353
432
  }
354
- const phrase = (
355
- // The non-fold branch is a minute list, which has segments.
356
- listPastThe(
433
+ const minuteLead = (
434
+ // The non-fold branch is a minute list, which has segments. An
435
+ // offset/uneven step enumerated to that list reads as a stride.
436
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
357
437
  segmentWords(ir.analyses.segments.minute, opts),
358
438
  "minute",
359
439
  "hour",
360
440
  opts
361
- ) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
441
+ )
362
442
  );
443
+ const cadence = unevenHourCadence(ir, opts);
444
+ const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(ir, opts) : minuteLead + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts);
363
445
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
364
446
  }
365
447
  function foldedHourWindows(ir, plan, opts) {
@@ -406,24 +488,50 @@ var renderers = {
406
488
  singleMinute: renderSingleMinute,
407
489
  standaloneSeconds: renderStandaloneSeconds
408
490
  };
491
+ function renderStride(stride, opts) {
492
+ const { interval, start, last, cycle, unit, anchor } = stride;
493
+ const cadence = "every " + getNumber(interval, opts) + " " + unit + "s";
494
+ const tiles = cycle % interval === 0;
495
+ if (start === 0 && tiles) {
496
+ return cadence;
497
+ }
498
+ if (start < interval && tiles) {
499
+ return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
500
+ }
501
+ const num = seriesNumber([start, last], opts);
502
+ return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
503
+ }
504
+ function singleValues(segments) {
505
+ const values = [];
506
+ for (const segment of segments) {
507
+ if (segment.kind !== "single") {
508
+ return null;
509
+ }
510
+ values.push(+segment.value);
511
+ }
512
+ return values;
513
+ }
514
+ function strideFromSegments(segments, unit, anchor, opts) {
515
+ const values = singleValues(segments);
516
+ const step = values && arithmeticStep(values);
517
+ return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
518
+ }
409
519
  function stepCycle60(segment, unit, anchor, opts) {
410
520
  if (segment.startToken.indexOf("-") !== -1) {
411
521
  return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
412
522
  }
413
523
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
414
- const interval = segment.interval;
415
- if (start !== 0) {
416
- if (segment.fires.length <= 3) {
417
- return listPastThe(
418
- numberWords(segment.fires, opts),
419
- unit,
420
- anchor,
421
- opts
422
- );
423
- }
424
- return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
524
+ if (start !== 0 && segment.fires.length <= 3) {
525
+ return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
425
526
  }
426
- return "every " + getNumber(interval, opts) + " " + unit + "s";
527
+ return renderStride({
528
+ interval: segment.interval,
529
+ start,
530
+ last: segment.fires[segment.fires.length - 1],
531
+ cycle: 60,
532
+ unit,
533
+ anchor
534
+ }, opts);
427
535
  }
428
536
  function stepHours(segment, opts) {
429
537
  if (segment.startToken.indexOf("-") !== -1) {
@@ -439,6 +547,141 @@ function stepHours(segment, opts) {
439
547
  }
440
548
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
441
549
  }
550
+ function hourStrideCadence(stride, opts) {
551
+ const { start, interval, last } = stride;
552
+ const cadence = "every " + getNumber(interval, opts) + " hours";
553
+ const tiles = 24 % interval === 0;
554
+ if (start === 0 && tiles) {
555
+ return cadence;
556
+ }
557
+ if (start < interval && tiles) {
558
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts);
559
+ }
560
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
561
+ }
562
+ function offsetCleanStride(stride) {
563
+ return stride.start < stride.interval && 24 % stride.interval === 0;
564
+ }
565
+ function unevenHourCadence(ir, opts) {
566
+ const stride = hourStride(ir);
567
+ if (!stride || offsetCleanStride(stride)) {
568
+ return null;
569
+ }
570
+ return hourStrideCadence(stride, opts);
571
+ }
572
+ function hourListStride(values) {
573
+ if (values.length < 2) {
574
+ return null;
575
+ }
576
+ const interval = values[1] - values[0];
577
+ if (interval < 2) {
578
+ return null;
579
+ }
580
+ for (let i = 2; i < values.length; i += 1) {
581
+ if (values[i] - values[i - 1] !== interval) {
582
+ return null;
583
+ }
584
+ }
585
+ if (values[0] !== 0 && values.length < 5) {
586
+ return null;
587
+ }
588
+ return { interval, last: values[values.length - 1], start: values[0] };
589
+ }
590
+ function hourStride(ir) {
591
+ const segments = ir.analyses.segments.hour;
592
+ if (segments.length === 1 && segments[0].kind === "step") {
593
+ const segment = segments[0];
594
+ if (segment.fires.length < 2) {
595
+ return null;
596
+ }
597
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
598
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
599
+ }
600
+ const values = singleValues(segments);
601
+ return values && hourListStride(values);
602
+ }
603
+ function subMinuteSecond(ir) {
604
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
605
+ }
606
+ function hourCadenceLead(ir, minute, opts) {
607
+ if (minute === 0) {
608
+ if (subMinuteSecond(ir)) {
609
+ return secondsClause(ir, "minute", opts) + " for one minute";
610
+ }
611
+ return secondsClause(ir, "hour", opts);
612
+ }
613
+ const minutePhrase = getNumber(minute, opts) + " " + pluralize(minute, "minute") + " past the hour";
614
+ if (ir.pattern.second === "0") {
615
+ return minutePhrase;
616
+ }
617
+ return secondsClause(ir, "minute", opts) + ", " + minutePhrase;
618
+ }
619
+ function hourCadence(ir, minute, opts) {
620
+ const stride = hourStride(ir);
621
+ if (!stride) {
622
+ return null;
623
+ }
624
+ const fires = (stride.last - stride.start) / stride.interval + 1;
625
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
626
+ return null;
627
+ }
628
+ const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
629
+ if (confinement) {
630
+ return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
631
+ }
632
+ if (minute === 0 && ir.pattern.second === "0") {
633
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
634
+ }
635
+ return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
636
+ }
637
+ function cleanStrideSegment(ir) {
638
+ const segments = ir.analyses.segments.hour;
639
+ const segment = segments.length === 1 && segments[0];
640
+ if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
641
+ return null;
642
+ }
643
+ return segment;
644
+ }
645
+ function hasHourWindow(ir) {
646
+ return ir.analyses.segments.hour.some(function range(segment) {
647
+ return segment.kind === "range";
648
+ });
649
+ }
650
+ function hourRangeWindowTail(ir, opts) {
651
+ const windows = [];
652
+ const singles = [];
653
+ ir.analyses.segments.hour.forEach(function classify(segment) {
654
+ if (segment.kind === "range") {
655
+ windows.push("from " + getTime(
656
+ { hour: +segment.bounds[0], minute: 0 },
657
+ opts
658
+ ) + through(opts) + getTime({ hour: +segment.bounds[1], minute: 0 }, opts));
659
+ } else if (segment.kind === "step") {
660
+ singles.push(...segment.fires);
661
+ } else {
662
+ singles.push(+segment.value);
663
+ }
664
+ });
665
+ let phrase = "every hour " + joinList(windows, opts);
666
+ if (singles.length) {
667
+ phrase += " and at " + joinList(singles.map(function time(hour) {
668
+ return getTime({ hour, minute: 0 }, opts);
669
+ }), opts);
670
+ }
671
+ return phrase;
672
+ }
673
+ function hourRangeCadence(ir, minute, opts) {
674
+ if (minute !== 0 || !hasHourWindow(ir)) {
675
+ return null;
676
+ }
677
+ if (ir.pattern.second === "0") {
678
+ return null;
679
+ }
680
+ if (subMinuteSecond(ir)) {
681
+ return secondsClause(ir, "minute", opts) + " for one minute during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(ir, opts);
682
+ }
683
+ return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
684
+ }
442
685
  function seriesNumber(values, opts) {
443
686
  const anyBig = values.some(function big(v) {
444
687
  return +v > 10;