cronli5 0.1.5 → 0.1.7

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/cronli5.js CHANGED
@@ -84,6 +84,26 @@ function arithmeticStep(values) {
84
84
  }
85
85
  return { start: values[0], interval, last: values[values.length - 1] };
86
86
  }
87
+ function weekdayDisplayKey(value) {
88
+ return value === 0 ? 7 : value;
89
+ }
90
+ function orderWeekdaysForDisplay(segments) {
91
+ const flattened = segments.flatMap(function flat(segment) {
92
+ return segment.kind === "step" ? segment.fires.map(function single(value) {
93
+ return { kind: "single", value: "" + value };
94
+ }) : [segment];
95
+ });
96
+ function key(segment) {
97
+ return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
98
+ }
99
+ return flattened.map(function index(segment, position) {
100
+ return [segment, position];
101
+ }).sort(function byDisplayKey(a, b) {
102
+ return key(a[0]) - key(b[0]) || a[1] - b[1];
103
+ }).map(function unwrap(pair) {
104
+ return pair[0];
105
+ });
106
+ }
87
107
  function toFieldNumber(token, numberMap) {
88
108
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
89
109
  }
@@ -946,7 +966,17 @@ function renderSecondsWithinMinute(ir, plan, opts) {
946
966
  }
947
967
  function composeHourCadence(ir, plan, opts) {
948
968
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
949
- return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
969
+ if (!clockRest || ir.shapes.minute !== "single") {
970
+ return null;
971
+ }
972
+ const minute = +ir.pattern.minute;
973
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
974
+ }
975
+ function clockTimesConfinement(ir, rest, opts) {
976
+ if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
977
+ return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, rest, opts);
978
+ }
979
+ return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, rest, opts);
950
980
  }
951
981
  function renderComposeSeconds(ir, plan, opts) {
952
982
  const cadence = composeHourCadence(ir, plan, opts);
@@ -954,16 +984,14 @@ function renderComposeSeconds(ir, plan, opts) {
954
984
  return cadence;
955
985
  }
956
986
  if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
957
- const minute = plan.rest.times[0].minute;
958
- if (+minute === 0) {
959
- return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, plan.rest, opts);
960
- }
961
- return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, plan.rest, opts);
987
+ return clockTimesConfinement(ir, plan.rest, opts);
962
988
  }
963
989
  if (ir.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && ir.pattern.minute === "*/2") {
964
990
  return "every second of every other minute" + trailingQualifier(ir, opts);
965
991
  }
966
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
992
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
993
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
994
+ return lead + render(ir, plan.rest, opts);
967
995
  }
