cronli5 0.1.7 → 0.2.1
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 +68 -0
- package/README.md +2 -2
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +451 -144
- package/dist/cronli5.js +451 -144
- package/dist/lang/de.cjs +65 -65
- package/dist/lang/de.js +65 -65
- package/dist/lang/en.cjs +450 -141
- package/dist/lang/en.js +450 -141
- package/dist/lang/es.cjs +71 -72
- package/dist/lang/es.js +71 -72
- package/dist/lang/fi.cjs +71 -66
- package/dist/lang/fi.js +71 -66
- package/dist/lang/zh.cjs +36 -36
- package/dist/lang/zh.js +36 -36
- package/package.json +1 -1
- package/src/core/analyze.ts +14 -13
- package/src/core/ir.ts +13 -8
- package/src/core/shapes.ts +8 -1
- package/src/core/util.ts +86 -3
- package/src/core/validate.ts +1 -1
- package/src/cronli5.ts +3 -3
- package/src/lang/de/index.ts +30 -99
- package/src/lang/en/dialects.ts +6 -2
- package/src/lang/en/index.ts +820 -212
- package/src/lang/es/index.ts +36 -120
- package/src/lang/fi/index.ts +33 -104
- package/src/lang/zh/index.ts +23 -48
- package/src/types.ts +2 -2
- package/types/core/analyze.d.ts +2 -2
- package/types/core/ir.d.ts +8 -7
- package/types/core/shapes.d.ts +2 -1
- package/types/core/util.d.ts +17 -2
- package/types/types.d.ts +1 -1
package/dist/lang/en.js
CHANGED
|
@@ -34,6 +34,48 @@ function orderWeekdaysForDisplay(segments) {
|
|
|
34
34
|
return pair[0];
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
+
function segmentsOf(ir, field) {
|
|
38
|
+
return ir.analyses.segments[field] ?? [];
|
|
39
|
+
}
|
|
40
|
+
function stepSegment(ir, field) {
|
|
41
|
+
return segmentsOf(ir, field)[0];
|
|
42
|
+
}
|
|
43
|
+
function singleValues(segments) {
|
|
44
|
+
const values = [];
|
|
45
|
+
for (const segment of segments) {
|
|
46
|
+
if (segment.kind !== "single") {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
values.push(+segment.value);
|
|
50
|
+
}
|
|
51
|
+
return values;
|
|
52
|
+
}
|
|
53
|
+
function offsetCleanStride(stride) {
|
|
54
|
+
return stride.start < stride.interval && 24 % stride.interval === 0;
|
|
55
|
+
}
|
|
56
|
+
function hourListStride(values) {
|
|
57
|
+
if (values.length < 2) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const interval = values[1] - values[0];
|
|
61
|
+
if (interval < 2) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
for (let i = 2; i < values.length; i += 1) {
|
|
65
|
+
if (values[i] - values[i - 1] !== interval) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (values[0] !== 0 && values.length < 5) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return { interval, last: values[values.length - 1], start: values[0] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/core/shapes.ts
|
|
76
|
+
function isOpenStep(field) {
|
|
77
|
+
return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
|
|
78
|
+
}
|
|
37
79
|
|
|
38
80
|
// src/core/specs.ts
|
|
39
81
|
var maxClockTimes = 6;
|
|
@@ -78,7 +120,8 @@ var dialects = {
|
|
|
78
120
|
pm: "p.m.",
|
|
79
121
|
sep: ":",
|
|
80
122
|
serialComma: true,
|
|
81
|
-
through: " through "
|
|
123
|
+
through: " through ",
|
|
124
|
+
untilWindow: true
|
|
82
125
|
},
|
|
83
126
|
house: {
|
|
84
127
|
am: "AM",
|
|
@@ -95,7 +138,7 @@ var dialects = {
|
|
|
95
138
|
};
|
|
96
139
|
function resolveDialect(dialect) {
|
|
97
140
|
if (typeof dialect === "object" && dialect !== null) {
|
|
98
|
-
return { ...dialects.us, ...dialect };
|
|
141
|
+
return { ...dialects.us, untilWindow: false, ...dialect };
|
|
99
142
|
}
|
|
100
143
|
const name = dialect === "uk" ? "gb" : dialect;
|
|
101
144
|
return dialects[name] || dialects.us;
|
|
@@ -167,7 +210,9 @@ function normalizeOptions(options) {
|
|
|
167
210
|
};
|
|
168
211
|
}
|
|
169
212
|
function describe(ir, opts) {
|
|
170
|
-
|
|
213
|
+
const body = confinement(ir, opts) ?? render(ir, ir.plan, opts);
|
|
214
|
+
const lead = isDayUnion(ir, opts) ? dayUnionMonthLead(ir, opts) : "";
|
|
215
|
+
return applyYear(lead + body, ir, opts);
|
|
171
216
|
}
|
|
172
217
|
function render(ir, plan, opts) {
|
|
173
218
|
const renderer = renderers[plan.kind];
|
|
@@ -252,7 +297,7 @@ function secondsClause(ir, anchor, opts) {
|
|
|
252
297
|
}
|
|
253
298
|
if (shape === "step") {
|
|
254
299
|
return stepCycle60(
|
|
255
|
-
ir
|
|
300
|
+
stepSegment(ir, "second"),
|
|
256
301
|
"second",
|
|
257
302
|
anchor,
|
|
258
303
|
opts
|
|
@@ -260,19 +305,19 @@ function secondsClause(ir, anchor, opts) {
|
|
|
260
305
|
}
|
|
261
306
|
if (shape === "range") {
|
|
262
307
|
const bounds = secondField.split("-");
|
|
263
|
-
const num = seriesNumber(
|
|
308
|
+
const num = seriesNumber();
|
|
264
309
|
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
|
|
265
310
|
}
|
|
266
311
|
if (shape === "single") {
|
|
267
312
|
return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
|
|
268
313
|
}
|
|
269
314
|
return strideFromSegments(
|
|
270
|
-
ir
|
|
315
|
+
segmentsOf(ir, "second"),
|
|
271
316
|
"second",
|
|
272
317
|
anchor,
|
|
273
318
|
opts
|
|
274
319
|
) ?? listPastThe(
|
|
275
|
-
segmentWords(ir
|
|
320
|
+
segmentWords(segmentsOf(ir, "second"), opts),
|
|
276
321
|
"second",
|
|
277
322
|
anchor,
|
|
278
323
|
opts
|
|
@@ -289,15 +334,15 @@ function renderRangeOfMinutes(ir, plan, opts) {
|
|
|
289
334
|
return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
|
|
290
335
|
}
|
|
291
336
|
function renderMultipleMinutes(ir, plan, opts) {
|
|
292
|
-
const stride = strideFromSegments(ir
|
|
337
|
+
const stride = strideFromSegments(segmentsOf(ir, "minute"), "minute", "hour", opts);
|
|
293
338
|
return (stride ?? listPastThe(segmentWords(
|
|
294
|
-
ir
|
|
339
|
+
segmentsOf(ir, "minute"),
|
|
295
340
|
opts
|
|
296
341
|
), "minute", "hour", opts)) + trailingQualifier(ir, opts);
|
|
297
342
|
}
|
|
298
343
|
function renderMinuteFrequency(ir, plan, opts) {
|
|
299
344
|
let phrase = stepCycle60(
|
|
300
|
-
ir
|
|
345
|
+
stepSegment(ir, "minute"),
|
|
301
346
|
"minute",
|
|
302
347
|
"hour",
|
|
303
348
|
opts
|
|
@@ -306,9 +351,14 @@ function renderMinuteFrequency(ir, plan, opts) {
|
|
|
306
351
|
const cadence = unevenHourCadence(ir, opts);
|
|
307
352
|
phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
|
|
308
353
|
} else if (plan.hours.kind === "window") {
|
|
309
|
-
phrase += " " +
|
|
354
|
+
phrase += " " + rangeWindow({
|
|
355
|
+
continuous: false,
|
|
356
|
+
from: plan.hours.from,
|
|
357
|
+
throughMinute: plan.hours.last,
|
|
358
|
+
to: plan.hours.to
|
|
359
|
+
}, opts);
|
|
310
360
|
} else if (plan.hours.kind === "step") {
|
|
311
|
-
phrase += " " + everyNthHour(ir
|
|
361
|
+
phrase += " " + everyNthHour(stepSegment(ir, "hour"), opts);
|
|
312
362
|
}
|
|
313
363
|
return phrase + trailingQualifier(ir, opts);
|
|
314
364
|
}
|
|
@@ -326,15 +376,21 @@ function renderMinutesAcrossHours(ir, plan, opts) {
|
|
|
326
376
|
}
|
|
327
377
|
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
328
378
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
"
|
|
336
|
-
|
|
337
|
-
)
|
|
379
|
+
if (plan.form === "range") {
|
|
380
|
+
const lead2 = minuteRangeLead(ir.pattern.minute, opts);
|
|
381
|
+
if (cadence !== null) {
|
|
382
|
+
return lead2 + ", " + cadence + trailingQualifier(ir, opts);
|
|
383
|
+
}
|
|
384
|
+
if (singleHourFire(plan.times)) {
|
|
385
|
+
return lead2 + ", at " + hourTimesFromPlan(ir, plan.times, true, opts) + trailingQualifier(ir, opts);
|
|
386
|
+
}
|
|
387
|
+
return lead2 + " during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
388
|
+
}
|
|
389
|
+
const lead = strideFromSegments(segmentsOf(ir, "minute"), "minute", "hour", opts) ?? listPastThe(
|
|
390
|
+
segmentWords(segmentsOf(ir, "minute"), opts),
|
|
391
|
+
"minute",
|
|
392
|
+
"hour",
|
|
393
|
+
opts
|
|
338
394
|
);
|
|
339
395
|
if (cadence !== null) {
|
|
340
396
|
return lead + ", " + cadence + trailingQualifier(ir, opts);
|
|
@@ -356,12 +412,12 @@ function everyNthHour(segment, opts) {
|
|
|
356
412
|
return start === 0 ? base : base + " starting at " + getTime({ hour: start, minute: 0 }, opts);
|
|
357
413
|
}
|
|
358
414
|
function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
359
|
-
const segment = ir
|
|
415
|
+
const segment = stepSegment(ir, "hour");
|
|
360
416
|
if (plan.form === "wildcard") {
|
|
361
417
|
return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
|
|
362
418
|
}
|
|
363
|
-
const lead = plan.form === "list" ? strideFromSegments(ir
|
|
364
|
-
segmentWords(ir
|
|
419
|
+
const lead = plan.form === "list" ? strideFromSegments(segmentsOf(ir, "minute"), "minute", "hour", opts) ?? listPastThe(
|
|
420
|
+
segmentWords(segmentsOf(ir, "minute"), opts),
|
|
365
421
|
"minute",
|
|
366
422
|
"hour",
|
|
367
423
|
opts
|
|
@@ -371,7 +427,7 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
|
371
427
|
}
|
|
372
428
|
function minuteRangeLead(minuteField, opts) {
|
|
373
429
|
const bounds = minuteField.split("-");
|
|
374
|
-
const num = seriesNumber(
|
|
430
|
+
const num = seriesNumber();
|
|
375
431
|
return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
|
|
376
432
|
}
|
|
377
433
|
function renderEveryHour(ir, plan, opts) {
|
|
@@ -392,12 +448,12 @@ function rangeMinuteLead(ir, opts) {
|
|
|
392
448
|
return "every hour";
|
|
393
449
|
}
|
|
394
450
|
return strideFromSegments(
|
|
395
|
-
ir
|
|
451
|
+
segmentsOf(ir, "minute"),
|
|
396
452
|
"minute",
|
|
397
453
|
"hour",
|
|
398
454
|
opts
|
|
399
455
|
) ?? listPastThe(
|
|
400
|
-
segmentWords(ir
|
|
456
|
+
segmentWords(segmentsOf(ir, "minute"), opts),
|
|
401
457
|
"minute",
|
|
402
458
|
"hour",
|
|
403
459
|
opts
|
|
@@ -408,14 +464,28 @@ function renderHourStep(ir, plan, opts) {
|
|
|
408
464
|
if (cadence !== null) {
|
|
409
465
|
return cadence + trailingQualifier(ir, opts);
|
|
410
466
|
}
|
|
411
|
-
return stepHours(ir
|
|
467
|
+
return stepHours(stepSegment(ir, "hour"), opts) + trailingQualifier(ir, opts);
|
|
412
468
|
}
|
|
413
469
|
function boundedWindow(plan) {
|
|
414
|
-
const
|
|
415
|
-
|
|
470
|
+
const continuous = plan.minuteForm === "wildcard";
|
|
471
|
+
const closeMinute = continuous ? plan.boundMinute ?? 0 : 0;
|
|
472
|
+
return { from: plan.from, closeMinute, to: plan.to, continuous };
|
|
473
|
+
}
|
|
474
|
+
function rangeWindow(window, opts) {
|
|
475
|
+
const { from, to, throughMinute, continuous } = window;
|
|
476
|
+
const open = "from " + getTime({ hour: from, minute: 0 }, opts);
|
|
477
|
+
if (opts.style.untilWindow && !opts.short && from !== to) {
|
|
478
|
+
return continuous ? open + " until " + getTime({ hour: (to + 1) % 24, minute: 0 }, opts) : open + through(opts) + getTime({ hour: to, minute: 0 }, opts);
|
|
479
|
+
}
|
|
480
|
+
return open + through(opts) + getTime({ hour: to, minute: throughMinute }, opts);
|
|
416
481
|
}
|
|
417
482
|
function hourWindow(window, opts) {
|
|
418
|
-
return
|
|
483
|
+
return rangeWindow({
|
|
484
|
+
continuous: window.continuous,
|
|
485
|
+
from: window.from,
|
|
486
|
+
throughMinute: window.closeMinute,
|
|
487
|
+
to: window.to
|
|
488
|
+
}, opts);
|
|
419
489
|
}
|
|
420
490
|
function renderClockTimes(ir, plan, opts) {
|
|
421
491
|
if (ir.shapes.minute === "single") {
|
|
@@ -434,7 +504,10 @@ function renderClockTimes(ir, plan, opts) {
|
|
|
434
504
|
plain
|
|
435
505
|
}, opts);
|
|
436
506
|
});
|
|
437
|
-
return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts);
|
|
507
|
+
return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts) + dayUnionTrail(ir, opts);
|
|
508
|
+
}
|
|
509
|
+
function dayUnionTrail(ir, opts) {
|
|
510
|
+
return isDayUnion(ir, opts) ? dayUnionCondition(ir, opts) : "";
|
|
438
511
|
}
|
|
439
512
|
function renderCompactClockTimes(ir, plan, opts) {
|
|
440
513
|
if (plan.fold) {
|
|
@@ -442,20 +515,20 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
442
515
|
if (cadence2 !== null) {
|
|
443
516
|
return cadence2;
|
|
444
517
|
}
|
|
445
|
-
const hasRange = ir
|
|
518
|
+
const hasRange = segmentsOf(ir, "hour").some(function range(segment) {
|
|
446
519
|
return segment.kind === "range";
|
|
447
520
|
});
|
|
448
521
|
if (hasRange && !ir.analyses.clockSecond) {
|
|
449
522
|
return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
|
|
450
523
|
}
|
|
451
524
|
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
452
|
-
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
525
|
+
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts) + dayUnionTrail(ir, opts);
|
|
453
526
|
}
|
|
454
527
|
const minuteLead = (
|
|
455
528
|
// The non-fold branch is a minute list, which has segments. An
|
|
456
529
|
// offset/uneven step enumerated to that list reads as a stride.
|
|
457
|
-
strideFromSegments(ir
|
|
458
|
-
segmentWords(ir
|
|
530
|
+
strideFromSegments(segmentsOf(ir, "minute"), "minute", "hour", opts) ?? listPastThe(
|
|
531
|
+
segmentWords(segmentsOf(ir, "minute"), opts),
|
|
459
532
|
"minute",
|
|
460
533
|
"hour",
|
|
461
534
|
opts
|
|
@@ -468,26 +541,162 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
468
541
|
function foldedHourWindows(ir, plan, opts) {
|
|
469
542
|
const minute = plan.minute;
|
|
470
543
|
const windows = [];
|
|
471
|
-
const
|
|
472
|
-
|
|
544
|
+
const times = collectHourOutliers(ir).map(function time(hour) {
|
|
545
|
+
return getTime({ hour, minute }, opts);
|
|
546
|
+
});
|
|
547
|
+
segmentsOf(ir, "hour").forEach(function classify(segment) {
|
|
473
548
|
if (segment.kind === "range") {
|
|
474
|
-
windows.push(
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
} else {
|
|
481
|
-
singles.push(+segment.value);
|
|
549
|
+
windows.push(rangeWindow({
|
|
550
|
+
continuous: false,
|
|
551
|
+
from: +segment.bounds[0],
|
|
552
|
+
throughMinute: minute,
|
|
553
|
+
to: +segment.bounds[1]
|
|
554
|
+
}, opts));
|
|
482
555
|
}
|
|
483
556
|
});
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
557
|
+
const phrase = rangeMinuteLead(ir, opts) + " " + joinList(windows, opts);
|
|
558
|
+
return phrase + outlierTail(times, opts);
|
|
559
|
+
}
|
|
560
|
+
function collectHourOutliers(ir) {
|
|
561
|
+
const hours = [];
|
|
562
|
+
segmentsOf(ir, "hour").forEach(function classify(segment) {
|
|
563
|
+
if (segment.kind === "step") {
|
|
564
|
+
hours.push(...segment.fires);
|
|
565
|
+
} else if (segment.kind !== "range") {
|
|
566
|
+
hours.push(+segment.value);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
return hours;
|
|
570
|
+
}
|
|
571
|
+
function outlierTail(times, opts) {
|
|
572
|
+
if (!times.length) {
|
|
573
|
+
return "";
|
|
489
574
|
}
|
|
490
|
-
return
|
|
575
|
+
return " and at " + joinList(times, opts);
|
|
576
|
+
}
|
|
577
|
+
function isCadenceField(token) {
|
|
578
|
+
return token === "*" || token.startsWith("*/") && token.indexOf("-") === -1;
|
|
579
|
+
}
|
|
580
|
+
function leadingCadence(ir, opts) {
|
|
581
|
+
const { second, minute } = ir.pattern;
|
|
582
|
+
if (isCadenceField(second)) {
|
|
583
|
+
return { secondLead: true, text: secondsClause(ir, "minute", opts) };
|
|
584
|
+
}
|
|
585
|
+
if (second === "0" && isCadenceField(minute)) {
|
|
586
|
+
const text = minute === "*" ? "every minute" : (
|
|
587
|
+
// A clean minute step's first segment is a step segment.
|
|
588
|
+
stepCycle60(
|
|
589
|
+
stepSegment(ir, "minute"),
|
|
590
|
+
"minute",
|
|
591
|
+
"hour",
|
|
592
|
+
opts
|
|
593
|
+
)
|
|
594
|
+
);
|
|
595
|
+
return { secondLead: false, text };
|
|
596
|
+
}
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
function minuteConfinement(ir, opts) {
|
|
600
|
+
const minute = ir.pattern.minute;
|
|
601
|
+
if (minute === "*") {
|
|
602
|
+
return "";
|
|
603
|
+
}
|
|
604
|
+
if (isCadenceField(minute)) {
|
|
605
|
+
return " of every other minute";
|
|
606
|
+
}
|
|
607
|
+
const segments = segmentsOf(ir, "minute");
|
|
608
|
+
if (ir.shapes.minute === "single") {
|
|
609
|
+
return " during minute :" + pad(minute);
|
|
610
|
+
}
|
|
611
|
+
if (ir.shapes.minute === "range") {
|
|
612
|
+
const bounds = minute.split("-");
|
|
613
|
+
return " during minutes :" + pad(bounds[0]) + through(opts) + ":" + pad(bounds[1]);
|
|
614
|
+
}
|
|
615
|
+
const values = segmentWords(segments, opts).map(function colon(word) {
|
|
616
|
+
return ":" + pad(word);
|
|
617
|
+
});
|
|
618
|
+
return " during minutes " + joinList(values, opts);
|
|
619
|
+
}
|
|
620
|
+
function hourConfinement(ir, opts) {
|
|
621
|
+
const hour = ir.pattern.hour;
|
|
622
|
+
if (hour === "*") {
|
|
623
|
+
const minutePinned = ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute);
|
|
624
|
+
return minutePinned ? " of every hour" : "";
|
|
625
|
+
}
|
|
626
|
+
if (isCadenceField(hour)) {
|
|
627
|
+
return hour === "*/2" ? " of every other hour" : "";
|
|
628
|
+
}
|
|
629
|
+
if (ir.shapes.hour === "single") {
|
|
630
|
+
const h = +hour;
|
|
631
|
+
if (ir.shapes.minute === "step") {
|
|
632
|
+
return " from " + getTime({ hour: h, minute: 0 }, opts) + " until " + getTime({ hour: (h + 1) % 24, minute: 0 }, opts);
|
|
633
|
+
}
|
|
634
|
+
if (ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute)) {
|
|
635
|
+
return " at " + getTime({ hour: h, minute: 0 }, opts);
|
|
636
|
+
}
|
|
637
|
+
return " of the " + getTime({ hour: h, minute: 0 }, opts) + " hour";
|
|
638
|
+
}
|
|
639
|
+
if (ir.shapes.hour === "range") {
|
|
640
|
+
const bounds = hour.split("-");
|
|
641
|
+
return " " + rangeWindow({
|
|
642
|
+
continuous: ir.pattern.minute === "*",
|
|
643
|
+
from: +bounds[0],
|
|
644
|
+
throughMinute: 0,
|
|
645
|
+
to: +bounds[1]
|
|
646
|
+
}, opts);
|
|
647
|
+
}
|
|
648
|
+
return " during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours";
|
|
649
|
+
}
|
|
650
|
+
function isContiguousHourRange(ir) {
|
|
651
|
+
return ir.shapes.hour === "range";
|
|
652
|
+
}
|
|
653
|
+
function confinableHour(ir) {
|
|
654
|
+
if (ir.shapes.hour !== "step") {
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
const segment = stepSegment(ir, "hour");
|
|
658
|
+
return ir.pattern.hour === "*/2" || segment.startToken.indexOf("-") !== -1;
|
|
659
|
+
}
|
|
660
|
+
function isMinuteStride(ir) {
|
|
661
|
+
if (ir.shapes.minute !== "list") {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
const values = singleValues(segmentsOf(ir, "minute"));
|
|
665
|
+
return values !== null && arithmeticStep(values) !== null;
|
|
666
|
+
}
|
|
667
|
+
function confinementEligible(ir, lead) {
|
|
668
|
+
const { minute, hour } = ir.pattern;
|
|
669
|
+
const minuteStep = isCadenceField(minute) && minute !== "*";
|
|
670
|
+
if (!confinableHour(ir)) {
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
if (lead.secondLead) {
|
|
674
|
+
if (minuteStep) {
|
|
675
|
+
return minute === "*/2" && !isContiguousHourRange(ir);
|
|
676
|
+
}
|
|
677
|
+
if (isMinuteStride(ir) || ir.shapes.minute === "list" && ir.shapes.hour === "list") {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
if (hour === "*/2") {
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
return ir.shapes.hour === "single" && minute === "*/2";
|
|
686
|
+
}
|
|
687
|
+
function confinement(ir, opts) {
|
|
688
|
+
if (!opts.style.untilWindow || opts.short) {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
if (ir.pattern.minute === "*" && ir.pattern.hour === "*") {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
const lead = leadingCadence(ir, opts);
|
|
695
|
+
if (!lead || !confinementEligible(ir, lead)) {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
const minutePart = lead.secondLead ? minuteConfinement(ir, opts) : "";
|
|
699
|
+
return lead.text + minutePart + hourConfinement(ir, opts) + trailingQualifier(ir, opts);
|
|
491
700
|
}
|
|
492
701
|
var renderers = {
|
|
493
702
|
clockTimes: renderClockTimes,
|
|
@@ -519,19 +728,9 @@ function renderStride(stride, opts) {
|
|
|
519
728
|
if (start < interval && tiles) {
|
|
520
729
|
return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
521
730
|
}
|
|
522
|
-
const num = seriesNumber(
|
|
731
|
+
const num = seriesNumber();
|
|
523
732
|
return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
|
|
524
733
|
}
|
|
525
|
-
function singleValues(segments) {
|
|
526
|
-
const values = [];
|
|
527
|
-
for (const segment of segments) {
|
|
528
|
-
if (segment.kind !== "single") {
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
values.push(+segment.value);
|
|
532
|
-
}
|
|
533
|
-
return values;
|
|
534
|
-
}
|
|
535
734
|
function strideFromSegments(segments, unit, anchor, opts) {
|
|
536
735
|
const values = singleValues(segments);
|
|
537
736
|
const step = values && arithmeticStep(values);
|
|
@@ -580,9 +779,6 @@ function hourStrideCadence(stride, opts) {
|
|
|
580
779
|
}
|
|
581
780
|
return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
|
|
582
781
|
}
|
|
583
|
-
function offsetCleanStride(stride) {
|
|
584
|
-
return stride.start < stride.interval && 24 % stride.interval === 0;
|
|
585
|
-
}
|
|
586
782
|
function unevenHourCadence(ir, opts) {
|
|
587
783
|
const stride = hourStride(ir);
|
|
588
784
|
if (!stride || offsetCleanStride(stride)) {
|
|
@@ -590,26 +786,8 @@ function unevenHourCadence(ir, opts) {
|
|
|
590
786
|
}
|
|
591
787
|
return hourStrideCadence(stride, opts);
|
|
592
788
|
}
|
|
593
|
-
function hourListStride(values) {
|
|
594
|
-
if (values.length < 2) {
|
|
595
|
-
return null;
|
|
596
|
-
}
|
|
597
|
-
const interval = values[1] - values[0];
|
|
598
|
-
if (interval < 2) {
|
|
599
|
-
return null;
|
|
600
|
-
}
|
|
601
|
-
for (let i = 2; i < values.length; i += 1) {
|
|
602
|
-
if (values[i] - values[i - 1] !== interval) {
|
|
603
|
-
return null;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
if (values[0] !== 0 && values.length < 5) {
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
return { interval, last: values[values.length - 1], start: values[0] };
|
|
610
|
-
}
|
|
611
789
|
function hourStride(ir) {
|
|
612
|
-
const segments = ir
|
|
790
|
+
const segments = segmentsOf(ir, "hour");
|
|
613
791
|
if (segments.length === 1 && segments[0].kind === "step") {
|
|
614
792
|
const segment = segments[0];
|
|
615
793
|
if (segment.fires.length < 2) {
|
|
@@ -646,9 +824,9 @@ function hourCadence(ir, minute, opts) {
|
|
|
646
824
|
if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
|
|
647
825
|
return null;
|
|
648
826
|
}
|
|
649
|
-
const
|
|
650
|
-
if (
|
|
651
|
-
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(
|
|
827
|
+
const minuteZeroStride = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
|
|
828
|
+
if (minuteZeroStride) {
|
|
829
|
+
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(minuteZeroStride, opts) + trailingQualifier(ir, opts);
|
|
652
830
|
}
|
|
653
831
|
if (minute === 0 && ir.pattern.second === "0") {
|
|
654
832
|
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
@@ -656,7 +834,7 @@ function hourCadence(ir, minute, opts) {
|
|
|
656
834
|
return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
657
835
|
}
|
|
658
836
|
function cleanStrideSegment(ir) {
|
|
659
|
-
const segments = ir
|
|
837
|
+
const segments = segmentsOf(ir, "hour");
|
|
660
838
|
const segment = segments.length === 1 && segments[0];
|
|
661
839
|
if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
|
|
662
840
|
return null;
|
|
@@ -664,32 +842,28 @@ function cleanStrideSegment(ir) {
|
|
|
664
842
|
return segment;
|
|
665
843
|
}
|
|
666
844
|
function hasHourWindow(ir) {
|
|
667
|
-
return ir
|
|
845
|
+
return segmentsOf(ir, "hour").some(function range(segment) {
|
|
668
846
|
return segment.kind === "range";
|
|
669
847
|
});
|
|
670
848
|
}
|
|
671
849
|
function hourRangeWindowTail(ir, opts) {
|
|
672
850
|
const windows = [];
|
|
673
|
-
const
|
|
674
|
-
ir
|
|
851
|
+
const outlierHours = collectHourOutliers(ir);
|
|
852
|
+
segmentsOf(ir, "hour").forEach(function classify(segment) {
|
|
675
853
|
if (segment.kind === "range") {
|
|
676
|
-
windows.push(
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
} else {
|
|
683
|
-
singles.push(+segment.value);
|
|
854
|
+
windows.push(rangeWindow({
|
|
855
|
+
continuous: false,
|
|
856
|
+
from: +segment.bounds[0],
|
|
857
|
+
throughMinute: 0,
|
|
858
|
+
to: +segment.bounds[1]
|
|
859
|
+
}, opts));
|
|
684
860
|
}
|
|
685
861
|
});
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
return phrase;
|
|
862
|
+
const phrase = "every hour " + joinList(windows, opts);
|
|
863
|
+
const times = outlierHours.map(function time(hour) {
|
|
864
|
+
return getTime({ hour, minute: 0 }, opts);
|
|
865
|
+
});
|
|
866
|
+
return phrase + outlierTail(times, opts);
|
|
693
867
|
}
|
|
694
868
|
function hourRangeCadence(ir, minute, opts) {
|
|
695
869
|
if (minute !== 0 || !hasHourWindow(ir)) {
|
|
@@ -703,25 +877,29 @@ function hourRangeCadence(ir, minute, opts) {
|
|
|
703
877
|
}
|
|
704
878
|
return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
|
|
705
879
|
}
|
|
706
|
-
function seriesNumber(
|
|
707
|
-
const anyBig = values.some(function big(v) {
|
|
708
|
-
return +v > 10;
|
|
709
|
-
});
|
|
880
|
+
function seriesNumber() {
|
|
710
881
|
return function format(n) {
|
|
711
|
-
return
|
|
882
|
+
return "" + n;
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function listNumber(count, opts) {
|
|
886
|
+
return count > 1 ? function asNumeral(n) {
|
|
887
|
+
return "" + n;
|
|
888
|
+
} : function spelled(n) {
|
|
889
|
+
return getNumber(n, opts);
|
|
712
890
|
};
|
|
713
891
|
}
|
|
714
892
|
function numberWords(fires, opts) {
|
|
715
|
-
return fires.map(
|
|
893
|
+
return fires.map(listNumber(fires.length, opts));
|
|
716
894
|
}
|
|
717
895
|
function segmentWords(segments, opts) {
|
|
718
|
-
const
|
|
896
|
+
const count = segments.reduce(function tally(sum, segment) {
|
|
719
897
|
if (segment.kind === "range") {
|
|
720
|
-
return
|
|
898
|
+
return sum + 1;
|
|
721
899
|
}
|
|
722
|
-
return segment.kind === "step" ? segment.fires :
|
|
723
|
-
});
|
|
724
|
-
const num =
|
|
900
|
+
return sum + (segment.kind === "step" ? segment.fires.length : 1);
|
|
901
|
+
}, 0);
|
|
902
|
+
const num = listNumber(count, opts);
|
|
725
903
|
return segments.flatMap(function word(segment) {
|
|
726
904
|
if (segment.kind === "range") {
|
|
727
905
|
return [num(segment.bounds[0]) + through(opts) + num(segment.bounds[1])];
|
|
@@ -753,6 +931,9 @@ function hourTimes(hours, opts) {
|
|
|
753
931
|
});
|
|
754
932
|
return joinList(times, opts);
|
|
755
933
|
}
|
|
934
|
+
function singleHourFire(times) {
|
|
935
|
+
return times.kind === "fires" && times.fires.length === 1;
|
|
936
|
+
}
|
|
756
937
|
function hourTimesFromPlan(ir, times, atContext, opts) {
|
|
757
938
|
if (times.kind === "fires") {
|
|
758
939
|
return hourTimes(times.fires, opts);
|
|
@@ -767,7 +948,7 @@ function segmentHours(segment) {
|
|
|
767
948
|
}
|
|
768
949
|
function hourSegmentTimes(ir, fold, atContext, opts) {
|
|
769
950
|
const { minute, second } = fold;
|
|
770
|
-
const segments = ir
|
|
951
|
+
const segments = segmentsOf(ir, "hour");
|
|
771
952
|
const plain = mixedTwelve(segments.flatMap(function entries(segment) {
|
|
772
953
|
return segmentHours(segment).map(function entry(hour) {
|
|
773
954
|
return { hour: +hour, minute, second };
|
|
@@ -800,28 +981,47 @@ function disambiguateTimes(pieces, segments, atContext) {
|
|
|
800
981
|
return index === 0 ? piece : "at " + piece;
|
|
801
982
|
});
|
|
802
983
|
}
|
|
803
|
-
function
|
|
984
|
+
function joinWith(items, conjunction, opts) {
|
|
804
985
|
if (items.length <= 1) {
|
|
805
986
|
return items.join("");
|
|
806
987
|
}
|
|
807
988
|
if (items.length === 2) {
|
|
808
|
-
return items[0] +
|
|
989
|
+
return items[0] + conjunction + items[1];
|
|
809
990
|
}
|
|
810
|
-
const
|
|
811
|
-
return items.slice(0, -1).join(", ") +
|
|
991
|
+
const tail = opts.style.serialComma ? "," + conjunction : conjunction;
|
|
992
|
+
return items.slice(0, -1).join(", ") + tail + items[items.length - 1];
|
|
993
|
+
}
|
|
994
|
+
function joinList(items, opts) {
|
|
995
|
+
return joinWith(items, " and ", opts);
|
|
996
|
+
}
|
|
997
|
+
function joinOr(items, opts) {
|
|
998
|
+
return joinWith(items, " or ", opts);
|
|
812
999
|
}
|
|
813
|
-
var trailingWords = {
|
|
1000
|
+
var trailingWords = {
|
|
1001
|
+
all: "",
|
|
1002
|
+
month: "in ",
|
|
1003
|
+
recurringWeekday: true,
|
|
1004
|
+
stepDate: "on ",
|
|
1005
|
+
weekday: "on "
|
|
1006
|
+
};
|
|
814
1007
|
var leadingWords = {
|
|
815
1008
|
all: "every day",
|
|
816
1009
|
month: "every day in ",
|
|
1010
|
+
recurringWeekday: false,
|
|
817
1011
|
stepDate: "",
|
|
818
1012
|
weekday: "every "
|
|
819
1013
|
};
|
|
820
1014
|
function trailingQualifier(ir, opts) {
|
|
1015
|
+
if (isDayUnion(ir, opts)) {
|
|
1016
|
+
return dayUnionCondition(ir, opts);
|
|
1017
|
+
}
|
|
821
1018
|
const phrase = dayQualifier(ir, trailingWords, opts);
|
|
822
1019
|
return phrase && " " + phrase;
|
|
823
1020
|
}
|
|
824
1021
|
function interpretDayQualifier(ir, opts) {
|
|
1022
|
+
if (isDayUnion(ir, opts)) {
|
|
1023
|
+
return "";
|
|
1024
|
+
}
|
|
825
1025
|
return dayQualifier(ir, leadingWords, opts) + " ";
|
|
826
1026
|
}
|
|
827
1027
|
function dayQualifier(ir, words, opts) {
|
|
@@ -833,7 +1033,11 @@ function dayQualifier(ir, words, opts) {
|
|
|
833
1033
|
return datePhrase(ir, words, opts);
|
|
834
1034
|
}
|
|
835
1035
|
if (pattern.weekday !== "*") {
|
|
836
|
-
const
|
|
1036
|
+
const quartzWeekday = quartzWeekdayPhrase(pattern.weekday, opts);
|
|
1037
|
+
if (quartzWeekday) {
|
|
1038
|
+
return monthScopeForRecurrence(quartzWeekday, ir, opts);
|
|
1039
|
+
}
|
|
1040
|
+
const weekdays = words.weekday + weekdayPhrase(ir, words.recurringWeekday, opts);
|
|
837
1041
|
return weekdays + monthScope(ir, opts);
|
|
838
1042
|
}
|
|
839
1043
|
if (pattern.month !== "*") {
|
|
@@ -845,10 +1049,14 @@ function datePhrase(ir, words, opts) {
|
|
|
845
1049
|
const pattern = ir.pattern;
|
|
846
1050
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
847
1051
|
if (quartzDate) {
|
|
848
|
-
return quartzDate
|
|
1052
|
+
return monthScopeForRecurrence(quartzDate, ir, opts);
|
|
849
1053
|
}
|
|
850
1054
|
if (isOpenStep(pattern.date)) {
|
|
851
|
-
return
|
|
1055
|
+
return monthScopeForRecurrence(
|
|
1056
|
+
words.stepDate + stepDates(pattern.date),
|
|
1057
|
+
ir,
|
|
1058
|
+
opts
|
|
1059
|
+
);
|
|
852
1060
|
}
|
|
853
1061
|
if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
|
|
854
1062
|
return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
|
|
@@ -860,13 +1068,88 @@ function datePhrase(ir, words, opts) {
|
|
|
860
1068
|
}
|
|
861
1069
|
function monthFoldsIntoDate(ir) {
|
|
862
1070
|
return !oddEvenMonth(ir.pattern.month) && // Reached only with a restricted month, which has segments.
|
|
863
|
-
ir
|
|
1071
|
+
segmentsOf(ir, "month").every(function flat(segment) {
|
|
864
1072
|
return segment.kind !== "range";
|
|
865
1073
|
});
|
|
866
1074
|
}
|
|
1075
|
+
function isDayUnion(ir, opts) {
|
|
1076
|
+
return ir.pattern.date !== "*" && ir.pattern.weekday !== "*" && !!opts.style.untilWindow && !opts.short;
|
|
1077
|
+
}
|
|
1078
|
+
function dayUnionCondition(ir, opts) {
|
|
1079
|
+
const pieces = [
|
|
1080
|
+
...dayUnionDatePieces(ir, opts),
|
|
1081
|
+
...dayUnionWeekdayPieces(ir, opts)
|
|
1082
|
+
];
|
|
1083
|
+
return " whenever the day is " + joinOr(pieces, opts);
|
|
1084
|
+
}
|
|
1085
|
+
function dayUnionMonthLead(ir, opts) {
|
|
1086
|
+
if (ir.pattern.month === "*") {
|
|
1087
|
+
return "";
|
|
1088
|
+
}
|
|
1089
|
+
return "in " + monthName(ir, opts) + " ";
|
|
1090
|
+
}
|
|
1091
|
+
function dayUnionDatePieces(ir, opts) {
|
|
1092
|
+
const dateField = ir.pattern.date;
|
|
1093
|
+
const quartz = quartzDatePhrase(dateField, opts);
|
|
1094
|
+
if (quartz) {
|
|
1095
|
+
return [quartz.replace(/^on /, "")];
|
|
1096
|
+
}
|
|
1097
|
+
const oddEven = oddEvenDay(dateField);
|
|
1098
|
+
if (oddEven) {
|
|
1099
|
+
return [oddEven];
|
|
1100
|
+
}
|
|
1101
|
+
const pieces = [];
|
|
1102
|
+
segmentsOf(ir, "date").forEach(function expand(segment) {
|
|
1103
|
+
if (segment.kind === "range") {
|
|
1104
|
+
pieces.push("from the " + getOrdinal(segment.bounds[0]) + through(opts) + "the " + getOrdinal(segment.bounds[1]));
|
|
1105
|
+
} else if (segment.kind === "step") {
|
|
1106
|
+
segment.fires.forEach(function fire(value) {
|
|
1107
|
+
pieces.push("the " + getOrdinal(value));
|
|
1108
|
+
});
|
|
1109
|
+
} else {
|
|
1110
|
+
pieces.push("the " + getOrdinal(segment.value));
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
return pieces;
|
|
1114
|
+
}
|
|
1115
|
+
function dayUnionWeekdayPieces(ir, opts) {
|
|
1116
|
+
const weekdayField = ir.pattern.weekday;
|
|
1117
|
+
const quartz = quartzWeekdayPhrase(weekdayField, opts);
|
|
1118
|
+
if (quartz) {
|
|
1119
|
+
return [quartz.replace(/^on /, "")];
|
|
1120
|
+
}
|
|
1121
|
+
const pieces = [];
|
|
1122
|
+
segmentsOf(ir, "weekday").forEach(function expand(segment) {
|
|
1123
|
+
if (segment.kind === "range" && segment.bounds[0] === "1" && segment.bounds[1] === "5") {
|
|
1124
|
+
pieces.push("a weekday");
|
|
1125
|
+
} else if (segment.kind === "range") {
|
|
1126
|
+
pieces.push("a " + getWeekday(segment.bounds[0], opts) + through(opts) + "a " + getWeekday(segment.bounds[1], opts));
|
|
1127
|
+
} else if (segment.kind === "step") {
|
|
1128
|
+
segment.fires.forEach(function fire(value) {
|
|
1129
|
+
pieces.push("a " + getWeekday(value, opts));
|
|
1130
|
+
});
|
|
1131
|
+
} else {
|
|
1132
|
+
pieces.push("a " + getWeekday(segment.value, opts));
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
return pieces;
|
|
1136
|
+
}
|
|
1137
|
+
function oddEvenDay(dateField) {
|
|
1138
|
+
if (!isOpenStep(dateField)) {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
const [start, step] = dateField.split("/");
|
|
1142
|
+
if (+step !== 2) {
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
if (start === "*" || start === "1") {
|
|
1146
|
+
return "an odd-numbered day";
|
|
1147
|
+
}
|
|
1148
|
+
return start === "2" ? "an even-numbered day" : null;
|
|
1149
|
+
}
|
|
867
1150
|
function dateOrWeekday(ir, opts) {
|
|
868
1151
|
const pattern = ir.pattern;
|
|
869
|
-
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, opts);
|
|
1152
|
+
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, false, opts);
|
|
870
1153
|
if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
|
|
871
1154
|
return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
|
|
872
1155
|
}
|
|
@@ -917,10 +1200,13 @@ function quartzWeekdayPhrase(weekdayField, opts) {
|
|
|
917
1200
|
function monthDatePhrase(ir, opts) {
|
|
918
1201
|
const month = monthName(ir, opts);
|
|
919
1202
|
const days = renderSegments(
|
|
920
|
-
ir
|
|
1203
|
+
segmentsOf(ir, "date"),
|
|
921
1204
|
opts.style.ordinals ? getOrdinal : cardinalDay,
|
|
922
1205
|
opts
|
|
923
1206
|
);
|
|
1207
|
+
if (opts.style.dayFirst && ir.shapes.date === "single" && ir.shapes.month !== "single") {
|
|
1208
|
+
return "the " + getOrdinal(ir.pattern.date) + " of " + month;
|
|
1209
|
+
}
|
|
924
1210
|
return opts.style.dayFirst ? days + " " + month : month + " " + days;
|
|
925
1211
|
}
|
|
926
1212
|
function cardinalDay(value) {
|
|
@@ -932,6 +1218,19 @@ function monthScope(ir, opts) {
|
|
|
932
1218
|
}
|
|
933
1219
|
return " in " + monthName(ir, opts);
|
|
934
1220
|
}
|
|
1221
|
+
function monthScopeForRecurrence(phrase, ir, opts) {
|
|
1222
|
+
if (ir.pattern.month === "*") {
|
|
1223
|
+
return phrase;
|
|
1224
|
+
}
|
|
1225
|
+
const carriesRecurrence = phrase.indexOf(" of the month") !== -1;
|
|
1226
|
+
if (carriesRecurrence && ir.shapes.month === "range") {
|
|
1227
|
+
return phrase.replace(" of the month", " of each month") + " from " + monthName(ir, opts);
|
|
1228
|
+
}
|
|
1229
|
+
if (carriesRecurrence && (ir.shapes.month === "single" || ir.shapes.month === "step")) {
|
|
1230
|
+
return phrase.replace(" of the month", "") + " in " + monthName(ir, opts);
|
|
1231
|
+
}
|
|
1232
|
+
return phrase + " in " + monthName(ir, opts);
|
|
1233
|
+
}
|
|
935
1234
|
function stepDates(dateField) {
|
|
936
1235
|
const parts = dateField.split("/");
|
|
937
1236
|
const interval = +parts[1];
|
|
@@ -944,14 +1243,14 @@ function stepDates(dateField) {
|
|
|
944
1243
|
return phrase;
|
|
945
1244
|
}
|
|
946
1245
|
function dateOrdinals(ir, opts) {
|
|
947
|
-
return renderSegments(ir
|
|
1246
|
+
return renderSegments(segmentsOf(ir, "date"), getOrdinal, opts);
|
|
948
1247
|
}
|
|
949
1248
|
function monthName(ir, opts) {
|
|
950
1249
|
const oddEven = oddEvenMonth(ir.pattern.month);
|
|
951
1250
|
if (oddEven) {
|
|
952
1251
|
return oddEven;
|
|
953
1252
|
}
|
|
954
|
-
return renderSegments(ir
|
|
1253
|
+
return renderSegments(segmentsOf(ir, "month"), function name(value) {
|
|
955
1254
|
return getMonth(value, opts);
|
|
956
1255
|
}, opts);
|
|
957
1256
|
}
|
|
@@ -968,11 +1267,21 @@ function oddEvenMonth(monthField) {
|
|
|
968
1267
|
}
|
|
969
1268
|
return start === "2" ? "every even-numbered month" : null;
|
|
970
1269
|
}
|
|
971
|
-
function weekdayPhrase(ir, opts) {
|
|
972
|
-
const segments = orderWeekdaysForDisplay(ir
|
|
973
|
-
|
|
1270
|
+
function weekdayPhrase(ir, recurring, opts) {
|
|
1271
|
+
const segments = orderWeekdaysForDisplay(segmentsOf(ir, "weekday"));
|
|
1272
|
+
const hasRange = segments.some(function range(segment) {
|
|
1273
|
+
return segment.kind === "range";
|
|
1274
|
+
});
|
|
1275
|
+
const name = recurring && !hasRange ? function plural(value) {
|
|
1276
|
+
return pluralWeekday(value, opts);
|
|
1277
|
+
} : function singular(value) {
|
|
974
1278
|
return getWeekday(value, opts);
|
|
975
|
-
}
|
|
1279
|
+
};
|
|
1280
|
+
return renderSegments(segments, name, opts);
|
|
1281
|
+
}
|
|
1282
|
+
function pluralWeekday(value, opts) {
|
|
1283
|
+
const name = getWeekday(value, opts);
|
|
1284
|
+
return opts.short ? name : name + "s";
|
|
976
1285
|
}
|
|
977
1286
|
function renderSegments(segments, word, opts) {
|
|
978
1287
|
const pieces = [];
|
|
@@ -987,16 +1296,13 @@ function renderSegments(segments, word, opts) {
|
|
|
987
1296
|
});
|
|
988
1297
|
return joinList(pieces, opts);
|
|
989
1298
|
}
|
|
990
|
-
function isOpenStep(field) {
|
|
991
|
-
return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
|
|
992
|
-
}
|
|
993
1299
|
function applyYear(description, ir, opts) {
|
|
994
1300
|
const yearField = ir.pattern.year;
|
|
995
1301
|
if (yearField === "*") {
|
|
996
1302
|
return description;
|
|
997
1303
|
}
|
|
998
1304
|
if (yearField.indexOf("/") !== -1) {
|
|
999
|
-
return description + " " + stepYears(yearField, opts);
|
|
1305
|
+
return description + ", " + stepYears(yearField, opts);
|
|
1000
1306
|
}
|
|
1001
1307
|
const label = yearLabel(yearField, opts);
|
|
1002
1308
|
if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
|
|
@@ -1009,6 +1315,9 @@ function yearLabel(yearField, opts) {
|
|
|
1009
1315
|
if (yearField.indexOf(",") !== -1) {
|
|
1010
1316
|
return joinList(yearField.split(","), opts);
|
|
1011
1317
|
}
|
|
1318
|
+
if (yearField.indexOf("-") !== -1) {
|
|
1319
|
+
return yearField.split("-").join(through(opts));
|
|
1320
|
+
}
|
|
1012
1321
|
return yearField;
|
|
1013
1322
|
}
|
|
1014
1323
|
function stepYears(yearField, opts) {
|
|
@@ -1018,7 +1327,7 @@ function stepYears(yearField, opts) {
|
|
|
1018
1327
|
if (interval <= 1) {
|
|
1019
1328
|
return "every year";
|
|
1020
1329
|
}
|
|
1021
|
-
let phrase = "every " + getNumber(interval, opts) + " years";
|
|
1330
|
+
let phrase = interval === 2 ? "every other year" : "every " + getNumber(interval, opts) + " years";
|
|
1022
1331
|
if (start !== "*" && start !== "0") {
|
|
1023
1332
|
phrase += " from " + start;
|
|
1024
1333
|
}
|