@hebcal/icalendar 5.0.5 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,529 +1,487 @@
1
- /*! @hebcal/icalendar v5.0.5 */
1
+ /*! @hebcal/icalendar v5.1.0 */
2
2
  import { flags, Locale, greg } from '@hebcal/core';
3
3
  import { murmur32HexSync } from 'murmurhash3';
4
4
  import { shouldRenderBrief, pad2, getEventCategories, makeAnchor, pad4, getHolidayDescription, getCalendarTitle, appendIsraelAndTracking, makeTorahMemoText } from '@hebcal/rest-api';
5
5
  import { promises } from 'fs';
6
6
 
7
7
  // DO NOT EDIT THIS AUTO-GENERATED FILE!
8
- const version = '5.0.5';
8
+ const version = '5.1.0';
9
9
 
10
10
  const vtimezoneCache = new Map();
11
11
  const CATEGORY = {
12
- candles: 'Holiday',
13
- dafyomi: 'Daf Yomi',
14
- mishnayomi: 'Mishna Yomi',
15
- nachyomi: 'Nach Yomi',
16
- yerushalmi: 'Yerushalmi Yomi',
17
- havdalah: 'Holiday',
18
- hebdate: null,
19
- holiday: 'Holiday',
20
- mevarchim: null,
21
- molad: null,
22
- omer: null,
23
- parashat: 'Parsha',
24
- roshchodesh: 'Holiday',
25
- user: 'Personal',
26
- zmanim: null,
12
+ candles: 'Holiday',
13
+ dafyomi: 'Daf Yomi',
14
+ mishnayomi: 'Mishna Yomi',
15
+ nachyomi: 'Nach Yomi',
16
+ yerushalmi: 'Yerushalmi Yomi',
17
+ havdalah: 'Holiday',
18
+ hebdate: null,
19
+ holiday: 'Holiday',
20
+ mevarchim: null,
21
+ molad: null,
22
+ omer: null,
23
+ parashat: 'Parsha',
24
+ roshchodesh: 'Holiday',
25
+ user: 'Personal',
26
+ zmanim: null,
27
27
  };
28
-
29
28
  /**
30
29
  * @private
31
- * @param {string[]} arr
32
- * @param {string} key
33
- * @param {string} val
34
30
  */
35
31
  function addOptional(arr, key, val) {
36
- if (val) {
37
- const str = IcalEvent.escape(val);
38
- arr.push(key + ':' + str);
39
- }
32
+ if (val) {
33
+ const str = IcalEvent.escape(val);
34
+ arr.push(key + ':' + str);
35
+ }
40
36
  }
41
-
42
37
  /**
43
38
  * @private
44
- * @param {string} url
45
- * @param {CalOptions} options
46
- * @return {string}
47
39
  */
48
40
  function appendTrackingToUrl(url, options) {
49
- if (!url) {
50
- return null;
51
- }
52
- const utmSource = options.utmSource || 'js';
53
- const utmMedium = options.utmMedium || 'icalendar';
54
- const utmCampaign = options.utmCampaign;
55
- return appendIsraelAndTracking(url,
56
- options.il, utmSource, utmMedium, utmCampaign);
41
+ if (!url) {
42
+ return null;
43
+ }
44
+ const utmSource = options.utmSource || 'js';
45
+ const utmMedium = options.utmMedium || 'icalendar';
46
+ const utmCampaign = options.utmCampaign;
47
+ return appendIsraelAndTracking(url, options.il, utmSource, utmMedium, utmCampaign);
57
48
  }
58
-
59
49
  const encoder = new TextEncoder();
60
50
  const char74re = /(.{1,74})/g;
61
-
51
+ const DAILY_LEARNING = flags.DAILY_LEARNING | flags.DAF_YOMI |
52
+ flags.MISHNA_YOMI | flags.YERUSHALMI_YOMI | flags.NACH_YOMI;
62
53
  /**
63
54
  * Represents an RFC 2445 iCalendar VEVENT
64
55
  */
