@hebcal/icalendar 5.1.1 → 6.0.1

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