cronli5 0.1.5 → 0.1.6
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 +28 -0
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +117 -20
- package/dist/cronli5.js +117 -20
- package/dist/lang/de.cjs +94 -19
- package/dist/lang/de.js +94 -19
- package/dist/lang/en.cjs +117 -20
- package/dist/lang/en.js +117 -20
- package/dist/lang/es.cjs +89 -14
- package/dist/lang/es.js +89 -14
- package/dist/lang/fi.cjs +107 -19
- package/dist/lang/fi.js +107 -19
- package/dist/lang/zh.cjs +90 -53
- package/dist/lang/zh.js +90 -53
- package/package.json +2 -2
- package/src/lang/de/index.ts +238 -51
- package/src/lang/en/index.ts +280 -46
- package/src/lang/es/index.ts +222 -31
- package/src/lang/fi/index.ts +245 -39
- package/src/lang/zh/index.ts +209 -94
package/src/lang/zh/index.ts
CHANGED
|
@@ -314,9 +314,30 @@ function renderMinutePast(ir: IR): string {
|
|
|
314
314
|
return minuteHourClause(ir);
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
//
|
|
317
|
+
// One hour segment as clock words by its form: a range is a span ("9点至20点"),
|
|
318
|
+
// a single is one clock word ("22点"), a step keeps its fires enumerated as
|
|
319
|
+
// clock words ("9点、11点、13点"). A range stated as a list element should read
|
|
320
|
+
// as the span the source wrote, not the hours it expands to — the same choice
|
|
321
|
+
// en/es/de/fi make ("from 9 a.m. through 8 p.m. and at 10 p.m.").
|
|
322
|
+
function hourSegmentWords(segment: Segment): string[] {
|
|
323
|
+
if (segment.kind === 'range') {
|
|
324
|
+
return [hourWord(+segment.bounds[0]) + '至' + hourWord(+segment.bounds[1])];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (segment.kind === 'step') {
|
|
328
|
+
return segment.fires.map(hourWord);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return [hourWord(+segment.value)];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// The hour field as clock words, by segment form: "9点、11点和13点" for a list
|
|
335
|
+
// of singles, "9点至20点和22点" for a range plus a single. Each segment renders
|
|
336
|
+
// as the operator the source wrote (range → span), not its expanded fires.
|
|
318
337
|
function hourList(ir: IR): string {
|
|
319
|
-
|
|
338
|
+
const words = fieldSegments(ir, 'hour').flatMap(hourSegmentWords);
|
|
339
|
+
|
|
340
|
+
return joinAnd(words);
|
|
320
341
|
}
|
|
321
342
|
|
|
322
343
|
// A frame that confines a cadence to active hours: a range gives "在F点至T点之
|
|
@@ -340,14 +361,17 @@ function renderMinuteFrequency(ir: IR, plan: PlanNode): string {
|
|
|
340
361
|
const base = stepClause(minuteStep, UNITS.minute, '分', '每小时');
|
|
341
362
|
const {hours} = plan as Extract<PlanNode, {kind: 'minuteFrequency'}>;
|
|
342
363
|
|
|
343
|
-
|
|
344
|
-
|
|
364
|
+
// An hour stride (a clean step, or an offset/non-tiling progression the core
|
|
365
|
+
// kept a step shape or enumerated to a list) leads the minute cadence:
|
|
366
|
+
// "每2小时每5分钟", "从2点起每6小时每15分钟". A clean cadence concatenates as
|
|
367
|
+
// before; a bounded cadence ends on "至K点", so a comma keeps that endpoint
|
|
368
|
+
// from gluing onto the minute clause ("从9点起每2小时,至17点,每2分钟").
|
|
369
|
+
if (hours.kind === 'step' || hours.kind === 'during') {
|
|
370
|
+
const hourCad = hourCadencePhrase(ir);
|
|
345
371
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
cadence(hourStep.interval, UNITS.hour) + base :
|
|
350
|
-
'在' + hourList(ir) + ',' + base;
|
|
372
|
+
if (hourCad !== null) {
|
|
373
|
+
return hourCad + (hourCad.indexOf('至') === -1 ? '' : ',') + base;
|
|
374
|
+
}
|
|
351
375
|
}
|
|
352
376
|
|
|
353
377
|
if (hours.kind === 'single' ||
|
|
@@ -383,15 +407,20 @@ function renderMinuteSpanInHour(ir: IR, plan: PlanNode): string {
|
|
|
383
407
|
}
|
|
384
408
|
|
|
385
409
|
// A minute clause across discrete hours. A wildcard minute reads "在9点、11点…,
|
|
386
|
-
// 每分钟"; a ranged/listed minute names it: "9点和17点,每小时0至30分,每分钟".
|
|
410
|
+
// 每分钟"; a ranged/listed minute names it: "9点和17点,每小时0至30分,每分钟". An
|
|
411
|
+
// hour progression reads as its cadence ("从9点起每2小时,至17点,每分钟") rather
|
|
412
|
+
// than the enumerated hours, the same idiom the minute field uses.
|
|
387
413
|
function renderMinutesAcrossHours(ir: IR, plan: PlanNode): string {
|
|
388
414
|
const {form} = plan as Extract<PlanNode, {kind: 'minutesAcrossHours'}>;
|
|
415
|
+
const hourCad = hourCadencePhrase(ir);
|
|
389
416
|
|
|
390
417
|
if (form === 'wildcard') {
|
|
391
|
-
return
|
|
418
|
+
return hourCad === null ?
|
|
419
|
+
'在' + hourList(ir) + ',每分钟' :
|
|
420
|
+
hourCad + ',每分钟';
|
|
392
421
|
}
|
|
393
422
|
|
|
394
|
-
return hourList(ir) + ',' + minuteHourClause(ir) + ',每分钟';
|
|
423
|
+
return (hourCad ?? hourList(ir)) + ',' + minuteHourClause(ir) + ',每分钟';
|
|
395
424
|
}
|
|
396
425
|
|
|
397
426
|
// A minute clause across a stepped hour field. A wildcard minute reads "每2小时
|
|
@@ -400,22 +429,22 @@ function renderMinuteSpanAcrossHourStep(ir: IR, plan: PlanNode): string {
|
|
|
400
429
|
const hourStep = stepSegment(ir, 'hour');
|
|
401
430
|
const {form} = plan as Extract<PlanNode, {kind: 'minuteSpanAcrossHourStep'}>;
|
|
402
431
|
|
|
403
|
-
// A minute list
|
|
404
|
-
// "
|
|
432
|
+
// A minute list reads as the hour cadence plus the minute list ("每2小时,
|
|
433
|
+
// 每小时0、25、50分"; offset "从1点起每2小时,每小时5分和30分"), the same compaction
|
|
434
|
+
// the wildcard/range minute already uses, rather than the enumerated hours.
|
|
405
435
|
if (form === 'list') {
|
|
406
|
-
return
|
|
436
|
+
return hourCadencePhrase(ir) + ',' + renderMinutePast(ir);
|
|
407
437
|
}
|
|
408
438
|
|
|
409
439
|
const minuteTail = form === 'wildcard' ?
|
|
410
440
|
'每分钟' :
|
|
411
441
|
minuteHourClause(ir) + ',每分钟';
|
|
412
442
|
|
|
413
|
-
// An offset stride (2/6 fires at 2,8,14,20)
|
|
414
|
-
//
|
|
443
|
+
// An offset or non-tiling stride (2/6 fires at 2,8,14,20) reads as its
|
|
444
|
+
// cadence ("从2点起每6小时"). A wildcard minute hangs off it with a comma; a
|
|
445
|
+
// named minute follows the cadence and its own comma.
|
|
415
446
|
if (hourStep.startToken !== '*') {
|
|
416
|
-
return
|
|
417
|
-
'在' + hourList(ir) + ',' + minuteTail :
|
|
418
|
-
hourList(ir) + ',' + minuteTail;
|
|
447
|
+
return hourCadencePhrase(ir) + ',' + minuteTail;
|
|
419
448
|
}
|
|
420
449
|
|
|
421
450
|
// A step-2 hour from midnight IS exactly the even hours; name them so, rather
|
|
@@ -435,12 +464,10 @@ function renderMinuteSpanAcrossHourStep(ir: IR, plan: PlanNode): string {
|
|
|
435
464
|
function renderClockTimes(ir: IR, plan: PlanNode, opts: Opts): string {
|
|
436
465
|
// An hour step (or arithmetic-progression hour list) under a single pinned
|
|
437
466
|
// minute reads as a cadence rather than a cross-product of clock times.
|
|
438
|
-
|
|
439
|
-
const cad = hourCadence(ir);
|
|
467
|
+
const cad = hourCadenceText(ir);
|
|
440
468
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
469
|
+
if (cad !== null) {
|
|
470
|
+
return cad;
|
|
444
471
|
}
|
|
445
472
|
|
|
446
473
|
const {times} = plan as Extract<PlanNode, {kind: 'clockTimes'}>;
|
|
@@ -454,20 +481,34 @@ function renderCompactClockTimes(ir: IR, plan: PlanNode): string {
|
|
|
454
481
|
// An hour step (or arithmetic-progression hour list) under the single pinned
|
|
455
482
|
// minute reads as a cadence, not a wall of clock times. (Returns null for an
|
|
456
483
|
// irregular list or a range, which keep enumerating below.)
|
|
457
|
-
|
|
458
|
-
const cad = hourCadence(ir);
|
|
484
|
+
const cad = hourCadenceText(ir);
|
|
459
485
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
486
|
+
if (cad !== null) {
|
|
487
|
+
return cad;
|
|
463
488
|
}
|
|
464
489
|
|
|
465
|
-
const
|
|
490
|
+
const compact = plan as Extract<PlanNode, {kind: 'compactClockTimes'}>;
|
|
466
491
|
const secs = fieldSegments(ir, 'second');
|
|
467
492
|
const tail = secs.length && ir.pattern.second !== '0' ?
|
|
468
493
|
',第' + valueText(secs) + '秒' : '';
|
|
469
494
|
|
|
470
|
-
|
|
495
|
+
// A multi-valued minute (`fold` false) names its whole set, never just its
|
|
496
|
+
// first fire — a list starting at 0 ("*/25" -> :00,:25,:50) must keep the
|
|
497
|
+
// minute clause, not drop it because the leading fire is 0. The hour reads as
|
|
498
|
+
// its bounded cadence when its fires form a progression ("从0点起每5小时,至20
|
|
499
|
+
// 点"), composed after the minute set, the same idiom the stepped-hour path
|
|
500
|
+
// uses; an irregular hour list keeps enumerating with the "在…" frame.
|
|
501
|
+
if (!compact.fold) {
|
|
502
|
+
const hourCad = hourCadencePhrase(ir);
|
|
503
|
+
|
|
504
|
+
return hourCad === null ?
|
|
505
|
+
minuteHourClause(ir) + ',在' + hourList(ir) + tail :
|
|
506
|
+
hourCad + ',' + minuteHourClause(ir) + tail;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// A single pinned minute past 0 leads with its clause; a pinned 0 folds into
|
|
510
|
+
// the hour times (the :00 is implicit).
|
|
511
|
+
if (compact.minute > 0) {
|
|
471
512
|
return minuteHourClause(ir) + ',在' + hourList(ir) + tail;
|
|
472
513
|
}
|
|
473
514
|
|
|
@@ -497,67 +538,71 @@ function renderHourRange(ir: IR, plan: PlanNode): string {
|
|
|
497
538
|
range.last + '分之间,每分钟';
|
|
498
539
|
}
|
|
499
540
|
|
|
500
|
-
// A stepped hour field: "每2小时",
|
|
501
|
-
//
|
|
502
|
-
//
|
|
541
|
+
// A stepped hour field as its cadence: "每2小时" (clean), "从1点起每2小时"
|
|
542
|
+
// (offset), "从9点起每2小时,至17点" (bounded). A stride that fires only twice
|
|
543
|
+
// reads instead as its two clock words ("凌晨0点和正午", "8点和20点"), shorter and
|
|
544
|
+
// clearer than a cadence for a pair.
|
|
503
545
|
function renderHourStep(ir: IR): string {
|
|
504
546
|
const segment = stepSegment(ir, 'hour');
|
|
505
547
|
|
|
506
|
-
if (segment.startToken !== '*') {
|
|
507
|
-
return hourList(ir);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// A step that fires only twice reads as two clock times ("凌晨0点和正午").
|
|
511
548
|
if (segment.fires.length <= 2) {
|
|
512
549
|
return joinAnd(segment.fires.map(hourWord));
|
|
513
550
|
}
|
|
514
551
|
|
|
515
|
-
return
|
|
552
|
+
return hourCadencePhrase(ir) as string;
|
|
516
553
|
}
|
|
517
554
|
|
|
518
555
|
// --- Hour-step cadence (the 24-cycle analog of renderStride). ---
|
|
519
556
|
|
|
520
|
-
// The hour field's stride, or null when the hour is not a cadence: a
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
function hourStride(
|
|
557
|
+
// The hour field's stride, or null when the hour is not a cadence: a step
|
|
558
|
+
// segment yields its {interval, start, last}; an all-single hour list yields
|
|
559
|
+
// one only when its values form a long-enough arithmetic progression (so an
|
|
560
|
+
// irregular list, or a too-short one like 9,17, keeps enumerating). An offset
|
|
561
|
+
// (start > 0) or non-tiling (interval ∤ 24) stride is still a cadence — Chinese
|
|
562
|
+
// names its start and endpoint ("从M点起每N小时,至K点"), the same idiom the
|
|
563
|
+
// minute field already uses — so it is no longer rejected. The IR is unchanged.
|
|
564
|
+
function hourStride(
|
|
565
|
+
ir: IR
|
|
566
|
+
): {interval: number; start: number; last: number} | null {
|
|
528
567
|
const segments = fieldSegments(ir, 'hour');
|
|
529
568
|
|
|
530
569
|
if (segments.length === 1 && segments[0].kind === 'step') {
|
|
531
|
-
const
|
|
532
|
-
const start = segment.startToken === '*' ? 0 : +segment.startToken;
|
|
533
|
-
|
|
534
|
-
if (start !== 0 || 24 % segment.interval !== 0) {
|
|
535
|
-
return null;
|
|
536
|
-
}
|
|
570
|
+
const {fires, interval} = segments[0];
|
|
537
571
|
|
|
538
|
-
return {interval:
|
|
539
|
-
last: segment.fires[segment.fires.length - 1]};
|
|
572
|
+
return {interval, start: fires[0], last: fires[fires.length - 1]};
|
|
540
573
|
}
|
|
541
574
|
|
|
542
575
|
const values = singleValues(segments);
|
|
543
|
-
const step = values && arithmeticStep(values);
|
|
544
576
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
577
|
+
return values && arithmeticStep(values);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// The hour field's cadence phrase ("每2小时", "从1点起每2小时", "从0点起每5小时,
|
|
581
|
+
// 至20点"), or null when the hour is not a single arithmetic progression (an
|
|
582
|
+
// irregular list, a range, or a too-short list keeps enumerating). The 24-cycle
|
|
583
|
+
// analog of strideFromSegments — it routes the stride through the one phrasing
|
|
584
|
+
// renderStride speaks, so a clean, offset, or non-tiling hour stride all read
|
|
585
|
+
// as the cadence the equivalent minute step does.
|
|
586
|
+
function hourCadencePhrase(ir: IR): string | null {
|
|
587
|
+
const stride = hourStride(ir);
|
|
548
588
|
|
|
549
|
-
return
|
|
589
|
+
return stride && renderStride({
|
|
590
|
+
...stride, cycle: 24, unit: UNITS.hour, mark: '点', anchor: ''
|
|
591
|
+
});
|
|
550
592
|
}
|
|
551
593
|
|
|
552
594
|
// Render an hour step (or arithmetic-progression hour list) under a single
|
|
553
|
-
// pinned minute and a second as a cadence —
|
|
554
|
-
// instead of cross-multiplying the hours into a wall of clock
|
|
555
|
-
// null when the hour is not a
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
//
|
|
560
|
-
//
|
|
595
|
+
// pinned minute and a second as a cadence — the hour cadence plus the
|
|
596
|
+
// minute/second — instead of cross-multiplying the hours into a wall of clock
|
|
597
|
+
// times. Returns null when the hour is not a stride, when the cross-product is
|
|
598
|
+
// short enough that enumeration is no longer than the cadence (a meaningful
|
|
599
|
+
// second makes every clock time carry a second, so any stride is worth
|
|
600
|
+
// compacting; otherwise the stride must exceed the clock-time cap, the same
|
|
601
|
+
// point at which the core itself stops enumerating), or when the cadence is
|
|
602
|
+
// bounded ("…,至K点"): a trailing minute fused onto its endpoint ("至20点0分")
|
|
603
|
+
// would read as a clock time, so a bounded stride keeps enumerating its fused
|
|
604
|
+
// clock times here, naming the cadence only where no minute follows it (the
|
|
605
|
+
// bare hour field). Renderer-only; the IR is unchanged.
|
|
561
606
|
function hourCadence(ir: IR): string | null {
|
|
562
607
|
const stride = hourStride(ir);
|
|
563
608
|
|
|
@@ -565,25 +610,33 @@ function hourCadence(ir: IR): string | null {
|
|
|
565
610
|
return null;
|
|
566
611
|
}
|
|
567
612
|
|
|
568
|
-
const fires = stride.last / stride.interval + 1;
|
|
613
|
+
const fires = (stride.last - stride.start) / stride.interval + 1;
|
|
569
614
|
|
|
570
615
|
if (ir.pattern.second === '0' && fires <= maxClockTimes) {
|
|
571
616
|
return null;
|
|
572
617
|
}
|
|
573
618
|
|
|
574
|
-
const prefix =
|
|
619
|
+
const prefix = hourCadencePhrase(ir) as string;
|
|
620
|
+
|
|
621
|
+
// A bounded cadence cannot carry a fused minute unambiguously; enumerate.
|
|
622
|
+
if (prefix.indexOf('至') !== -1) {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
|
|
575
626
|
const minute = +ir.pattern.minute;
|
|
576
627
|
const subMinute = ir.pattern.second === '*' || ir.shapes.second === 'step';
|
|
577
628
|
|
|
578
|
-
// A wildcard or sub-minute step second confined to minute 0 of
|
|
629
|
+
// A wildcard or sub-minute step second confined to minute 0 of the even-hour
|
|
579
630
|
// stride is a confinement, not a juxtaposed cadence. Reuse the even-hours
|
|
580
631
|
// idiom ("在偶数小时0分的每一秒") so the form does NOT contain the bare "每2小时"
|
|
581
632
|
// and can never be misread as the absorbing hour cadence (the same reason en
|
|
582
633
|
// says "for one minute during every other hour", not "every two hours"). The
|
|
583
|
-
// idiom exists only for the even-hour stride
|
|
584
|
-
// enumerating (return null) rather than coin a
|
|
634
|
+
// idiom exists only for the even-hour stride (interval 2 from midnight);
|
|
635
|
+
// another stride keeps enumerating (return null) rather than coin a
|
|
636
|
+
// misleading "…小时…" form.
|
|
585
637
|
if (minute === 0 && subMinute) {
|
|
586
|
-
return stride.interval === 2
|
|
638
|
+
return stride.interval === 2 && stride.start === 0 ?
|
|
639
|
+
'在偶数小时0分' + secondTail(ir) : null;
|
|
587
640
|
}
|
|
588
641
|
|
|
589
642
|
// A pinned minute 0 folds into the cadence with the explicit "0分" so the
|
|
@@ -599,6 +652,24 @@ function hourCadence(ir: IR): string | null {
|
|
|
599
652
|
prefix + minute + '分' + secondTail(ir);
|
|
600
653
|
}
|
|
601
654
|
|
|
655
|
+
// The cadence a clock-point core (clockTimes/compactClockTimes/composeSeconds)
|
|
656
|
+
// renders an hour stride to, or null. A bare hour stride (minute 0 on the plain
|
|
657
|
+
// :00 second) is the cadence phrase itself — "每2小时", "从0点起每5小时,至20点" —
|
|
658
|
+
// so a short non-tiling stride like */5, which hourCadence keeps enumerating
|
|
659
|
+
// (no minute to fold, nothing to disambiguate), still reads as the cadence. A
|
|
660
|
+
// pinned minute or meaningful second folds into the cadence via hourCadence.
|
|
661
|
+
function hourCadenceText(ir: IR): string | null {
|
|
662
|
+
if (ir.shapes.minute !== 'single') {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (+ir.pattern.minute === 0 && ir.pattern.second === '0') {
|
|
667
|
+
return hourCadencePhrase(ir);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return hourCadence(ir);
|
|
671
|
+
}
|
|
672
|
+
|
|
602
673
|
// The fused second tail for an hour cadence: "的每一秒" for a wildcard second,
|
|
603
674
|
// else "的" + the second's own clause ("的第30秒", "的每15秒").
|
|
604
675
|
function secondTail(ir: IR): string {
|
|
@@ -676,11 +747,13 @@ function minuteClause(ir: IR): string {
|
|
|
676
747
|
return valueList(fieldSegments(ir, 'minute'), '分');
|
|
677
748
|
}
|
|
678
749
|
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
750
|
+
// A single second folds into each clock time a clockTimes rest renders
|
|
751
|
+
// ("9点5分30秒"), so it is already spoken; appending the second clause again
|
|
752
|
+
// would double it. A wildcard/list/range second does not fold, so it still
|
|
753
|
+
// leads its own clause after the clock times.
|
|
754
|
+
function clockRestCarriesSecond(rest: PlanNode): boolean {
|
|
755
|
+
return rest.kind === 'clockTimes' &&
|
|
756
|
+
rest.times.some((time) => Boolean(time.second));
|
|
684
757
|
}
|
|
685
758
|
|
|
686
759
|
// minute = 0 ("on the hour"): render the rest schedule and attach the second.
|
|
@@ -710,11 +783,10 @@ function composeSecondsOnHour(ir: IR, plan: PlanNode, opts: Opts): string {
|
|
|
710
783
|
}
|
|
711
784
|
|
|
712
785
|
const restText = render(ir, rest, opts);
|
|
786
|
+
const secTail = clockRestCarriesSecond(rest) ? '' : sec;
|
|
713
787
|
|
|
714
|
-
if (
|
|
715
|
-
|
|
716
|
-
return '每天' + restText + sec;
|
|
717
|
-
}
|
|
788
|
+
if (composedClock && isDaily(ir)) {
|
|
789
|
+
return '每天' + restText + secTail;
|
|
718
790
|
}
|
|
719
791
|
|
|
720
792
|
// A stated minute (e.g. minute 0 under a sub-minute second) takes the same
|
|
@@ -723,7 +795,7 @@ function composeSecondsOnHour(ir: IR, plan: PlanNode, opts: Opts): string {
|
|
|
723
795
|
return restText + ',' + sec;
|
|
724
796
|
}
|
|
725
797
|
|
|
726
|
-
return restText +
|
|
798
|
+
return restText + secTail;
|
|
727
799
|
}
|
|
728
800
|
|
|
729
801
|
// A minute pinned to 0 under specific clock hours (not a compacted cadence): a
|
|
@@ -733,6 +805,15 @@ function composeSecondsOnHour(ir: IR, plan: PlanNode, opts: Opts): string {
|
|
|
733
805
|
// :00, not 3,600 across the hour) stays visible. The daily frame leads with
|
|
734
806
|
// 每天; a weekday or date qualifier is added by describe().
|
|
735
807
|
function composeMinuteZeroClocks(ir: IR, sec: string): string {
|
|
808
|
+
// An hour RANGE (or a list whose segments include a range) reads as the span
|
|
809
|
+
// the source wrote ("9点至17点"), not the wall of clock words it expands to —
|
|
810
|
+
// the hour-RANGE analog of the hour-step cadence. A pure single-value list
|
|
811
|
+
// (9,17) has no range to span and keeps enumerating below.
|
|
812
|
+
if (hasHourWindow(ir)) {
|
|
813
|
+
return isDaily(ir) ? '每天' + hourRangeWindow(ir, sec) :
|
|
814
|
+
hourRangeWindow(ir, sec);
|
|
815
|
+
}
|
|
816
|
+
|
|
736
817
|
const clocks = hourFires(ir).map(function clock(hour): string {
|
|
737
818
|
// Noon's word (正午) already pins 12:00, so the "0分" is redundant for it;
|
|
738
819
|
// midnight (凌晨0点) and other hours still need it to pin the minute.
|
|
@@ -747,13 +828,45 @@ function composeMinuteZeroClocks(ir: IR, sec: string): string {
|
|
|
747
828
|
return isDaily(ir) ? '每天' + core : core;
|
|
748
829
|
}
|
|
749
830
|
|
|
831
|
+
// Whether the hour field is a range — or a list whose segments include a
|
|
832
|
+
// range — and so forms a window ("9点至17点") rather than a wall of clock
|
|
833
|
+
// words. A pure single-value list (9,17) has no range to span; a step is
|
|
834
|
+
// handled by hourStride/hourCadence.
|
|
835
|
+
function hasHourWindow(ir: IR): boolean {
|
|
836
|
+
return fieldSegments(ir, 'hour').some(function range(segment) {
|
|
837
|
+
return segment.kind === 'range';
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// The hour-range window under a pinned minute 0 and a meaningful or wildcard
|
|
842
|
+
// second: the hour span list ("9点至17点", "9点至20点和22点") plus the second.
|
|
843
|
+
// A wildcard or sub-minute step second pins the explicit "0分" so the
|
|
844
|
+
// one-minute confinement stays visible ("9点至17点0分的每一秒"), distinct from
|
|
845
|
+
// the bare hourly window ("在9点至17点之间,每小时"); a single/list/range second
|
|
846
|
+
// reads as a clock-point span with the second appended ("9点至17点,第30秒"),
|
|
847
|
+
// matching the folded compact form for the same shape.
|
|
848
|
+
function hourRangeWindow(ir: IR, sec: string): string {
|
|
849
|
+
const span = hourList(ir);
|
|
850
|
+
|
|
851
|
+
if (ir.pattern.second === '*' || ir.shapes.second === 'step') {
|
|
852
|
+
return span + '0分' + (sec === '每秒' ? '的每一秒' : '的' + sec);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return span + ',' + sec;
|
|
856
|
+
}
|
|
857
|
+
|
|
750
858
|
// Wildcard or stepped minute: hang the "每分钟/每N分钟每秒" tail off the hour.
|
|
751
859
|
function composeSecondsCadence(ir: IR): string {
|
|
752
860
|
const sec = secondClause(ir);
|
|
753
861
|
const tail = minuteClause(ir) + sec;
|
|
754
862
|
|
|
755
|
-
|
|
756
|
-
|
|
863
|
+
const hourCad = hourCadencePhrase(ir);
|
|
864
|
+
|
|
865
|
+
if (hourCad !== null) {
|
|
866
|
+
// The cadence absorbs the tail with "的" ("每2小时的每分钟每秒",
|
|
867
|
+
// "从1点起每2小时的每分钟每秒"); a bounded cadence ends on "至K点", so its tail
|
|
868
|
+
// takes a comma to keep that endpoint from reading as a fused clock time.
|
|
869
|
+
return hourCad + (hourCad.indexOf('至') === -1 ? '的' : ',') + tail;
|
|
757
870
|
}
|
|
758
871
|
|
|
759
872
|
if (ir.shapes.hour === 'single') {
|
|
@@ -805,9 +918,10 @@ function composeSecondsListed(ir: IR): string {
|
|
|
805
918
|
return minutes + ',' + sec;
|
|
806
919
|
}
|
|
807
920
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
921
|
+
const hourCad = hourCadencePhrase(ir);
|
|
922
|
+
|
|
923
|
+
if (hourCad !== null) {
|
|
924
|
+
return hourCad + ',' + minutes + ',' + sec;
|
|
811
925
|
}
|
|
812
926
|
|
|
813
927
|
return hourFrame(ir) + minutes + ',' + sec;
|
|
@@ -1141,10 +1255,11 @@ function composeWindow(ir: IR, core: string): string {
|
|
|
1141
1255
|
return qualifier(ir) + core;
|
|
1142
1256
|
}
|
|
1143
1257
|
|
|
1144
|
-
// Whether an hour cadence applies — a single pinned minute over
|
|
1145
|
-
//
|
|
1258
|
+
// Whether an hour cadence applies — a single pinned minute over an hour stride
|
|
1259
|
+
// (clean, offset, or non-tiling) — so the clock-point plans take the cadence
|
|
1260
|
+
// frame, not the daily one.
|
|
1146
1261
|
function hourCadenceApplies(ir: IR): boolean {
|
|
1147
|
-
return
|
|
1262
|
+
return hourCadenceText(ir) !== null;
|
|
1148
1263
|
}
|
|
1149
1264
|
|
|
1150
1265
|
function describe(ir: IR, opts: Opts): string {
|