rrule-ts 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,69 @@
1
1
  import type { RRuleOptions } from '../types.js';
2
- /** Options accepted by toText. */
2
+ import type { Result } from '../result.js';
3
+ import type { LocalePack } from '../locales/types.js';
4
+ export type { LocalePack, LocaleTokens } from '../locales/types.js';
5
+ export { COMPLEX_RULE_FALLBACK } from './toText.js';
6
+ /** Options accepted by toText and fromText. */
3
7
  export interface TextOptions {
4
- /** Locale identifier, e.g. 'en' or 'de'. Defaults to 'en'. */
5
- locale?: string;
8
+ /**
9
+ * Locale pack to use for rendering. Defaults to the built-in English locale
10
+ * when omitted. Pass 'en' or 'de' (imported from rrule-ts/locales/en or
11
+ * rrule-ts/locales/de) to override.
12
+ */
13
+ locale?: LocalePack;
6
14
  }
7
15
  /**
8
- * Convert `RRuleOptions` to a human-readable recurrence description.
16
+ * Convert RRuleOptions to a human-readable recurrence description.
9
17
  *
10
- * @example toText({ freq: 'WEEKLY', byDay: [{ weekday: 'MO', ordinal: undefined }] })
11
- * // 'every week on Monday' (expansion phase)
18
+ * Never throws. Returns '(complex recurrence rule)' for rules that cannot be
19
+ * naturally phrased in the target locale (e.g. BYWEEKNO, BYYEARDAY, unusual
20
+ * BYSETPOS combinations). This is the only case where the output is not a
21
+ * grammatical sentence.
12
22
  *
13
- * @stub Not yet implemented.
14
- * TODO(expansion-phase): implement locale-aware text rendering.
23
+ * @example
24
+ * toText({ freq: 'DAILY' })
25
+ * // 'every day'
26
+ *
27
+ * @example
28
+ * toText({ freq: 'WEEKLY', byDay: [{ weekday: 'MO', ordinal: undefined }] })
29
+ * // 'every week on Monday'
30
+ *
31
+ * @example
32
+ * import { de } from 'rrule-ts/locales/de'
33
+ * toText({ freq: 'DAILY' }, { locale: de })
34
+ * // 'jeden Tag'
15
35
  */
16
- export declare function toText(_options: RRuleOptions, _textOptions?: TextOptions): never;
36
+ export declare function toText(options: RRuleOptions, textOptions?: TextOptions): string;
17
37
  /**
18
- * Parse a human-readable recurrence description into `RRuleOptions`.
38
+ * Parse a human-readable English recurrence description into RRuleOptions.
39
+ *
40
+ * Accepts only canonical output produced by toText with the EN locale.
41
+ * Not a general natural-language parser.
42
+ *
43
+ * fromText is EN-only. There is no locale parameter because DE (and other
44
+ * locales) parsing is not supported: the parser grammar is tied to the
45
+ * English strings produced by toText. Passing non-English text returns Err.
46
+ *
47
+ * Round-trip guarantee: for all RRuleOptions x where toText(x, {locale: en})
48
+ * does not return the complex-rule fallback string,
49
+ * fromText(toText(x, {locale: en})).value deep-equals x
50
+ * (excluding dtstart, tzid, wkst, bySecond which are absent from text output,
51
+ * and excluding byMinute=[0] which is indistinguishable from byMinute=undefined,
52
+ * and excluding BYSETPOS + single plain weekday which is reconstructed as an
53
+ * ordinal BYDAY entry, losing the original bySetPos field).
54
+ *
55
+ * Returns Err if the text cannot be parsed as EN toText output.
56
+ *
57
+ * Out of scope: BYWEEKNO, BYYEARDAY, BYSECOND, WKST,
58
+ * multi-value BYSETPOS, free-form prose not produced by toText.
59
+ *
60
+ * @example
61
+ * fromText('every day')
62
+ * // { ok: true, value: { freq: 'DAILY' } }
19
63
  *
20
- * @stub Not yet implemented.
21
- * TODO(expansion-phase): implement natural-language RRULE parser.
64
+ * @example
65
+ * fromText('every week on Monday')
66
+ * // { ok: true, value: { freq: 'WEEKLY', byDay: [{ weekday: 'MO', ordinal: undefined }] } }
22
67
  */