968
996
  function durationHours(ir, plan, opts) {
969
997
  const hours = plan.times.map(function clock(time) {
@@ -1046,7 +1074,8 @@ function renderMinuteFrequency(ir, plan, opts) {
1046
1074
  opts
1047
1075
  );
1048
1076
  if (plan.hours.kind === "during") {
1049
- phrase += " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
1077
+ const cadence = unevenHourCadence(ir, opts);
1078
+ phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
1050
1079
  } else if (plan.hours.kind === "window") {
1051
1080
  phrase += " " + hourWindow(plan.hours, opts);
1052
1081
  } else if (plan.hours.kind === "step") {
@@ -1061,10 +1090,13 @@ function renderMinuteSpanInHour(ir, plan, opts) {
1061
1090
  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);
1062
1091
  }
1063
1092
  function renderMinutesAcrossHours(ir, plan, opts) {
1093
+ const cadence = unevenHourCadence(ir, opts);
1064
1094
  if (plan.form === "wildcard") {
1095
+ if (cadence !== null) {
1096
+ return "every minute, " + cadence + trailingQualifier(ir, opts);
1097
+ }
1065
1098
  return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
1066
1099
  }
1067
- const times = hourTimesFromPlan(ir, plan.times, true, opts);
1068
1100
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
1069
1101
  // The 'list' form is a minute list, which has segments; an offset/uneven
1070
1102
  // step enumerated to that list reads as a stride.
@@ -1075,6 +1107,10 @@ function renderMinutesAcrossHours(ir, plan, opts) {
1075
1107
  opts
1076
1108
  )
1077
1109
  );
1110
+ if (cadence !== null) {
1111
+ return lead + ", " + cadence + trailingQualifier(ir, opts);
1112
+ }
1113
+ const times = hourTimesFromPlan(ir, plan.times, true, opts);
1078
1114
  return lead + ", at " + times + trailingQualifier(ir, opts);
1079
1115
  }
1080
1116
  var stepOrdinals = {
@@ -1101,7 +1137,8 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
1101
1137
  "hour",
1102
1138
  opts
1103
1139
  ) : minuteRangeLead(ir.pattern.minute, opts);
1104
- return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
1140
+ const cadence = unevenHourCadence(ir, opts);
1141
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
1105
1142
  }
1106
1143
  function minuteRangeLead(minuteField, opts) {
1107
1144
  const bounds = minuteField.split("-");
@@ -1138,17 +1175,23 @@ function rangeMinuteLead(ir, opts) {
1138
1175
  );
1139
1176
  }
1140
1177
  function renderHourStep(ir, plan, opts) {
1178
+ const cadence = unevenHourCadence(ir, opts);
1179
+ if (cadence !== null) {
1180
+ return cadence + trailingQualifier(ir, opts);
1181
+ }
1141
1182
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
1142
1183
  }
1143
1184
  function boundedWindow(plan) {
1144
- return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
1185
+ const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
1186
+ return { from: plan.from, last, to: plan.to };
1145
1187
  }
1146
1188
  function hourWindow(window, opts) {
1147
1189
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
1148
1190
  }
1149
1191
  function renderClockTimes(ir, plan, opts) {
1150
1192
  if (ir.shapes.minute === "single") {
1151
- const cadence = hourCadence(ir, +ir.pattern.minute, opts);
1193
+ const minute = +ir.pattern.minute;
1194
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
1152
1195
  if (cadence !== null) {
1153
1196
  return cadence;
1154
1197
  }
@@ -1166,9 +1209,9 @@ function renderClockTimes(ir, plan, opts) {
1166
1209
  }
1167
1210
  function renderCompactClockTimes(ir, plan, opts) {
1168
1211
  if (plan.fold) {
1169
- const cadence = hourCadence(ir, +plan.minute, opts);
1170
- if (cadence !== null) {
1171
- return cadence;
1212
+ const cadence2 = hourCadence(ir, +plan.minute, opts) ?? hourRangeCadence(ir, +plan.minute, opts);
1213
+ if (cadence2 !== null) {
1214
+ return cadence2;
1172
1215
  }
1173
1216
  const hasRange = ir.analyses.segments.hour.some(function range(segment) {
1174
1217
  return segment.kind === "range";
@@ -1179,16 +1222,18 @@ function renderCompactClockTimes(ir, plan, opts) {
1179
1222
  const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
1180
1223
  return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
1181
1224
  }
1182
- const phrase = (
1225
+ const minuteLead = (
1183
1226
  // The non-fold branch is a minute list, which has segments. An
1184
1227
  // offset/uneven step enumerated to that list reads as a stride.
1185
- (strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1228
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1186
1229
  segmentWords(ir.analyses.segments.minute, opts),
1187
1230
  "minute",
1188
1231
  "hour",
1189
1232
  opts
1190
- )) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
1233
+ )
1191
1234
  );
1235
+ const cadence = unevenHourCadence(ir, opts);
1236
+ const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(ir, opts) : minuteLead + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts);
1192
1237
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
1193
1238
  }
1194
1239
  function foldedHourWindows(ir, plan, opts) {
@@ -1306,16 +1351,46 @@ function hourStrideCadence(stride, opts) {
1306
1351
  }
1307
1352
  return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
1308
1353
  }
1354
+ function offsetCleanStride(stride) {
1355
+ return stride.start < stride.interval && 24 % stride.interval === 0;
1356
+ }
1357
+ function unevenHourCadence(ir, opts) {
1358
+ const stride = hourStride(ir);
1359
+ if (!stride || offsetCleanStride(stride)) {
1360
+ return null;
1361
+ }
1362
+ return hourStrideCadence(stride, opts);
1363
+ }
1364
+ function hourListStride(values) {
1365
+ if (values.length < 2) {
1366
+ return null;
1367
+ }
1368
+ const interval = values[1] - values[0];
1369
+ if (interval < 2) {
1370
+ return null;
1371
+ }
1372
+ for (let i = 2; i < values.length; i += 1) {
1373
+ if (values[i] - values[i - 1] !== interval) {
1374
+ return null;
1375
+ }
1376
+ }
1377
+ if (values[0] !== 0 && values.length < 5) {
1378
+ return null;
1379
+ }
1380
+ return { interval, last: values[values.length - 1], start: values[0] };
1381
+ }
1309
1382
  function hourStride(ir) {
1310
1383
  const segments = ir.analyses.segments.hour;
1311
1384
  if (segments.length === 1 && segments[0].kind === "step") {
1312
1385
  const segment = segments[0];
1386
+ if (segment.fires.length < 2) {
1387
+ return null;
1388
+ }
1313
1389
  const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
1314
1390
  return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
1315
1391
  }
1316
1392
  const values = singleValues(segments);
1317
- const step = values && arithmeticStep(values);
1318
- return step || null;
1393
+ return values && hourListStride(values);
1319
1394
  }
1320
1395
  function subMinuteSecond(ir) {
1321
1396
  return ir.pattern.second === "*" || ir.shapes.second === "step";
@@ -1339,13 +1414,16 @@ function hourCadence(ir, minute, opts) {
1339
1414
  return null;
1340
1415
  }
1341
1416
  const fires = (stride.last - stride.start) / stride.interval + 1;
1342
- if (ir.pattern.second === "0" && fires <= maxClockTimes) {
1417
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
1343
1418
  return null;
1344
1419
  }
1345
1420
  const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
1346
1421
  if (confinement) {
1347
1422
  return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
1348
1423
  }
1424
+ if (minute === 0 && ir.pattern.second === "0") {
1425
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
1426
+ }
1349
1427
  return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
1350
1428
  }
1351
1429
  function cleanStrideSegment(ir) {
@@ -1356,6 +1434,46 @@ function cleanStrideSegment(ir) {
1356
1434
  }
1357
1435
  return segment;
1358
1436
  }
1437
+ function hasHourWindow(ir) {
1438
+ return ir.analyses.segments.hour.some(function range(segment) {
1439
+ return segment.kind === "range";
1440
+ });
1441
+ }
1442
+ function hourRangeWindowTail(ir, opts) {
1443
+ const windows = [];
1444
+ const singles = [];
1445
+ ir.analyses.segments.hour.forEach(function classify(segment) {
1446
+ if (segment.kind === "range") {
1447
+ windows.push("from " + getTime(
1448
+ { hour: +segment.bounds[0], minute: 0 },
1449
+ opts
1450
+ ) + through(opts) + getTime({ hour: +segment.bounds[1], minute: 0 }, opts));
1451
+ } else if (segment.kind === "step") {
1452
+ singles.push(...segment.fires);
1453
+ } else {
1454
+ singles.push(+segment.value);
1455
+ }
1456
+ });
1457
+ let phrase = "every hour " + joinList(windows, opts);
1458
+ if (singles.length) {
1459
+ phrase += " and at " + joinList(singles.map(function time(hour) {
1460
+ return getTime({ hour, minute: 0 }, opts);
1461
+ }), opts);
1462
+ }
1463
+ return phrase;
1464
+ }
1465
+ function hourRangeCadence(ir, minute, opts) {
1466
+ if (minute !== 0 || !hasHourWindow(ir)) {
1467
+ return null;
1468
+ }
1469
+ if (ir.pattern.second === "0") {
1470
+ return null;
1471
+ }
1472
+ if (subMinuteSecond(ir)) {
1473
+ return secondsClause(ir, "minute", opts) + " for one minute during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(ir, opts);
1474
+ }
1475
+ return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
1476
+ }
1359
1477
  function seriesNumber(values, opts) {
1360
1478
  const anyBig = values.some(function big(v) {
1361
1479
  return +v > 10;
@@ -1520,17 +1638,27 @@ function monthFoldsIntoDate(ir) {
1520
1638
  function dateOrWeekday(ir, opts) {
1521
1639
  const pattern = ir.pattern;
1522
1640
  const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, opts);
1641
+ if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
1642
+ return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
1643
+ }
1644
+ return datePart(ir, opts) + " or " + weekdayPart + orMonthScope(ir, opts);
1645
+ }
1646
+ function datePart(ir, opts) {
1647
+ const pattern = ir.pattern;
1523
1648
  const quartzDate = quartzDatePhrase(pattern.date, opts);
1524
1649
  if (quartzDate) {
1525
- return quartzDate + monthScope(ir, opts) + " or " + weekdayPart;
1650
+ return quartzDate;
1526
1651
  }
1527
1652
  if (isOpenStep(pattern.date)) {
1528
- return stepDates(pattern.date) + monthScope(ir, opts) + " or " + weekdayPart;
1653
+ return stepDates(pattern.date);
1529
1654
  }
1530
- if (pattern.month !== "*" && monthFoldsIntoDate(ir)) {
1531
- return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
1655
+ return "on the " + dateOrdinals(ir, opts);
1656
+ }
1657
+ function orMonthScope(ir, opts) {
1658
+ if (ir.pattern.month === "*") {
1659
+ return "";
1532
1660
  }
1533
- return "on the " + dateOrdinals(ir, opts) + " or " + weekdayPart + monthScope(ir, opts);
1661
+ return ", in " + monthName(ir, opts);
1534
1662
  }
1535
1663
  function quartzDatePhrase(dateField, opts) {
1536
1664
  if (dateField === "L") {
@@ -1612,7 +1740,8 @@ function oddEvenMonth(monthField) {
1612
1740
  return start === "2" ? "every even-numbered month" : null;
1613
1741
  }
1614
1742
  function weekdayPhrase(ir, opts) {
1615
- return renderSegments(ir.analyses.segments.weekday, function name(value) {
1743
+ const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday);
1744
+ return renderSegments(segments, function name(value) {
1616
1745
  return getWeekday(value, opts);
1617
1746
  }, opts);
1618
1747
  }