cronli5 0.8.3 → 0.8.5
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 +47 -0
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +19 -1
- package/dist/cronli5.js +19 -1
- package/dist/lang/de.cjs +49 -3
- package/dist/lang/de.js +49 -3
- package/dist/lang/en.cjs +19 -1
- package/dist/lang/en.js +19 -1
- package/dist/lang/es.cjs +68 -5
- package/dist/lang/es.js +68 -5
- package/dist/lang/fi.cjs +30 -0
- package/dist/lang/fi.js +30 -0
- package/dist/lang/fr.cjs +63 -25
- package/dist/lang/fr.js +63 -25
- package/dist/lang/pt.cjs +64 -25
- package/dist/lang/pt.js +64 -25
- package/dist/lang/zh.cjs +13 -3
- package/dist/lang/zh.js +13 -3
- package/package.json +1 -1
- package/src/lang/de/index.ts +119 -7
- package/src/lang/en/index.ts +57 -3
- package/src/lang/es/index.ts +158 -12
- package/src/lang/fi/index.ts +77 -0
- package/src/lang/fr/index.ts +132 -33
- package/src/lang/pt/index.ts +131 -36
- package/src/lang/zh/index.ts +35 -9
package/src/lang/fi/index.ts
CHANGED
|
@@ -346,6 +346,13 @@ function renderSecondsWithinMinute(
|
|
|
346
346
|
units.second.gen + ' kohdalla' + trailingQualifier(schedule, opts);
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
// A second LIST or RANGE under a single minute folds both into one shared
|
|
350
|
+
// "kohdalla" ("joka tunti 30 minuutin ja 5 ja 10 sekunnin kohdalla"), never
|
|
351
|
+
// the comma juxtaposition; a STEP second is a cadence and keeps its clause.
|
|
352
|
+
if (secondsConfinesMinute(schedule)) {
|
|
353
|
+
return secondsConfinement(schedule, opts);
|
|
354
|
+
}
|
|
355
|
+
|
|
349
356
|
return secondsLeadClause(schedule, opts) + ', ' +
|
|
350
357
|
atMarks(minuteField, units.minute, true) +
|
|
351
358
|
trailingQualifier(schedule, opts);
|
|
@@ -480,6 +487,68 @@ function isSteppedMinuteSeconds(
|
|
|
480
487
|
minuteStride(schedule) !== null;
|
|
481
488
|
}
|
|
482
489
|
|
|
490
|
+
// Whether a clock-point second (list, range, or single) sits under a restricted
|
|
491
|
+
// minute and a wildcard hour — the shape that must CONFINE the minute rather
|
|
492
|
+
// than juxtapose it behind a comma (two independent schedules). A second LIST
|
|
493
|
+
// the core enumerated from a step (`3/2`) is really a stride cadence and stays
|
|
494
|
+
// out. The single-second + single-minute pair folds into one shared "kohdalla"
|
|
495
|
+
// already ("joka tunti 30 minuutin ja 15 sekunnin kohdalla") and is excluded.
|
|
496
|
+
function secondsConfinesMinute(schedule: Schedule): boolean {
|
|
497
|
+
const {second, minute, hour} = schedule.shapes;
|
|
498
|
+
|
|
499
|
+
if (second === 'list') {
|
|
500
|
+
const values = singleValues(segmentsOf(schedule, 'second'));
|
|
501
|
+
|
|
502
|
+
if (values && arithmeticStep(values)) {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const clockPoint = second === 'single' || second === 'range' ||
|
|
508
|
+
second === 'list';
|
|
509
|
+
|
|
510
|
+
return clockPoint && minute !== 'wildcard' && hour === 'wildcard' &&
|
|
511
|
+
!(second === 'single' && minute === 'single');
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Whether a compose-seconds plan over a minute-cadence/list/range rest carries
|
|
515
|
+
// a clock-point second that must confine the minute — the gate for the "的"/
|
|
516
|
+
// shared-"kohdalla" fusion in `renderComposeSeconds`, kept out of that function
|
|
517
|
+
// to hold its branch count down.
|
|
518
|
+
function composeConfinesMinute(
|
|
519
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>, schedule: Schedule
|
|
520
|
+
): boolean {
|
|
521
|
+
const minuteRest = plan.rest.kind === 'minuteFrequency' ||
|
|
522
|
+
plan.rest.kind === 'multipleMinutes' ||
|
|
523
|
+
plan.rest.kind === 'rangeOfMinutes';
|
|
524
|
+
|
|
525
|
+
return minuteRest && secondsConfinesMinute(schedule);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Confine a restricted minute under a clock-point second. A STEPPED minute
|
|
529
|
+
// keeps the essive ordinal cadence frame ("joka kuudentena minuuttina jokaisen
|
|
530
|
+
// tunnin minuutista 4 alkaen") and the seconds trail as their postposition,
|
|
531
|
+
// mirroring the open-minute "joka minuutti 5, 10 ja 15 sekunnin kohdalla". A
|
|
532
|
+
// list, range, or single minute folds BOTH fields into one shared "kohdalla"
|
|
533
|
+
// ("joka tunti 0, 15 ja 30 minuutin ja 5, 10 ja 15 sekunnin kohdalla"), the
|
|
534
|
+
// same fusion the single-second case uses — never the comma juxtaposition.
|
|
535
|
+
function secondsConfinement(
|
|
536
|
+
schedule: Schedule, opts: NormalizedOptions
|
|
537
|
+
): string {
|
|
538
|
+
const stride = minuteStride(schedule);
|
|
539
|
+
|
|
540
|
+
if (stride && schedule.pattern.minute !== '*/2') {
|
|
541
|
+
return minuteStepConfinement(schedule, stride, opts);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const minuteDigits = joinList(segmentWords(segmentsOf(schedule, 'minute')));
|
|
545
|
+
const secondDigits = joinList(segmentWords(segmentsOf(schedule, 'second')));
|
|
546
|
+
|
|
547
|
+
return units.minute.mark + ' ' + minuteDigits + ' ' + units.minute.gen +
|
|
548
|
+
' ja ' + secondDigits + ' ' + units.second.gen + ' kohdalla' +
|
|
549
|
+
trailingQualifier(schedule, opts);
|
|
550
|
+
}
|
|
551
|
+
|
|
483
552
|
function renderComposeSeconds(
|
|
484
553
|
schedule: Schedule,
|
|
485
554
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -505,6 +574,14 @@ function renderComposeSeconds(
|
|
|
505
574
|
return minuteStepConfinement(schedule, minuteStride(schedule)!, opts);
|
|
506
575
|
}
|
|
507
576
|
|
|
577
|
+
// A clock-point second (list/range/single) under a restricted minute confines
|
|
578
|
+
// that minute, never the comma juxtaposition that reads as two schedules: a
|
|
579
|
+
// stepped minute keeps its essive frame with the seconds postposition; a
|
|
580
|
+
// list, range, or single minute folds both into one shared "kohdalla".
|
|
581
|
+
if (composeConfinesMinute(plan, schedule)) {
|
|
582
|
+
return secondsConfinement(schedule, opts);
|
|
583
|
+
}
|
|
584
|
+
|
|
508
585
|
// When the rest is a minute-step cadence, the step leads and the second
|
|
509
586
|
// anchor follows after a comma (the comma marks the granularity boundary
|
|
510
587
|
// between the two levels, not a flat list).
|
package/src/lang/fr/index.ts
CHANGED
|
@@ -127,16 +127,6 @@ const weekdayNames = [
|
|
|
127
127
|
const nthWeekdayMasculine =
|
|
128
128
|
[null, 'premier', 'deuxième', 'troisième', 'quatrième', 'cinquième'];
|
|
129
129
|
|
|
130
|
-
// French ordinals (gender-neutral "-ième") for a stepped-minute cadence under a
|
|
131
|
-
// seconds lead ("à la sixième minute"). The interval-2 step keeps its own
|
|
132
|
-
// idiom and never reaches here; a lookup miss falls back to the cardinal-with-
|
|
133
|
-
// preposition form, which still confines (see `minuteStepConfinement`).
|
|
134
|
-
const stepOrdinals: Record<number, string> = {
|
|
135
|
-
3: 'troisième', 4: 'quatrième', 5: 'cinquième', 6: 'sixième',
|
|
136
|
-
7: 'septième', 8: 'huitième', 9: 'neuvième', 10: 'dixième',
|
|
137
|
-
12: 'douzième', 15: 'quinzième', 20: 'vingtième', 30: 'trentième'
|
|
138
|
-
};
|
|
139
|
-
|
|
140
130
|
// Normalize raw user options.
|
|
141
131
|
function normalizeOptions(options?: Cronli5Options): Opts {
|
|
142
132
|
options = options || {};
|
|
@@ -227,6 +217,14 @@ function renderSecondsWithinMinute(
|
|
|
227
217
|
trailingQualifier(schedule, opts);
|
|
228
218
|
}
|
|
229
219
|
|
|
220
|
+
// A second LIST or RANGE under a single minute confines that minute in the
|
|
221
|
+
// genitive ("aux secondes 5 et 10 de la minute 30 de chaque heure"), never
|
|
222
|
+
// the comma juxtaposition; a STEP second is a cadence and keeps its own lead.
|
|
223
|
+
if (secondsConfinesMinute(schedule)) {
|
|
224
|
+
return secondsBareLead(schedule) + ' ' +
|
|
225
|
+
confinedMinutePhrase(schedule) + trailingQualifier(schedule, opts);
|
|
226
|
+
}
|
|
227
|
+
|
|
230
228
|
return secondsLeadClause(schedule, opts) + ', à la minute ' + minuteField +
|
|
231
229
|
' de chaque heure' + trailingQualifier(schedule, opts);
|
|
232
230
|
}
|
|
@@ -309,30 +307,22 @@ function minuteStride(
|
|
|
309
307
|
return values && arithmeticStep(values);
|
|
310
308
|
}
|
|
311
309
|
|
|
312
|
-
// A stepped minute under a wildcard
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
|
|
310
|
+
// A stepped minute under a wildcard hour: the second clause leads, a COMMA,
|
|
311
|
+
// then the minute's own STANDALONE cardinal cadence ("chaque seconde, toutes
|
|
312
|
+
// les six minutes à partir de la minute 4 de chaque heure"; "aux secondes 5,
|
|
313
|
+
// 10 et 15, toutes les six minutes …"). The ordinal "à la sixième minute" read
|
|
314
|
+
// as a single minute (the 10th), not the every-sixth series; the standalone
|
|
315
|
+
// cardinal "toutes les six minutes" reads it correctly and handles every stride
|
|
316
|
+
// (offset, bounded, uneven) for free. The lead is the cadence clause for a
|
|
317
|
+
// wildcard/stepped second, the bare clock-point clause for a list/range/single.
|
|
318
|
+
function steppedMinuteConfinement(
|
|
319
319
|
schedule: Schedule,
|
|
320
|
-
|
|
320
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
321
|
+
lead: string,
|
|
321
322
|
opts: Opts
|
|
322
323
|
): string {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
'à la ' + ordinal + ' minute' :
|
|
326
|
-
'à la minute toutes les ' + numero(stride.interval, opts);
|
|
327
|
-
|
|
328
|
-
const tail = chooseStride({...stride, cycle: 60}, {
|
|
329
|
-
bare: () => '',
|
|
330
|
-
offset: () => ' à partir de la minute ' + stride.start,
|
|
331
|
-
bounded: () => ' de la minute ' + stride.start + ' à ' + stride.last
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
return secondsLeadClause(schedule, opts) + ' ' + head + tail +
|
|
335
|
-
' de chaque heure' + trailingQualifier(schedule, opts);
|
|
324
|
+
return lead + ', ' + render(schedule, plan.rest, opts) +
|
|
325
|
+
trailingQualifier(schedule, opts);
|
|
336
326
|
}
|
|
337
327
|
|
|
338
328
|
// Whether a stepped minute fills a wildcard hour under a wildcard/stepped
|
|
@@ -350,6 +340,108 @@ function isSteppedMinuteSeconds(
|
|
|
350
340
|
minuteStride(schedule) !== null;
|
|
351
341
|
}
|
|
352
342
|
|
|
343
|
+
// The leading seconds words for a clock-point second, WITHOUT the trailing "de
|
|
344
|
+
// chaque minute" anchor: a confined second attaches to the CONFINED minute ("de
|
|
345
|
+
// la sixième minute…"), so the generic minute anchor would be redundant.
|
|
346
|
+
function secondsBareLead(schedule: Schedule): string {
|
|
347
|
+
const secondField = schedule.pattern.second;
|
|
348
|
+
const shape = schedule.shapes.second;
|
|
349
|
+
|
|
350
|
+
if (shape === 'range') {
|
|
351
|
+
const bounds = secondField.split('-');
|
|
352
|
+
|
|
353
|
+
return 'chaque seconde de ' + bounds[0] + ' à ' + bounds[1];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (shape === 'single') {
|
|
357
|
+
return 'à la seconde ' + secondField;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return 'aux secondes ' +
|
|
361
|
+
joinList(segmentWords(segmentsOf(schedule, 'second')));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// The CONFINED-minute genitive phrase a clock-point second attaches to ("des
|
|
365
|
+
// minutes 0, 15 et 30 de chaque heure", "de la minute 30 de chaque heure", "de
|
|
366
|
+
// chaque minute de 0 à 30 de chaque heure"). A stepped minute is handled by the
|
|
367
|
+
// standalone-cadence confinement before this point; a list, range, or single
|
|
368
|
+
// names the minute(s) in the genitive — so the bare seconds lead never stacks a
|
|
369
|
+
// redundant "de chaque minute".
|
|
370
|
+
function confinedMinutePhrase(schedule: Schedule): string {
|
|
371
|
+
if (schedule.shapes.minute === 'range') {
|
|
372
|
+
return 'de ' + minuteRangeLead(schedule.pattern.minute) +
|
|
373
|
+
' de chaque heure';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (schedule.shapes.minute === 'list') {
|
|
377
|
+
return 'des minutes ' +
|
|
378
|
+
joinList(segmentWords(segmentsOf(schedule, 'minute'))) +
|
|
379
|
+
' de chaque heure';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return 'de la minute ' + schedule.pattern.minute + ' de chaque heure';
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Whether a clock-point second (list, range, or single) sits under a restricted
|
|
386
|
+
// minute and a wildcard hour — the shape that must CONFINE the minute in the
|
|
387
|
+
// genitive rather than juxtapose it behind a comma (two independent schedules).
|
|
388
|
+
// A second LIST the core enumerated from a step (`3/2`) is really a stride
|
|
389
|
+
// cadence and stays out. The single-second + single-minute pair folds into one
|
|
390
|
+
// coherent clock point and is excluded.
|
|
391
|
+
function secondsConfinesMinute(schedule: Schedule): boolean {
|
|
392
|
+
const {second, minute, hour} = schedule.shapes;
|
|
393
|
+
|
|
394
|
+
if (second === 'list') {
|
|
395
|
+
const values = singleValues(segmentsOf(schedule, 'second'));
|
|
396
|
+
|
|
397
|
+
if (values && arithmeticStep(values)) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const clockPoint = second === 'single' || second === 'range' ||
|
|
403
|
+
second === 'list';
|
|
404
|
+
|
|
405
|
+
return clockPoint && minute !== 'wildcard' && hour === 'wildcard' &&
|
|
406
|
+
!(second === 'single' && minute === 'single');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// The minute-confinement rendering for a compose-seconds plan, or null when the
|
|
410
|
+
// plan is not one. A STEPPED minute (cadence or clock-point second) leads with
|
|
411
|
+
// the second clause, a comma, then the minute's own standalone cardinal
|
|
412
|
+
// cadence; a CLOCK-POINT second over a LIST/RANGE/SINGLE minute uses the
|
|
413
|
+
// genitive form anchored to the confined minute. Both bind the second beneath
|
|
414
|
+
// the minute instead of juxtaposing the two behind a bare comma + "de chaque
|
|
415
|
+
// minute". Folded into one helper so `renderComposeSeconds` carries a single
|
|
416
|
+
// branch.
|
|
417
|
+
function minuteConfinementRender(
|
|
418
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
419
|
+
schedule: Schedule, opts: Opts
|
|
420
|
+
): string | null {
|
|
421
|
+
if (isSteppedMinuteSeconds(schedule, plan)) {
|
|
422
|
+
return steppedMinuteConfinement(schedule, plan,
|
|
423
|
+
secondsLeadClause(schedule, opts), opts);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const minuteRest = plan.rest.kind === 'minuteFrequency' ||
|
|
427
|
+
plan.rest.kind === 'multipleMinutes' ||
|
|
428
|
+
plan.rest.kind === 'rangeOfMinutes';
|
|
429
|
+
|
|
430
|
+
if (minuteRest && secondsConfinesMinute(schedule)) {
|
|
431
|
+
// A clock-point second over a STEPPED minute reuses the standalone cardinal
|
|
432
|
+
// cadence the same way; only a list/range/single minute keeps the genitive.
|
|
433
|
+
if (minuteStride(schedule) && schedule.pattern.minute !== '*/2') {
|
|
434
|
+
return steppedMinuteConfinement(schedule, plan,
|
|
435
|
+
secondsBareLead(schedule), opts);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return secondsBareLead(schedule) + ' ' +
|
|
439
|
+
confinedMinutePhrase(schedule) + trailingQualifier(schedule, opts);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
|
|
353
445
|
function renderComposeSeconds(
|
|
354
446
|
schedule: Schedule,
|
|
355
447
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -397,8 +489,15 @@ function renderComposeSeconds(
|
|
|
397
489
|
// the second cadence to the ordinal minute cadence ("chaque seconde à la
|
|
398
490
|
// sixième minute à partir de la minute 4 de chaque heure"), never the comma
|
|
399
491
|
// juxtaposition that reads as two independent cadences.
|
|
400
|
-
|
|
401
|
-
|
|
492
|
+
// A second confines the minute restriction (open hour), never the comma
|
|
493
|
+
// juxtaposition that reads as two independent cadences: a CADENCE second over
|
|
494
|
+
// a stepped minute uses the ordinal-cadence form ("chaque seconde à la
|
|
495
|
+
// sixième minute …"); a CLOCK-POINT second uses the genitive form anchored to
|
|
496
|
+
// the confined minute ("aux secondes 5, 10 et 15 de la sixième minute …").
|
|
497
|
+
const confined = minuteConfinementRender(plan, schedule, opts);
|
|
498
|
+
|
|
499
|
+
if (confined !== null) {
|
|
500
|
+
return confined;
|
|
402
501
|
}
|
|
403
502
|
|
|
404
503
|
// A wildcard second under a minute */2 with a wildcard hour juxtaposes two
|
package/src/lang/pt/index.ts
CHANGED
|
@@ -144,16 +144,6 @@ const nthWeekdayMasculine =
|
|
|
144
144
|
const nthWeekdayFeminine =
|
|
145
145
|
[null, 'primeira', 'segunda', 'terceira', 'quarta', 'quinta'];
|
|
146
146
|
|
|
147
|
-
// Portuguese ordinals (masculine) for a stepped-minute cadence under a seconds
|
|
148
|
-
// lead ("a cada segundo no sexto minuto"). The interval-2 step keeps its own
|
|
149
|
-
// idiom and never reaches here, so the colliding "segundo" is unused; a lookup
|
|
150
|
-
// miss falls back to the cardinal-with-"no" form, which still confines.
|
|
151
|
-
const stepOrdinals: Record<number, string> = {
|
|
152
|
-
3: 'terceiro', 4: 'quarto', 5: 'quinto', 6: 'sexto', 7: 'sétimo',
|
|
153
|
-
8: 'oitavo', 9: 'nono', 10: 'décimo', 12: 'décimo segundo',
|
|
154
|
-
15: 'décimo quinto', 20: 'vigésimo', 30: 'trigésimo'
|
|
155
|
-
};
|
|
156
|
-
|
|
157
147
|
// --- Contractions (the principal es->pt divergence). ---
|
|
158
148
|
//
|
|
159
149
|
// Portuguese fuses a preposition with the following article wherever es emitted
|
|
@@ -358,6 +348,14 @@ function renderSecondsWithinMinute(
|
|
|
358
348
|
trailingQualifier(schedule, opts);
|
|
359
349
|
}
|
|
360
350
|
|
|
351
|
+
// A second LIST or RANGE under a single minute confines that minute in the
|
|
352
|
+
// genitive ("nos segundos 5 e 10 do minuto 30 de cada hora"), never the comma
|
|
353
|
+
// juxtaposition; a STEP second is a cadence and keeps its own lead.
|
|
354
|
+
if (secondsConfinesMinute(schedule)) {
|
|
355
|
+
return secondsBareLead(schedule) + ' ' +
|
|
356
|
+
confinedMinutePhrase(schedule) + trailingQualifier(schedule, opts);
|
|
357
|
+
}
|
|
358
|
+
|
|
361
359
|
return secondsLeadClause(schedule, opts) + ', no minuto ' + minuteField +
|
|
362
360
|
' de cada hora' + trailingQualifier(schedule, opts);
|
|
363
361
|
}
|
|
@@ -444,29 +442,21 @@ function minuteStride(
|
|
|
444
442
|
}
|
|
445
443
|
|
|
446
444
|
// A stepped minute under a wildcard/stepped second and wildcard hour: bind the
|
|
447
|
-
// second
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
|
|
445
|
+
// second clause leads, a COMMA, then the minute's own STANDALONE cardinal
|
|
446
|
+
// cadence ("a cada segundo, a cada seis minutos a partir do minuto 4 de cada
|
|
447
|
+
// hora"; "nos segundos 5, 10 e 15, a cada seis minutos …"). The ordinal "no
|
|
448
|
+
// sexto minuto" read as a single minute (the 10th), not the every-sixth series;
|
|
449
|
+
// the standalone cardinal "a cada seis minutos" reads it correctly and handles
|
|
450
|
+
// every stride (offset, bounded, uneven) for free. The lead is the cadence
|
|
451
|
+
// clause for a wildcard/stepped second, the bare clock-point clause otherwise.
|
|
452
|
+
function steppedMinuteConfinement(
|
|
453
453
|
schedule: Schedule,
|
|
454
|
-
|
|
454
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
455
|
+
lead: string,
|
|
455
456
|
opts: Opts
|
|
456
457
|
): string {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
'no ' + ordinal + ' minuto' :
|
|
460
|
-
'a cada ' + numero(stride.interval, opts) + ' minutos';
|
|
461
|
-
|
|
462
|
-
const tail = chooseStride({...stride, cycle: 60}, {
|
|
463
|
-
bare: () => '',
|
|
464
|
-
offset: () => ' a partir do minuto ' + stride.start,
|
|
465
|
-
bounded: () => ' do minuto ' + stride.start + ' ao ' + stride.last
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
return secondsLeadClause(schedule, opts) + ' ' + head + tail +
|
|
469
|
-
' de cada hora' + trailingQualifier(schedule, opts);
|
|
458
|
+
return lead + ', ' + render(schedule, plan.rest, opts) +
|
|
459
|
+
trailingQualifier(schedule, opts);
|
|
470
460
|
}
|
|
471
461
|
|
|
472
462
|
// Whether a stepped minute fills a wildcard hour under a wildcard/stepped
|
|
@@ -484,6 +474,108 @@ function isSteppedMinuteSeconds(
|
|
|
484
474
|
minuteStride(schedule) !== null;
|
|
485
475
|
}
|
|
486
476
|
|
|
477
|
+
// The leading seconds words for a clock-point second, WITHOUT the trailing "de
|
|
478
|
+
// cada minuto" anchor: a confined second attaches to the CONFINED minute ("do
|
|
479
|
+
// sexto minuto…"), so the generic minute anchor would be redundant.
|
|
480
|
+
function secondsBareLead(schedule: Schedule): string {
|
|
481
|
+
const secondField = schedule.pattern.second;
|
|
482
|
+
const shape = schedule.shapes.second;
|
|
483
|
+
|
|
484
|
+
if (shape === 'range') {
|
|
485
|
+
const bounds = secondField.split('-');
|
|
486
|
+
|
|
487
|
+
return 'a cada segundo do ' + bounds[0] + ' ao ' + bounds[1];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (shape === 'single') {
|
|
491
|
+
return 'no segundo ' + secondField;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return 'nos segundos ' +
|
|
495
|
+
joinList(segmentWords(segmentsOf(schedule, 'second')));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// The CONFINED-minute genitive phrase a clock-point second attaches to ("dos
|
|
499
|
+
// minutos 0, 15 e 30 de cada hora", "do minuto 30 de cada hora", "de cada
|
|
500
|
+
// minuto do 0 ao 30 de cada hora"). A stepped minute is handled by the
|
|
501
|
+
// standalone-cadence confinement before this point; a list, range, or single
|
|
502
|
+
// names the minute(s) — so the bare seconds lead never stacks a redundant "de
|
|
503
|
+
// cada minuto".
|
|
504
|
+
function confinedMinutePhrase(schedule: Schedule): string {
|
|
505
|
+
if (schedule.shapes.minute === 'range') {
|
|
506
|
+
// `minuteRangeLead` is "a cada minuto do 0 ao 30"; the genitive "de"
|
|
507
|
+
// absorbs its leading "a" ("de cada minuto …", not "de a cada minuto").
|
|
508
|
+
const range = minuteRangeLead(schedule.pattern.minute).replace(/^a /u, '');
|
|
509
|
+
|
|
510
|
+
return 'de ' + range + ' de cada hora';
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (schedule.shapes.minute === 'list') {
|
|
514
|
+
return 'dos minutos ' +
|
|
515
|
+
joinList(segmentWords(segmentsOf(schedule, 'minute'))) + ' de cada hora';
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return 'do minuto ' + schedule.pattern.minute + ' de cada hora';
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Whether a clock-point second (list, range, or single) sits under a restricted
|
|
522
|
+
// minute and a wildcard hour — the shape that must CONFINE the minute in the
|
|
523
|
+
// genitive rather than juxtapose it behind a comma (two independent schedules).
|
|
524
|
+
// A second LIST the core enumerated from a step (`3/2`) is really a stride
|
|
525
|
+
// cadence and stays out. The single-second + single-minute pair folds into one
|
|
526
|
+
// coherent clock point and is excluded.
|
|
527
|
+
function secondsConfinesMinute(schedule: Schedule): boolean {
|
|
528
|
+
const {second, minute, hour} = schedule.shapes;
|
|
529
|
+
|
|
530
|
+
if (second === 'list') {
|
|
531
|
+
const values = singleValues(segmentsOf(schedule, 'second'));
|
|
532
|
+
|
|
533
|
+
if (values && arithmeticStep(values)) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const clockPoint = second === 'single' || second === 'range' ||
|
|
539
|
+
second === 'list';
|
|
540
|
+
|
|
541
|
+
return clockPoint && minute !== 'wildcard' && hour === 'wildcard' &&
|
|
542
|
+
!(second === 'single' && minute === 'single');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// The minute-confinement rendering for a compose-seconds plan, or null when the
|
|
546
|
+
// plan is not one. A CADENCE second over a stepped minute uses the ordinal
|
|
547
|
+
// cadence form; a CLOCK-POINT second (list/range/single) over any restricted
|
|
548
|
+
// minute uses the genitive form anchored to the confined minute. Both bind the
|
|
549
|
+
// second beneath the minute instead of juxtaposing the two behind a comma.
|
|
550
|
+
// Folded into one helper so `renderComposeSeconds` carries a single branch.
|
|
551
|
+
function minuteConfinementRender(
|
|
552
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
553
|
+
schedule: Schedule, opts: Opts
|
|
554
|
+
): string | null {
|
|
555
|
+
if (isSteppedMinuteSeconds(schedule, plan)) {
|
|
556
|
+
return steppedMinuteConfinement(schedule, plan,
|
|
557
|
+
secondsLeadClause(schedule, opts), opts);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const minuteRest = plan.rest.kind === 'minuteFrequency' ||
|
|
561
|
+
plan.rest.kind === 'multipleMinutes' ||
|
|
562
|
+
plan.rest.kind === 'rangeOfMinutes';
|
|
563
|
+
|
|
564
|
+
if (minuteRest && secondsConfinesMinute(schedule)) {
|
|
565
|
+
// A clock-point second over a STEPPED minute reuses the standalone cardinal
|
|
566
|
+
// cadence the same way; only a list/range/single minute keeps the genitive.
|
|
567
|
+
if (minuteStride(schedule) && schedule.pattern.minute !== '*/2') {
|
|
568
|
+
return steppedMinuteConfinement(schedule, plan,
|
|
569
|
+
secondsBareLead(schedule), opts);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return secondsBareLead(schedule) + ' ' +
|
|
573
|
+
confinedMinutePhrase(schedule) + trailingQualifier(schedule, opts);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
|
|
487
579
|
function renderComposeSeconds(
|
|
488
580
|
schedule: Schedule,
|
|
489
581
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -527,12 +619,15 @@ function renderComposeSeconds(
|
|
|
527
619
|
return dayFrame + ', ' + window + ', ' + cadence;
|
|
528
620
|
}
|
|
529
621
|
|
|
530
|
-
// A
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
|
|
535
|
-
|
|
622
|
+
// A second confines the minute restriction (open hour), never the comma
|
|
623
|
+
// juxtaposition that reads as two independent cadences: a CADENCE second over
|
|
624
|
+
// a stepped minute uses the ordinal-cadence form ("a cada segundo no sexto
|
|
625
|
+
// minuto …"); a CLOCK-POINT second uses the genitive form anchored to the
|
|
626
|
+
// confined minute ("nos segundos 5, 10 e 15 do sexto minuto …").
|
|
627
|
+
const confined = minuteConfinementRender(plan, schedule, opts);
|
|
628
|
+
|
|
629
|
+
if (confined !== null) {
|
|
630
|
+
return confined;
|
|
536
631
|
}
|
|
537
632
|
|
|
538
633
|
// A wildcard second under a minute */2 with a wildcard hour juxtaposes two
|
package/src/lang/zh/index.ts
CHANGED
|
@@ -833,8 +833,12 @@ function secondClause(schedule: Schedule): string {
|
|
|
833
833
|
|
|
834
834
|
const first = segs[0];
|
|
835
835
|
|
|
836
|
-
|
|
837
|
-
|
|
836
|
+
// A STEP-shaped second reads as its stride cadence ("每6秒"), whether written
|
|
837
|
+
// "*/6" or the offset-clean "0/6" — both fire 0,6,…,54 — never the enumerated
|
|
838
|
+
// "第0、6、…、54秒". stepClause routes a clean/offset-clean stride through the
|
|
839
|
+
// bare cadence and only lists a bounded `a-b/n` or a short offset.
|
|
840
|
+
if (segs.length === 1 && first.kind === 'step') {
|
|
841
|
+
return stepClause(first, '秒', '秒', '每分钟');
|
|
838
842
|
}
|
|
839
843
|
|
|
840
844
|
// An offset/uneven step the core enumerated to this list reads as a stride
|
|
@@ -1044,6 +1048,13 @@ function composeSecondsCadence(schedule: Schedule): string {
|
|
|
1044
1048
|
}
|
|
1045
1049
|
}
|
|
1046
1050
|
|
|
1051
|
+
// A CLOCK-POINT second (a single/list/range, not a stride cadence) under a
|
|
1052
|
+
// clean minute step fuses beneath the minute with "的" ("每6分钟的第30秒"),
|
|
1053
|
+
// never the comma that reads as two independent schedules.
|
|
1054
|
+
if (!secondIsCadence(schedule) && !secondIsStride(schedule)) {
|
|
1055
|
+
return minuteClause(schedule) + '的' + sec;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1047
1058
|
return sec + ',' + minuteClause(schedule);
|
|
1048
1059
|
}
|
|
1049
1060
|
|
|
@@ -1073,13 +1084,14 @@ function composeSecondsListed(schedule: Schedule): string {
|
|
|
1073
1084
|
}
|
|
1074
1085
|
|
|
1075
1086
|
if (schedule.shapes.hour === 'wildcard') {
|
|
1076
|
-
//
|
|
1077
|
-
// 每秒")
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1082
|
-
|
|
1087
|
+
// The minute(s) are stated and the hour is open, so the second — whether a
|
|
1088
|
+
// cadence ("每秒"/"每N秒") or a clock-point ("第5、10、15秒") — fuses beneath
|
|
1089
|
+
// the minute(s) with "的" ("每小时30分的每一秒", "每小时0、15、30分的第5、10、15
|
|
1090
|
+
// 秒"). The bare comma ("…,第5、10、15秒") reads as two independent schedules.
|
|
1091
|
+
// A second STRIDE the core enumerated to a list ("3/2" → "每2秒,至59秒") is a
|
|
1092
|
+
// bounded cadence with its own trailing ",至N秒"; it keeps the comma.
|
|
1093
|
+
return secondIsStride(schedule) ?
|
|
1094
|
+
minutes + ',' + sec : minutes + confinedSecondTail(sec);
|
|
1083
1095
|
}
|
|
1084
1096
|
|
|
1085
1097
|
const hourCad = unevenHourCadence(schedule);
|
|
@@ -1114,6 +1126,20 @@ function secondIsCadence(schedule: Schedule): boolean {
|
|
|
1114
1126
|
return schedule.pattern.second === '*' || schedule.shapes.second === 'step';
|
|
1115
1127
|
}
|
|
1116
1128
|
|
|
1129
|
+
// Whether a second LIST is really a stride the core enumerated from a step
|
|
1130
|
+
// ("3/2" → 3,5,…,59), spoken as a bounded cadence ("每2秒,至59秒") with its own
|
|
1131
|
+
// trailing comma — not a clock-point list ("第5、10、15秒"). Such a stride keeps
|
|
1132
|
+
// its comma form rather than fusing beneath the minute with "的".
|
|
1133
|
+
function secondIsStride(schedule: Schedule): boolean {
|
|
1134
|
+
if (schedule.shapes.second !== 'list') {
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const values = singleValues(segmentsOf(schedule, 'second'));
|
|
1139
|
+
|
|
1140
|
+
return values !== null && arithmeticStep(values) !== null;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1117
1143
|
// The "的"-fused second tail for a clause that already states its minute(s):
|
|
1118
1144
|
// "的每一秒" for a wildcard second, else "的" + the second's own cadence clause.
|
|
1119
1145
|
// The fusion binds the second beneath the minute rather than leaving a bare
|