23
- export declare function fromText(_text: string, _locale?: string): never;
68
+ export declare function fromText(text: string): Result<RRuleOptions, string>;
24
69
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;GAQG;AAEH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,CAAC,EAAE,WAAW,GAAG,KAAK,CAEhF;AAED;;;;;GAKG;AAEH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAE/D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAIrD,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,MAAM,CAE/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAEnE"}
@@ -1,28 +1,68 @@
1
1
  // Human-readable RRULE text rendering (import 'rrule-ts/text').
2
2
  //
3
- // Stubs for the expansion phase.
4
- // TODO(expansion-phase): implement toText and fromText with i18n locale packs.
3
+ // Public API for toText and fromText. The EN locale pack is bundled here as
4
+ // the default so consumers get natural English output without any imports.
5
+ // Other locales must be imported explicitly from 'rrule-ts/locales/en' or
6
+ // 'rrule-ts/locales/de', which keeps the DE locale fully tree-shakeable.
7
+ import { toText as buildToText } from './toText.js';
8
+ import { fromText as parseFromText } from './fromText.js';
9
+ export { COMPLEX_RULE_FALLBACK } from './toText.js';
5
10
  /**
6
- * Convert `RRuleOptions` to a human-readable recurrence description.
11
+ * Convert RRuleOptions to a human-readable recurrence description.
7
12
  *
8
- * @example toText({ freq: 'WEEKLY', byDay: [{ weekday: 'MO', ordinal: undefined }] })
9
- * // 'every week on Monday' (expansion phase)
13
+ * Never throws. Returns '(complex recurrence rule)' for rules that cannot be
14
+ * naturally phrased in the target locale (e.g. BYWEEKNO, BYYEARDAY, unusual
15
+ * BYSETPOS combinations). This is the only case where the output is not a
16
+ * grammatical sentence.
10
17
  *
11
- * @stub Not yet implemented.
12
- * TODO(expansion-phase): implement locale-aware text rendering.
18
+ * @example
19
+ * toText({ freq: 'DAILY' })
20
+ * // 'every day'
21
+ *
22
+ * @example
23
+ * toText({ freq: 'WEEKLY', byDay: [{ weekday: 'MO', ordinal: undefined }] })
24
+ * // 'every week on Monday'
25
+ *
26
+ * @example
27
+ * import { de } from 'rrule-ts/locales/de'
28
+ * toText({ freq: 'DAILY' }, { locale: de })
29
+ * // 'jeden Tag'
13
30
  */
