ical-generator
Version:
ical-generator is a small piece of code which generates ical calendar files
1 lines • 208 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":[],"sources":["../src/types.ts","../src/tools.ts","../src/attendee.ts","../src/alarm.ts","../src/category.ts","../src/event.ts","../src/calendar.ts","../src/index.ts"],"sourcesContent":["export enum ICalEventRepeatingFreq {\n DAILY = 'DAILY',\n HOURLY = 'HOURLY',\n MINUTELY = 'MINUTELY',\n MONTHLY = 'MONTHLY',\n SECONDLY = 'SECONDLY',\n WEEKLY = 'WEEKLY',\n YEARLY = 'YEARLY',\n}\n\n/**\n * Used in ICalEvent.travelTime()\n *\n * Controls whether Apple clients give suggestions like \"Time to leave\" notifications, route prompts in Apple Maps, etc.\n */\nexport enum ICalEventTravelTimeSuggestion {\n AUTOMATIC = 'AUTOMATIC',\n DISABLED = 'DISABLED',\n ENABLED = 'ENABLED',\n}\n\n/**\n * Used in ICalEvent.travelTime()\n *\n * Controls which mode of transportation is used by Apple Calendar clients for calculating travel time and suggesting routes\n */\nexport enum ICalEventTravelTimeTransportation {\n BICYCLE = 'BICYCLE',\n CAR = 'CAR',\n TRANSIT = 'TRANSIT',\n WALKING = 'WALKING',\n}\n\nexport enum ICalWeekday {\n FR = 'FR',\n MO = 'MO',\n SA = 'SA',\n SU = 'SU',\n TH = 'TH',\n TU = 'TU',\n WE = 'WE',\n}\n\n/**\n * ical-generator supports [native Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date),\n * [moment.js](https://momentjs.com/) (and [moment-timezone](https://momentjs.com/timezone/), [Day.js](https://day.js.org/en/),\n * [Luxon](https://moment.github.io/luxon/)'s [DateTime](https://moment.github.io/luxon/docs/class/src/datetime.js~DateTime.html)\n * and [Temporal](https://tc39.es/proposal-temporal/docs/) objects. You can also pass a string which is then passed to javascript's Date internally.\n */\nexport type ICalDateTimeValue =\n | Date\n | ICalDayJsStub\n | ICalLuxonDateTimeStub\n | ICalMomentStub\n | ICalMomentTimezoneStub\n | ICalTemporalInstantStub\n | ICalTemporalPlainDateStub\n | ICalTemporalPlainDateTimeStub\n | ICalTemporalZonedDateTimeStub\n | string;\n\nexport interface ICalDayJsStub {\n format(format?: string): string;\n isValid(): boolean;\n toDate(): Date;\n toJSON(): string;\n tz?(zone?: string): ICalDayJsStub;\n utc?(): ICalDayJsStub;\n}\n\nexport interface ICalDescription {\n html?: string;\n plain: string;\n}\n\nexport interface ICalEventTravelTime {\n seconds: number;\n startFrom?: {\n location: ICalLocation;\n transportation: ICalEventTravelTimeTransportation;\n };\n suggestionBehavior?: ICalEventTravelTimeSuggestion;\n}\n\nexport interface ICalGeo {\n lat: number;\n lon: number;\n}\n\nexport type ICalLocation = ICalLocationWithoutTitle | ICalLocationWithTitle;\n\nexport interface ICalLocationWithoutTitle {\n geo: ICalGeo;\n}\n\nexport interface ICalLocationWithTitle {\n address?: string;\n geo?: ICalGeo;\n radius?: number;\n title: string;\n}\n\nexport interface ICalLuxonDateTimeStub {\n get isValid(): boolean;\n setZone(zone?: string): ICalLuxonDateTimeStub;\n toFormat(fmt: string): string;\n toJSDate(): Date;\n toJSON(): null | string;\n zone: { type: string };\n}\n\nexport interface ICalMomentDurationStub {\n asSeconds(): number;\n}\n\nexport interface ICalMomentStub {\n clone(): ICalMomentStub;\n format(format?: string): string;\n isValid(): boolean;\n toDate(): Date;\n toJSON(): string;\n utc(): ICalMomentStub;\n}\n\nexport interface ICalMomentTimezoneStub extends ICalMomentStub {\n clone(): ICalMomentTimezoneStub;\n tz(): string | undefined;\n tz(timezone: string): ICalMomentTimezoneStub;\n utc(): ICalMomentTimezoneStub;\n}\n\nexport interface ICalOrganizer {\n email: string;\n mailto?: string;\n name: string;\n sentBy?: string;\n}\n\nexport interface ICalRepeatingOptions {\n byDay?: ICalWeekday | ICalWeekday[];\n byMonth?: number | number[];\n byMonthDay?: number | number[];\n bySetPos?: number | number[];\n count?: number;\n exclude?: ICalDateTimeValue | ICalDateTimeValue[];\n freq: ICalEventRepeatingFreq;\n interval?: number;\n startOfWeek?: ICalWeekday;\n until?: ICalDateTimeValue;\n}\n\nexport interface ICalRRuleStub {\n between(\n after: Date,\n before: Date,\n inc?: boolean,\n iterator?: (d: Date, len: number) => boolean,\n ): Date[];\n toString(): string;\n}\n\nexport interface ICalTemporalInstantStub {\n epochMilliseconds: number;\n epochSeconds?: number;\n toJSON(): string;\n toString(): string;\n toZonedDateTimeISO(timeZone: string): ICalTemporalZonedDateTimeStub;\n}\n\nexport interface ICalTemporalPlainDateStub {\n day: number;\n month: number;\n toJSON(): string;\n toString(): string;\n year: number;\n}\n\nexport interface ICalTemporalPlainDateTimeStub {\n day: number;\n hour: number;\n minute: number;\n month: number;\n second: number;\n toJSON(): string;\n toPlainDate(): ICalTemporalPlainDateStub;\n toString(): string;\n toZonedDateTime(timeZone: string): ICalTemporalZonedDateTimeStub;\n year: number;\n}\n\nexport interface ICalTemporalZonedDateTimeStub {\n day: number;\n hour: number;\n minute: number;\n month: number;\n second: number;\n timeZoneId: string;\n toInstant(): ICalTemporalInstantStub;\n toJSON(): string;\n toPlainDateTime(): ICalTemporalPlainDateTimeStub;\n toString(): string;\n withTimeZone(timeZone: string): ICalTemporalZonedDateTimeStub;\n year: number;\n}\n\nexport interface ICalTimezone {\n generator?: (timezone: string) => null | string;\n name: null | string;\n}\n\nexport interface ICalTZDateStub extends Date {\n timeZone?: string;\n withTimeZone(timezone?: null | string): ICalTZDateStub;\n}\n","'use strict';\n\nimport {\n type ICalDateTimeValue,\n type ICalDayJsStub,\n type ICalLuxonDateTimeStub,\n type ICalMomentDurationStub,\n type ICalMomentStub,\n type ICalMomentTimezoneStub,\n type ICalOrganizer,\n type ICalRRuleStub,\n type ICalTemporalInstantStub,\n type ICalTemporalPlainDateStub,\n type ICalTemporalPlainDateTimeStub,\n type ICalTemporalZonedDateTimeStub,\n type ICalTZDateStub,\n} from './types.ts';\n\nexport function addOrGetCustomAttributes(\n data: { x: [string, string][] },\n keyOrArray:\n | [string, string][]\n | Record<string, string>\n | { key: string; value: string }[],\n): void;\nexport function addOrGetCustomAttributes(\n data: { x: [string, string][] },\n keyOrArray: string,\n value: string,\n): void;\nexport function addOrGetCustomAttributes(data: {\n x: [string, string][];\n}): { key: string; value: string }[];\nexport function addOrGetCustomAttributes(\n data: { x: [string, string][] },\n keyOrArray?:\n | [string, string][]\n | Record<string, string>\n | string\n | undefined\n | { key: string; value: string }[],\n value?: string | undefined,\n): void | { key: string; value: string }[] {\n if (Array.isArray(keyOrArray)) {\n data.x = keyOrArray.map(\n (o: [string, string] | { key: string; value: string }) => {\n if (Array.isArray(o)) {\n return o;\n }\n if (typeof o.key !== 'string' || typeof o.value !== 'string') {\n throw new Error('Either key or value is not a string!');\n }\n if (o.key.substr(0, 2) !== 'X-') {\n throw new Error('Key has to start with `X-`!');\n }\n\n return [o.key, o.value] as [string, string];\n },\n );\n } else if (typeof keyOrArray === 'object') {\n data.x = Object.entries(keyOrArray).map(([key, value]) => {\n if (typeof key !== 'string' || typeof value !== 'string') {\n throw new Error('Either key or value is not a string!');\n }\n if (key.substr(0, 2) !== 'X-') {\n throw new Error('Key has to start with `X-`!');\n }\n\n return [key, value];\n });\n } else if (typeof keyOrArray === 'string' && typeof value === 'string') {\n if (keyOrArray.substr(0, 2) !== 'X-') {\n throw new Error('Key has to start with `X-`!');\n }\n\n data.x.push([keyOrArray, value]);\n } else {\n return data.x.map((a) => ({\n key: a[0],\n value: a[1],\n }));\n }\n}\n\n/**\n * Checks if the given input is a valid date and\n * returns the internal representation (= moment object)\n */\nexport function checkDate(\n value: ICalDateTimeValue,\n attribute: string,\n): ICalDateTimeValue {\n // Date & String\n if (\n (value instanceof Date && isNaN(value.getTime())) ||\n (typeof value === 'string' && isNaN(new Date(value).getTime()))\n ) {\n throw new Error(`\\`${attribute}\\` has to be a valid date!`);\n }\n if (value instanceof Date || typeof value === 'string') {\n return value;\n }\n\n // Temporal types - these are always valid if they exist\n // Check this first to avoid false positives with other date libraries\n if (\n typeof value === 'object' &&\n value !== null &&\n (isTemporalZonedDateTime(value) ||\n isTemporalPlainDateTime(value) ||\n isTemporalPlainDate(value) ||\n isTemporalInstant(value))\n ) {\n return value;\n }\n\n // Luxon\n if (isLuxonDate(value) && value.isValid === true) {\n return value;\n }\n\n // Moment / Moment Timezone\n if ((isMoment(value) || isDayjs(value)) && value.isValid()) {\n return value;\n }\n\n throw new Error(`\\`${attribute}\\` has to be a valid date!`);\n}\n/**\n * Checks if the given string `value` is a\n * valid one for the type `type`\n */\nexport function checkEnum(\n type: Record<string, string>,\n value: unknown,\n): unknown {\n const allowedValues = Object.values(type);\n const valueStr = String(value).toUpperCase();\n\n if (!valueStr || !allowedValues.includes(valueStr)) {\n throw new Error(\n `Input must be one of the following: ${allowedValues.join(', ')}`,\n );\n }\n\n return valueStr;\n}\n/**\n * Check the given string or ICalOrganizer. Parses\n * the string for name and email address if possible.\n *\n * @param attribute Attribute name for error messages\n * @param value Value to parse name/email from\n */\nexport function checkNameAndMail(\n attribute: string,\n value: ICalOrganizer | string,\n): ICalOrganizer {\n let result: ICalOrganizer | null = null;\n\n if (typeof value === 'string') {\n const match = value.match(/^(.+) ?<([^>]+)>$/);\n if (match) {\n result = {\n email: match[2].trim(),\n name: match[1].trim(),\n };\n } else if (value.includes('@')) {\n result = {\n email: value.trim(),\n name: value.trim(),\n };\n }\n } else if (typeof value === 'object') {\n result = {\n email: value.email,\n mailto: value.mailto,\n name: value.name,\n sentBy: value.sentBy,\n };\n }\n\n if (!result && typeof value === 'string') {\n throw new Error(\n '`' +\n attribute +\n \"` isn't formated correctly. See https://sebbo2002.github.io/ical-generator/develop/\" +\n 'reference/interfaces/ICalOrganizer.html',\n );\n } else if (!result) {\n throw new Error(\n '`' +\n attribute +\n '` needs to be a valid formed string or an object. See https://sebbo2002.github.io/' +\n 'ical-generator/develop/reference/interfaces/ICalOrganizer.html',\n );\n }\n\n if (!result.name) {\n throw new Error('`' + attribute + '.name` is empty!');\n }\n if (!result.email) {\n throw new Error('`' + attribute + '.email` is empty!');\n }\n\n return result;\n}\n/**\n * Escapes special characters in the given string\n */\nexport function escape(str: string | unknown, inQuotes: boolean): string {\n return String(str)\n .replace(inQuotes ? /[\\\\\"]/g : /[\\\\;,]/g, function (match) {\n return '\\\\' + match;\n })\n .replace(/(?:\\r\\n|\\r|\\n)/g, '\\\\n');\n}\n\n/**\n * Trim line length of given string\n */\nexport function foldLines(input: string): string {\n return input\n .split('\\r\\n')\n .map(function (line) {\n let result = '';\n let c = 0;\n for (let i = 0; i < line.length; i++) {\n let ch = line.charAt(i);\n\n // surrogate pair, see https://mathiasbynens.be/notes/javascript-encoding#surrogate-pairs\n if (ch >= '\\ud800' && ch <= '\\udbff') {\n ch += line.charAt(++i);\n }\n\n // TextEncoder is available in browsers and node.js >= 11.0.0\n const charsize = new TextEncoder().encode(ch).length;\n c += charsize;\n if (c > 74) {\n result += '\\r\\n ';\n c = charsize;\n }\n\n result += ch;\n }\n return result;\n })\n .join('\\r\\n');\n}\n\n/**\n * Converts a valid date/time object supported by this library to a string.\n */\nexport function formatDate(\n timezone: null | string,\n d: ICalDateTimeValue,\n dateonly?: boolean,\n floating?: boolean,\n): string {\n if (timezone?.startsWith('/')) {\n timezone = timezone.substr(1);\n }\n\n if (typeof d === 'string' || d instanceof Date) {\n // TZDate is an extension of the native Date object.\n // @see https://github.com/date-fns/tz for more information.\n const m = isTZDate(d) ? d.withTimeZone(timezone) : new Date(d);\n\n // (!dateonly && !floating) || !timezone => utc\n let s =\n m.getUTCFullYear().toString().padStart(4, '0') +\n String(m.getUTCMonth() + 1).padStart(2, '0') +\n m.getUTCDate().toString().padStart(2, '0');\n\n // (dateonly || floating) && timezone => tz\n if (timezone) {\n s =\n m.getFullYear().toString().padStart(2, '0') +\n String(m.getMonth() + 1).padStart(2, '0') +\n m.getDate().toString().padStart(2, '0');\n }\n\n if (dateonly) {\n return s;\n }\n\n if (timezone) {\n s +=\n 'T' +\n m.getHours().toString().padStart(2, '0') +\n m.getMinutes().toString().padStart(2, '0') +\n m.getSeconds().toString().padStart(2, '0');\n\n return s;\n }\n\n s +=\n 'T' +\n m.getUTCHours().toString().padStart(2, '0') +\n m.getUTCMinutes().toString().padStart(2, '0') +\n m.getUTCSeconds().toString().padStart(2, '0') +\n (floating ? '' : 'Z');\n\n return s;\n } else if (isMoment(d)) {\n // @see https://momentjs.com/timezone/docs/#/using-timezones/parsing-in-zone/\n const m = timezone\n ? isMomentTZ(d) && !d.tz()\n ? d.clone().tz(timezone)\n : d\n : floating || (dateonly && isMomentTZ(d) && d.tz())\n ? d\n : d.utc();\n\n return (\n m.format('YYYYMMDD') +\n (!dateonly\n ? 'T' + m.format('HHmmss') + (floating || timezone ? '' : 'Z')\n : '')\n );\n } else if (isLuxonDate(d)) {\n const m = timezone\n ? d.setZone(timezone)\n : floating || (dateonly && d.zone.type !== 'system')\n ? d\n : d.setZone('utc');\n\n return (\n m.toFormat('yyyyLLdd') +\n (!dateonly\n ? 'T' + m.toFormat('HHmmss') + (floating || timezone ? '' : 'Z')\n : '')\n );\n } else if (isTemporalZonedDateTime(d)) {\n let t = d;\n\n // try to convert to the specified timezone\n if (timezone) {\n t = d.withTimeZone(d.timeZoneId);\n }\n if (!timezone && d.timeZoneId !== 'UTC') {\n t = d.withTimeZone('UTC');\n }\n\n return formatDate(\n null,\n t.toPlainDateTime(),\n dateonly,\n\n // keep floating if specified or if a timezone is set\n // to remove the 'Z' UTC specifier from the output\n floating || !!timezone,\n );\n } else if (isTemporalPlainDateTime(d)) {\n // Temporal.PlainDateTime - floating time or convert to timezone\n if (dateonly) {\n return (\n d.year.toString().padStart(4, '0') +\n d.month.toString().padStart(2, '0') +\n d.day.toString().padStart(2, '0')\n );\n }\n\n if (timezone) {\n // Convert to ZonedDateTime in the specified timezone\n const zoned = d.toZonedDateTime(timezone);\n return formatDate(timezone, zoned, dateonly, floating);\n }\n\n // Floating time - no timezone, no Z\n return (\n d.year.toString().padStart(4, '0') +\n d.month.toString().padStart(2, '0') +\n d.day.toString().padStart(2, '0') +\n 'T' +\n d.hour.toString().padStart(2, '0') +\n d.minute.toString().padStart(2, '0') +\n d.second.toString().padStart(2, '0') +\n (floating || timezone ? '' : 'Z')\n );\n } else if (isTemporalPlainDate(d)) {\n // Temporal.PlainDate - date only\n return (\n d.year.toString().padStart(4, '0') +\n d.month.toString().padStart(2, '0') +\n d.day.toString().padStart(2, '0') +\n (!dateonly ? 'T000000' + (floating || timezone ? '' : 'Z') : '')\n );\n } else if (isTemporalInstant(d)) {\n // Temporal.Instant - convert to ZonedDateTime first\n const targetTz = timezone || 'UTC';\n const zoned = d.toZonedDateTimeISO(targetTz);\n return formatDate(timezone, zoned, dateonly, floating);\n } else {\n // @see https://day.js.org/docs/en/plugin/utc\n\n let m = d;\n if (timezone) {\n m = typeof d.tz === 'function' ? d.tz(timezone) : d;\n } else if (floating) {\n // m = d;\n } else if (typeof d.utc === 'function') {\n m = d.utc();\n } else {\n throw new Error(\n 'Unable to convert dayjs object to UTC value: UTC plugin is not available!',\n );\n }\n\n return (\n m.format('YYYYMMDD') +\n (!dateonly\n ? 'T' + m.format('HHmmss') + (floating || timezone ? '' : 'Z')\n : '')\n );\n }\n}\n\n/**\n * Converts a valid date/time object supported by this library to a string.\n * For information about this format, see RFC 5545, section 3.3.5\n * https://tools.ietf.org/html/rfc5545#section-3.3.5\n */\nexport function formatDateTZ(\n timezone: null | string,\n property: string,\n date: Date | ICalDateTimeValue | string,\n eventData?: { floating?: boolean | null; timezone?: null | string },\n): string {\n let tzParam = '';\n let floating = eventData?.floating || false;\n\n if (eventData?.timezone) {\n tzParam = ';TZID=' + eventData.timezone;\n\n // This isn't a 'floating' event because it has a timezone;\n // but we use it to omit the 'Z' UTC specifier in formatDate()\n floating = true;\n }\n\n return (\n property + tzParam + ':' + formatDate(timezone, date, false, floating)\n );\n}\n\nexport function generateCustomAttributes(data: {\n x: [string, string][];\n}): string {\n const str = data.x\n .map(([key, value]) => key.toUpperCase() + ':' + escape(value, false))\n .join('\\r\\n');\n return str.length ? str + '\\r\\n' : '';\n}\n\nexport function isDayjs(value: ICalDateTimeValue): value is ICalDayJsStub {\n return (\n typeof value === 'object' &&\n value !== null &&\n !(value instanceof Date) &&\n !isMoment(value) &&\n !isLuxonDate(value) &&\n !isTemporal(value)\n );\n}\n\nexport function isLuxonDate(\n value: ICalDateTimeValue,\n): value is ICalLuxonDateTimeStub {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'toJSDate' in value &&\n typeof value.toJSDate === 'function' &&\n !isTemporal(value)\n );\n}\nexport function isMoment(value: ICalDateTimeValue): value is ICalMomentStub {\n return (\n value != null &&\n // @ts-expect-error _isAMomentObject is a private property\n value._isAMomentObject != null &&\n !isTemporal(value)\n );\n}\nexport function isMomentDuration(\n value: unknown,\n): value is ICalMomentDurationStub {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'asSeconds' in value &&\n typeof value.asSeconds === 'function'\n );\n}\nexport function isMomentTZ(\n value: ICalDateTimeValue,\n): value is ICalMomentTimezoneStub {\n return isMoment(value) && 'tz' in value && typeof value.tz === 'function';\n}\n\nexport function isRRule(value: unknown): value is ICalRRuleStub {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'between' in value &&\n typeof value.between === 'function' &&\n typeof value.toString === 'function'\n );\n}\n\nexport function isTemporal(\n value: ICalDateTimeValue,\n): value is\n | ICalTemporalInstantStub\n | ICalTemporalPlainDateStub\n | ICalTemporalPlainDateTimeStub\n | ICalTemporalZonedDateTimeStub {\n return (\n isTemporalZonedDateTime(value) ||\n isTemporalPlainDateTime(value) ||\n isTemporalPlainDate(value) ||\n isTemporalInstant(value)\n );\n}\n\nexport function isTemporalInstant(\n value: ICalDateTimeValue,\n): value is ICalTemporalInstantStub {\n return (\n typeof value === 'object' &&\n value !== null &&\n !isTemporalZonedDateTime(value) &&\n !isTemporalPlainDateTime(value) &&\n !isTemporalPlainDate(value) &&\n 'toZonedDateTimeISO' in value &&\n typeof value.toZonedDateTimeISO === 'function' &&\n !('year' in value) &&\n !('timeZoneId' in value)\n );\n}\n\nexport function isTemporalPlainDate(\n value: ICalDateTimeValue,\n): value is ICalTemporalPlainDateStub {\n return (\n typeof value === 'object' &&\n value !== null &&\n !isTemporalZonedDateTime(value) &&\n !isTemporalPlainDateTime(value) &&\n 'toPlainDateTime' in value &&\n typeof value.toPlainDateTime === 'function' &&\n 'year' in value &&\n typeof value.year === 'number' &&\n 'month' in value &&\n typeof value.month === 'number' &&\n 'day' in value &&\n typeof value.day === 'number' &&\n !('hour' in value) &&\n !('timeZoneId' in value) &&\n !('epochSeconds' in value)\n );\n}\n\nexport function isTemporalPlainDateTime(\n value: ICalDateTimeValue,\n): value is ICalTemporalPlainDateTimeStub {\n return (\n typeof value === 'object' &&\n value !== null &&\n !isTemporalZonedDateTime(value) &&\n 'toZonedDateTime' in value &&\n typeof value.toZonedDateTime === 'function' &&\n 'toPlainDate' in value &&\n typeof value.toPlainDate === 'function' &&\n 'year' in value &&\n typeof value.year === 'number' &&\n 'month' in value &&\n typeof value.month === 'number' &&\n 'day' in value &&\n typeof value.day === 'number' &&\n 'hour' in value &&\n typeof value.hour === 'number' &&\n 'minute' in value &&\n typeof value.minute === 'number' &&\n 'second' in value &&\n typeof value.second === 'number' &&\n !('timeZone' in value)\n );\n}\n\nexport function isTemporalZonedDateTime(\n value: ICalDateTimeValue,\n): value is ICalTemporalZonedDateTimeStub {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'timeZoneId' in value &&\n typeof value.timeZoneId === 'string' &&\n 'toPlainDateTime' in value &&\n typeof value.toPlainDateTime === 'function' &&\n 'year' in value &&\n typeof value.year === 'number' &&\n 'month' in value &&\n typeof value.month === 'number' &&\n 'day' in value &&\n typeof value.day === 'number' &&\n 'hour' in value &&\n typeof value.hour === 'number' &&\n 'minute' in value &&\n typeof value.minute === 'number' &&\n 'second' in value &&\n typeof value.second === 'number'\n );\n}\n\nexport function isTZDate(value: ICalDateTimeValue): value is ICalTZDateStub {\n return (\n value instanceof Date &&\n 'internal' in value &&\n value.internal instanceof Date &&\n 'withTimeZone' in value &&\n typeof value.withTimeZone === 'function' &&\n 'tzComponents' in value &&\n typeof value.tzComponents === 'function'\n );\n}\n\nexport function toDate(value: ICalDateTimeValue): Date {\n if (typeof value === 'string' || value instanceof Date) {\n return new Date(value);\n }\n\n if (isTemporalZonedDateTime(value)) {\n // Convert ZonedDateTime to Instant, then to Date\n const instant = value.toInstant();\n return new Date(instant.epochMilliseconds);\n }\n\n if (isTemporalPlainDateTime(value)) {\n // PlainDateTime has no timezone, treat as UTC\n return new Date(\n Date.UTC(\n value.year,\n value.month - 1,\n value.day,\n value.hour,\n value.minute,\n value.second,\n ),\n );\n }\n\n if (isTemporalPlainDate(value)) {\n // PlainDate has no time, treat as midnight UTC\n return new Date(Date.UTC(value.year, value.month - 1, value.day));\n }\n\n if (isTemporalInstant(value)) {\n // Instant has epochMilliseconds\n return new Date(value.epochMilliseconds);\n }\n\n if (isLuxonDate(value)) {\n return value.toJSDate();\n }\n\n return value.toDate();\n}\n\nexport function toDurationString(seconds: number): string {\n let string = '';\n\n // < 0\n if (seconds < 0) {\n string = '-';\n seconds *= -1;\n }\n\n string += 'P';\n\n // DAYS\n if (seconds >= 86400) {\n string += Math.floor(seconds / 86400) + 'D';\n seconds %= 86400;\n }\n if (!seconds && string.length > 1) {\n return string;\n }\n\n string += 'T';\n\n // HOURS\n if (seconds >= 3600) {\n string += Math.floor(seconds / 3600) + 'H';\n seconds %= 3600;\n }\n\n // MINUTES\n if (seconds >= 60) {\n string += Math.floor(seconds / 60) + 'M';\n seconds %= 60;\n }\n\n // SECONDS\n if (seconds > 0) {\n string += seconds + 'S';\n } else if (string.length <= 2) {\n string += '0S';\n }\n\n return string;\n}\n\nexport function toJSON(\n value: ICalDateTimeValue | null | undefined,\n): null | string | undefined {\n if (!value) {\n return null;\n }\n if (typeof value === 'string') {\n return value;\n }\n\n // Temporal.ZonedDateTime needs special handling to convert to UTC first\n // as [Timezone] info is not supported as a string input format\n if (isTemporalZonedDateTime(value)) {\n return toJSON(value.withTimeZone('UTC').toPlainDateTime());\n }\n\n // Temporal types have toJSON() method that returns ISO strings\n if (isTemporal(value)) {\n return value.toJSON();\n }\n\n return value.toJSON();\n}\n","'use strict';\n\nimport type ICalAlarm from './alarm.ts';\nimport type ICalEvent from './event.ts';\n\nimport {\n addOrGetCustomAttributes,\n checkEnum,\n checkNameAndMail,\n escape,\n} from './tools.ts';\n\nexport enum ICalAttendeeRole {\n CHAIR = 'CHAIR',\n NON = 'NON-PARTICIPANT',\n OPT = 'OPT-PARTICIPANT',\n REQ = 'REQ-PARTICIPANT',\n}\n\n// ref: https://datatracker.ietf.org/doc/html/rfc6638#section-7.1\nexport enum ICalAttendeeScheduleAgent {\n CLIENT = 'CLIENT',\n NONE = 'NONE',\n SERVER = 'SERVER',\n}\n\nexport enum ICalAttendeeStatus {\n ACCEPTED = 'ACCEPTED',\n DECLINED = 'DECLINED',\n DELEGATED = 'DELEGATED',\n NEEDSACTION = 'NEEDS-ACTION',\n TENTATIVE = 'TENTATIVE',\n}\n\n// ref: https://tools.ietf.org/html/rfc2445#section-4.2.3\nexport enum ICalAttendeeType {\n GROUP = 'GROUP',\n INDIVIDUAL = 'INDIVIDUAL',\n RESOURCE = 'RESOURCE',\n ROOM = 'ROOM',\n UNKNOWN = 'UNKNOWN',\n}\nexport interface ICalAttendeeData {\n delegatedFrom?: ICalAttendee | ICalAttendeeData | null | string;\n delegatedTo?: ICalAttendee | ICalAttendeeData | null | string;\n delegatesFrom?: ICalAttendee | ICalAttendeeData | null | string;\n delegatesTo?: ICalAttendee | ICalAttendeeData | null | string;\n email: string;\n mailto?: null | string;\n name?: null | string;\n role?: ICalAttendeeRole;\n rsvp?: boolean | null;\n scheduleAgent?: ICalAttendeeScheduleAgent | ICalXName | null;\n sentBy?: null | string;\n status?: ICalAttendeeStatus | null;\n type?: ICalAttendeeType | null;\n x?:\n | [string, string][]\n | Record<string, string>\n | { key: string; value: string }[];\n}\n\nexport interface ICalAttendeeJSONData {\n delegatedFrom: null | string;\n delegatedTo: null | string;\n email: string;\n mailto: null | string;\n name: null | string;\n role: ICalAttendeeRole;\n rsvp: boolean | null;\n sentBy: null | string;\n status: ICalAttendeeStatus | null;\n type: ICalAttendeeType | null;\n x: { key: string; value: string }[];\n}\n\nexport type ICalXName = `X-${string}`;\n\ninterface ICalInternalAttendeeData {\n delegatedFrom: ICalAttendee | null;\n delegatedTo: ICalAttendee | null;\n email: string;\n mailto: null | string;\n name: null | string;\n role: ICalAttendeeRole;\n rsvp: boolean | null;\n scheduleAgent: ICalAttendeeScheduleAgent | ICalXName | null;\n sentBy: null | string;\n status: ICalAttendeeStatus | null;\n type: ICalAttendeeType | null;\n x: [string, string][];\n}\n\n/**\n * Usually you get an {@link ICalAttendee} object like this:\n *\n * ```javascript\n * import ical from 'ical-generator';\n * const calendar = ical();\n * const event = calendar.createEvent();\n * const attendee = event.createAttendee({ email: 'mail@example.com' });\n * ```\n *\n * You can also use the {@link ICalAttendee} object directly:\n *\n * ```javascript\n * import ical, {ICalAttendee} from 'ical-generator';\n * const attendee = new ICalAttendee({ email: 'mail@example.com' });\n * event.attendees([attendee]);\n * ```\n */\nexport default class ICalAttendee {\n private readonly data: ICalInternalAttendeeData;\n private readonly parent: ICalAlarm | ICalEvent;\n\n /**\n * Constructor of {@link ICalAttendee}. The event reference is\n * required to query the calendar's timezone when required.\n *\n * @param data Attendee Data\n * @param parent Reference to ICalEvent object\n */\n constructor(data: ICalAttendeeData, parent: ICalAlarm | ICalEvent) {\n this.data = {\n delegatedFrom: null,\n delegatedTo: null,\n email: '',\n mailto: null,\n name: null,\n role: ICalAttendeeRole.REQ,\n rsvp: null,\n scheduleAgent: null,\n sentBy: null,\n status: null,\n type: null,\n x: [],\n };\n this.parent = parent;\n if (!this.parent) {\n throw new Error('`event` option required!');\n }\n if (!data.email) {\n throw new Error('No value for `email` in ICalAttendee given!');\n }\n\n if (data.name !== undefined) this.name(data.name);\n if (data.email !== undefined) this.email(data.email);\n if (data.mailto !== undefined) this.mailto(data.mailto);\n if (data.sentBy !== undefined) this.sentBy(data.sentBy);\n if (data.status !== undefined) this.status(data.status);\n if (data.role !== undefined) this.role(data.role);\n if (data.rsvp !== undefined) this.rsvp(data.rsvp);\n if (data.type !== undefined) this.type(data.type);\n if (data.delegatedTo !== undefined) this.delegatedTo(data.delegatedTo);\n if (data.delegatedFrom !== undefined)\n this.delegatedFrom(data.delegatedFrom);\n if (data.delegatesTo) this.delegatesTo(data.delegatesTo);\n if (data.delegatesFrom) this.delegatesFrom(data.delegatesFrom);\n if (data.scheduleAgent !== undefined)\n this.scheduleAgent(data.scheduleAgent);\n if (data.x !== undefined) this.x(data.x);\n }\n\n /**\n * Get the attendee's delegated-from field\n * @since 0.2.0\n */\n delegatedFrom(): ICalAttendee | null;\n /**\n * Set the attendee's delegated-from field\n *\n * Creates a new Attendee if the passed object is not already a\n * {@link ICalAttendee} object. Will set the `delegatedTo` and\n * `delegatedFrom` attributes.\n *\n * @param delegatedFrom\n */\n delegatedFrom(\n delegatedFrom: ICalAttendee | ICalAttendeeData | null | string,\n ): this;\n delegatedFrom(\n delegatedFrom?: ICalAttendee | ICalAttendeeData | null | string,\n ): ICalAttendee | null | this {\n if (delegatedFrom === undefined) {\n return this.data.delegatedFrom;\n }\n\n if (!delegatedFrom) {\n this.data.delegatedFrom = null;\n } else if (typeof delegatedFrom === 'string') {\n this.data.delegatedFrom = new ICalAttendee(\n checkNameAndMail('delegatedFrom', delegatedFrom),\n this.parent,\n );\n } else if (delegatedFrom instanceof ICalAttendee) {\n this.data.delegatedFrom = delegatedFrom;\n } else {\n this.data.delegatedFrom = new ICalAttendee(\n delegatedFrom,\n this.parent,\n );\n }\n\n return this;\n }\n\n /**\n * Get the attendee's delegated-to value.\n * @since 0.2.0\n */\n delegatedTo(): ICalAttendee | null;\n /**\n * Set the attendee's delegated-to field.\n *\n * Creates a new Attendee if the passed object is not already a\n * {@link ICalAttendee} object. Will set the `delegatedTo` and\n * `delegatedFrom` attributes.\n *\n * Will also set the `status` to `DELEGATED`, if attribute is set.\n *\n * ```javascript\n * const cal = ical();\n * const event = cal.createEvent();\n * const attendee = cal.createAttendee();\n *\n * attendee.delegatesTo({email: 'foo@bar.com', name: 'Foo'});\n ```\n *\n * @since 0.2.0\n */\n delegatedTo(\n delegatedTo: ICalAttendee | ICalAttendeeData | null | string,\n ): this;\n delegatedTo(\n delegatedTo?: ICalAttendee | ICalAttendeeData | null | string,\n ): ICalAttendee | null | this {\n if (delegatedTo === undefined) {\n return this.data.delegatedTo;\n }\n if (!delegatedTo) {\n this.data.delegatedTo = null;\n if (this.data.status === ICalAttendeeStatus.DELEGATED) {\n this.data.status = null;\n }\n return this;\n }\n\n if (typeof delegatedTo === 'string') {\n this.data.delegatedTo = new ICalAttendee(\n checkNameAndMail('delegatedTo', delegatedTo),\n this.parent,\n );\n } else if (delegatedTo instanceof ICalAttendee) {\n this.data.delegatedTo = delegatedTo;\n } else {\n this.data.delegatedTo = new ICalAttendee(delegatedTo, this.parent);\n }\n\n this.data.status = ICalAttendeeStatus.DELEGATED;\n return this;\n }\n\n /**\n * Create a new attendee this attendee delegates from and returns\n * this new attendee. Creates a new attendee if the passed object\n * is not already an {@link ICalAttendee}.\n *\n * ```javascript\n * const cal = ical();\n * const event = cal.createEvent();\n * const attendee = cal.createAttendee();\n *\n * attendee.delegatesFrom({email: 'foo@bar.com', name: 'Foo'});\n * ```\n *\n * @since 0.2.0\n */\n delegatesFrom(\n options: ICalAttendee | ICalAttendeeData | string,\n ): ICalAttendee {\n const a =\n options instanceof ICalAttendee\n ? options\n : this.parent.createAttendee(options);\n this.delegatedFrom(a);\n a.delegatedTo(this);\n return a;\n }\n\n /**\n * Create a new attendee this attendee delegates to and returns\n * this new attendee. Creates a new attendee if the passed object\n * is not already an {@link ICalAttendee}.\n *\n * ```javascript\n * const cal = ical();\n * const event = cal.createEvent();\n * const attendee = cal.createAttendee();\n *\n * attendee.delegatesTo({email: 'foo@bar.com', name: 'Foo'});\n * ```\n *\n * @since 0.2.0\n */\n delegatesTo(\n options: ICalAttendee | ICalAttendeeData | string,\n ): ICalAttendee {\n const a =\n options instanceof ICalAttendee\n ? options\n : this.parent.createAttendee(options);\n this.delegatedTo(a);\n a.delegatedFrom(this);\n return a;\n }\n /**\n * Get the attendee's email address\n * @since 0.2.0\n */\n email(): string;\n /**\n * Set the attendee's email address\n * @since 0.2.0\n */\n email(email: string): this;\n email(email?: string): string | this {\n if (!email) {\n return this.data.email;\n }\n\n this.data.email = email;\n return this;\n }\n /**\n * Get the attendee's email address\n * @since 1.3.0\n */\n mailto(): null | string;\n /**\n * Set the attendee's email address\n * @since 1.3.0\n */\n mailto(mailto: null | string): this;\n mailto(mailto?: null | string): null | string | this {\n if (mailto === undefined) {\n return this.data.mailto;\n }\n\n this.data.mailto = mailto || null;\n return this;\n }\n /**\n * Get the attendee's name\n * @since 0.2.0\n */\n name(): null | string;\n /**\n * Set the attendee's name\n * @since 0.2.0\n */\n name(name: null | string): this;\n name(name?: null | string): null | string | this {\n if (name === undefined) {\n return this.data.name;\n }\n\n this.data.name = name || null;\n return this;\n }\n /**\n * Get attendee's role\n * @since 0.2.0\n */\n role(): ICalAttendeeRole;\n /**\n * Set the attendee's role, defaults to `REQ` / `REQ-PARTICIPANT`.\n * Checkout {@link ICalAttendeeRole} for available roles.\n *\n * @since 0.2.0\n */\n role(role: ICalAttendeeRole): this;\n role(role?: ICalAttendeeRole): ICalAttendeeRole | this {\n if (role === undefined) {\n return this.data.role;\n }\n\n this.data.role = checkEnum(ICalAttendeeRole, role) as ICalAttendeeRole;\n return this;\n }\n /**\n * Get attendee's RSVP expectation\n * @since 0.2.1\n */\n rsvp(): boolean | null;\n /**\n * Set the attendee's RSVP expectation\n * @since 0.2.1\n */\n rsvp(rsvp: boolean | null): this;\n rsvp(rsvp?: boolean | null): boolean | null | this {\n if (rsvp === undefined) {\n return this.data.rsvp;\n }\n if (rsvp === null) {\n this.data.rsvp = null;\n return this;\n }\n\n this.data.rsvp = Boolean(rsvp);\n return this;\n }\n /**\n * Get attendee's schedule agent\n * @since 9.0.0\n */\n scheduleAgent(): ICalAttendeeScheduleAgent | ICalXName;\n /**\n * Set attendee's schedule agent\n * See {@link ICalAttendeeScheduleAgent} for available values.\n * You can also use custom X-* values.\n *\n * @since 9.0.0\n */\n scheduleAgent(\n scheduleAgent: ICalAttendeeScheduleAgent | ICalXName | null,\n ): this;\n scheduleAgent(\n scheduleAgent?: ICalAttendeeScheduleAgent | ICalXName | null,\n ): ICalAttendeeScheduleAgent | ICalXName | null | this {\n if (scheduleAgent === undefined) {\n return this.data.scheduleAgent;\n }\n if (!scheduleAgent) {\n this.data.scheduleAgent = null;\n return this;\n }\n\n if (\n typeof scheduleAgent == 'string' &&\n scheduleAgent.toUpperCase().startsWith('X-')\n ) {\n this.data.scheduleAgent = scheduleAgent as string as ICalXName;\n return this;\n }\n\n this.data.scheduleAgent = checkEnum(\n ICalAttendeeScheduleAgent,\n scheduleAgent,\n ) as ICalAttendeeScheduleAgent;\n return this;\n }\n /**\n * Get the acting user's email adress\n * @since 3.3.0\n */\n sentBy(): null | string;\n /**\n * Set the acting user's email adress\n * @since 3.3.0\n */\n sentBy(email: null | string): this;\n sentBy(email?: null | string): null | string | this {\n if (!email) {\n return this.data.sentBy;\n }\n\n this.data.sentBy = email;\n return this;\n }\n /**\n * Get attendee's status\n * @since 0.2.0\n */\n status(): ICalAttendeeStatus | null;\n /**\n * Set the attendee's status. See {@link ICalAttendeeStatus}\n * for available status options.\n *\n * @since 0.2.0\n */\n status(status: ICalAttendeeStatus | null): this;\n status(\n status?: ICalAttendeeStatus | null,\n ): ICalAttendeeStatus | null | this {\n if (status === undefined) {\n return this.data.status;\n }\n if (!status) {\n this.data.status = null;\n return this;\n }\n\n this.data.status = checkEnum(\n ICalAttendeeStatus,\n status,\n ) as ICalAttendeeStatus;\n return this;\n }\n /**\n * Return a shallow copy of the attendee's options for JSON stringification.\n * Can be used for persistence.\n *\n * @since 0.2.4\n */\n toJSON(): ICalAttendeeJSONData {\n return Object.assign({}, this.data, {\n delegatedFrom: this.data.delegatedFrom?.email() || null,\n delegatedTo: this.data.delegatedTo?.email() || null,\n x: this.x(),\n });\n }\n /**\n * Return generated attendee as a string.\n *\n * ```javascript\n * console.log(attendee.toString()); // → ATTENDEE;ROLE=…\n * ```\n */\n toString(): string {\n let g = 'ATTENDEE';\n\n if (!this.data.email) {\n throw new Error('No value for `email` in ICalAttendee given!');\n }\n\n // ROLE\n g += ';ROLE=' + this.data.role;\n\n // TYPE\n if (this.data.type) {\n g += ';CUTYPE=' + this.data.type;\n }\n\n // PARTSTAT\n if (this.data.status) {\n g += ';PARTSTAT=' + this.data.status;\n }\n\n // RSVP\n if (this.data.rsvp !== null) {\n g += ';RSVP=' + this.data.rsvp.toString().toUpperCase();\n }\n\n // SENT-BY\n if (this.data.sentBy !== null) {\n g += ';SENT-BY=\"mailto:' + this.data.sentBy + '\"';\n }\n\n // DELEGATED-TO\n if (this.data.delegatedTo) {\n g += ';DELEGATED-TO=\"' + this.data.delegatedTo.email() + '\"';\n }\n\n // DELEGATED-FROM\n if (this.data.delegatedFrom) {\n g += ';DELEGATED-FROM=\"' + this.data.delegatedFrom.email() + '\"';\n }\n\n // CN / Name\n if (this.data.name) {\n g += ';CN=\"' + escape(this.data.name, true) + '\"';\n }\n\n // EMAIL\n if (this.data.email && this.data.mailto) {\n g += ';EMAIL=' + escape(this.data.email, false);\n }\n\n // SCHEDULE-AGENT\n if (this.data.scheduleAgent) {\n g += ';SCHEDULE-AGENT=' + this.data.scheduleAgent;\n }\n\n // CUSTOM X ATTRIBUTES\n if (this.data.x.length) {\n g +=\n ';' +\n this.data.x\n .map(\n ([key, value]) =>\n key.toUpperCase() + '=' + escape(value, false),\n )\n .join(';');\n }\n\n g +=\n ':MAILTO:' +\n escape(this.data.mailto || this.data.email, false) +\n '\\r\\n';\n\n return g;\n }\n\n /**\n * Get attendee's type (a.k.a. CUTYPE)\n * @since 0.2.3\n */\n type(): ICalAttendeeType;\n /**\n * Set attendee's type (a.k.a. CUTYPE).\n * See {@link ICalAttendeeType} for available status options.\n *\n * @since 0.2.3\n */\n type(type: ICalAttendeeType | null): this;\n type(type?: ICalAttendeeType | null): ICalAttendeeType | null | this {\n if (type === undefined) {\n return this.data.type;\n }\n if (!type) {\n this.data.type = null;\n return this;\n }\n\n this.data.type = checkEnum(ICalAttendeeType, type) as ICalAttendeeType;\n return this;\n }\n /**\n * Set X-* attributes. Woun't filter double attributes,\n * which are also added by another method (e.g. status),\n * so these attributes may be inserted twice.\n *\n * ```javascript\n * attendee.x([\n * {\n * key: \"X-MY-CUSTOM-ATTR\",\n * value: \"1337!\"\n * }\n * ]);\n *\n * attendee.x([\n * [\"X-MY-CUSTOM-ATTR\", \"1337!\"]\n * ]);\n *\n * attendee.x({\n * \"X-MY-CUSTOM-ATTR\": \"1337!\"\n * });\n * ```\n *\n * @since 1.9.0\n */\n x(\n keyOrArray:\n | [string, string][]\n | Record<string, string>\n | { key: string; value: string }[],\n ): this;\n /**\n * Set a X-* attribute. Woun't filter double attributes,\n * which are also added by another method (e.g. status),\n * so these attributes may be inserted twice.\n *\n * ```javascript\n * attendee.x(\"X-MY-CUSTOM-ATTR\", \"1337!\");\n * ```\n *\n * @since 1.9.0\n */\n x(keyOrArray: string, value: string): this;\n /**\n * Get all custom X-* attributes.\n * @since 1.9.0\n */\n x(): { key: string; value: string }[];\n x(\n keyOrArray?:\n | [string, string][]\n | Record<string, string>\n | string\n | { key: string; value: string }[],\n value?: string,\n ): this | void | { key: string; value: string }[] {\n if (keyOrArray === undefined) {\n return addOrGetCustomAttributes(this.data);\n }\n\n if (typeof keyOrArray === 'string' && typeof value === 'string') {\n addOrGetCustomAttributes(this.data, keyOrArray, value);\n } else if (typeof keyOrArray === 'object') {\n addOrGetCustomAttributes(this.data, keyOrArray);\n } else {\n throw new Error('Either key or value is not a string!');\n }\n\n return this;\n }\n}\n","'use strict';\n\nimport type ICalEvent from './event.ts';\n\nimport ICalAttendee, { type ICalAttendeeData } from './attendee.ts';\nimport {\n addOrGetCustomAttributes,\n checkDate,\n checkNameAndMail,\n escape,\n formatDate,\n generateCustomAttributes,\n toDurationString,\n toJSON,\n} from './tools.ts';\nimport { type ICalDateTimeValue } from './types.ts';\n\nexport enum ICalAlarmType {\n audio = 'audio',\n display = 'display',\n email = 'email',\n}\n\nexport const ICalAlarmRelatesTo = {\n end: 'END',\n start: 'START',\n} as const;\n\nexport interface ICalAlarmBaseData {\n attach?: ICalAttachment | null | string;\n attendees?: ICalAttendee[] | ICalAttendeeData[];\n description?: null | string;\n relatesTo?: ICalAlarmRelatesTo | null;\n repeat?: ICalAlarmRepeatData | null;\n summary?: null | string;\n type?: ICalAlarmType;\n x?:\n | [string, string][]\n | Record<string, string>\n | { key: string; value: string }[];\n}\n\nexport type ICalAlarmData =\n | ICalAlarmBaseData\n | ICalAlarmTriggerAfterData\n | ICalAlarmTriggerBeforeData\n | ICalAlarmTriggerData;\n\nexport interface ICalAlarmJSONData {\n attach: ICalAttachment | null;\n attendees: ICalAttendee[];\n description: null | string;\n interval: null | number;\n relatesTo: ICalAlarmRelatesTo | null;\n repeat: ICalAlarmRepeatData | null;\n summary: null | string;\n trigger: number | string;\n type: ICalAlarmType;\n x: { key: string; value: string }[];\n}\n\nexport type ICalAlarmRelatesTo =\n (typeof ICalA