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.js
CHANGED
|
@@ -849,7 +849,8 @@ var dialects = {
|
|
|
849
849
|
pm: "p.m.",
|
|
850
850
|
sep: ":",
|
|
851
851
|
serialComma: true,
|
|
852
|
-
through: " through "
|
|
852
|
+
through: " through ",
|
|
853
|
+
untilWindow: true
|
|
853
854
|
},
|
|
854
855
|
house: {
|
|
855
856
|
am: "AM",
|
|
@@ -866,7 +867,7 @@ var dialects = {
|
|
|
866
867
|
};
|
|
867
868
|
function resolveDialect(dialect) {
|
|
868
869
|
if (typeof dialect === "object" && dialect !== null) {
|
|
869
|
-
return { ...dialects.us, ...dialect };
|
|
870
|
+
return { ...dialects.us, untilWindow: false, ...dialect };
|
|
870
871
|
}
|
|
871
872
|
const name = dialect === "uk" ? "gb" : dialect;
|
|
872
873
|
return dialects[name] || dialects.us;
|
|
@@ -938,7 +939,9 @@ function normalizeOptions(options) {
|
|
|
938
939
|
};
|
|
939
940
|
}
|
|
940
941
|
function describe(ir, opts) {
|
|
941
|
-
|
|
942
|
+
const body = confinement(ir, opts) ?? render(ir, ir.plan, opts);
|
|
943
|
+
const lead = isDayUnion(ir, opts) ? dayUnionMonthLead(ir, opts) : "";
|
|
944
|
+
return applyYear(lead + body, ir, opts);
|
|
942
945
|
}
|
|
943
946
|
function render(ir, plan, opts) {
|
|
944
947
|
const renderer = renderers[plan.kind];
|
|
@@ -1031,7 +1034,7 @@ function secondsClause(ir, anchor, opts) {
|
|
|
1031
1034
|
}
|
|
1032
1035
|
if (shape === "range") {
|
|
1033
1036
|
const bounds = secondField.split("-");
|
|
1034
|
-
const num = seriesNumber(
|
|
1037
|
+
const num = seriesNumber();
|
|
1035
1038
|
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
|
|
1036
1039
|
}
|
|
1037
1040
|
if (shape === "single") {
|
|
@@ -1097,15 +1100,21 @@ function renderMinutesAcrossHours(ir, plan, opts) {
|
|
|
1097
1100
|
}
|
|
1098
1101
|
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1099
1102
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
"
|
|
1107
|
-
|
|
1108
|
-
)
|
|
1103
|
+
if (plan.form === "range") {
|
|
1104
|
+
const lead2 = minuteRangeLead(ir.pattern.minute, opts);
|
|
1105
|
+
if (cadence !== null) {
|
|
1106
|
+
return lead2 + ", " + cadence + trailingQualifier(ir, opts);
|
|
1107
|
+
}
|
|
1108
|
+
if (singleHourFire(plan.times)) {
|
|
1109
|
+
return lead2 + ", at " + hourTimesFromPlan(ir, plan.times, true, opts) + trailingQualifier(ir, opts);
|
|
1110
|
+
}
|
|
1111
|
+
return lead2 + " during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1112
|
+
}
|
|
1113
|
+
const lead = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
|
|
1114
|
+
segmentWords(ir.analyses.segments.minute, opts),
|
|
1115
|
+
"minute",
|
|
1116
|
+
"hour",
|
|
1117
|
+
opts
|
|
1109
1118
|
);
|
|
1110
1119
|
if (cadence !== null) {
|
|
1111
1120
|
return lead + ", " + cadence + trailingQualifier(ir, opts);
|
|
@@ -1142,7 +1151,7 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
|
1142
1151
|
}
|
|
1143
1152
|
function minuteRangeLead(minuteField, opts) {
|
|
1144
1153
|
const bounds = minuteField.split("-");
|
|
1145
|
-
const num = seriesNumber(
|
|
1154
|
+
const num = seriesNumber();
|
|
1146
1155
|
return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
|
|
1147
1156
|
}
|
|
1148
1157
|
function renderEveryHour(ir, plan, opts) {
|
|
@@ -1185,8 +1194,15 @@ function boundedWindow(plan) {
|
|
|
1185
1194
|
const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
|
|
1186
1195
|
return { from: plan.from, last, to: plan.to };
|
|
1187
1196
|
}
|
|
1197
|
+
function rangeWindow(from, to, throughMinute, opts) {
|
|
1198
|
+
const open = "from " + getTime({ hour: from, minute: 0 }, opts);
|
|
1199
|
+
if (opts.style.untilWindow && !opts.short && from !== to) {
|
|
1200
|
+
return open + " until " + getTime({ hour: (to + 1) % 24, minute: 0 }, opts);
|
|
1201
|
+
}
|
|
1202
|
+
return open + through(opts) + getTime({ hour: to, minute: throughMinute }, opts);
|
|
1203
|
+
}
|
|
1188
1204
|
function hourWindow(window, opts) {
|
|
1189
|
-
return
|
|
1205
|
+
return rangeWindow(window.from, window.to, window.last, opts);
|
|
1190
1206
|
}
|
|
1191
1207
|
function renderClockTimes(ir, plan, opts) {
|
|
1192
1208
|
if (ir.shapes.minute === "single") {
|
|
@@ -1205,7 +1221,10 @@ function renderClockTimes(ir, plan, opts) {
|
|
|
1205
1221
|
plain
|
|
1206
1222
|
}, opts);
|
|
1207
1223
|
});
|
|
1208
|
-
return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts);
|
|
1224
|
+
return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts) + dayUnionTrail(ir, opts);
|
|
1225
|
+
}
|
|
1226
|
+
function dayUnionTrail(ir, opts) {
|
|
1227
|
+
return isDayUnion(ir, opts) ? dayUnionCondition(ir, opts) : "";
|
|
1209
1228
|
}
|
|
1210
1229
|
function renderCompactClockTimes(ir, plan, opts) {
|
|
1211
1230
|
if (plan.fold) {
|
|
@@ -1220,7 +1239,7 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1220
1239
|
return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
|
|
1221
1240
|
}
|
|
1222
1241
|
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
1223
|
-
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
1242
|
+
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts) + dayUnionTrail(ir, opts);
|
|
1224
1243
|
}
|
|
1225
1244
|
const minuteLead = (
|
|
1226
1245
|
// The non-fold branch is a minute list, which has segments. An
|
|
@@ -1239,26 +1258,161 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1239
1258
|
function foldedHourWindows(ir, plan, opts) {
|
|
1240
1259
|
const minute = plan.minute;
|
|
1241
1260
|
const windows = [];
|
|
1242
|
-
const
|
|
1261
|
+
const outliers = collectHourOutliers(ir);
|
|
1262
|
+
const times = outliers.hours.map(function time(hour) {
|
|
1263
|
+
return getTime({ hour, minute }, opts);
|
|
1264
|
+
});
|
|
1243
1265
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1244
1266
|
if (segment.kind === "range") {
|
|
1245
|
-
windows.push(
|
|
1246
|
-
|
|
1267
|
+
windows.push(rangeWindow(
|
|
1268
|
+
+segment.bounds[0],
|
|
1269
|
+
+segment.bounds[1],
|
|
1270
|
+
minute,
|
|
1247
1271
|
opts
|
|
1248
|
-
)
|
|
1249
|
-
} else if (segment.kind === "step") {
|
|
1250
|
-
singles.push(...segment.fires);
|
|
1251
|
-
} else {
|
|
1252
|
-
singles.push(+segment.value);
|
|
1272
|
+
));
|
|
1253
1273
|
}
|
|
1254
1274
|
});
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1275
|
+
const phrase = rangeMinuteLead(ir, opts) + " " + joinList(windows, opts);
|
|
1276
|
+
return phrase + outlierTail(times, outliers.pureStrays, opts);
|
|
1277
|
+
}
|
|
1278
|
+
function collectHourOutliers(ir) {
|
|
1279
|
+
const hours = [];
|
|
1280
|
+
let pureStrays = true;
|
|
1281
|
+
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1282
|
+
if (segment.kind === "step") {
|
|
1283
|
+
hours.push(...segment.fires);
|
|
1284
|
+
pureStrays = false;
|
|
1285
|
+
} else if (segment.kind !== "range") {
|
|
1286
|
+
hours.push(+segment.value);
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
return { hours, pureStrays };
|
|
1290
|
+
}
|
|
1291
|
+
function outlierTail(times, pureStrays, opts) {
|
|
1292
|
+
if (!times.length) {
|
|
1293
|
+
return "";
|
|
1260
1294
|
}
|
|
1261
|
-
|
|
1295
|
+
const connector = pureStrays && opts.style.untilWindow && !opts.short ? " plus " : " and at ";
|
|
1296
|
+
return connector + joinList(times, opts);
|
|
1297
|
+
}
|
|
1298
|
+
function isCadenceField(token) {
|
|
1299
|
+
return token === "*" || token.startsWith("*/") && token.indexOf("-") === -1;
|
|
1300
|
+
}
|
|
1301
|
+
function leadingCadence(ir, opts) {
|
|
1302
|
+
const { second, minute } = ir.pattern;
|
|
1303
|
+
if (isCadenceField(second)) {
|
|
1304
|
+
return { secondLead: true, text: secondsClause(ir, "minute", opts) };
|
|
1305
|
+
}
|
|
1306
|
+
if (second === "0" && isCadenceField(minute)) {
|
|
1307
|
+
const text = minute === "*" ? "every minute" : (
|
|
1308
|
+
// A clean minute step's first segment is a step segment.
|
|
1309
|
+
stepCycle60(
|
|
1310
|
+
ir.analyses.segments.minute[0],
|
|
1311
|
+
"minute",
|
|
1312
|
+
"hour",
|
|
1313
|
+
opts
|
|
1314
|
+
)
|
|
1315
|
+
);
|
|
1316
|
+
return { secondLead: false, text };
|
|
1317
|
+
}
|
|
1318
|
+
return null;
|
|
1319
|
+
}
|
|
1320
|
+
function minuteConfinement(ir, opts) {
|
|
1321
|
+
const minute = ir.pattern.minute;
|
|
1322
|
+
if (minute === "*") {
|
|
1323
|
+
return "";
|
|
1324
|
+
}
|
|
1325
|
+
if (isCadenceField(minute)) {
|
|
1326
|
+
return " of every other minute";
|
|
1327
|
+
}
|
|
1328
|
+
const segments = ir.analyses.segments.minute;
|
|
1329
|
+
if (ir.shapes.minute === "single") {
|
|
1330
|
+
return " during minute :" + pad(minute);
|
|
1331
|
+
}
|
|
1332
|
+
if (ir.shapes.minute === "range") {
|
|
1333
|
+
const bounds = minute.split("-");
|
|
1334
|
+
return " during minutes :" + pad(bounds[0]) + through(opts) + ":" + pad(bounds[1]);
|
|
1335
|
+
}
|
|
1336
|
+
const values = segmentWords(segments, opts).map(function colon(word) {
|
|
1337
|
+
return ":" + pad(word);
|
|
1338
|
+
});
|
|
1339
|
+
return " during minutes " + joinList(values, opts);
|
|
1340
|
+
}
|
|
1341
|
+
function hourConfinement(ir, opts) {
|
|
1342
|
+
const hour = ir.pattern.hour;
|
|
1343
|
+
if (hour === "*") {
|
|
1344
|
+
const minutePinned = ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute);
|
|
1345
|
+
return minutePinned ? " of every hour" : "";
|
|
1346
|
+
}
|
|
1347
|
+
if (isCadenceField(hour)) {
|
|
1348
|
+
return hour === "*/2" ? " of every other hour" : "";
|
|
1349
|
+
}
|
|
1350
|
+
if (ir.shapes.hour === "single") {
|
|
1351
|
+
const h = +hour;
|
|
1352
|
+
if (ir.shapes.minute === "step") {
|
|
1353
|
+
return " from " + getTime({ hour: h, minute: 0 }, opts) + " until " + getTime({ hour: (h + 1) % 24, minute: 0 }, opts);
|
|
1354
|
+
}
|
|
1355
|
+
if (ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute)) {
|
|
1356
|
+
return " at " + getTime({ hour: h, minute: 0 }, opts);
|
|
1357
|
+
}
|
|
1358
|
+
return " of the " + getTime({ hour: h, minute: 0 }, opts) + " hour";
|
|
1359
|
+
}
|
|
1360
|
+
if (ir.shapes.hour === "range") {
|
|
1361
|
+
const bounds = hour.split("-");
|
|
1362
|
+
return " " + rangeWindow(+bounds[0], +bounds[1], 0, opts);
|
|
1363
|
+
}
|
|
1364
|
+
return " during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours";
|
|
1365
|
+
}
|
|
1366
|
+
function isContiguousHourRange(ir) {
|
|
1367
|
+
return ir.shapes.hour === "range";
|
|
1368
|
+
}
|
|
1369
|
+
function confinableHour(ir) {
|
|
1370
|
+
if (ir.shapes.hour !== "step") {
|
|
1371
|
+
return true;
|
|
1372
|
+
}
|
|
1373
|
+
const segment = ir.analyses.segments.hour[0];
|
|
1374
|
+
return ir.pattern.hour === "*/2" || segment.startToken.indexOf("-") !== -1;
|
|
1375
|
+
}
|
|
1376
|
+
function isMinuteStride(ir) {
|
|
1377
|
+
if (ir.shapes.minute !== "list") {
|
|
1378
|
+
return false;
|
|
1379
|
+
}
|
|
1380
|
+
const values = singleValues(ir.analyses.segments.minute);
|
|
1381
|
+
return values !== null && arithmeticStep(values) !== null;
|
|
1382
|
+
}
|
|
1383
|
+
function confinementEligible(ir, lead) {
|
|
1384
|
+
const { minute, hour } = ir.pattern;
|
|
1385
|
+
const minuteStep = isCadenceField(minute) && minute !== "*";
|
|
1386
|
+
if (!confinableHour(ir)) {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
if (lead.secondLead) {
|
|
1390
|
+
if (minuteStep) {
|
|
1391
|
+
return minute === "*/2" && !isContiguousHourRange(ir);
|
|
1392
|
+
}
|
|
1393
|
+
if (isMinuteStride(ir) || ir.shapes.minute === "list" && ir.shapes.hour === "list") {
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1398
|
+
if (hour === "*/2") {
|
|
1399
|
+
return true;
|
|
1400
|
+
}
|
|
1401
|
+
return ir.shapes.hour === "single" && minute === "*/2";
|
|
1402
|
+
}
|
|
1403
|
+
function confinement(ir, opts) {
|
|
1404
|
+
if (!opts.style.untilWindow || opts.short) {
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1407
|
+
if (ir.pattern.minute === "*" && ir.pattern.hour === "*") {
|
|
1408
|
+
return null;
|
|
1409
|
+
}
|
|
1410
|
+
const lead = leadingCadence(ir, opts);
|
|
1411
|
+
if (!lead || !confinementEligible(ir, lead)) {
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
const minutePart = lead.secondLead ? minuteConfinement(ir, opts) : "";
|
|
1415
|
+
return lead.text + minutePart + hourConfinement(ir, opts) + trailingQualifier(ir, opts);
|
|
1262
1416
|
}
|
|
1263
1417
|
var renderers = {
|
|
1264
1418
|
clockTimes: renderClockTimes,
|
|
@@ -1290,7 +1444,7 @@ function renderStride(stride, opts) {
|
|
|
1290
1444
|
if (start < interval && tiles) {
|
|
1291
1445
|
return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
1292
1446
|
}
|
|
1293
|
-
const num = seriesNumber(
|
|
1447
|
+
const num = seriesNumber();
|
|
1294
1448
|
return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
|
|
1295
1449
|
}
|
|
1296
1450
|
function singleValues(segments) {
|
|
@@ -1417,9 +1571,9 @@ function hourCadence(ir, minute, opts) {
|
|
|
1417
1571
|
if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
|
|
1418
1572
|
return null;
|
|
1419
1573
|
}
|
|
1420
|
-
const
|
|
1421
|
-
if (
|
|
1422
|
-
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(
|
|
1574
|
+
const minuteZeroStride = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
|
|
1575
|
+
if (minuteZeroStride) {
|
|
1576
|
+
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(minuteZeroStride, opts) + trailingQualifier(ir, opts);
|
|
1423
1577
|
}
|
|
1424
1578
|
if (minute === 0 && ir.pattern.second === "0") {
|
|
1425
1579
|
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
@@ -1441,26 +1595,22 @@ function hasHourWindow(ir) {
|
|
|
1441
1595
|
}
|
|
1442
1596
|
function hourRangeWindowTail(ir, opts) {
|
|
1443
1597
|
const windows = [];
|
|
1444
|
-
const
|
|
1598
|
+
const outliers = collectHourOutliers(ir);
|
|
1445
1599
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1446
1600
|
if (segment.kind === "range") {
|
|
1447
|
-
windows.push(
|
|
1448
|
-
|
|
1601
|
+
windows.push(rangeWindow(
|
|
1602
|
+
+segment.bounds[0],
|
|
1603
|
+
+segment.bounds[1],
|
|
1604
|
+
0,
|
|
1449
1605
|
opts
|
|
1450
|
-
)
|
|
1451
|
-
} else if (segment.kind === "step") {
|
|
1452
|
-
singles.push(...segment.fires);
|
|
1453
|
-
} else {
|
|
1454
|
-
singles.push(+segment.value);
|
|
1606
|
+
));
|
|
1455
1607
|
}
|
|
1456
1608
|
});
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
}
|
|
1463
|
-
return phrase;
|
|
1609
|
+
const phrase = "every hour " + joinList(windows, opts);
|
|
1610
|
+
const times = outliers.hours.map(function time(hour) {
|
|
1611
|
+
return getTime({ hour, minute: 0 }, opts);
|
|
1612
|
+
});
|
|
1613
|
+
return phrase + outlierTail(times, outliers.pureStrays, opts);
|
|
1464
1614
|
}
|
|
1465
1615
|
function hourRangeCadence(ir, minute, opts) {
|
|
1466
1616
|
if (minute !== 0 || !hasHourWindow(ir)) {
|
|
@@ -1474,25 +1624,29 @@ function hourRangeCadence(ir, minute, opts) {
|
|
|
1474
1624
|
}
|
|
1475
1625
|
return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
|
|
1476
1626
|
}
|
|
1477
|
-
function seriesNumber(
|
|
1478
|
-
const anyBig = values.some(function big(v) {
|
|
1479
|
-
return +v > 10;
|
|
1480
|
-
});
|
|
1627
|
+
function seriesNumber() {
|
|
1481
1628
|
return function format(n) {
|
|
1482
|
-
return
|
|
1629
|
+
return "" + n;
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
function listNumber(count, opts) {
|
|
1633
|
+
return count > 1 ? function asNumeral(n) {
|
|
1634
|
+
return "" + n;
|
|
1635
|
+
} : function spelled(n) {
|
|
1636
|
+
return getNumber(n, opts);
|
|
1483
1637
|
};
|
|
1484
1638
|
}
|
|
1485
1639
|
function numberWords(fires, opts) {
|
|
1486
|
-
return fires.map(
|
|
1640
|
+
return fires.map(listNumber(fires.length, opts));
|
|
1487
1641
|
}
|
|
1488
1642
|
function segmentWords(segments, opts) {
|
|
1489
|
-
const
|
|
1643
|
+
const count = segments.reduce(function tally(sum, segment) {
|
|
1490
1644
|
if (segment.kind === "range") {
|
|
1491
|
-
return
|
|
1645
|
+
return sum + 1;
|
|
1492
1646
|
}
|
|
1493
|
-
return segment.kind === "step" ? segment.fires :
|
|
1494
|
-
});
|
|
1495
|
-
const num =
|
|
1647
|
+
return sum + (segment.kind === "step" ? segment.fires.length : 1);
|
|
1648
|
+
}, 0);
|
|
1649
|
+
const num = listNumber(count, opts);
|
|
1496
1650
|
return segments.flatMap(function word(segment) {
|
|
1497
1651
|
if (segment.kind === "range") {
|
|
1498
1652
|
return [num(segment.bounds[0]) + through(opts) + num(segment.bounds[1])];
|
|
@@ -1524,6 +1678,9 @@ function hourTimes(hours, opts) {
|
|
|
1524
1678
|
});
|
|
1525
1679
|
return joinList(times, opts);
|
|
1526
1680
|
}
|
|
1681
|
+
function singleHourFire(times) {
|
|
1682
|
+
return times.kind === "fires" && times.fires.length === 1;
|
|
1683
|
+
}
|
|
1527
1684
|
function hourTimesFromPlan(ir, times, atContext, opts) {
|
|
1528
1685
|
if (times.kind === "fires") {
|
|
1529
1686
|
return hourTimes(times.fires, opts);
|
|
@@ -1571,28 +1728,47 @@ function disambiguateTimes(pieces, segments, atContext) {
|
|
|
1571
1728
|
return index === 0 ? piece : "at " + piece;
|
|
1572
1729
|
});
|
|
1573
1730
|
}
|
|
1574
|
-
function
|
|
1731
|
+
function joinWith(items, conjunction, opts) {
|
|
1575
1732
|
if (items.length <= 1) {
|
|
1576
1733
|
return items.join("");
|
|
1577
1734
|
}
|
|
1578
1735
|
if (items.length === 2) {
|
|
1579
|
-
return items[0] +
|
|
1736
|
+
return items[0] + conjunction + items[1];
|
|
1580
1737
|
}
|
|
1581
|
-
const
|
|
1582
|
-
return items.slice(0, -1).join(", ") +
|
|
1738
|
+
const tail = opts.style.serialComma ? "," + conjunction : conjunction;
|
|
1739
|
+
return items.slice(0, -1).join(", ") + tail + items[items.length - 1];
|
|
1740
|
+
}
|
|
1741
|
+
function joinList(items, opts) {
|
|
1742
|
+
return joinWith(items, " and ", opts);
|
|
1743
|
+
}
|
|
1744
|
+
function joinOr(items, opts) {
|
|
1745
|
+
return joinWith(items, " or ", opts);
|
|
1583
1746
|
}
|
|
1584
|
-
var trailingWords = {
|
|
1747
|
+
var trailingWords = {
|
|
1748
|
+
all: "",
|
|
1749
|
+
month: "in ",
|
|
1750
|
+
recurringWeekday: true,
|
|
1751
|
+
stepDate: "on ",
|
|
1752
|
+
weekday: "on "
|
|
1753
|
+
};
|
|
1585
1754
|
var leadingWords = {
|
|
1586
1755
|
all: "every day",
|
|
1587
1756
|
month: "every day in ",
|
|
1757
|
+
recurringWeekday: false,
|
|
1588
1758
|
stepDate: "",
|
|
1589
1759
|
weekday: "every "
|
|
1590
1760
|
};
|
|
1591
1761
|
function trailingQualifier(ir, opts) {
|
|
1762
|
+
if (isDayUnion(ir, opts)) {
|
|
1763
|
+
return dayUnionCondition(ir, opts);
|
|
1764
|
+
}
|
|
1592
1765
|
const phrase = dayQualifier(ir, trailingWords, opts);
|
|
1593
1766
|
return phrase && " " + phrase;
|
|
1594
1767
|
}
|
|
1595
1768
|
function interpretDayQualifier(ir, opts) {
|
|
1769
|
+
if (isDayUnion(ir, opts)) {
|
|
1770
|
+
return "";
|
|
1771
|
+
}
|
|
1596
1772
|
return dayQualifier(ir, leadingWords, opts) + " ";
|
|
1597
1773
|
}
|
|
1598
1774
|
function dayQualifier(ir, words, opts) {
|
|
@@ -1604,7 +1780,11 @@ function dayQualifier(ir, words, opts) {
|
|
|
1604
1780
|
return datePhrase(ir, words, opts);
|
|
1605
1781
|
}
|
|
1606
1782
|
if (pattern.weekday !== "*") {
|
|
1607
|
-
const
|
|
1783
|
+
const quartzWeekday = quartzWeekdayPhrase(pattern.weekday, opts);
|
|
1784
|
+
if (quartzWeekday) {
|
|
1785
|
+
return monthScopeForRecurrence(quartzWeekday, ir, opts);
|
|
1786
|
+
}
|
|
1787
|
+
const weekdays = words.weekday + weekdayPhrase(ir, words.recurringWeekday, opts);
|
|
1608
1788
|
return weekdays + monthScope(ir, opts);
|
|
1609
1789
|
}
|
|
1610
1790
|
if (pattern.month !== "*") {
|
|
@@ -1616,10 +1796,14 @@ function datePhrase(ir, words, opts) {
|
|
|
1616
1796
|
const pattern = ir.pattern;
|
|
1617
1797
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
1618
1798
|
if (quartzDate) {
|
|
1619
|
-
return quartzDate
|
|
1799
|
+
return monthScopeForRecurrence(quartzDate, ir, opts);
|
|
1620
1800
|
}
|
|
1621
1801
|
if (isOpenStep(pattern.date)) {
|
|
1622
|
-
return
|
|
1802
|
+
return monthScopeForRecurrence(
|
|
1803
|
+
words.stepDate + stepDates(pattern.date),
|
|
1804
|
+
ir,
|
|
1805
|
+
opts
|
|
1806
|
+
);
|
|
1623
1807
|
}
|
|
1624
1808
|
if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
|
|
1625
1809
|
return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
|
|
@@ -1635,9 +1819,84 @@ function monthFoldsIntoDate(ir) {
|
|
|
1635
1819
|
return segment.kind !== "range";
|
|
1636
1820
|
});
|
|
1637
1821
|
}
|
|
1822
|
+
function isDayUnion(ir, opts) {
|
|
1823
|
+
return ir.pattern.date !== "*" && ir.pattern.weekday !== "*" && !!opts.style.untilWindow && !opts.short;
|
|
1824
|
+
}
|
|
1825
|
+
function dayUnionCondition(ir, opts) {
|
|
1826
|
+
const pieces = [
|
|
1827
|
+
...dayUnionDatePieces(ir, opts),
|
|
1828
|
+
...dayUnionWeekdayPieces(ir, opts)
|
|
1829
|
+
];
|
|
1830
|
+
return " whenever the day is " + joinOr(pieces, opts);
|
|
1831
|
+
}
|
|
1832
|
+
function dayUnionMonthLead(ir, opts) {
|
|
1833
|
+
if (ir.pattern.month === "*") {
|
|
1834
|
+
return "";
|
|
1835
|
+
}
|
|
1836
|
+
return "in " + monthName(ir, opts) + " ";
|
|
1837
|
+
}
|
|
1838
|
+
function dayUnionDatePieces(ir, opts) {
|
|
1839
|
+
const dateField = ir.pattern.date;
|
|
1840
|
+
const quartz = quartzDatePhrase(dateField, opts);
|
|
1841
|
+
if (quartz) {
|
|
1842
|
+
return [quartz.replace(/^on /, "")];
|
|
1843
|
+
}
|
|
1844
|
+
const oddEven = oddEvenDay(dateField);
|
|
1845
|
+
if (oddEven) {
|
|
1846
|
+
return [oddEven];
|
|
1847
|
+
}
|
|
1848
|
+
const pieces = [];
|
|
1849
|
+
ir.analyses.segments.date.forEach(function expand(segment) {
|
|
1850
|
+
if (segment.kind === "range") {
|
|
1851
|
+
pieces.push("from the " + getOrdinal(segment.bounds[0]) + through(opts) + "the " + getOrdinal(segment.bounds[1]));
|
|
1852
|
+
} else if (segment.kind === "step") {
|
|
1853
|
+
segment.fires.forEach(function fire(value) {
|
|
1854
|
+
pieces.push("the " + getOrdinal(value));
|
|
1855
|
+
});
|
|
1856
|
+
} else {
|
|
1857
|
+
pieces.push("the " + getOrdinal(segment.value));
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
return pieces;
|
|
1861
|
+
}
|
|
1862
|
+
function dayUnionWeekdayPieces(ir, opts) {
|
|
1863
|
+
const weekdayField = ir.pattern.weekday;
|
|
1864
|
+
const quartz = quartzWeekdayPhrase(weekdayField, opts);
|
|
1865
|
+
if (quartz) {
|
|
1866
|
+
return [quartz.replace(/^on /, "")];
|
|
1867
|
+
}
|
|
1868
|
+
const pieces = [];
|
|
1869
|
+
ir.analyses.segments.weekday.forEach(function expand(segment) {
|
|
1870
|
+
if (segment.kind === "range" && segment.bounds[0] === "1" && segment.bounds[1] === "5") {
|
|
1871
|
+
pieces.push("a weekday");
|
|
1872
|
+
} else if (segment.kind === "range") {
|
|
1873
|
+
pieces.push("a " + getWeekday(segment.bounds[0], opts) + through(opts) + "a " + getWeekday(segment.bounds[1], opts));
|
|
1874
|
+
} else if (segment.kind === "step") {
|
|
1875
|
+
segment.fires.forEach(function fire(value) {
|
|
1876
|
+
pieces.push("a " + getWeekday(value, opts));
|
|
1877
|
+
});
|
|
1878
|
+
} else {
|
|
1879
|
+
pieces.push("a " + getWeekday(segment.value, opts));
|
|
1880
|
+
}
|
|
1881
|
+
});
|
|
1882
|
+
return pieces;
|
|
1883
|
+
}
|
|
1884
|
+
function oddEvenDay(dateField) {
|
|
1885
|
+
if (!isOpenStep(dateField)) {
|
|
1886
|
+
return null;
|
|
1887
|
+
}
|
|
1888
|
+
const [start, step] = dateField.split("/");
|
|
1889
|
+
if (+step !== 2) {
|
|
1890
|
+
return null;
|
|
1891
|
+
}
|
|
1892
|
+
if (start === "*" || start === "1") {
|
|
1893
|
+
return "an odd-numbered day";
|
|
1894
|
+
}
|
|
1895
|
+
return start === "2" ? "an even-numbered day" : null;
|
|
1896
|
+
}
|
|
1638
1897
|
function dateOrWeekday(ir, opts) {
|
|
1639
1898
|
const pattern = ir.pattern;
|
|
1640
|
-
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, opts);
|
|
1899
|
+
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, false, opts);
|
|
1641
1900
|
if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
|
|
1642
1901
|
return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
|
|
1643
1902
|
}
|
|
@@ -1692,6 +1951,9 @@ function monthDatePhrase(ir, opts) {
|
|
|
1692
1951
|
opts.style.ordinals ? getOrdinal : cardinalDay,
|
|
1693
1952
|
opts
|
|
1694
1953
|
);
|
|
1954
|
+
if (opts.style.dayFirst && ir.shapes.date === "single" && ir.shapes.month !== "single") {
|
|
1955
|
+
return "the " + getOrdinal(ir.pattern.date) + " of " + month;
|
|
1956
|
+
}
|
|
1695
1957
|
return opts.style.dayFirst ? days + " " + month : month + " " + days;
|
|
1696
1958
|
}
|
|
1697
1959
|
function cardinalDay(value) {
|
|
@@ -1703,6 +1965,19 @@ function monthScope(ir, opts) {
|
|
|
1703
1965
|
}
|
|
1704
1966
|
return " in " + monthName(ir, opts);
|
|
1705
1967
|
}
|
|
1968
|
+
function monthScopeForRecurrence(phrase, ir, opts) {
|
|
1969
|
+
if (ir.pattern.month === "*") {
|
|
1970
|
+
return phrase;
|
|
1971
|
+
}
|
|
1972
|
+
const carriesRecurrence = phrase.indexOf(" of the month") !== -1;
|
|
1973
|
+
if (carriesRecurrence && ir.shapes.month === "range") {
|
|
1974
|
+
return phrase.replace(" of the month", " of each month") + " from " + monthName(ir, opts);
|
|
1975
|
+
}
|
|
1976
|
+
if (carriesRecurrence && (ir.shapes.month === "single" || ir.shapes.month === "step")) {
|
|
1977
|
+
return phrase.replace(" of the month", "") + " in " + monthName(ir, opts);
|
|
1978
|
+
}
|
|
1979
|
+
return phrase + " in " + monthName(ir, opts);
|
|
1980
|
+
}
|
|
1706
1981
|
function stepDates(dateField) {
|
|
1707
1982
|
const parts = dateField.split("/");
|
|
1708
1983
|
const interval = +parts[1];
|
|
@@ -1739,11 +2014,21 @@ function oddEvenMonth(monthField) {
|
|
|
1739
2014
|
}
|
|
1740
2015
|
return start === "2" ? "every even-numbered month" : null;
|
|
1741
2016
|
}
|
|
1742
|
-
function weekdayPhrase(ir, opts) {
|
|
2017
|
+
function weekdayPhrase(ir, recurring, opts) {
|
|
1743
2018
|
const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday);
|
|
1744
|
-
|
|
2019
|
+
const hasRange = segments.some(function range(segment) {
|
|
2020
|
+
return segment.kind === "range";
|
|
2021
|
+
});
|
|
2022
|
+
const name = recurring && !hasRange ? function plural(value) {
|
|
2023
|
+
return pluralWeekday(value, opts);
|
|
2024
|
+
} : function singular(value) {
|
|
1745
2025
|
return getWeekday(value, opts);
|
|
1746
|
-
}
|
|
2026
|
+
};
|
|
2027
|
+
return renderSegments(segments, name, opts);
|
|
2028
|
+
}
|
|
2029
|
+
function pluralWeekday(value, opts) {
|
|
2030
|
+
const name = getWeekday(value, opts);
|
|
2031
|
+
return opts.short ? name : name + "s";
|
|
1747
2032
|
}
|
|
1748
2033
|
function renderSegments(segments, word, opts) {
|
|
1749
2034
|
const pieces = [];
|
|
@@ -1767,7 +2052,7 @@ function applyYear(description, ir, opts) {
|
|
|
1767
2052
|
return description;
|
|
1768
2053
|
}
|
|
1769
2054
|
if (yearField.indexOf("/") !== -1) {
|
|
1770
|
-
return description + " " + stepYears(yearField, opts);
|
|
2055
|
+
return description + ", " + stepYears(yearField, opts);
|
|
1771
2056
|
}
|
|
1772
2057
|
const label = yearLabel(yearField, opts);
|
|
1773
2058
|
if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
|
|
@@ -1780,6 +2065,9 @@ function yearLabel(yearField, opts) {
|
|
|
1780
2065
|
if (yearField.indexOf(",") !== -1) {
|
|
1781
2066
|
return joinList(yearField.split(","), opts);
|
|
1782
2067
|
}
|
|
2068
|
+
if (yearField.indexOf("-") !== -1) {
|
|
2069
|
+
return yearField.split("-").join(through(opts));
|
|
2070
|
+
}
|
|
1783
2071
|
return yearField;
|
|
1784
2072
|
}
|
|
1785
2073
|
function stepYears(yearField, opts) {
|
|
@@ -1789,7 +2077,7 @@ function stepYears(yearField, opts) {
|
|
|
1789
2077
|
if (interval <= 1) {
|
|
1790
2078
|
return "every year";
|
|
1791
2079
|
}
|
|
1792
|
-
let phrase = "every " + getNumber(interval, opts) + " years";
|
|
2080
|
+
let phrase = interval === 2 ? "every other year" : "every " + getNumber(interval, opts) + " years";
|
|
1793
2081
|
if (start !== "*" && start !== "0") {
|
|
1794
2082
|
phrase += " from " + start;
|
|
1795
2083
|
}
|