14
- // v8 ignore next 3
15
- export function toText(_options, _textOptions) {
16
- throw new Error('not implemented: toText (coming in expansion phase)');
31
+ export function toText(options, textOptions) {
32
+ return buildToText(options, textOptions?.locale);
17
33
  }
18
34
  /**
19
- * Parse a human-readable recurrence description into `RRuleOptions`.
35
+ * Parse a human-readable English recurrence description into RRuleOptions.
36
+ *
37
+ * Accepts only canonical output produced by toText with the EN locale.
38
+ * Not a general natural-language parser.
39
+ *
40
+ * fromText is EN-only. There is no locale parameter because DE (and other
41
+ * locales) parsing is not supported: the parser grammar is tied to the
42
+ * English strings produced by toText. Passing non-English text returns Err.
43
+ *
44
+ * Round-trip guarantee: for all RRuleOptions x where toText(x, {locale: en})
45
+ * does not return the complex-rule fallback string,
46
+ * fromText(toText(x, {locale: en})).value deep-equals x
47
+ * (excluding dtstart, tzid, wkst, bySecond which are absent from text output,
48
+ * and excluding byMinute=[0] which is indistinguishable from byMinute=undefined,
49
+ * and excluding BYSETPOS + single plain weekday which is reconstructed as an
50
+ * ordinal BYDAY entry, losing the original bySetPos field).
51
+ *
52
+ * Returns Err if the text cannot be parsed as EN toText output.
53
+ *
54
+ * Out of scope: BYWEEKNO, BYYEARDAY, BYSECOND, WKST,
55
+ * multi-value BYSETPOS, free-form prose not produced by toText.
56
+ *
57
+ * @example
58
+ * fromText('every day')
59
+ * // { ok: true, value: { freq: 'DAILY' } }
20
60
  *
21
- * @stub Not yet implemented.
22
- * TODO(expansion-phase): implement natural-language RRULE parser.
61
+ * @example
62
+ * fromText('every week on Monday')
63
+ * // { ok: true, value: { freq: 'WEEKLY', byDay: [{ weekday: 'MO', ordinal: undefined }] } }
23
64
  */
24
- // v8 ignore next 3
25
- export function fromText(_text, _locale) {
26
- throw new Error('not implemented: fromText (coming in expansion phase)');
65
+ export function fromText(text) {
66
+ return parseFromText(text);
27
67
  }
28
68
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,iCAAiC;AACjC,+EAA+E;AAU/E;;;;;;;;GAQG;AACH,mBAAmB;AACnB,MAAM,UAAU,MAAM,CAAC,QAAsB,EAAE,YAA0B;IACvE,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;AACxE,CAAC;AAED;;;;;GAKG;AACH,mBAAmB;AACnB,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,OAAgB;IACtD,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAA;AAC1E,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,yEAAyE;AAKzE,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,eAAe,CAAA;AAGzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAYnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,MAAM,CAAC,OAAqB,EAAE,WAAyB;IACrE,OAAO,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,CAAA;AAClD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAA;AAC5B,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { RRuleOptions } from '../types.js';
2
+ import type { LocalePack } from '../locales/types.js';
3
+ /**
4
+ * Returned (for the EN locale) for rules that cannot be phrased naturally.
5
+ * Tied to en.complexRuleFallback so string comparisons remain valid when
6
+ * the EN locale is in use. For other locales, compare against
7
+ * locale.complexRuleFallback instead.
8
+ */
9
+ export declare const COMPLEX_RULE_FALLBACK: string;
10
+ /**
11
+ * Convert RRuleOptions to a human-readable recurrence description.
12
+ *
13
+ * @param options - Parsed RRULE options.
14
+ * @param locale - Optional locale pack. Defaults to the built-in English locale.
15
+ * @returns Human-readable string, or '(complex recurrence rule)' for rules
16
+ * that cannot be expressed naturally (BYWEEKNO, BYYEARDAY, complex BYSETPOS).
17
+ * Never throws.
18
+ */
19
+ export declare function toText(options: RRuleOptions, locale?: LocalePack): string;
20
+ //# sourceMappingURL=toText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toText.d.ts","sourceRoot":"","sources":["../../src/text/toText.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAW,MAAM,aAAa,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAOrD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAyB,CAAA;AAkL3D;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,GAAE,UAAe,GAAG,MAAM,CAgD7E"}
@@ -0,0 +1,230 @@
1
+ // Internal toText implementation for rrule-ts.
2
+ //
3
+ // Assembles a human-readable recurrence description from RRuleOptions.
4
+ // All clause construction logic lives here. The public API surface is
5
+ // in src/text/index.ts.
6
+ //
7
+ // Clause assembly order:
8
+ // [interval phrase] [BYMONTH clause] [BYDAY clause] [BYMONTHDAY clause]
9
+ // [BYHOUR+BYMINUTE clause] [, COUNT | UNTIL]
10
+ import { en } from '../locales/en.js';
11
+ // ---------------------------------------------------------------------------
12
+ // Constants
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * Returned (for the EN locale) for rules that cannot be phrased naturally.
16
+ * Tied to en.complexRuleFallback so string comparisons remain valid when
17
+ * the EN locale is in use. For other locales, compare against
18
+ * locale.complexRuleFallback instead.
19
+ */
20
+ export const COMPLEX_RULE_FALLBACK = en.complexRuleFallback;
21
+ /**
22
+ * Maps RFC 5545 weekday abbreviation to the LocalePack weekdays array index.
23
+ * MO=0 (Monday) through SU=6 (Sunday), matching LocalePack.weekdays order.
24
+ */
25
+ const WEEKDAY_INDEX = {
26
+ MO: 0,
27
+ TU: 1,
28
+ WE: 2,
29
+ TH: 3,
30
+ FR: 4,
31
+ SA: 5,
32
+ SU: 6,
33
+ };
34
+ /** Weekday abbreviations that form the standard Mon-Fri business-day set. */
35
+ const MON_TO_FRI = new Set(['MO', 'TU', 'WE', 'TH', 'FR']);
36
+ // ---------------------------------------------------------------------------
37
+ // Helpers
38
+ // ---------------------------------------------------------------------------
39
+ /**
40
+ * Join non-empty strings with a single space.
41
+ * Filters out empty strings so that empty tokens (e.g. DE tokens.the = '')
42
+ * do not produce double spaces in the output.
43
+ */
44
+ function sp(...parts) {
45
+ return parts.filter(Boolean).join(' ');
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Complexity detection
49
+ // ---------------------------------------------------------------------------
50
+ /**
51
+ * Return true when a rule cannot be phrased as a natural sentence.
52
+ *
53
+ * Fallback triggers:
54
+ * - BYWEEKNO: no natural English phrasing
55
+ * - BYYEARDAY: no natural English phrasing
56
+ * - BYSETPOS with multiple positions
57
+ * - BYSETPOS combined with BY* parts other than BYDAY
58
+ * - BYSETPOS + BYDAY where BYDAY contains ordinals
59
+ * - BYSETPOS + multiple plain weekdays that are not exactly Mon-Fri
60
+ */
61
+ function isComplex(opts) {
62
+ if (opts.byWeekNo !== undefined)
63
+ return true;
64
+ if (opts.byYearDay !== undefined)
65
+ return true;
66
+ // BYDAY + BYMONTHDAY together cannot be phrased naturally: the two constraints
67
+ // would need a compound clause that no locale currently supports.
68
+ if (opts.byDay !== undefined && opts.byMonthDay !== undefined)
69
+ return true;
70
+ // Multiple BYMINUTE values cannot be paired with each BYHOUR in a single
71
+ // natural time phrase; only one minute value per render is supported.
72
+ if (opts.byMinute !== undefined && opts.byMinute.length > 1)
73
+ return true;
74
+ if (opts.bySetPos !== undefined) {
75
+ // Multiple positions have no single natural phrase.
76
+ if (opts.bySetPos.length > 1)
77
+ return true;
78
+ // BYSETPOS combined with any BY* other than BYDAY.
79
+ const hasNonBydayBy = opts.byMonth !== undefined ||
80
+ opts.byMonthDay !== undefined ||
81
+ opts.byHour !== undefined ||
82
+ opts.byMinute !== undefined ||
83
+ opts.bySecond !== undefined;
84
+ if (hasNonBydayBy)
85
+ return true;
86
+ // BYSETPOS must come with BYDAY.
87
+ if (opts.byDay === undefined)
88
+ return true;
89
+ // BYSETPOS + ordinal BYDAY is contradictory.
90
+ if (opts.byDay.some((d) => d.ordinal !== undefined))
91
+ return true;
92
+ // Multiple plain weekdays: only Mon-Fri (weekday set) with BYSETPOS=-1
93
+ // ("last weekday") is naturally phraseable. Any other BYSETPOS value with
94
+ // a multi-weekday set cannot be phrased as a single natural sentence.
95
+ const weekdays = opts.byDay.map((d) => d.weekday);
96
+ if (weekdays.length > 1) {
97
+ const isWeekdaySet = weekdays.length === 5 && weekdays.every((w) => MON_TO_FRI.has(w));
98
+ if (!isWeekdaySet)
99
+ return true;
100
+ // Mon-Fri 5-day set: only BYSETPOS=-1 (last weekday) is phraseable.
101
+ if (opts.bySetPos[0] !== -1)
102
+ return true;
103
+ }
104
+ }
105
+ return false;
106
+ }
107
+ // ---------------------------------------------------------------------------
108
+ // Clause builders
109
+ // ---------------------------------------------------------------------------
110
+ /** Build the BYDAY clause. Handles plain weekdays, ordinal weekdays, and BYSETPOS. */
111
+ function buildByDayClause(opts, locale) {
112
+ const byDay = opts.byDay;
113
+ // BYSETPOS pattern: single position value, plain weekdays only, no other BY*.
114
+ if (opts.bySetPos !== undefined) {
115
+ const pos = opts.bySetPos[0];
116
+ const weekdays = byDay.map((d) => d.weekday);
117
+ // BYSETPOS=-1 + Mon-Fri: 'on the last weekday'
118
+ if (pos === -1 && weekdays.length === 5 && weekdays.every((w) => MON_TO_FRI.has(w))) {
119
+ return sp(locale.tokens.on, locale.tokens.the, locale.tokens.last, locale.tokens.weekday);
120
+ }
121
+ // BYSETPOS + single weekday: 'on [the] [ordinal] [weekday]'
122
+ const weekdayName = locale.weekdays[WEEKDAY_INDEX[weekdays[0]]];
123
+ const ordinalStr = pos === -1 ? locale.tokens.last : locale.formatOrdinal(pos);
124
+ return sp(locale.tokens.on, locale.tokens.the, ordinalStr, weekdayName);
125
+ }
126
+ const hasOrdinals = byDay.some((d) => d.ordinal !== undefined);
127
+ if (!hasOrdinals) {
128
+ // Plain weekdays: WEEKLY/DAILY context. 'on Monday', 'on Monday and Wednesday'
129
+ const names = byDay.map((d) => locale.weekdays[WEEKDAY_INDEX[d.weekday]]);
130
+ return sp(locale.tokens.on, locale.formatList(names));
131
+ }
132
+ // Ordinal weekdays: MONTHLY/YEARLY context.
133
+ // Each entry renders as '[the] [ordinal] [weekday]'; the list is then joined
134
+ // and prefixed with 'on'. The 'the' token is empty in DE, so sp() filters it.
135
+ const items = byDay.map((d) => {
136
+ const name = locale.weekdays[WEEKDAY_INDEX[d.weekday]];
137
+ const ord = d.ordinal;
138
+ if (ord === -1) {
139
+ // Use tokens.last for the adjectival 'last' form.
140
+ return sp(locale.tokens.the, locale.tokens.last, name);
141
+ }
142
+ if (ord < -1) {
143
+ // Negative ordinals below -1 (e.g. -2MO = 2nd-to-last Monday).
144
+ // Delegate to the locale so each language can produce a natural phrase.
145
+ return sp(locale.tokens.the, locale.formatNthToLastOrdinal(Math.abs(ord)), name);
146
+ }
147
+ return sp(locale.tokens.the, locale.formatOrdinal(ord), name);
148
+ });
149
+ return sp(locale.tokens.on, locale.formatList(items));
150
+ }
151
+ /** Build the BYMONTHDAY clause. Handles positive values, -1, and other negatives. */
152
+ function buildByMonthDayClause(opts, locale) {
153
+ const byMonthDay = opts.byMonthDay;
154
+ const items = byMonthDay.map((n) => {
155
+ if (n === -1)
156
+ return locale.tokens.lastDay;
157
+ if (n < -1) {
158
+ // Delegate to the locale so non-English locales produce grammatically
159
+ // correct phrases (e.g. DE: 'vorletzten Tag' for n=-2).
160
+ return locale.formatNthToLastDay(Math.abs(n));
161
+ }
162
+ return locale.formatOrdinal(n);
163
+ });
164
+ // 'on the [list]' in EN; 'am [list]' in DE (tokens.the is empty for DE).
165
+ return sp(locale.tokens.on, locale.tokens.the, locale.formatList(items));
166
+ }
167
+ /** Build the BYHOUR+BYMINUTE time clause. */
168
+ function buildTimeClause(opts, locale) {
169
+ const byHour = opts.byHour;
170
+ // When byMinute is absent, use :00 for each hour.
171
+ // When byMinute is present, pair each hour with the first byMinute value.
172
+ const minute = opts.byMinute !== undefined ? opts.byMinute[0] : 0;
173
+ const times = byHour.map((h) => `${h}:${String(minute).padStart(2, '0')}`);
174
+ return sp(locale.tokens.at, locale.formatList(times));
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Exported function
178
+ // ---------------------------------------------------------------------------
179
+ /**
180
+ * Convert RRuleOptions to a human-readable recurrence description.
181
+ *
182
+ * @param options - Parsed RRULE options.
183
+ * @param locale - Optional locale pack. Defaults to the built-in English locale.
184
+ * @returns Human-readable string, or '(complex recurrence rule)' for rules
185
+ * that cannot be expressed naturally (BYWEEKNO, BYYEARDAY, complex BYSETPOS).
186
+ * Never throws.
187
+ */
188
+ export function toText(options, locale = en) {
189
+ if (isComplex(options))
190
+ return locale.complexRuleFallback;
191
+ const parts = [];
192
+ // 1. Interval phrase: always present.
193
+ const interval = options.interval ?? 1;
194
+ parts.push(locale.formatInterval(interval, options.freq));
195
+ // 2. BYMONTH clause (skipped when BYSETPOS is present because BYSETPOS +
196
+ // BYMONTH is already marked complex above, so this guard is defensive).
197
+ if (options.byMonth !== undefined && options.bySetPos === undefined) {
198
+ if (options.byMonth.length === 1) {
199
+ const monthName = locale.months[options.byMonth[0] - 1];
200
+ parts.push(sp(locale.tokens.inSingular, monthName));
201
+ }
202
+ else {
203
+ const monthNames = options.byMonth.map((m) => locale.months[m - 1]);
204
+ parts.push(sp(locale.tokens.in, locale.formatList(monthNames)));
205
+ }
206
+ }
207
+ // 3. BYDAY clause (also handles BYSETPOS patterns).
208
+ if (options.byDay !== undefined) {
209
+ parts.push(buildByDayClause(options, locale));
210
+ }
211
+ // 4. BYMONTHDAY clause (only rendered when BYDAY is absent; the two clauses
212
+ // describe the same period so combining them in one rule is uncommon).
213
+ if (options.byMonthDay !== undefined && options.byDay === undefined) {
214
+ parts.push(buildByMonthDayClause(options, locale));
215
+ }
216
+ // 5. BYHOUR + BYMINUTE time clause.
217
+ if (options.byHour !== undefined) {
218
+ parts.push(buildTimeClause(options, locale));
219
+ }
220
+ // 6. COUNT or UNTIL terminator. COUNT uses ', ' prefix; UNTIL uses ' ' (space).
221
+ const main = parts.join(' ');
222
+ if (options.count !== undefined) {
223
+ return `${main}, ${locale.formatCount(options.count)}`;
224
+ }
225
+ if (options.until !== undefined) {
226
+ return `${main} ${locale.tokens.until} ${locale.formatDate(options.until)}`;
227
+ }
228
+ return main;
229
+ }
230
+ //# sourceMappingURL=toText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toText.js","sourceRoot":"","sources":["../../src/text/toText.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrule-ts",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "RFC 5545 RRULE parser, validator, stringifier, and expander. Temporal-native, zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",