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.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
|
}
|
|
@@ -829,7 +849,8 @@ var dialects = {
|
|
|
829
849
|
pm: "p.m.",
|
|
830
850
|
sep: ":",
|
|
831
851
|
serialComma: true,
|
|
832
|
-
through: " through "
|
|
852
|
+
through: " through ",
|
|
853
|
+
untilWindow: true
|
|
833
854
|
},
|
|
834
855
|
house: {
|
|
835
856
|
am: "AM",
|
|
@@ -846,7 +867,7 @@ var dialects = {
|
|
|
846
867
|
};
|
|
847
868
|
function resolveDialect(dialect) {
|
|
848
869
|
if (typeof dialect === "object" && dialect !== null) {
|
|
849
|
-
return { ...dialects.us, ...dialect };
|
|
870
|
+
return { ...dialects.us, untilWindow: false, ...dialect };
|
|
850
871
|
}
|
|
851
872
|
const name = dialect === "uk" ? "gb" : dialect;
|
|
852
873
|
return dialects[name] || dialects.us;
|
|
@@ -918,7 +939,9 @@ function normalizeOptions(options) {
|
|
|
918
939
|
};
|
|
919
940
|
}
|
|
920
941
|
function describe(ir, opts) {
|
|
921
|
-
|
|
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);
|
|
922
945
|
}
|
|
923
946
|
function render(ir, plan, opts) {
|
|
924
947
|
const renderer = renderers[plan.kind];
|
|
@@ -1011,7 +1034,7 @@ function secondsClause(ir, anchor, opts) {
|
|
|
1011
1034
|
}
|
|
1012
1035
|
if (shape === "range") {
|
|
1013
1036
|
const bounds = secondField.split("-");
|
|
1014
|
-
const num = seriesNumber(
|
|
1037
|
+
const num = seriesNumber();
|
|
1015
1038
|
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
|
|
1016
1039
|
}
|
|
1017
1040
|
if (shape === "single") {
|
|
@@ -1077,15 +1100,21 @@ function renderMinutesAcrossHours(ir, plan, opts) {
|
|
|
1077
1100
|
}
|
|
1078
1101
|
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1079
1102
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
"
|
|
1087
|
-
|
|
1088
|
-
)
|
|
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
|
|
1089
1118
|
);
|
|
1090
1119
|
if (cadence !== null) {
|
|
1091
1120
|
return lead + ", " + cadence + trailingQualifier(ir, opts);
|
|
@@ -1122,7 +1151,7 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
|
1122
1151
|
}
|
|
1123
1152
|
function minuteRangeLead(minuteField, opts) {
|
|
1124
1153
|
const bounds = minuteField.split("-");
|
|
1125
|
-
const num = seriesNumber(
|
|
1154
|
+
const num = seriesNumber();
|
|
1126
1155
|
return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
|
|
1127
1156
|
}
|
|
1128
1157
|
function renderEveryHour(ir, plan, opts) {
|
|
@@ -1162,10 +1191,18 @@ function renderHourStep(ir, plan, opts) {
|
|
|
1162
1191
|
return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
|
|
1163
1192
|
}
|
|
1164
1193
|
function boundedWindow(plan) {
|
|
1165
|
-
|
|
1194
|
+
const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
|
|
1195
|
+
return { from: plan.from, last, to: plan.to };
|
|
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);
|
|
1166
1203
|
}
|
|
1167
1204
|
function hourWindow(window, opts) {
|
|
1168
|
-
return
|
|
1205
|
+
return rangeWindow(window.from, window.to, window.last, opts);
|
|
1169
1206
|
}
|
|
1170
1207
|
function renderClockTimes(ir, plan, opts) {
|
|
1171
1208
|
if (ir.shapes.minute === "single") {
|
|
@@ -1184,7 +1221,10 @@ function renderClockTimes(ir, plan, opts) {
|
|
|
1184
1221
|
plain
|
|
1185
1222
|
}, opts);
|
|
1186
1223
|
});
|
|
1187
|
-
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) : "";
|
|
1188
1228
|
}
|
|
1189
1229
|
function renderCompactClockTimes(ir, plan, opts) {
|
|
1190
1230
|
if (plan.fold) {
|
|
@@ -1199,7 +1239,7 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1199
1239
|
return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
|
|
1200
1240
|
}
|
|
1201
1241
|
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
1202
|
-
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
1242
|
+
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts) + dayUnionTrail(ir, opts);
|
|
1203
1243
|
}
|
|
1204
1244
|
const minuteLead = (
|
|
1205
1245
|
// The non-fold branch is a minute list, which has segments. An
|
|
@@ -1218,26 +1258,161 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1218
1258
|
function foldedHourWindows(ir, plan, opts) {
|
|
1219
1259
|
const minute = plan.minute;
|
|
1220
1260
|
const windows = [];
|
|
1221
|
-
const
|
|
1261
|
+
const outliers = collectHourOutliers(ir);
|
|
1262
|
+
const times = outliers.hours.map(function time(hour) {
|
|
1263
|
+
return getTime({ hour, minute }, opts);
|
|
1264
|
+
});
|
|
1222
1265
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1223
1266
|
if (segment.kind === "range") {
|
|
1224
|
-
windows.push(
|
|
1225
|
-
|
|
1267
|
+
windows.push(rangeWindow(
|
|
1268
|
+
+segment.bounds[0],
|
|
1269
|
+
+segment.bounds[1],
|
|
1270
|
+
minute,
|
|
1226
1271
|
opts
|
|
1227
|
-
)
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1272
|
+
));
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
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);
|
|
1232
1287
|
}
|
|
1233
1288
|
});
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1289
|
+
return { hours, pureStrays };
|
|
1290
|
+
}
|
|
1291
|
+
function outlierTail(times, pureStrays, opts) {
|
|
1292
|
+
if (!times.length) {
|
|
1293
|
+
return "";
|
|
1239
1294
|
}
|
|
1240
|
-
|
|
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);
|
|
1241
1416
|
}
|
|
1242
1417
|
var renderers = {
|
|
1243
1418
|
clockTimes: renderClockTimes,
|
|
@@ -1269,7 +1444,7 @@ function renderStride(stride, opts) {
|
|
|
1269
1444
|
if (start < interval && tiles) {
|
|
1270
1445
|
return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
1271
1446
|
}
|
|
1272
|
-
const num = seriesNumber(
|
|
1447
|
+
const num = seriesNumber();
|
|
1273
1448
|
return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
|
|
1274
1449
|
}
|
|
1275
1450
|
function singleValues(segments) {
|
|
@@ -1396,9 +1571,9 @@ function hourCadence(ir, minute, opts) {
|
|
|
1396
1571
|
if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
|
|
1397
1572
|
return null;
|
|
1398
1573
|
}
|
|
1399
|
-
const
|
|
1400
|
-
if (
|
|
1401
|
-
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);
|
|
1402
1577
|
}
|
|
1403
1578
|
if (minute === 0 && ir.pattern.second === "0") {
|
|
1404
1579
|
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
@@ -1420,26 +1595,22 @@ function hasHourWindow(ir) {
|
|
|
1420
1595
|
}
|
|
1421
1596
|
function hourRangeWindowTail(ir, opts) {
|
|
1422
1597
|
const windows = [];
|
|
1423
|
-
const
|
|
1598
|
+
const outliers = collectHourOutliers(ir);
|
|
1424
1599
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1425
1600
|
if (segment.kind === "range") {
|
|
1426
|
-
windows.push(
|
|
1427
|
-
|
|
1601
|
+
windows.push(rangeWindow(
|
|
1602
|
+
+segment.bounds[0],
|
|
1603
|
+
+segment.bounds[1],
|
|
1604
|
+
0,
|
|
1428
1605
|
opts
|
|
1429
|
-
)
|
|
1430
|
-
} else if (segment.kind === "step") {
|
|
1431
|
-
singles.push(...segment.fires);
|
|
1432
|
-
} else {
|
|
1433
|
-
singles.push(+segment.value);
|
|
1606
|
+
));
|
|
1434
1607
|
}
|
|
1435
1608
|
});
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
}
|
|
1442
|
-
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);
|
|
1443
1614
|
}
|
|
1444
1615
|
function hourRangeCadence(ir, minute, opts) {
|
|
1445
1616
|
if (minute !== 0 || !hasHourWindow(ir)) {
|
|
@@ -1453,25 +1624,29 @@ function hourRangeCadence(ir, minute, opts) {
|
|
|
1453
1624
|
}
|
|
1454
1625
|
return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
|
|
1455
1626
|
}
|
|
1456
|
-
function seriesNumber(
|
|
1457
|
-
const anyBig = values.some(function big(v) {
|
|
1458
|
-
return +v > 10;
|
|
1459
|
-
});
|
|
1627
|
+
function seriesNumber() {
|
|
1460
1628
|
return function format(n) {
|
|
1461
|
-
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);
|
|
1462
1637
|
};
|
|
1463
1638
|
}
|
|
1464
1639
|
function numberWords(fires, opts) {
|
|
1465
|
-
return fires.map(
|
|
1640
|
+
return fires.map(listNumber(fires.length, opts));
|
|
1466
1641
|
}
|
|
1467
1642
|
function segmentWords(segments, opts) {
|
|
1468
|
-
const
|
|
1643
|
+
const count = segments.reduce(function tally(sum, segment) {
|
|
1469
1644
|
if (segment.kind === "range") {
|
|
1470
|
-
return
|
|
1645
|
+
return sum + 1;
|
|
1471
1646
|
}
|
|
1472
|
-
return segment.kind === "step" ? segment.fires :
|
|
1473
|
-
});
|
|
1474
|
-
const num =
|
|
1647
|
+
return sum + (segment.kind === "step" ? segment.fires.length : 1);
|
|
1648
|
+
}, 0);
|
|
1649
|
+
const num = listNumber(count, opts);
|
|
1475
1650
|
return segments.flatMap(function word(segment) {
|
|
1476
1651
|
if (segment.kind === "range") {
|
|
1477
1652
|
return [num(segment.bounds[0]) + through(opts) + num(segment.bounds[1])];
|
|
@@ -1503,6 +1678,9 @@ function hourTimes(hours, opts) {
|
|
|
1503
1678
|
});
|
|
1504
1679
|
return joinList(times, opts);
|
|
1505
1680
|
}
|
|
1681
|
+
function singleHourFire(times) {
|
|
1682
|
+
return times.kind === "fires" && times.fires.length === 1;
|
|
1683
|
+
}
|
|
1506
1684
|
function hourTimesFromPlan(ir, times, atContext, opts) {
|
|
1507
1685
|
if (times.kind === "fires") {
|
|
1508
1686
|
return hourTimes(times.fires, opts);
|
|
@@ -1550,28 +1728,47 @@ function disambiguateTimes(pieces, segments, atContext) {
|
|
|
1550
1728
|
return index === 0 ? piece : "at " + piece;
|
|
1551
1729
|
});
|
|
1552
1730
|
}
|
|
1553
|
-
function
|
|
1731
|
+
function joinWith(items, conjunction, opts) {
|
|
1554
1732
|
if (items.length <= 1) {
|
|
1555
1733
|
return items.join("");
|
|
1556
1734
|
}
|
|
1557
1735
|
if (items.length === 2) {
|
|
1558
|
-
return items[0] +
|
|
1736
|
+
return items[0] + conjunction + items[1];
|
|
1559
1737
|
}
|
|
1560
|
-
const
|
|
1561
|
-
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);
|
|
1562
1746
|
}
|
|
1563
|
-
var trailingWords = {
|
|
1747
|
+
var trailingWords = {
|
|
1748
|
+
all: "",
|
|
1749
|
+
month: "in ",
|
|
1750
|
+
recurringWeekday: true,
|
|
1751
|
+
stepDate: "on ",
|
|
1752
|
+
weekday: "on "
|
|
1753
|
+
};
|
|
1564
1754
|
var leadingWords = {
|
|
1565
1755
|
all: "every day",
|
|
1566
1756
|
month: "every day in ",
|
|
1757
|
+
recurringWeekday: false,
|
|
1567
1758
|
stepDate: "",
|
|
1568
1759
|
weekday: "every "
|
|
1569
1760
|
};
|
|
1570
1761
|
function trailingQualifier(ir, opts) {
|
|
1762
|
+
if (isDayUnion(ir, opts)) {
|
|
1763
|
+
return dayUnionCondition(ir, opts);
|
|
1764
|
+
}
|
|
1571
1765
|
const phrase = dayQualifier(ir, trailingWords, opts);
|
|
1572
1766
|
return phrase && " " + phrase;
|
|
1573
1767
|
}
|
|
1574
1768
|
function interpretDayQualifier(ir, opts) {
|
|
1769
|
+
if (isDayUnion(ir, opts)) {
|
|
1770
|
+
return "";
|
|
1771
|
+
}
|
|
1575
1772
|
return dayQualifier(ir, leadingWords, opts) + " ";
|
|
1576
1773
|
}
|
|
1577
1774
|
function dayQualifier(ir, words, opts) {
|
|
@@ -1583,7 +1780,11 @@ function dayQualifier(ir, words, opts) {
|
|
|
1583
1780
|
return datePhrase(ir, words, opts);
|
|
1584
1781
|
}
|
|
1585
1782
|
if (pattern.weekday !== "*") {
|
|
1586
|
-
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);
|
|
1587
1788
|
return weekdays + monthScope(ir, opts);
|
|
1588
1789
|
}
|
|
1589
1790
|
if (pattern.month !== "*") {
|
|
@@ -1595,10 +1796,14 @@ function datePhrase(ir, words, opts) {
|
|
|
1595
1796
|
const pattern = ir.pattern;
|
|
1596
1797
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
1597
1798
|
if (quartzDate) {
|
|
1598
|
-
return quartzDate
|
|
1799
|
+
return monthScopeForRecurrence(quartzDate, ir, opts);
|
|
1599
1800
|
}
|
|
1600
1801
|
if (isOpenStep(pattern.date)) {
|
|
1601
|
-
return
|
|
1802
|
+
return monthScopeForRecurrence(
|
|
1803
|
+
words.stepDate + stepDates(pattern.date),
|
|
1804
|
+
ir,
|
|
1805
|
+
opts
|
|
1806
|
+
);
|
|
1602
1807
|
}
|
|
1603
1808
|
if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
|
|
1604
1809
|
return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
|
|
@@ -1614,20 +1819,105 @@ function monthFoldsIntoDate(ir) {
|
|
|
1614
1819
|
return segment.kind !== "range";
|
|
1615
1820
|
});
|
|
1616
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
|
+
}
|
|
1617
1897
|
function dateOrWeekday(ir, opts) {
|
|
1618
1898
|
const pattern = ir.pattern;
|
|
1619
|
-
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, opts);
|
|
1899
|
+
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, false, opts);
|
|
1900
|
+
if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
|
|
1901
|
+
return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
|
|
1902
|
+
}
|
|
1903
|
+
return datePart(ir, opts) + " or " + weekdayPart + orMonthScope(ir, opts);
|
|
1904
|
+
}
|
|
1905
|
+
function datePart(ir, opts) {
|
|
1906
|
+
const pattern = ir.pattern;
|
|
1620
1907
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
1621
1908
|
if (quartzDate) {
|
|
1622
|
-
return quartzDate
|
|
1909
|
+
return quartzDate;
|
|
1623
1910
|
}
|
|
1624
1911
|
if (isOpenStep(pattern.date)) {
|
|
1625
|
-
return stepDates(pattern.date)
|
|
1912
|
+
return stepDates(pattern.date);
|
|
1626
1913
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1914
|
+
return "on the " + dateOrdinals(ir, opts);
|
|
1915
|
+
}
|
|
1916
|
+
function orMonthScope(ir, opts) {
|
|
1917
|
+
if (ir.pattern.month === "*") {
|
|
1918
|
+
return "";
|
|
1629
1919
|
}
|
|
1630
|
-
return "
|
|
1920
|
+
return ", in " + monthName(ir, opts);
|
|
1631
1921
|
}
|
|
1632
1922
|
function quartzDatePhrase(dateField, opts) {
|
|
1633
1923
|
if (dateField === "L") {
|
|
@@ -1661,6 +1951,9 @@ function monthDatePhrase(ir, opts) {
|
|
|
1661
1951
|
opts.style.ordinals ? getOrdinal : cardinalDay,
|
|
1662
1952
|
opts
|
|
1663
1953
|
);
|
|
1954
|
+
if (opts.style.dayFirst && ir.shapes.date === "single" && ir.shapes.month !== "single") {
|
|
1955
|
+
return "the " + getOrdinal(ir.pattern.date) + " of " + month;
|
|
1956
|
+
}
|
|
1664
1957
|
return opts.style.dayFirst ? days + " " + month : month + " " + days;
|
|
1665
1958
|
}
|
|
1666
1959
|
function cardinalDay(value) {
|
|
@@ -1672,6 +1965,19 @@ function monthScope(ir, opts) {
|
|
|
1672
1965
|
}
|
|
1673
1966
|
return " in " + monthName(ir, opts);
|
|
1674
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
|
+
}
|
|
1675
1981
|
function stepDates(dateField) {
|
|
1676
1982
|
const parts = dateField.split("/");
|
|
1677
1983
|
const interval = +parts[1];
|
|
@@ -1708,10 +2014,21 @@ function oddEvenMonth(monthField) {
|
|
|
1708
2014
|
}
|
|
1709
2015
|
return start === "2" ? "every even-numbered month" : null;
|
|
1710
2016
|
}
|
|
1711
|
-
function weekdayPhrase(ir, opts) {
|
|
1712
|
-
|
|
2017
|
+
function weekdayPhrase(ir, recurring, opts) {
|
|
2018
|
+
const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday);
|
|
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) {
|
|
1713
2025
|
return getWeekday(value, opts);
|
|
1714
|
-
}
|
|
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";
|
|
1715
2032
|
}
|
|
1716
2033
|
function renderSegments(segments, word, opts) {
|
|
1717
2034
|
const pieces = [];
|
|
@@ -1735,7 +2052,7 @@ function applyYear(description, ir, opts) {
|
|
|
1735
2052
|
return description;
|
|
1736
2053
|
}
|
|
1737
2054
|
if (yearField.indexOf("/") !== -1) {
|
|
1738
|
-
return description + " " + stepYears(yearField, opts);
|
|
2055
|
+
return description + ", " + stepYears(yearField, opts);
|
|
1739
2056
|
}
|
|
1740
2057
|
const label = yearLabel(yearField, opts);
|
|
1741
2058
|
if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
|
|
@@ -1748,6 +2065,9 @@ function yearLabel(yearField, opts) {
|
|
|
1748
2065
|
if (yearField.indexOf(",") !== -1) {
|
|
1749
2066
|
return joinList(yearField.split(","), opts);
|
|
1750
2067
|
}
|
|
2068
|
+
if (yearField.indexOf("-") !== -1) {
|
|
2069
|
+
return yearField.split("-").join(through(opts));
|
|
2070
|
+
}
|
|
1751
2071
|
return yearField;
|
|
1752
2072
|
}
|
|
1753
2073
|
function stepYears(yearField, opts) {
|
|
@@ -1757,7 +2077,7 @@ function stepYears(yearField, opts) {
|
|
|
1757
2077
|
if (interval <= 1) {
|
|
1758
2078
|
return "every year";
|
|
1759
2079
|
}
|
|
1760
|
-
let phrase = "every " + getNumber(interval, opts) + " years";
|
|
2080
|
+
let phrase = interval === 2 ? "every other year" : "every " + getNumber(interval, opts) + " years";
|
|
1761
2081
|
if (start !== "*" && start !== "0") {
|
|
1762
2082
|
phrase += " from " + start;
|
|
1763
2083
|
}
|