cronli5 0.1.6 → 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 +86 -0
- package/README.md +6 -6
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +401 -81
- package/dist/cronli5.js +401 -81
- package/dist/lang/de.cjs +63 -17
- package/dist/lang/de.js +63 -17
- package/dist/lang/en.cjs +401 -81
- package/dist/lang/en.js +401 -81
- package/dist/lang/es.cjs +68 -5
- package/dist/lang/es.js +68 -5
- package/dist/lang/fi.cjs +21 -1
- package/dist/lang/fi.js +21 -1
- package/dist/lang/zh.cjs +38 -7
- package/dist/lang/zh.js +38 -7
- package/package.json +1 -1
- package/src/core/ir.ts +5 -0
- package/src/core/util.ts +52 -1
- package/src/lang/de/index.ts +95 -25
- package/src/lang/en/dialects.ts +6 -2
- package/src/lang/en/index.ts +781 -117
- package/src/lang/es/index.ts +85 -9
- package/src/lang/fi/index.ts +6 -2
- package/src/lang/zh/index.ts +44 -18
- package/types/core/ir.d.ts +1 -0
- package/types/core/util.d.ts +10 -1
package/dist/cronli5.cjs
CHANGED
|
@@ -110,6 +110,26 @@ function arithmeticStep(values) {
|
|
|
110
110
|
}
|
|
111
111
|
return { start: values[0], interval, last: values[values.length - 1] };
|
|
112
112
|
}
|
|
113
|
+
function weekdayDisplayKey(value) {
|
|
114
|
+
return value === 0 ? 7 : value;
|
|
115
|
+
}
|
|
116
|
+
function orderWeekdaysForDisplay(segments) {
|
|
117
|
+
const flattened = segments.flatMap(function flat(segment) {
|
|
118
|
+
return segment.kind === "step" ? segment.fires.map(function single(value) {
|
|
119
|
+
return { kind: "single", value: "" + value };
|
|
120
|
+
}) : [segment];
|
|
121
|
+
});
|
|
122
|
+
function key(segment) {
|
|
123
|
+
return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
|
|
124
|
+
}
|
|
125
|
+
return flattened.map(function index(segment, position) {
|
|
126
|
+
return [segment, position];
|
|
127
|
+
}).sort(function byDisplayKey(a, b) {
|
|
128
|
+
return key(a[0]) - key(b[0]) || a[1] - b[1];
|
|
129
|
+
}).map(function unwrap(pair) {
|
|
130
|
+
return pair[0];
|
|
131
|
+
});
|
|
132
|
+
}
|
|
113
133
|
function toFieldNumber(token, numberMap) {
|
|
114
134
|
return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
|
|
115
135
|
}
|
|
@@ -855,7 +875,8 @@ var dialects = {
|
|
|
855
875
|
pm: "p.m.",
|
|
856
876
|
sep: ":",
|
|
857
877
|
serialComma: true,
|
|
858
|
-
through: " through "
|
|
878
|
+
through: " through ",
|
|
879
|
+
untilWindow: true
|
|
859
880
|
},
|
|
860
881
|
house: {
|
|
861
882
|
am: "AM",
|
|
@@ -872,7 +893,7 @@ var dialects = {
|
|
|
872
893
|
};
|
|
873
894
|
function resolveDialect(dialect) {
|
|
874
895
|
if (typeof dialect === "object" && dialect !== null) {
|
|
875
|
-
return { ...dialects.us, ...dialect };
|
|
896
|
+
return { ...dialects.us, untilWindow: false, ...dialect };
|
|
876
897
|
}
|
|
877
898
|
const name = dialect === "uk" ? "gb" : dialect;
|
|
878
899
|
return dialects[name] || dialects.us;
|
|
@@ -944,7 +965,9 @@ function normalizeOptions(options) {
|
|
|
944
965
|
};
|
|
945
966
|
}
|
|
946
967
|
function describe(ir, opts) {
|
|
947
|
-
|
|
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);
|
|
948
971
|
}
|
|
949
972
|
function render(ir, plan, opts) {
|
|
950
973
|
const renderer = renderers[plan.kind];
|
|
@@ -1037,7 +1060,7 @@ function secondsClause(ir, anchor, opts) {
|
|
|
1037
1060
|
}
|
|
1038
1061
|
if (shape === "range") {
|
|
1039
1062
|
const bounds = secondField.split("-");
|
|
1040
|
-
const num = seriesNumber(
|
|
1063
|
+
const num = seriesNumber();
|
|
1041
1064
|
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
|
|
1042
1065
|
}
|
|
1043
1066
|
if (shape === "single") {
|
|
@@ -1103,15 +1126,21 @@ function renderMinutesAcrossHours(ir, plan, opts) {
|
|
|
1103
1126
|
}
|
|
1104
1127
|
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1105
1128
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
"
|
|
1113
|
-
|
|
1114
|
-
)
|
|
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
|
|
1115
1144
|
);
|
|
1116
1145
|
if (cadence !== null) {
|
|
1117
1146
|
return lead + ", " + cadence + trailingQualifier(ir, opts);
|
|
@@ -1148,7 +1177,7 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
|
1148
1177
|
}
|
|
1149
1178
|
function minuteRangeLead(minuteField, opts) {
|
|
1150
1179
|
const bounds = minuteField.split("-");
|
|
1151
|
-
const num = seriesNumber(
|
|
1180
|
+
const num = seriesNumber();
|
|
1152
1181
|
return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
|
|
1153
1182
|
}
|
|
1154
1183
|
function renderEveryHour(ir, plan, opts) {
|
|
@@ -1188,10 +1217,18 @@ function renderHourStep(ir, plan, opts) {
|
|
|
1188
1217
|
return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
|
|
1189
1218
|
}
|
|
1190
1219
|
function boundedWindow(plan) {
|
|
1191
|
-
|
|
1220
|
+
const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
|
|
1221
|
+
return { from: plan.from, last, to: plan.to };
|
|
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);
|
|
1192
1229
|
}
|
|
1193
1230
|
function hourWindow(window, opts) {
|
|
1194
|
-
return
|
|
1231
|
+
return rangeWindow(window.from, window.to, window.last, opts);
|
|
1195
1232
|
}
|
|
1196
1233
|
function renderClockTimes(ir, plan, opts) {
|
|
1197
1234
|
if (ir.shapes.minute === "single") {
|
|
@@ -1210,7 +1247,10 @@ function renderClockTimes(ir, plan, opts) {
|
|
|
1210
1247
|
plain
|
|
1211
1248
|
}, opts);
|
|
1212
1249
|
});
|
|
1213
|
-
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) : "";
|
|
1214
1254
|
}
|
|
1215
1255
|
function renderCompactClockTimes(ir, plan, opts) {
|
|
1216
1256
|
if (plan.fold) {
|
|
@@ -1225,7 +1265,7 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1225
1265
|
return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
|
|
1226
1266
|
}
|
|
1227
1267
|
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
1228
|
-
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
1268
|
+
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts) + dayUnionTrail(ir, opts);
|
|
1229
1269
|
}
|
|
1230
1270
|
const minuteLead = (
|
|
1231
1271
|
// The non-fold branch is a minute list, which has segments. An
|
|
@@ -1244,26 +1284,161 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1244
1284
|
function foldedHourWindows(ir, plan, opts) {
|
|
1245
1285
|
const minute = plan.minute;
|
|
1246
1286
|
const windows = [];
|
|
1247
|
-
const
|
|
1287
|
+
const outliers = collectHourOutliers(ir);
|
|
1288
|
+
const times = outliers.hours.map(function time(hour) {
|
|
1289
|
+
return getTime({ hour, minute }, opts);
|
|
1290
|
+
});
|
|
1248
1291
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1249
1292
|
if (segment.kind === "range") {
|
|
1250
|
-
windows.push(
|
|
1251
|
-
|
|
1293
|
+
windows.push(rangeWindow(
|
|
1294
|
+
+segment.bounds[0],
|
|
1295
|
+
+segment.bounds[1],
|
|
1296
|
+
minute,
|
|
1252
1297
|
opts
|
|
1253
|
-
)
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1298
|
+
));
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
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);
|
|
1258
1313
|
}
|
|
1259
1314
|
});
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1315
|
+
return { hours, pureStrays };
|
|
1316
|
+
}
|
|
1317
|
+
function outlierTail(times, pureStrays, opts) {
|
|
1318
|
+
if (!times.length) {
|
|
1319
|
+
return "";
|
|
1265
1320
|
}
|
|
1266
|
-
|
|
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);
|
|
1267
1442
|
}
|
|
1268
1443
|
var renderers = {
|
|
1269
1444
|
clockTimes: renderClockTimes,
|
|
@@ -1295,7 +1470,7 @@ function renderStride(stride, opts) {
|
|
|
1295
1470
|
if (start < interval && tiles) {
|
|
1296
1471
|
return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
1297
1472
|
}
|
|
1298
|
-
const num = seriesNumber(
|
|
1473
|
+
const num = seriesNumber();
|
|
1299
1474
|
return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
|
|
1300
1475
|
}
|
|
1301
1476
|
function singleValues(segments) {
|
|
@@ -1422,9 +1597,9 @@ function hourCadence(ir, minute, opts) {
|
|
|
1422
1597
|
if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
|
|
1423
1598
|
return null;
|
|
1424
1599
|
}
|
|
1425
|
-
const
|
|
1426
|
-
if (
|
|
1427
|
-
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);
|
|
1428
1603
|
}
|
|
1429
1604
|
if (minute === 0 && ir.pattern.second === "0") {
|
|
1430
1605
|
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
@@ -1446,26 +1621,22 @@ function hasHourWindow(ir) {
|
|
|
1446
1621
|
}
|
|
1447
1622
|
function hourRangeWindowTail(ir, opts) {
|
|
1448
1623
|
const windows = [];
|
|
1449
|
-
const
|
|
1624
|
+
const outliers = collectHourOutliers(ir);
|
|
1450
1625
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1451
1626
|
if (segment.kind === "range") {
|
|
1452
|
-
windows.push(
|
|
1453
|
-
|
|
1627
|
+
windows.push(rangeWindow(
|
|
1628
|
+
+segment.bounds[0],
|
|
1629
|
+
+segment.bounds[1],
|
|
1630
|
+
0,
|
|
1454
1631
|
opts
|
|
1455
|
-
)
|
|
1456
|
-
} else if (segment.kind === "step") {
|
|
1457
|
-
singles.push(...segment.fires);
|
|
1458
|
-
} else {
|
|
1459
|
-
singles.push(+segment.value);
|
|
1632
|
+
));
|
|
1460
1633
|
}
|
|
1461
1634
|
});
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
}
|
|
1468
|
-
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);
|
|
1469
1640
|
}
|
|
1470
1641
|
function hourRangeCadence(ir, minute, opts) {
|
|
1471
1642
|
if (minute !== 0 || !hasHourWindow(ir)) {
|
|
@@ -1479,25 +1650,29 @@ function hourRangeCadence(ir, minute, opts) {
|
|
|
1479
1650
|
}
|
|
1480
1651
|
return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
|
|
1481
1652
|
}
|
|
1482
|
-
function seriesNumber(
|
|
1483
|
-
const anyBig = values.some(function big(v) {
|
|
1484
|
-
return +v > 10;
|
|
1485
|
-
});
|
|
1653
|
+
function seriesNumber() {
|
|
1486
1654
|
return function format(n) {
|
|
1487
|
-
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);
|
|
1488
1663
|
};
|
|
1489
1664
|
}
|
|
1490
1665
|
function numberWords(fires, opts) {
|
|
1491
|
-
return fires.map(
|
|
1666
|
+
return fires.map(listNumber(fires.length, opts));
|
|
1492
1667
|
}
|
|
1493
1668
|
function segmentWords(segments, opts) {
|
|
1494
|
-
const
|
|
1669
|
+
const count = segments.reduce(function tally(sum, segment) {
|
|
1495
1670
|
if (segment.kind === "range") {
|
|
1496
|
-
return
|
|
1671
|
+
return sum + 1;
|
|
1497
1672
|
}
|
|
1498
|
-
return segment.kind === "step" ? segment.fires :
|
|
1499
|
-
});
|
|
1500
|
-
const num =
|
|
1673
|
+
return sum + (segment.kind === "step" ? segment.fires.length : 1);
|
|
1674
|
+
}, 0);
|
|
1675
|
+
const num = listNumber(count, opts);
|
|
1501
1676
|
return segments.flatMap(function word(segment) {
|
|
1502
1677
|
if (segment.kind === "range") {
|
|
1503
1678
|
return [num(segment.bounds[0]) + through(opts) + num(segment.bounds[1])];
|
|
@@ -1529,6 +1704,9 @@ function hourTimes(hours, opts) {
|
|
|
1529
1704
|
});
|
|
1530
1705
|
return joinList(times, opts);
|
|
1531
1706
|
}
|
|
1707
|
+
function singleHourFire(times) {
|
|
1708
|
+
return times.kind === "fires" && times.fires.length === 1;
|
|
1709
|
+
}
|
|
1532
1710
|
function hourTimesFromPlan(ir, times, atContext, opts) {
|
|
1533
1711
|
if (times.kind === "fires") {
|
|
1534
1712
|
return hourTimes(times.fires, opts);
|
|
@@ -1576,28 +1754,47 @@ function disambiguateTimes(pieces, segments, atContext) {
|
|
|
1576
1754
|
return index === 0 ? piece : "at " + piece;
|
|
1577
1755
|
});
|
|
1578
1756
|
}
|
|
1579
|
-
function
|
|
1757
|
+
function joinWith(items, conjunction, opts) {
|
|
1580
1758
|
if (items.length <= 1) {
|
|
1581
1759
|
return items.join("");
|
|
1582
1760
|
}
|
|
1583
1761
|
if (items.length === 2) {
|
|
1584
|
-
return items[0] +
|
|
1762
|
+
return items[0] + conjunction + items[1];
|
|
1585
1763
|
}
|
|
1586
|
-
const
|
|
1587
|
-
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);
|
|
1588
1772
|
}
|
|
1589
|
-
var trailingWords = {
|
|
1773
|
+
var trailingWords = {
|
|
1774
|
+
all: "",
|
|
1775
|
+
month: "in ",
|
|
1776
|
+
recurringWeekday: true,
|
|
1777
|
+
stepDate: "on ",
|
|
1778
|
+
weekday: "on "
|
|
1779
|
+
};
|
|
1590
1780
|
var leadingWords = {
|
|
1591
1781
|
all: "every day",
|
|
1592
1782
|
month: "every day in ",
|
|
1783
|
+
recurringWeekday: false,
|
|
1593
1784
|
stepDate: "",
|
|
1594
1785
|
weekday: "every "
|
|
1595
1786
|
};
|
|
1596
1787
|
function trailingQualifier(ir, opts) {
|
|
1788
|
+
if (isDayUnion(ir, opts)) {
|
|
1789
|
+
return dayUnionCondition(ir, opts);
|
|
1790
|
+
}
|
|
1597
1791
|
const phrase = dayQualifier(ir, trailingWords, opts);
|
|
1598
1792
|
return phrase && " " + phrase;
|
|
1599
1793
|
}
|
|
1600
1794
|
function interpretDayQualifier(ir, opts) {
|
|
1795
|
+
if (isDayUnion(ir, opts)) {
|
|
1796
|
+
return "";
|
|
1797
|
+
}
|
|
1601
1798
|
return dayQualifier(ir, leadingWords, opts) + " ";
|
|
1602
1799
|
}
|
|
1603
1800
|
function dayQualifier(ir, words, opts) {
|
|
@@ -1609,7 +1806,11 @@ function dayQualifier(ir, words, opts) {
|
|
|
1609
1806
|
return datePhrase(ir, words, opts);
|
|
1610
1807
|
}
|
|
1611
1808
|
if (pattern.weekday !== "*") {
|
|
1612
|
-
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);
|
|
1613
1814
|
return weekdays + monthScope(ir, opts);
|
|
1614
1815
|
}
|
|
1615
1816
|
if (pattern.month !== "*") {
|
|
@@ -1621,10 +1822,14 @@ function datePhrase(ir, words, opts) {
|
|
|
1621
1822
|
const pattern = ir.pattern;
|
|
1622
1823
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
1623
1824
|
if (quartzDate) {
|
|
1624
|
-
return quartzDate
|
|
1825
|
+
return monthScopeForRecurrence(quartzDate, ir, opts);
|
|
1625
1826
|
}
|
|
1626
1827
|
if (isOpenStep(pattern.date)) {
|
|
1627
|
-
return
|
|
1828
|
+
return monthScopeForRecurrence(
|
|
1829
|
+
words.stepDate + stepDates(pattern.date),
|
|
1830
|
+
ir,
|
|
1831
|
+
opts
|
|
1832
|
+
);
|
|
1628
1833
|
}
|
|
1629
1834
|
if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
|
|
1630
1835
|
return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
|
|
@@ -1640,20 +1845,105 @@ function monthFoldsIntoDate(ir) {
|
|
|
1640
1845
|
return segment.kind !== "range";
|
|
1641
1846
|
});
|
|
1642
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
|
+
}
|
|
1643
1923
|
function dateOrWeekday(ir, opts) {
|
|
1644
1924
|
const pattern = ir.pattern;
|
|
1645
|
-
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, opts);
|
|
1925
|
+
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, false, opts);
|
|
1926
|
+
if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
|
|
1927
|
+
return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
|
|
1928
|
+
}
|
|
1929
|
+
return datePart(ir, opts) + " or " + weekdayPart + orMonthScope(ir, opts);
|
|
1930
|
+
}
|
|
1931
|
+
function datePart(ir, opts) {
|
|
1932
|
+
const pattern = ir.pattern;
|
|
1646
1933
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
1647
1934
|
if (quartzDate) {
|
|
1648
|
-
return quartzDate
|
|
1935
|
+
return quartzDate;
|
|
1649
1936
|
}
|
|
1650
1937
|
if (isOpenStep(pattern.date)) {
|
|
1651
|
-
return stepDates(pattern.date)
|
|
1938
|
+
return stepDates(pattern.date);
|
|
1652
1939
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1940
|
+
return "on the " + dateOrdinals(ir, opts);
|
|
1941
|
+
}
|
|
1942
|
+
function orMonthScope(ir, opts) {
|
|
1943
|
+
if (ir.pattern.month === "*") {
|
|
1944
|
+
return "";
|
|
1655
1945
|
}
|
|
1656
|
-
return "
|
|
1946
|
+
return ", in " + monthName(ir, opts);
|
|
1657
1947
|
}
|
|
1658
1948
|
function quartzDatePhrase(dateField, opts) {
|
|
1659
1949
|
if (dateField === "L") {
|
|
@@ -1687,6 +1977,9 @@ function monthDatePhrase(ir, opts) {
|
|
|
1687
1977
|
opts.style.ordinals ? getOrdinal : cardinalDay,
|
|
1688
1978
|
opts
|
|
1689
1979
|
);
|
|
1980
|
+
if (opts.style.dayFirst && ir.shapes.date === "single" && ir.shapes.month !== "single") {
|
|
1981
|
+
return "the " + getOrdinal(ir.pattern.date) + " of " + month;
|
|
1982
|
+
}
|
|
1690
1983
|
return opts.style.dayFirst ? days + " " + month : month + " " + days;
|
|
1691
1984
|
}
|
|
1692
1985
|
function cardinalDay(value) {
|
|
@@ -1698,6 +1991,19 @@ function monthScope(ir, opts) {
|
|
|
1698
1991
|
}
|
|
1699
1992
|
return " in " + monthName(ir, opts);
|
|
1700
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
|
+
}
|
|
1701
2007
|
function stepDates(dateField) {
|
|
1702
2008
|
const parts = dateField.split("/");
|
|
1703
2009
|
const interval = +parts[1];
|
|
@@ -1734,10 +2040,21 @@ function oddEvenMonth(monthField) {
|
|
|
1734
2040
|
}
|
|
1735
2041
|
return start === "2" ? "every even-numbered month" : null;
|
|
1736
2042
|
}
|
|
1737
|
-
function weekdayPhrase(ir, opts) {
|
|
1738
|
-
|
|
2043
|
+
function weekdayPhrase(ir, recurring, opts) {
|
|
2044
|
+
const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday);
|
|
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) {
|
|
1739
2051
|
return getWeekday(value, opts);
|
|
1740
|
-
}
|
|
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";
|
|
1741
2058
|
}
|
|
1742
2059
|
function renderSegments(segments, word, opts) {
|
|
1743
2060
|
const pieces = [];
|
|
@@ -1761,7 +2078,7 @@ function applyYear(description, ir, opts) {
|
|
|
1761
2078
|
return description;
|
|
1762
2079
|
}
|
|
1763
2080
|
if (yearField.indexOf("/") !== -1) {
|
|
1764
|
-
return description + " " + stepYears(yearField, opts);
|
|
2081
|
+
return description + ", " + stepYears(yearField, opts);
|
|
1765
2082
|
}
|
|
1766
2083
|
const label = yearLabel(yearField, opts);
|
|
1767
2084
|
if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
|
|
@@ -1774,6 +2091,9 @@ function yearLabel(yearField, opts) {
|
|
|
1774
2091
|
if (yearField.indexOf(",") !== -1) {
|
|
1775
2092
|
return joinList(yearField.split(","), opts);
|
|
1776
2093
|
}
|
|
2094
|
+
if (yearField.indexOf("-") !== -1) {
|
|
2095
|
+
return yearField.split("-").join(through(opts));
|
|
2096
|
+
}
|
|
1777
2097
|
return yearField;
|
|
1778
2098
|
}
|
|
1779
2099
|
function stepYears(yearField, opts) {
|
|
@@ -1783,7 +2103,7 @@ function stepYears(yearField, opts) {
|
|
|
1783
2103
|
if (interval <= 1) {
|
|
1784
2104
|
return "every year";
|
|
1785
2105
|
}
|
|
1786
|
-
let phrase = "every " + getNumber(interval, opts) + " years";
|
|
2106
|
+
let phrase = interval === 2 ? "every other year" : "every " + getNumber(interval, opts) + " years";
|
|
1787
2107
|
if (start !== "*" && start !== "0") {
|
|
1788
2108
|
phrase += " from " + start;
|
|
1789
2109
|
}
|