65
56
  class IcalEvent {
66
- /**
67
- * Builds an IcalEvent object from a Hebcal Event
68
- * @param {Event} ev
69
- * @param {CalOptions} options
70
- */
71
- constructor(ev, options={}) {
72
- this.ev = ev;
73
- this.options = options;
74
- this.dtstamp = options.dtstamp || IcalEvent.makeDtstamp(new Date());
75
- if (typeof ev.sequence === 'number') {
76
- this.sequence = ev.sequence;
77
- } else if (typeof options.sequence === 'number') {
78
- this.sequence = options.sequence;
79
- }
80
- const timed = this.timed = Boolean(ev.eventTime);
81
- const locale = options.locale;
82
- let subj = shouldRenderBrief(ev) ? ev.renderBrief(locale) : ev.render(locale);
83
- const mask = ev.getFlags();
84
- if (ev.locationName) {
85
- this.locationName = ev.locationName;
86
- } else if (mask & flags.DAF_YOMI) {
87
- this.locationName = Locale.gettext('Daf Yomi', locale);
88
- } else if (mask & flags.YERUSHALMI_YOMI) {
89
- this.locationName = Locale.gettext('Yerushalmi Yomi', locale);
90
- } else if (mask & flags.NACH_YOMI) {
91
- this.locationName = Locale.gettext('Nach Yomi', locale);
92
- } else if (mask & flags.MISHNA_YOMI) {
93
- this.locationName = Locale.gettext('Mishna Yomi', locale);
94
- } else if (timed && options.location) {
95
- this.locationName = options.location.getShortName();
96
- } else if (ev.category) {
97
- this.locationName = Locale.gettext(ev.category, locale);
98
- }
99
- const date = IcalEvent.formatYYYYMMDD(ev.getDate().greg());
100
- this.startDate = this.isoDateOnly = date;
101
- this.dtargs = '';
102
- this.transp = 'TRANSPARENT';
103
- this.busyStatus = 'FREE';
104
- if (timed) {
105
- let [hour, minute] = ev.eventTimeStr.split(':');
106
- hour = +hour;
107
- minute = +minute;
108
- this.startDate += 'T' + pad2(hour) + pad2(minute) + '00';
109
- this.endDate = this.startDate;
110
- if (options.location?.getTzid()) {
111
- this.dtargs = `;TZID=${options.location.getTzid()}`;
112
- }
113
- } else {
114
- this.endDate = IcalEvent.formatYYYYMMDD(ev.getDate().next().greg());
115
- // for all-day untimed, use DTEND;VALUE=DATE intsead of DURATION:P1D.
116
- // It's more compatible with everthing except ancient versions of
117
- // Lotus Notes circa 2004
118
- this.dtargs = ';VALUE=DATE';
119
- if (mask & flags.CHAG) {
120
- this.transp = 'OPAQUE';
121
- this.busyStatus = 'OOF';
122
- }
123
- }
124
-
125
- if (options.emoji) {
126
- const prefix = ev.getEmoji();
127
- if (prefix) {
128
- if (mask & flags.OMER_COUNT) {
129
- subj = subj + ' ' + prefix;
130
- } else {
131
- subj = prefix + ' ' + subj;
132
- }
133
- }
134
- }
135
-
136
- // make subject safe for iCalendar
137
- subj = IcalEvent.escape(subj);
138
-
139
- if (options.appendHebrewToSubject) {
140
- const hebrew = ev.renderBrief('he');
141
- if (hebrew) {
142
- subj += ` / ${hebrew}`;
143
- }
144
- }
145
- this.subj = subj;
146
- this.category = ev.category || CATEGORY[getEventCategories(ev)?.[0]];
147
- }
148
-
149
-
150
- /**
151
- * @return {string}
152
- */
153
- getAlarm() {
154
- const ev = this.ev;
155
- const mask = ev.getFlags();
156
- const evAlarm = ev.alarm;
157
- if (typeof evAlarm === 'string') {
158
- return 'TRIGGER:' + evAlarm;
159
- } else if (typeof evAlarm === 'boolean' && !evAlarm) {
160
- return null;
161
- } else if (greg.isDate(evAlarm)) {
162
- evAlarm.setSeconds(0);
163
- return 'TRIGGER;VALUE=DATE-TIME:' + IcalEvent.makeDtstamp(evAlarm);
164
- } else if (mask & flags.OMER_COUNT) {
165
- return 'TRIGGER:-P0DT3H30M0S'; // 8:30pm Omer alarm evening before
166
- } else if (mask & flags.USER_EVENT) {
167
- return 'TRIGGER:-P0DT12H0M0S'; // noon the day before
168
- } else if (this.timed && ev.getDesc().startsWith('Candle lighting')) {
169
- return 'TRIGGER:-P0DT0H10M0S';
170
- }
171
- return null;
172
- }
173
-
174
- /**
175
- * @return {string}
176
- */
177
- getUid() {
178
- const options = this.options;
179
- const digest = murmur32HexSync(this.ev.getDesc());
180
- let uid = `hebcal-${this.isoDateOnly}-${digest}`;
181
- if (this.timed && options.location) {
182
- const loc = options.location;
183
- if (loc.getGeoId()) {
184
- uid += `-${loc.getGeoId()}`;
185
- } else if (loc.getName()) {
186
- uid += '-' + makeAnchor(loc.getName());
187
- }
188
- }
189
- return uid;
190
- }
191
-
192
- /**
193
- * @return {string[]}
194
- */
195
- getLongLines() {
196
- if (this.lines) return this.lines;
197
- const categoryLine = this.category ? [`CATEGORIES:${this.category}`] : [];
198
- const uid = this.ev.uid || this.getUid();
199
- if (this.sequence) {
200
- categoryLine.unshift(`SEQUENCE:${this.sequence}`);
57
+ /**
58
+ * Builds an IcalEvent object from a Hebcal Event
59
+ */
60
+ constructor(ev, options = {}) {
61
+ var _a;
62
+ this.ev = ev;
63
+ const opts = Object.assign({}, options);
64
+ this.options = opts;
65
+ this.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
66
+ if (typeof ev.sequence === 'number') {
67
+ this.sequence = ev.sequence;
68
+ }
69
+ else if (typeof opts.sequence === 'number') {
70
+ this.sequence = opts.sequence;
71
+ }
72
+ const timed = this.timed = Boolean(ev.eventTime);
73
+ const locale = opts.locale;
74
+ const location = opts.location;
75
+ let subj = shouldRenderBrief(ev) ? ev.renderBrief(locale) : ev.render(locale);
76
+ const mask = ev.getFlags();
77
+ if (ev.locationName) {
78
+ this.locationName = ev.locationName;
79
+ }
80
+ else if (timed && location) {
81
+ this.locationName = location.getShortName();
82
+ }
83
+ else if ((mask & DAILY_LEARNING) && ev.category) {
84
+ this.locationName = Locale.gettext(ev.category, locale);
85
+ }
86
+ const date = IcalEvent.formatYYYYMMDD(ev.getDate().greg());
87
+ this.startDate = this.isoDateOnly = date;
88
+ this.dtargs = '';
89
+ this.transp = 'TRANSPARENT';
90
+ this.busyStatus = 'FREE';
91
+ if (timed) {
92
+ let [hour, minute] = ev.eventTimeStr.split(':');
93
+ hour = +hour;
94
+ minute = +minute;
95
+ this.startDate += 'T' + pad2(hour) + pad2(minute) + '00';
96
+ this.endDate = this.startDate;
97
+ if (location === null || location === void 0 ? void 0 : location.getTzid()) {
98
+ this.dtargs = `;TZID=${location.getTzid()}`;
99
+ }
100
+ }
101
+ else {
102
+ this.endDate = IcalEvent.formatYYYYMMDD(ev.getDate().next().greg());
103
+ // for all-day untimed, use DTEND;VALUE=DATE intsead of DURATION:P1D.
104
+ // It's more compatible with everthing except ancient versions of
105
+ // Lotus Notes circa 2004
106
+ this.dtargs = ';VALUE=DATE';
107
+ if (mask & flags.CHAG) {
108
+ this.transp = 'OPAQUE';
109
+ this.busyStatus = 'OOF';
110
+ }
111
+ }
112
+ if (opts.emoji) {
113
+ const prefix = ev.getEmoji();
114
+ if (prefix) {
115
+ if (mask & flags.OMER_COUNT) {
116
+ subj = subj + ' ' + prefix;
117
+ }
118
+ else {
119
+ subj = prefix + ' ' + subj;
120
+ }
121
+ }
122
+ }
123
+ // make subject safe for iCalendar
124
+ subj = IcalEvent.escape(subj);
125
+ if (opts.appendHebrewToSubject) {
126
+ const hebrew = ev.renderBrief('he');
127
+ if (hebrew) {
128
+ subj += ` / ${hebrew}`;
129
+ }
130
+ }
131
+ this.subj = subj;
132
+ this.category = ev.category || CATEGORY[(_a = getEventCategories(ev)) === null || _a === void 0 ? void 0 : _a[0]];
201
133
  }
202
- const arr = this.lines = [
203
- 'BEGIN:VEVENT',
204
- `DTSTAMP:${this.dtstamp}`,
205
- ].concat(categoryLine).concat([
206
- `SUMMARY:${this.subj}`,
207
- `DTSTART${this.dtargs}:${this.startDate}`,
208
- `DTEND${this.dtargs}:${this.endDate}`,
209
- `UID:${uid}`,
210
- `TRANSP:${this.transp}`,
211
- `X-MICROSOFT-CDO-BUSYSTATUS:${this.busyStatus}`,
212
- ]);
213
-
214
- if (!this.timed) {
215
- arr.push('X-MICROSOFT-CDO-ALLDAYEVENT:TRUE');
134
+ getAlarm() {
135
+ const ev = this.ev;
136
+ const mask = ev.getFlags();
137
+ const evAlarm = ev.alarm;
138
+ if (typeof evAlarm === 'string') {
139
+ return 'TRIGGER:' + evAlarm;
140
+ }
141
+ else if (typeof evAlarm === 'boolean' && !evAlarm) {
142
+ return null;
143
+ }
144
+ else if (greg.isDate(evAlarm)) {
145
+ const alarmDt = evAlarm;
146
+ alarmDt.setSeconds(0);
147
+ return 'TRIGGER;VALUE=DATE-TIME:' + IcalEvent.makeDtstamp(alarmDt);
148
+ }
149
+ else if (mask & flags.OMER_COUNT) {
150
+ return 'TRIGGER:-P0DT3H30M0S'; // 8:30pm Omer alarm evening before
151
+ }
152
+ else if (mask & flags.USER_EVENT) {
153
+ return 'TRIGGER:-P0DT12H0M0S'; // noon the day before
154
+ }
155
+ else if (this.timed && ev.getDesc().startsWith('Candle lighting')) {
156
+ return 'TRIGGER:-P0DT0H10M0S';
157
+ }
158
+ return null;
216
159
  }
217
-
218
- const ev = this.ev;
219
- const mask = ev.getFlags();
220
- const isUserEvent = Boolean(mask & flags.USER_EVENT);
221
- if (!isUserEvent) {
222
- arr.push('CLASS:PUBLIC');
160
+ /**
161
+ * @return {string}
162
+ */
163
+ getUid() {
164
+ if (this.ev.uid) {
165
+ return this.ev.uid;
166
+ }
167
+ const options = this.options;
168
+ const digest = murmur32HexSync(this.ev.getDesc());
169
+ let uid = `hebcal-${this.isoDateOnly}-${digest}`;
170
+ const loc = options.location;
171
+ if (this.timed && loc) {
172
+ if (loc.getGeoId()) {
173
+ uid += `-${loc.getGeoId()}`;
174
+ }
175
+ else if (loc.getName()) {
176
+ uid += '-' + makeAnchor(loc.getName());
177
+ }
178
+ }
179
+ return uid;
223
180
  }
224
-
225
- const options = this.options;
226
- // create memo (holiday descr, Torah, etc)
227
- const memo = createMemo(ev, options);
228
- addOptional(arr, 'DESCRIPTION', memo);
229
- addOptional(arr, 'LOCATION', this.locationName);
230
- if (this.timed && options.location) {
231
- arr.push('GEO:' + options.location.getLatitude() + ';' + options.location.getLongitude());
181
+ /**
182
+ * @return {string[]}
183
+ */
184
+ getLongLines() {
185
+ if (this.lines)
186
+ return this.lines;
187
+ const categoryLine = this.category ? [`CATEGORIES:${this.category}`] : [];
188
+ const uid = this.getUid();
189
+ if (this.sequence) {
190
+ categoryLine.unshift(`SEQUENCE:${this.sequence}`);
191
+ }
192
+ const arr = this.lines = [
193
+ 'BEGIN:VEVENT',
194
+ `DTSTAMP:${this.dtstamp}`,
195
+ ].concat(categoryLine).concat([
196
+ `SUMMARY:${this.subj}`,
197
+ `DTSTART${this.dtargs}:${this.startDate}`,
198
+ `DTEND${this.dtargs}:${this.endDate}`,
199
+ `UID:${uid}`,
200
+ `TRANSP:${this.transp}`,
201
+ `X-MICROSOFT-CDO-BUSYSTATUS:${this.busyStatus}`,
202
+ ]);
203
+ if (!this.timed) {
204
+ arr.push('X-MICROSOFT-CDO-ALLDAYEVENT:TRUE');
205
+ }
206
+ const ev = this.ev;
207
+ const mask = ev.getFlags();
208
+ const isUserEvent = Boolean(mask & flags.USER_EVENT);
209
+ if (!isUserEvent) {
210
+ arr.push('CLASS:PUBLIC');
211
+ }
212
+ const options = this.options;
213
+ // create memo (holiday descr, Torah, etc)
214
+ const memo = createMemo(ev, options);
215
+ addOptional(arr, 'DESCRIPTION', memo);
216
+ addOptional(arr, 'LOCATION', this.locationName);
217
+ const loc = options.location;
218
+ if (this.timed && loc) {
219
+ arr.push('GEO:' + loc.getLatitude() + ';' + loc.getLongitude());
220
+ }
221
+ const trigger = this.getAlarm();
222
+ if (trigger) {
223
+ arr.push('BEGIN:VALARM', 'ACTION:DISPLAY', 'DESCRIPTION:Event reminder', `${trigger}`, 'END:VALARM');
224
+ }
225
+ arr.push('END:VEVENT');
226
+ return arr;
232
227
  }
233
-
234
- const trigger = this.getAlarm();
235
- if (trigger) {
236
- arr.push(
237
- 'BEGIN:VALARM',
238
- 'ACTION:DISPLAY',
239
- 'DESCRIPTION:Event reminder',
240
- `${trigger}`,
241
- 'END:VALARM',
242
- );
228
+ /**
229
+ * @return {string}
230
+ */
231
+ toString() {
232
+ return this.getLines().join('\r\n');
243
233
  }
244
-
245
- arr.push('END:VEVENT');
246
-
247
- return arr;
248
- }
249
-
250
- /**
251
- * @return {string}
252
- */
253
- toString() {
254
- return this.getLines().join('\r\n');
255
- }
256
-
257
- /**
258
- * fold lines to 75 characters
259
- * @return {string[]}
260
- */
261
- getLines() {
262
- return this.getLongLines().map(IcalEvent.fold);
263
- }
264
-
265
- /**
266
- * fold line to 75 characters
267
- * @param {string} line
268
- * @return {string}
269
- */
270
- static fold(line) {
271
- let isASCII = true;
272
- for (let i = 0; i < line.length; i++) {
273
- if (line.charCodeAt(i) > 255) {
274
- isASCII = false;
275
- break;
276
- }
234
+ /**
235
+ * fold lines to 75 characters
236
+ * @return {string[]}
237
+ */
238
+ getLines() {
239
+ return this.getLongLines().map(IcalEvent.fold);
277
240
  }
278
- if (isASCII) {
279
- return line.length <= 74 ? line : line.match(char74re).join('\r\n ');
241
+ /**
242
+ * fold line to 75 characters
243
+ * @param {string} line
244
+ * @return {string}
245
+ */
246
+ static fold(line) {
247
+ let isASCII = true;
248
+ for (let i = 0; i < line.length; i++) {
249
+ if (line.charCodeAt(i) > 255) {
250
+ isASCII = false;
251
+ break;
252
+ }
253
+ }
254
+ if (isASCII) {
255
+ if (line.length <= 74) {
256
+ return line;
257
+ }
258
+ const matches = line.match(char74re);
259
+ return matches.join('\r\n ');
260
+ }
261
+ if (encoder.encode(line).length <= 74) {
262
+ return line;
263
+ }
264
+ // iterate unicode character by character, making sure
265
+ // that adding a new character would keep the line <= 75 octets
266
+ let result = '';
267
+ let current = '';
268
+ let len = 0;
269
+ for (let i = 0; i < line.length; i++) {
270
+ const char = line[i];
271
+ const octets = char.charCodeAt(0) < 256 ? 1 : encoder.encode(char).length;
272
+ const newlen = len + octets;
273
+ if (newlen < 75) {
274
+ current += char;
275
+ len = newlen;
276
+ }
277
+ else {
278
+ result += current + '\r\n ';
279
+ line = line.substring(i);
280
+ current = '';
281
+ len = 0;
282
+ i = -1;
283
+ }
284
+ }
285
+ return result + current;
280
286
  }
281
- if (encoder.encode(line).length <= 74) {
282
- return line;
287
+ /**
288
+ * @param {string} str
289
+ * @return {string}
290
+ */
291
+ static escape(str) {
292
+ if (str.indexOf(',') !== -1) {
293
+ str = str.replace(/,/g, '\\,');
294
+ }
295
+ if (str.indexOf(';') !== -1) {
296
+ str = str.replace(/;/g, '\\;');
297
+ }
298
+ return str;
283
299
  }
284
- // iterate unicode character by character, making sure
285
- // that adding a new character would keep the line <= 75 octets
286
- let result = '';
287
- let current = '';
288
- let len = 0;
289
- for (let i = 0; i < line.length; i++) {
290
- const char = line[i];
291
- const octets = char.charCodeAt(0) < 256 ? 1 : encoder.encode(char).length;
292
- const newlen = len + octets;
293
- if (newlen < 75) {
294
- current += char;
295
- len = newlen;
296
- } else {
297
- result += current + '\r\n ';
298
- line = line.substring(i);
299
- current = '';
300
- len = 0;
301
- i = -1;
302
- }
300
+ /**
301
+ * @param {Date} dt
302
+ * @return {string}
303
+ */
304
+ static formatYYYYMMDD(dt) {
305
+ return pad4(dt.getFullYear()) +
306
+ pad2(dt.getMonth() + 1) + pad2(dt.getDate());
303
307
  }
304
- return result + current;
305
- }
306
-
307
- /**
308
- * @param {string} str
309
- * @return {string}
310
- */
311
- static escape(str) {
312
- if (str.indexOf(',') !== -1) {
313
- str = str.replace(/,/g, '\\,');
308
+ /**
309
+ * Returns UTC string for iCalendar
310
+ * @param {Date} dt
311
+ * @return {string}
312
+ */
313
+ static makeDtstamp(dt) {
314
+ const s = dt.toISOString();
315
+ return s.slice(0, 4) + s.slice(5, 7) + s.slice(8, 13) +
316
+ s.slice(14, 16) + s.slice(17, 19) + 'Z';
314
317
  }
315
- if (str.indexOf(';') !== -1) {
316
- str = str.replace(/;/g, '\\;');
318
+ /** @return {string} */
319
+ static version() {
320
+ return version;
317
321
  }
318
- return str;
319
- }
320
-
321
- /**
322
- * @param {Date} dt
323
- * @return {string}
324
- */
325
- static formatYYYYMMDD(dt) {
326
- return pad4(dt.getFullYear()) +
327
- pad2(dt.getMonth() + 1) + pad2(dt.getDate());
328
- }
329
-
330
- /**
331
- * Returns UTC string for iCalendar
332
- * @param {Date} dt
333
- * @return {string}
334
- */
335
- static makeDtstamp(dt) {
336
- const s = dt.toISOString();
337
- return s.slice(0, 4) + s.slice(5, 7) + s.slice(8, 13) +
338
- s.slice(14, 16) + s.slice(17, 19) + 'Z';
339
- }
340
-
341
- /** @return {string} */
342
- static version() {
343
- return version;
344
- }
345
322
  }
346
-
347
323
  /**
348
324
  * Transforms a single Event into a VEVENT string
349
- * @param {Event} ev
350
- * @param {CalOptions} options
351
325
  * @return {string} multi-line result, delimited by \r\n
352
326
  */
353
327
  function eventToIcal(ev, options) {
354
- const ical = new IcalEvent(ev, options);
355
- return ical.toString();
328
+ const ical = new IcalEvent(ev, options);
329
+ return ical.toString();
356
330
  }
357
-
358
331
  const torahMemoCache = new Map();
359
-
360
- const HOLIDAY_IGNORE_MASK = flags.DAF_YOMI | flags.OMER_COUNT |
361
- flags.SHABBAT_MEVARCHIM | flags.MOLAD | flags.USER_EVENT |
362
- flags.MISHNA_YOMI | flags.YERUSHALMI_YOMI | flags.NACH_YOMI |
363
- flags.HEBREW_DATE | flags.DAILY_LEARNING;
364
-
332
+ const HOLIDAY_IGNORE_MASK = DAILY_LEARNING | flags.OMER_COUNT |
333
+ flags.SHABBAT_MEVARCHIM | flags.MOLAD | flags.USER_EVENT |
334
+ flags.HEBREW_DATE;
365
335
  /**
366
336
  * @private
367
- * @param {Event} ev
368
- * @param {boolean} il
369
- * @return {string}
370
337
  */
371
338
  function makeTorahMemo(ev, il) {
372
- if ((ev.getFlags() & HOLIDAY_IGNORE_MASK) || ev.eventTime) {
373
- return '';
374
- }
375
- const hd = ev.getDate();
376
- const yy = hd.getFullYear();
377
- const mm = hd.getMonth();
378
- const dd = hd.getDate();
379
- const key = [yy, mm, dd, il ? '1' : '0', ev.getDesc()].join('-');
380
- let memo = torahMemoCache.get(key);
381
- if (typeof memo === 'string') {
339
+ if ((ev.getFlags() & HOLIDAY_IGNORE_MASK) || ev.eventTime) {
340
+ return '';
341
+ }
342
+ const hd = ev.getDate();
343
+ const yy = hd.getFullYear();
344
+ const mm = hd.getMonth();
345
+ const dd = hd.getDate();
346
+ const key = [yy, mm, dd, il ? '1' : '0', ev.getDesc()].join('-');
347
+ let memo = torahMemoCache.get(key);
348
+ if (typeof memo === 'string') {
349
+ return memo;
350
+ }
351
+ memo = makeTorahMemoText(ev, il).replace(/\n/g, '\\n');
352
+ torahMemoCache.set(key, memo);
382
353
  return memo;
383
- }
384
- memo = makeTorahMemoText(ev, il).replace(/\n/g, '\\n');
385
- torahMemoCache.set(key, memo);
386
- return memo;
387
354
  }
388
-
389
355
  /**
390
356
  * @private
391
- * @param {Event} e
392
- * @param {CalOptions} options
393
- * @return {string}
394
357
  */
395
- function createMemo(e, options) {
396
- let memo = e.memo;
397
- if (typeof memo === 'string' && memo.length && memo.indexOf('\n') !== -1) {
398
- memo = memo.replace(/\n/g, '\\n');
399
- }
400
- const desc = e.getDesc();
401
- if (desc === 'Havdalah' || desc === 'Candle lighting') {
402
- return memo || '';
403
- }
404
- const mask = e.getFlags();
405
- if (mask & flags.OMER_COUNT) {
406
- const sefira = [e.sefira('en'), e.sefira('he'), e.sefira('translit')].join('\\n');
407
- return e.getTodayIs('en') + '\\n\\n' + e.getTodayIs('he') + '\\n\\n' + sefira;
408
- }
409
- const url = appendTrackingToUrl(e.url(), options);
410
- const torahMemo = makeTorahMemo(e, options.il);
411
- if (!memo) {
412
- if (typeof e.linkedEvent !== 'undefined') {
413
- memo = e.linkedEvent.render(options.locale);
414
- } else {
415
- memo = getHolidayDescription(e);
358
+ function createMemo(ev, options) {
359
+ let memo = ev.memo;
360
+ if (typeof memo === 'string' && memo.length && memo.indexOf('\n') !== -1) {
361
+ memo = memo.replace(/\n/g, '\\n');
362
+ }
363
+ const desc = ev.getDesc();
364
+ if (desc === 'Havdalah' || desc === 'Candle lighting') {
365
+ return memo || '';
366
+ }
367
+ const mask = ev.getFlags();
368
+ if (mask & flags.OMER_COUNT) {
369
+ const omerEv = ev;
370
+ const sefira = [omerEv.sefira('en'), omerEv.sefira('he'), omerEv.sefira('translit')].join('\\n');
371
+ return omerEv.getTodayIs('en') + '\\n\\n' + omerEv.getTodayIs('he') + '\\n\\n' + sefira;
372
+ }
373
+ const url = appendTrackingToUrl(ev.url(), options);
374
+ const torahMemo = makeTorahMemo(ev, options.il);
375
+ if (!memo) {
376
+ if (typeof ev.linkedEvent !== 'undefined') {
377
+ memo = ev.linkedEvent.render(options.locale);
378
+ }
379
+ else {
380
+ memo = getHolidayDescription(ev);
381
+ }
416
382
  }
417
- }
418
- if (torahMemo) {
419
- if (memo.length) {
420
- memo += '\\n\\n';
383
+ if (torahMemo) {
384
+ if (memo.length) {
385
+ memo += '\\n\\n';
386
+ }
387
+ memo += torahMemo;
421
388
  }
422
- memo += torahMemo;
423
- }
424
- if (url) {
425
- if (memo.length) {
426
- memo += '\\n\\n';
389
+ if (url) {
390
+ if (memo.length) {
391
+ memo += '\\n\\n';
392
+ }
393
+ memo += url;
427
394
  }
428
- memo += url;
429
- }
430
- return memo;
395
+ return memo;
431
396
  }
432
-
433
397
  /**
434
398
  * Generates an RFC 2445 iCalendar string from an array of events
435
- * @param {Event[]} events
436
- * @param {CalOptions} options
437
- * @return {Promise<string>}
438
399
  */
439
400
  async function eventsToIcalendar(events, options) {
440
- if (!events.length) throw new RangeError('Events can not be empty');
441
- if (!options) throw new TypeError('Invalid options object');
442
- const opts = Object.assign({}, options);
443
- opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
444
- if (!opts.title) {
445
- opts.title = getCalendarTitle(events, opts);
446
- }
447
- const icals = events.map((ev) => new IcalEvent(ev, opts));
448
- return icalEventsToString(icals, opts);
401
+ if (!options)
402
+ throw new TypeError('Invalid options object');
403
+ const opts = Object.assign({}, options);
404
+ opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
405
+ if (!opts.title) {
406
+ opts.title = getCalendarTitle(events, opts);
407
+ }
408
+ const icals = events.map((ev) => new IcalEvent(ev, opts));
409
+ return icalEventsToString(icals, opts);
449
410
  }
450
-
451
411
  /**
452
412
  * Generates an RFC 2445 iCalendar string from an array of IcalEvents
453
- * @param {IcalEvent[]} icals
454
- * @param {CalOptions} options
455
- * @return {Promise<string>}
456
413
  */
457
414
  async function icalEventsToString(icals, options) {
458
- const stream = [];
459
- const locale = options.locale || Locale.getLocaleName();
460
- const uclang = locale.toUpperCase();
461
- const opts = Object.assign({}, options);
462
- opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
463
- const title = opts.title ? IcalEvent.escape(opts.title) : 'Untitled';
464
- const caldesc = opts.caldesc ? IcalEvent.escape(opts.caldesc) :
465
- opts.yahrzeit ?
466
- 'Yahrzeits + Anniversaries from www.hebcal.com' :
467
- 'Jewish Holidays from www.hebcal.com';
468
- const prodid = opts.prodid || `-//hebcal.com/NONSGML Hebcal Calendar v1${version}//${uclang}`;
469
- const preamble = [
470
- 'BEGIN:VCALENDAR',
471
- 'VERSION:2.0',
472
- `PRODID:${prodid}`,
473
- 'CALSCALE:GREGORIAN',
474
- 'METHOD:PUBLISH',
475
- 'X-LOTUS-CHARSET:UTF-8',
476
- ];
477
- if (opts.publishedTTL !== false) {
478
- const publishedTTL = opts.publishedTTL || 'PT7D';
479
- preamble.push(`X-PUBLISHED-TTL:${publishedTTL}`);
480
- }
481
- preamble.push(`X-WR-CALNAME:${title}`);
482
- preamble.push(`X-WR-CALDESC:${caldesc}`);
483
- for (const line of preamble.map(IcalEvent.fold)) {
484
- stream.push(line);
485
- stream.push('\r\n');
486
- }
487
- if (opts.relcalid) {
488
- stream.push(IcalEvent.fold(`X-WR-RELCALID:${opts.relcalid}`));
489
- stream.push('\r\n');
490
- }
491
- if (opts.calendarColor) {
492
- stream.push(`X-APPLE-CALENDAR-COLOR:${opts.calendarColor}\r\n`);
493
- }
494
- const location = opts.location;
495
- const tzid = location?.getTzid();
496
- if (tzid) {
497
- stream.push(`X-WR-TIMEZONE;VALUE=TEXT:${tzid}\r\n`);
498
- const vtz = vtimezoneCache.get(tzid);
499
- if (typeof vtz === 'string') {
500
- stream.push(vtz);
501
- stream.push('\r\n');
502
- } else {
503
- const vtimezoneFilename = `./zoneinfo/${tzid}.ics`;
504
- try {
505
- const vtimezoneIcs = await promises.readFile(vtimezoneFilename, 'utf-8');
506
- const lines = vtimezoneIcs.split('\r\n');
507
- // ignore first 3 and last 1 lines
508
- const str = lines.slice(3, lines.length - 2).join('\r\n');
509
- stream.push(str);
415
+ const stream = [];
416
+ const locale = options.locale || Locale.getLocaleName();
417
+ const uclang = locale.toUpperCase();
418
+ const opts = Object.assign({}, options);
419
+ opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
420
+ const title = opts.title ? IcalEvent.escape(opts.title) : 'Untitled';
421
+ const caldesc = opts.caldesc ? IcalEvent.escape(opts.caldesc) :
422
+ opts.yahrzeit ?
423
+ 'Yahrzeits + Anniversaries from www.hebcal.com' :
424
+ 'Jewish Holidays from www.hebcal.com';
425
+ const prodid = opts.prodid || `-//hebcal.com/NONSGML Hebcal Calendar v1${version}//${uclang}`;
426
+ const preamble = [
427
+ 'BEGIN:VCALENDAR',
428
+ 'VERSION:2.0',
429
+ `PRODID:${prodid}`,
430
+ 'CALSCALE:GREGORIAN',
431
+ 'METHOD:PUBLISH',
432
+ 'X-LOTUS-CHARSET:UTF-8',
433
+ ];
434
+ if (opts.publishedTTL !== false) {
435
+ const publishedTTL = opts.publishedTTL || 'PT7D';
436
+ preamble.push(`X-PUBLISHED-TTL:${publishedTTL}`);
437
+ }
438
+ preamble.push(`X-WR-CALNAME:${title}`);
439
+ preamble.push(`X-WR-CALDESC:${caldesc}`);
440
+ for (const line of preamble.map(IcalEvent.fold)) {
441
+ stream.push(line);
510
442
  stream.push('\r\n');
511
- vtimezoneCache.set(tzid, str);
512
- } catch (error) {
513
- // ignore failure when no timezone definition to read
514
- }
515
443
  }
516
- }
517
-
518
- for (const ical of icals) {
519
- const lines = ical.getLines();
520
- for (const line of lines) {
521
- stream.push(line);
522
- stream.push('\r\n');
444
+ if (opts.relcalid) {
445
+ stream.push(IcalEvent.fold(`X-WR-RELCALID:${opts.relcalid}`));
446
+ stream.push('\r\n');
447
+ }
448
+ if (opts.calendarColor) {
449
+ stream.push(`X-APPLE-CALENDAR-COLOR:${opts.calendarColor}\r\n`);
450
+ }
451
+ const location = opts.location;
452
+ const tzid = location === null || location === void 0 ? void 0 : location.getTzid();
453
+ if (tzid) {
454
+ stream.push(`X-WR-TIMEZONE;VALUE=TEXT:${tzid}\r\n`);
455
+ const vtz = vtimezoneCache.get(tzid);
456
+ if (typeof vtz === 'string') {
457
+ stream.push(vtz);
458
+ stream.push('\r\n');
459
+ }
460
+ else {
461
+ const vtimezoneFilename = `./zoneinfo/${tzid}.ics`;
462
+ try {
463
+ const vtimezoneIcs = await promises.readFile(vtimezoneFilename, 'utf-8');
464
+ const lines = vtimezoneIcs.split('\r\n');
465
+ // ignore first 3 and last 1 lines
466
+ const str = lines.slice(3, lines.length - 2).join('\r\n');
467
+ stream.push(str);
468
+ stream.push('\r\n');
469
+ vtimezoneCache.set(tzid, str);
470
+ }
471
+ catch (error) {
472
+ // ignore failure when no timezone definition to read
473
+ }
474
+ }
475
+ }
476
+ for (const ical of icals) {
477
+ const lines = ical.getLines();
478
+ for (const line of lines) {
479
+ stream.push(line);
480
+ stream.push('\r\n');
481
+ }
523
482
  }
524
- }
525
- stream.push('END:VCALENDAR\r\n');
526
- return stream.join('');
483
+ stream.push('END:VCALENDAR\r\n');
484
+ return stream.join('');
527
485
  }
528
486
 
529
487
  export { IcalEvent, eventToIcal, eventsToIcalendar, icalEventsToString };