cronli5 0.1.7 → 0.2.0
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/CHANGELOG.md +43 -0
- package/README.md +5 -5
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +363 -75
- package/dist/cronli5.js +363 -75
- package/dist/lang/en.cjs +363 -75
- package/dist/lang/en.js +363 -75
- package/package.json +1 -1
- package/src/core/ir.ts +5 -0
- package/src/lang/en/dialects.ts +6 -2
- package/src/lang/en/index.ts +735 -102
- package/types/core/ir.d.ts +1 -0
package/dist/cronli5.cjs
CHANGED
|
@@ -875,7 +875,8 @@ var dialects = {
|
|
|
875
875
|
pm: "p.m.",
|
|
876
876
|
sep: ":",
|
|
877
877
|
serialComma: true,
|
|
878
|
-
through: " through "
|
|
878
|
+
through: " through ",
|
|
879
|
+
untilWindow: true
|
|
879
880
|
},
|
|
880
881
|
house: {
|
|
881
882
|
am: "AM",
|
|
@@ -892,7 +893,7 @@ var dialects = {
|
|
|
892
893
|
};
|
|
893
894
|
function resolveDialect(dialect) {
|
|
894
895
|
if (typeof dialect === "object" && dialect !== null) {
|
|
895
|
-
return { ...dialects.us, ...dialect };
|
|
896
|
+
return { ...dialects.us, untilWindow: false, ...dialect };
|
|
896
897
|
}
|
|
897
898
|
const name = dialect === "uk" ? "gb" : dialect;
|
|
898
899
|
return dialects[name] || dialects.us;
|
|
@@ -964,7 +965,9 @@ function normalizeOptions(options) {
|
|
|
964
965
|
};
|
|
965
966
|
}
|
|
966
967
|
function describe(ir, opts) {
|
|
967
|
-
|
|
968
|
+
const body = confinement(ir, opts) ?? render(ir, ir.plan, opts);
|
|
969
|
+
const lead = isDayUnion(ir, opts) ? dayUnionMonthLead(ir, opts) : "";
|
|
970
|
+
return applyYear(lead + body, ir, opts);
|
|
968
971
|
}
|
|
969
972
|
function render(ir, plan, opts) {
|
|
970
973
|
const renderer = renderers[plan.kind];
|
|
@@ -1057,7 +1060,7 @@ function secondsClause(ir, anchor, opts) {
|
|
|
1057
1060
|
}
|
|
1058
1061
|
if (shape === "range") {
|
|
1059
1062
|
const bounds = secondField.split("-");
|
|
1060
|
-
const num = seriesNumber(
|
|
1063
|
+
const num = seriesNumber();
|
|
1061
1064
|
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
|
|
1062
1065
|
}
|
|
1063
1066
|
if (shape === "single") {
|
|
@@ -1123,15 +1126,21 @@ function renderMinutesAcrossHours(ir, plan, opts) {
|
|
|
1123
1126
|
}
|
|
1124
1127
|
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1125
1128
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
"
|
|
1133
|
-
|
|
1134
|
-
)
|
|
1129
|
+
if (plan.form === "range") {
|
|
1130
|
+
const lead2 = minuteRangeLead(ir.pattern.minute, opts);
|
|
1131
|
+
if (cadence !== null) {
|
|
1132
|
+
return lead2 + ", " + cadence + trailingQualifier(ir, opts);
|
|
1133
|
+
}
|
|
1134
|
+
if (singleHourFire(plan.times)) {
|
|
1135
|
+
return lead2 + ", at " + hourTimesFromPlan(ir, plan.times, true, opts) + trailingQualifier(ir, opts);
|
|
1136
|
+
}
|
|
1137
|
+
return lead2 + " during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1138
|
+
}
|
|
1139
|
+
const lead = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
|
|
1140
|
+
segmentWords(ir.analyses.segments.minute, opts),
|
|
1141
|
+
"minute",
|
|
1142
|
+
"hour",
|
|
1143
|
+
opts
|
|
1135
1144
|
);
|
|
1136
1145
|
if (cadence !== null) {
|
|
1137
1146
|
return lead + ", " + cadence + trailingQualifier(ir, opts);
|
|
@@ -1168,7 +1177,7 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
|
1168
1177
|
}
|
|
1169
1178
|
function minuteRangeLead(minuteField, opts) {
|
|
1170
1179
|
const bounds = minuteField.split("-");
|
|
1171
|
-
const num = seriesNumber(
|
|
1180
|
+
const num = seriesNumber();
|
|
1172
1181
|
return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
|
|
1173
1182
|
}
|
|
1174
1183
|
function renderEveryHour(ir, plan, opts) {
|
|
@@ -1211,8 +1220,15 @@ function boundedWindow(plan) {
|
|
|
1211
1220
|
const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
|
|
1212
1221
|
return { from: plan.from, last, to: plan.to };
|
|
1213
1222
|
}
|
|
1223
|
+
function rangeWindow(from, to, throughMinute, opts) {
|
|
1224
|
+
const open = "from " + getTime({ hour: from, minute: 0 }, opts);
|
|
1225
|
+
if (opts.style.untilWindow && !opts.short && from !== to) {
|
|
1226
|
+
return open + " until " + getTime({ hour: (to + 1) % 24, minute: 0 }, opts);
|
|
1227
|
+
}
|
|
1228
|
+
return open + through(opts) + getTime({ hour: to, minute: throughMinute }, opts);
|
|
1229
|
+
}
|
|
1214
1230
|
function hourWindow(window, opts) {
|
|
1215
|
-
return
|
|
1231
|
+
return rangeWindow(window.from, window.to, window.last, opts);
|
|
1216
1232
|
}
|
|
1217
1233
|
function renderClockTimes(ir, plan, opts) {
|
|
1218
1234
|
if (ir.shapes.minute === "single") {
|
|
@@ -1231,7 +1247,10 @@ function renderClockTimes(ir, plan, opts) {
|
|
|
1231
1247
|
plain
|
|
1232
1248
|
}, opts);
|
|
1233
1249
|
});
|
|
1234
|
-
return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts);
|
|
1250
|
+
return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts) + dayUnionTrail(ir, opts);
|
|
1251
|
+
}
|
|
1252
|
+
function dayUnionTrail(ir, opts) {
|
|
1253
|
+
return isDayUnion(ir, opts) ? dayUnionCondition(ir, opts) : "";
|
|
1235
1254
|
}
|
|
1236
1255
|
function renderCompactClockTimes(ir, plan, opts) {
|
|
1237
1256
|
if (plan.fold) {
|
|
@@ -1246,7 +1265,7 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1246
1265
|
return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
|
|
1247
1266
|
}
|
|
1248
1267
|
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
1249
|
-
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
1268
|
+
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts) + dayUnionTrail(ir, opts);
|
|
1250
1269
|
}
|
|
1251
1270
|
const minuteLead = (
|
|
1252
1271
|
// The non-fold branch is a minute list, which has segments. An
|
|
@@ -1265,26 +1284,161 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1265
1284
|
function foldedHourWindows(ir, plan, opts) {
|
|
1266
1285
|
const minute = plan.minute;
|
|
1267
1286
|
const windows = [];
|
|
1268
|
-
const
|
|
1287
|
+
const outliers = collectHourOutliers(ir);
|
|
1288
|
+
const times = outliers.hours.map(function time(hour) {
|
|
1289
|
+
return getTime({ hour, minute }, opts);
|
|
1290
|
+
});
|
|
1269
1291
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1270
1292
|
if (segment.kind === "range") {
|
|
1271
|
-
windows.push(
|
|
1272
|
-
|
|
1293
|
+
windows.push(rangeWindow(
|
|
1294
|
+
+segment.bounds[0],
|
|
1295
|
+
+segment.bounds[1],
|
|
1296
|
+
minute,
|
|
1273
1297
|
opts
|
|
1274
|
-
)
|
|
1275
|
-
} else if (segment.kind === "step") {
|
|
1276
|
-
singles.push(...segment.fires);
|
|
1277
|
-
} else {
|
|
1278
|
-
singles.push(+segment.value);
|
|
1298
|
+
));
|
|
1279
1299
|
}
|
|
1280
1300
|
});
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1301
|
+
const phrase = rangeMinuteLead(ir, opts) + " " + joinList(windows, opts);
|
|
1302
|
+
return phrase + outlierTail(times, outliers.pureStrays, opts);
|
|
1303
|
+
}
|
|
1304
|
+
function collectHourOutliers(ir) {
|
|
1305
|
+
const hours = [];
|
|
1306
|
+
let pureStrays = true;
|
|
1307
|
+
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1308
|
+
if (segment.kind === "step") {
|
|
1309
|
+
hours.push(...segment.fires);
|
|
1310
|
+
pureStrays = false;
|
|
1311
|
+
} else if (segment.kind !== "range") {
|
|
1312
|
+
hours.push(+segment.value);
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
return { hours, pureStrays };
|
|
1316
|
+
}
|
|
1317
|
+
function outlierTail(times, pureStrays, opts) {
|
|
1318
|
+
if (!times.length) {
|
|
1319
|
+
return "";
|
|
1286
1320
|
}
|
|
1287
|
-
|
|
1321
|
+
const connector = pureStrays && opts.style.untilWindow && !opts.short ? " plus " : " and at ";
|
|
1322
|
+
return connector + joinList(times, opts);
|
|
1323
|
+
}
|
|
1324
|
+
function isCadenceField(token) {
|
|
1325
|
+
return token === "*" || token.startsWith("*/") && token.indexOf("-") === -1;
|
|
1326
|
+
}
|
|
1327
|
+
function leadingCadence(ir, opts) {
|
|
1328
|
+
const { second, minute } = ir.pattern;
|
|
1329
|
+
if (isCadenceField(second)) {
|
|
1330
|
+
return { secondLead: true, text: secondsClause(ir, "minute", opts) };
|
|
1331
|
+
}
|
|
1332
|
+
if (second === "0" && isCadenceField(minute)) {
|
|
1333
|
+
const text = minute === "*" ? "every minute" : (
|
|
1334
|
+
// A clean minute step's first segment is a step segment.
|
|
1335
|
+
stepCycle60(
|
|
1336
|
+
ir.analyses.segments.minute[0],
|
|
1337
|
+
"minute",
|
|
1338
|
+
"hour",
|
|
1339
|
+
opts
|
|
1340
|
+
)
|
|
1341
|
+
);
|
|
1342
|
+
return { secondLead: false, text };
|
|
1343
|
+
}
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
function minuteConfinement(ir, opts) {
|
|
1347
|
+
const minute = ir.pattern.minute;
|
|
1348
|
+
if (minute === "*") {
|
|
1349
|
+
return "";
|
|
1350
|
+
}
|
|
1351
|
+
if (isCadenceField(minute)) {
|
|
1352
|
+
return " of every other minute";
|
|
1353
|
+
}
|
|
1354
|
+
const segments = ir.analyses.segments.minute;
|
|
1355
|
+
if (ir.shapes.minute === "single") {
|
|
1356
|
+
return " during minute :" + pad(minute);
|
|
1357
|
+
}
|
|
1358
|
+
if (ir.shapes.minute === "range") {
|
|
1359
|
+
const bounds = minute.split("-");
|
|
1360
|
+
return " during minutes :" + pad(bounds[0]) + through(opts) + ":" + pad(bounds[1]);
|
|
1361
|
+
}
|
|
1362
|
+
const values = segmentWords(segments, opts).map(function colon(word) {
|
|
1363
|
+
return ":" + pad(word);
|
|
1364
|
+
});
|
|
1365
|
+
return " during minutes " + joinList(values, opts);
|
|
1366
|
+
}
|
|
1367
|
+
function hourConfinement(ir, opts) {
|
|
1368
|
+
const hour = ir.pattern.hour;
|
|
1369
|
+
if (hour === "*") {
|
|
1370
|
+
const minutePinned = ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute);
|
|
1371
|
+
return minutePinned ? " of every hour" : "";
|
|
1372
|
+
}
|
|
1373
|
+
if (isCadenceField(hour)) {
|
|
1374
|
+
return hour === "*/2" ? " of every other hour" : "";
|
|
1375
|
+
}
|
|
1376
|
+
if (ir.shapes.hour === "single") {
|
|
1377
|
+
const h = +hour;
|
|
1378
|
+
if (ir.shapes.minute === "step") {
|
|
1379
|
+
return " from " + getTime({ hour: h, minute: 0 }, opts) + " until " + getTime({ hour: (h + 1) % 24, minute: 0 }, opts);
|
|
1380
|
+
}
|
|
1381
|
+
if (ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute)) {
|
|
1382
|
+
return " at " + getTime({ hour: h, minute: 0 }, opts);
|
|
1383
|
+
}
|
|
1384
|
+
return " of the " + getTime({ hour: h, minute: 0 }, opts) + " hour";
|
|
1385
|
+
}
|
|
1386
|
+
if (ir.shapes.hour === "range") {
|
|
1387
|
+
const bounds = hour.split("-");
|
|
1388
|
+
return " " + rangeWindow(+bounds[0], +bounds[1], 0, opts);
|
|
1389
|
+
}
|
|
1390
|
+
return " during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours";
|
|
1391
|
+
}
|
|
1392
|
+
function isContiguousHourRange(ir) {
|
|
1393
|
+
return ir.shapes.hour === "range";
|
|
1394
|
+
}
|
|
1395
|
+
function confinableHour(ir) {
|
|
1396
|
+
if (ir.shapes.hour !== "step") {
|
|
1397
|
+
return true;
|
|
1398
|
+
}
|
|
1399
|
+
const segment = ir.analyses.segments.hour[0];
|
|
1400
|
+
return ir.pattern.hour === "*/2" || segment.startToken.indexOf("-") !== -1;
|
|
1401
|
+
}
|
|
1402
|
+
function isMinuteStride(ir) {
|
|
1403
|
+
if (ir.shapes.minute !== "list") {
|
|
1404
|
+
return false;
|
|
1405
|
+
}
|
|
1406
|
+
const values = singleValues(ir.analyses.segments.minute);
|
|
1407
|
+
return values !== null && arithmeticStep(values) !== null;
|
|
1408
|
+
}
|
|
1409
|
+
function confinementEligible(ir, lead) {
|
|
1410
|
+
const { minute, hour } = ir.pattern;
|
|
1411
|
+
const minuteStep = isCadenceField(minute) && minute !== "*";
|
|
1412
|
+
if (!confinableHour(ir)) {
|
|
1413
|
+
return false;
|
|
1414
|
+
}
|
|
1415
|
+
if (lead.secondLead) {
|
|
1416
|
+
if (minuteStep) {
|
|
1417
|
+
return minute === "*/2" && !isContiguousHourRange(ir);
|
|
1418
|
+
}
|
|
1419
|
+
if (isMinuteStride(ir) || ir.shapes.minute === "list" && ir.shapes.hour === "list") {
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
return true;
|
|
1423
|
+
}
|
|
1424
|
+
if (hour === "*/2") {
|
|
1425
|
+
return true;
|
|
1426
|
+
}
|
|
1427
|
+
return ir.shapes.hour === "single" && minute === "*/2";
|
|
1428
|
+
}
|
|
1429
|
+
function confinement(ir, opts) {
|
|
1430
|
+
if (!opts.style.untilWindow || opts.short) {
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
if (ir.pattern.minute === "*" && ir.pattern.hour === "*") {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
const lead = leadingCadence(ir, opts);
|
|
1437
|
+
if (!lead || !confinementEligible(ir, lead)) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
const minutePart = lead.secondLead ? minuteConfinement(ir, opts) : "";
|
|
1441
|
+
return lead.text + minutePart + hourConfinement(ir, opts) + trailingQualifier(ir, opts);
|
|
1288
1442
|
}
|
|
1289
1443
|
var renderers = {
|
|
1290
1444
|
clockTimes: renderClockTimes,
|
|
@@ -1316,7 +1470,7 @@ function renderStride(stride, opts) {
|
|
|
1316
1470
|
if (start < interval && tiles) {
|
|
1317
1471
|
return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
1318
1472
|
}
|
|
1319
|
-
const num = seriesNumber(
|
|
1473
|
+
const num = seriesNumber();
|
|
1320
1474
|
return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
|
|
1321
1475
|
}
|
|
1322
1476
|
function singleValues(segments) {
|
|
@@ -1443,9 +1597,9 @@ function hourCadence(ir, minute, opts) {
|
|
|
1443
1597
|
if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
|
|
1444
1598
|
return null;
|
|
1445
1599
|
}
|
|
1446
|
-
const
|
|
1447
|
-
if (
|
|
1448
|
-
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(
|
|
1600
|
+
const minuteZeroStride = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
|
|
1601
|
+
if (minuteZeroStride) {
|
|
1602
|
+
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(minuteZeroStride, opts) + trailingQualifier(ir, opts);
|
|
1449
1603
|
}
|
|
1450
1604
|
if (minute === 0 && ir.pattern.second === "0") {
|
|
1451
1605
|
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
@@ -1467,26 +1621,22 @@ function hasHourWindow(ir) {
|
|
|
1467
1621
|
}
|
|
1468
1622
|
function hourRangeWindowTail(ir, opts) {
|
|
1469
1623
|
const windows = [];
|
|
1470
|
-
const
|
|
1624
|
+
const outliers = collectHourOutliers(ir);
|
|
1471
1625
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1472
1626
|
if (segment.kind === "range") {
|
|
1473
|
-
windows.push(
|
|
1474
|
-
|
|
1627
|
+
windows.push(rangeWindow(
|
|
1628
|
+
+segment.bounds[0],
|
|
1629
|
+
+segment.bounds[1],
|
|
1630
|
+
0,
|
|
1475
1631
|
opts
|
|
1476
|
-
)
|
|
1477
|
-
} else if (segment.kind === "step") {
|
|
1478
|
-
singles.push(...segment.fires);
|
|
1479
|
-
} else {
|
|
1480
|
-
singles.push(+segment.value);
|
|
1632
|
+
));
|
|
1481
1633
|
}
|
|
1482
1634
|
});
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
}
|
|
1489
|
-
return phrase;
|
|
1635
|
+
const phrase = "every hour " + joinList(windows, opts);
|
|
1636
|
+
const times = outliers.hours.map(function time(hour) {
|
|
1637
|
+
return getTime({ hour, minute: 0 }, opts);
|
|
1638
|
+
});
|
|
1639
|
+
return phrase + outlierTail(times, outliers.pureStrays, opts);
|
|
1490
1640
|
}
|
|
1491
1641
|
function hourRangeCadence(ir, minute, opts) {
|
|
1492
1642
|
if (minute !== 0 || !hasHourWindow(ir)) {
|
|
@@ -1500,25 +1650,29 @@ function hourRangeCadence(ir, minute, opts) {
|
|
|
1500
1650
|
}
|
|
1501
1651
|
return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
|
|
1502
1652
|
}
|
|
1503
|
-
function seriesNumber(
|
|
1504
|
-
const anyBig = values.some(function big(v) {
|
|
1505
|
-
return +v > 10;
|
|
1506
|
-
});
|
|
1653
|
+
function seriesNumber() {
|
|
1507
1654
|
return function format(n) {
|
|
1508
|
-
return
|
|
1655
|
+
return "" + n;
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
function listNumber(count, opts) {
|
|
1659
|
+
return count > 1 ? function asNumeral(n) {
|
|
1660
|
+
return "" + n;
|
|
1661
|
+
} : function spelled(n) {
|
|
1662
|
+
return getNumber(n, opts);
|
|
1509
1663
|
};
|
|
1510
1664
|
}
|
|
1511
1665
|
function numberWords(fires, opts) {
|
|
1512
|
-
return fires.map(
|
|
1666
|
+
return fires.map(listNumber(fires.length, opts));
|
|
1513
1667
|
}
|
|
1514
1668
|
function segmentWords(segments, opts) {
|
|
1515
|
-
const
|
|
1669
|
+
const count = segments.reduce(function tally(sum, segment) {
|
|
1516
1670
|
if (segment.kind === "range") {
|
|
1517
|
-
return
|
|
1671
|
+
return sum + 1;
|
|
1518
1672
|
}
|
|
1519
|
-
return segment.kind === "step" ? segment.fires :
|
|
1520
|
-
});
|
|
1521
|
-
const num =
|
|
1673
|
+
return sum + (segment.kind === "step" ? segment.fires.length : 1);
|
|
1674
|
+
}, 0);
|
|
1675
|
+
const num = listNumber(count, opts);
|
|
1522
1676
|
return segments.flatMap(function word(segment) {
|
|
1523
1677
|
if (segment.kind === "range") {
|
|
1524
1678
|
return [num(segment.bounds[0]) + through(opts) + num(segment.bounds[1])];
|
|
@@ -1550,6 +1704,9 @@ function hourTimes(hours, opts) {
|
|
|
1550
1704
|
});
|
|
1551
1705
|
return joinList(times, opts);
|
|
1552
1706
|
}
|
|
1707
|
+
function singleHourFire(times) {
|
|
1708
|
+
return times.kind === "fires" && times.fires.length === 1;
|
|
1709
|
+
}
|
|
1553
1710
|
function hourTimesFromPlan(ir, times, atContext, opts) {
|
|
1554
1711
|
if (times.kind === "fires") {
|
|
1555
1712
|
return hourTimes(times.fires, opts);
|
|
@@ -1597,28 +1754,47 @@ function disambiguateTimes(pieces, segments, atContext) {
|
|
|
1597
1754
|
return index === 0 ? piece : "at " + piece;
|
|
1598
1755
|
});
|
|
1599
1756
|
}
|
|
1600
|
-
function
|
|
1757
|
+
function joinWith(items, conjunction, opts) {
|
|
1601
1758
|
if (items.length <= 1) {
|
|
1602
1759
|
return items.join("");
|
|
1603
1760
|
}
|
|
1604
1761
|
if (items.length === 2) {
|
|
1605
|
-
return items[0] +
|
|
1762
|
+
return items[0] + conjunction + items[1];
|
|
1606
1763
|
}
|
|
1607
|
-
const
|
|
1608
|
-
return items.slice(0, -1).join(", ") +
|
|
1764
|
+
const tail = opts.style.serialComma ? "," + conjunction : conjunction;
|
|
1765
|
+
return items.slice(0, -1).join(", ") + tail + items[items.length - 1];
|
|
1766
|
+
}
|
|
1767
|
+
function joinList(items, opts) {
|
|
1768
|
+
return joinWith(items, " and ", opts);
|
|
1769
|
+
}
|
|
1770
|
+
function joinOr(items, opts) {
|
|
1771
|
+
return joinWith(items, " or ", opts);
|
|
1609
1772
|
}
|
|
1610
|
-
var trailingWords = {
|
|
1773
|
+
var trailingWords = {
|
|
1774
|
+
all: "",
|
|
1775
|
+
month: "in ",
|
|
1776
|
+
recurringWeekday: true,
|
|
1777
|
+
stepDate: "on ",
|
|
1778
|
+
weekday: "on "
|
|
1779
|
+
};
|
|
1611
1780
|
var leadingWords = {
|
|
1612
1781
|
all: "every day",
|
|
1613
1782
|
month: "every day in ",
|
|
1783
|
+
recurringWeekday: false,
|
|
1614
1784
|
stepDate: "",
|
|
1615
1785
|
weekday: "every "
|
|
1616
1786
|
};
|
|
1617
1787
|
function trailingQualifier(ir, opts) {
|
|
1788
|
+
if (isDayUnion(ir, opts)) {
|
|
1789
|
+
return dayUnionCondition(ir, opts);
|
|
1790
|
+
}
|
|
1618
1791
|
const phrase = dayQualifier(ir, trailingWords, opts);
|
|
1619
1792
|
return phrase && " " + phrase;
|
|
1620
1793
|
}
|
|
1621
1794
|
function interpretDayQualifier(ir, opts) {
|
|
1795
|
+
if (isDayUnion(ir, opts)) {
|
|
1796
|
+
return "";
|
|
1797
|
+
}
|
|
1622
1798
|
return dayQualifier(ir, leadingWords, opts) + " ";
|
|
1623
1799
|
}
|
|
1624
1800
|
function dayQualifier(ir, words, opts) {
|
|
@@ -1630,7 +1806,11 @@ function dayQualifier(ir, words, opts) {
|
|
|
1630
1806
|
return datePhrase(ir, words, opts);
|
|
1631
1807
|
}
|
|
1632
1808
|
if (pattern.weekday !== "*") {
|
|
1633
|
-
const
|
|
1809
|
+
const quartzWeekday = quartzWeekdayPhrase(pattern.weekday, opts);
|
|
1810
|
+
if (quartzWeekday) {
|
|
1811
|
+
return monthScopeForRecurrence(quartzWeekday, ir, opts);
|
|
1812
|
+
}
|
|
1813
|
+
const weekdays = words.weekday + weekdayPhrase(ir, words.recurringWeekday, opts);
|
|
1634
1814
|
return weekdays + monthScope(ir, opts);
|
|
1635
1815
|
}
|
|
1636
1816
|
if (pattern.month !== "*") {
|
|
@@ -1642,10 +1822,14 @@ function datePhrase(ir, words, opts) {
|
|
|
1642
1822
|
const pattern = ir.pattern;
|
|
1643
1823
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
1644
1824
|
if (quartzDate) {
|
|
1645
|
-
return quartzDate
|
|
1825
|
+
return monthScopeForRecurrence(quartzDate, ir, opts);
|
|
1646
1826
|
}
|
|
1647
1827
|
if (isOpenStep(pattern.date)) {
|
|
1648
|
-
return
|
|
1828
|
+
return monthScopeForRecurrence(
|
|
1829
|
+
words.stepDate + stepDates(pattern.date),
|
|
1830
|
+
ir,
|
|
1831
|
+
opts
|
|
1832
|
+
);
|
|
1649
1833
|
}
|
|
1650
1834
|
if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
|
|
1651
1835
|
return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
|
|
@@ -1661,9 +1845,84 @@ function monthFoldsIntoDate(ir) {
|
|
|
1661
1845
|
return segment.kind !== "range";
|
|
1662
1846
|
});
|
|
1663
1847
|
}
|
|
1848
|
+
function isDayUnion(ir, opts) {
|
|
1849
|
+
return ir.pattern.date !== "*" && ir.pattern.weekday !== "*" && !!opts.style.untilWindow && !opts.short;
|
|
1850
|
+
}
|
|
1851
|
+
function dayUnionCondition(ir, opts) {
|
|
1852
|
+
const pieces = [
|
|
1853
|
+
...dayUnionDatePieces(ir, opts),
|
|
1854
|
+
...dayUnionWeekdayPieces(ir, opts)
|
|
1855
|
+
];
|
|
1856
|
+
return " whenever the day is " + joinOr(pieces, opts);
|
|
1857
|
+
}
|
|
1858
|
+
function dayUnionMonthLead(ir, opts) {
|
|
1859
|
+
if (ir.pattern.month === "*") {
|
|
1860
|
+
return "";
|
|
1861
|
+
}
|
|
1862
|
+
return "in " + monthName(ir, opts) + " ";
|
|
1863
|
+
}
|
|
1864
|
+
function dayUnionDatePieces(ir, opts) {
|
|
1865
|
+
const dateField = ir.pattern.date;
|
|
1866
|
+
const quartz = quartzDatePhrase(dateField, opts);
|
|
1867
|
+
if (quartz) {
|
|
1868
|
+
return [quartz.replace(/^on /, "")];
|
|
1869
|
+
}
|
|
1870
|
+
const oddEven = oddEvenDay(dateField);
|
|
1871
|
+
if (oddEven) {
|
|
1872
|
+
return [oddEven];
|
|
1873
|
+
}
|
|
1874
|
+
const pieces = [];
|
|
1875
|
+
ir.analyses.segments.date.forEach(function expand(segment) {
|
|
1876
|
+
if (segment.kind === "range") {
|
|
1877
|
+
pieces.push("from the " + getOrdinal(segment.bounds[0]) + through(opts) + "the " + getOrdinal(segment.bounds[1]));
|
|
1878
|
+
} else if (segment.kind === "step") {
|
|
1879
|
+
segment.fires.forEach(function fire(value) {
|
|
1880
|
+
pieces.push("the " + getOrdinal(value));
|
|
1881
|
+
});
|
|
1882
|
+
} else {
|
|
1883
|
+
pieces.push("the " + getOrdinal(segment.value));
|
|
1884
|
+
}
|
|
1885
|
+
});
|
|
1886
|
+
return pieces;
|
|
1887
|
+
}
|
|
1888
|
+
function dayUnionWeekdayPieces(ir, opts) {
|
|
1889
|
+
const weekdayField = ir.pattern.weekday;
|
|
1890
|
+
const quartz = quartzWeekdayPhrase(weekdayField, opts);
|
|
1891
|
+
if (quartz) {
|
|
1892
|
+
return [quartz.replace(/^on /, "")];
|
|
1893
|
+
}
|
|
1894
|
+
const pieces = [];
|
|
1895
|
+
ir.analyses.segments.weekday.forEach(function expand(segment) {
|
|
1896
|
+
if (segment.kind === "range" && segment.bounds[0] === "1" && segment.bounds[1] === "5") {
|
|
1897
|
+
pieces.push("a weekday");
|
|
1898
|
+
} else if (segment.kind === "range") {
|
|
1899
|
+
pieces.push("a " + getWeekday(segment.bounds[0], opts) + through(opts) + "a " + getWeekday(segment.bounds[1], opts));
|
|
1900
|
+
} else if (segment.kind === "step") {
|
|
1901
|
+
segment.fires.forEach(function fire(value) {
|
|
1902
|
+
pieces.push("a " + getWeekday(value, opts));
|
|
1903
|
+
});
|
|
1904
|
+
} else {
|
|
1905
|
+
pieces.push("a " + getWeekday(segment.value, opts));
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
return pieces;
|
|
1909
|
+
}
|
|
1910
|
+
function oddEvenDay(dateField) {
|
|
1911
|
+
if (!isOpenStep(dateField)) {
|
|
1912
|
+
return null;
|
|
1913
|
+
}
|
|
1914
|
+
const [start, step] = dateField.split("/");
|
|
1915
|
+
if (+step !== 2) {
|
|
1916
|
+
return null;
|
|
1917
|
+
}
|
|
1918
|
+
if (start === "*" || start === "1") {
|
|
1919
|
+
return "an odd-numbered day";
|
|
1920
|
+
}
|
|
1921
|
+
return start === "2" ? "an even-numbered day" : null;
|
|
1922
|
+
}
|
|
1664
1923
|
function dateOrWeekday(ir, opts) {
|
|
1665
1924
|
const pattern = ir.pattern;
|
|
1666
|
-
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, opts);
|
|
1925
|
+
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, false, opts);
|
|
1667
1926
|
if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
|
|
1668
1927
|
return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
|
|
1669
1928
|
}
|
|
@@ -1718,6 +1977,9 @@ function monthDatePhrase(ir, opts) {
|
|
|
1718
1977
|
opts.style.ordinals ? getOrdinal : cardinalDay,
|
|
1719
1978
|
opts
|
|
1720
1979
|
);
|
|
1980
|
+
if (opts.style.dayFirst && ir.shapes.date === "single" && ir.shapes.month !== "single") {
|
|
1981
|
+
return "the " + getOrdinal(ir.pattern.date) + " of " + month;
|
|
1982
|
+
}
|
|
1721
1983
|
return opts.style.dayFirst ? days + " " + month : month + " " + days;
|
|
1722
1984
|
}
|
|
1723
1985
|
function cardinalDay(value) {
|
|
@@ -1729,6 +1991,19 @@ function monthScope(ir, opts) {
|
|
|
1729
1991
|
}
|
|
1730
1992
|
return " in " + monthName(ir, opts);
|
|
1731
1993
|
}
|
|
1994
|
+
function monthScopeForRecurrence(phrase, ir, opts) {
|
|
1995
|
+
if (ir.pattern.month === "*") {
|
|
1996
|
+
return phrase;
|
|
1997
|
+
}
|
|
1998
|
+
const carriesRecurrence = phrase.indexOf(" of the month") !== -1;
|
|
1999
|
+
if (carriesRecurrence && ir.shapes.month === "range") {
|
|
2000
|
+
return phrase.replace(" of the month", " of each month") + " from " + monthName(ir, opts);
|
|
2001
|
+
}
|
|
2002
|
+
if (carriesRecurrence && (ir.shapes.month === "single" || ir.shapes.month === "step")) {
|
|
2003
|
+
return phrase.replace(" of the month", "") + " in " + monthName(ir, opts);
|
|
2004
|
+
}
|
|
2005
|
+
return phrase + " in " + monthName(ir, opts);
|
|
2006
|
+
}
|
|
1732
2007
|
function stepDates(dateField) {
|
|
1733
2008
|
const parts = dateField.split("/");
|
|
1734
2009
|
const interval = +parts[1];
|
|
@@ -1765,11 +2040,21 @@ function oddEvenMonth(monthField) {
|
|
|
1765
2040
|
}
|
|
1766
2041
|
return start === "2" ? "every even-numbered month" : null;
|
|
1767
2042
|
}
|
|
1768
|
-
function weekdayPhrase(ir, opts) {
|
|
2043
|
+
function weekdayPhrase(ir, recurring, opts) {
|
|
1769
2044
|
const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday);
|
|
1770
|
-
|
|
2045
|
+
const hasRange = segments.some(function range(segment) {
|
|
2046
|
+
return segment.kind === "range";
|
|
2047
|
+
});
|
|
2048
|
+
const name = recurring && !hasRange ? function plural(value) {
|
|
2049
|
+
return pluralWeekday(value, opts);
|
|
2050
|
+
} : function singular(value) {
|
|
1771
2051
|
return getWeekday(value, opts);
|
|
1772
|
-
}
|
|
2052
|
+
};
|
|
2053
|
+
return renderSegments(segments, name, opts);
|
|
2054
|
+
}
|
|
2055
|
+
function pluralWeekday(value, opts) {
|
|
2056
|
+
const name = getWeekday(value, opts);
|
|
2057
|
+
return opts.short ? name : name + "s";
|
|
1773
2058
|
}
|
|
1774
2059
|
function renderSegments(segments, word, opts) {
|
|
1775
2060
|
const pieces = [];
|
|
@@ -1793,7 +2078,7 @@ function applyYear(description, ir, opts) {
|
|
|
1793
2078
|
return description;
|
|
1794
2079
|
}
|
|
1795
2080
|
if (yearField.indexOf("/") !== -1) {
|
|
1796
|
-
return description + " " + stepYears(yearField, opts);
|
|
2081
|
+
return description + ", " + stepYears(yearField, opts);
|
|
1797
2082
|
}
|
|
1798
2083
|
const label = yearLabel(yearField, opts);
|
|
1799
2084
|
if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
|
|
@@ -1806,6 +2091,9 @@ function yearLabel(yearField, opts) {
|
|
|
1806
2091
|
if (yearField.indexOf(",") !== -1) {
|
|
1807
2092
|
return joinList(yearField.split(","), opts);
|
|
1808
2093
|
}
|
|
2094
|
+
if (yearField.indexOf("-") !== -1) {
|
|
2095
|
+
return yearField.split("-").join(through(opts));
|
|
2096
|
+
}
|
|
1809
2097
|
return yearField;
|
|
1810
2098
|
}
|
|
1811
2099
|
function stepYears(yearField, opts) {
|
|
@@ -1815,7 +2103,7 @@ function stepYears(yearField, opts) {
|
|
|
1815
2103
|
if (interval <= 1) {
|
|
1816
2104
|
return "every year";
|
|
1817
2105
|
}
|
|
1818
|
-
let phrase = "every " + getNumber(interval, opts) + " years";
|
|
2106
|
+
let phrase = interval === 2 ? "every other year" : "every " + getNumber(interval, opts) + " years";
|
|
1819
2107
|
if (start !== "*" && start !== "0") {
|
|
1820
2108
|
phrase += " from " + start;
|
|
1821
2109
|
}
|