@nextcloud/calendar-js
Version:
Small library that wraps ICAL.js and provide more convenient means for editing
1,771 lines • 246 kB
JavaScript
import ICAL from "ical.js";
import { Timezone, getTimezoneManager } from "@nextcloud/timezones";
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class AbstractParser {
/**
* @class
*
* @param {object=} options Object of options
* @param {boolean=} options.extractGlobalProperties Whether or not to preserve properties from the VCALENDAR component (defaults to false)
* @param {boolean=} options.removeRSVPForAttendees Whether or not to remove RSVP from attendees (defaults to false)
* @param {boolean=} options.includeTimezones Whether or not to include timezones (defaults to false)
* @param {boolean=} options.preserveMethod Whether or not to preserve the iCalendar method (defaults to false)
* @param {boolean=} options.processFreeBusy Whether or not to process VFreeBusy components (defaults to false)
*/
constructor(options = {}) {
if (new.target === AbstractParser) {
throw new TypeError("Cannot instantiate abstract class AbstractParser");
}
this._options = Object.assign({}, options);
this._name = null;
this._color = null;
this._sourceURL = null;
this._refreshInterval = null;
this._calendarTimezone = null;
this._errors = [];
}
/**
* Gets the name extracted from the calendar-data
*
* @return {string | null}
*/
getName() {
return this._name;
}
/**
* Gets the color extracted from the calendar-data
*
* @return {string | null}
*/
getColor() {
return this._color;
}
/**
* Gets whether this import can be converted into a webcal subscription
*
* @return {boolean}
*/
offersWebcalFeed() {
return this._sourceURL !== null;
}
/**
* Gets the url pointing to the webcal source
*
* @return {string | null}
*/
getSourceURL() {
return this._sourceURL;
}
/**
* Gets the recommended refresh rate to update this subscription
*
* @return {string | null}
*/
getRefreshInterval() {
return this._refreshInterval;
}
/**
* Gets the default timezone of this calendar
*
* @return {string}
*/
getCalendarTimezone() {
return this._calendarTimezone;
}
/**
* {String|Object} data
*
* @param {any} data The data to parse
* @throws TypeError
*/
parse(data) {
throw new TypeError("Abstract method not implemented by subclass");
}
/**
* Returns one CalendarComponent at a time
*/
*getItemIterator() {
throw new TypeError("Abstract method not implemented by subclass");
}
/**
* Get an array of all items
*
* @return {CalendarComponent[]}
*/
getAllItems() {
return Array.from(this.getItemIterator());
}
/**
* Returns a boolean whether or not the parsed data contains vevents
*
* @return {boolean}
*/
containsVEvents() {
return false;
}
/**
* Returns a boolean whether or not the parsed data contains vjournals
*
* @return {boolean}
*/
containsVJournals() {
return false;
}
/**
* Returns a boolean whether or not the parsed data contains vtodos
*
* @return {boolean}
*/
containsVTodos() {
return false;
}
/**
* Returns a boolean whether or not the parsed data contains vfreebusys
*
* @return {boolean}
*/
containsVFreeBusy() {
return false;
}
/**
* Returns a boolean whether
*
* @return {boolean}
*/
hasErrors() {
return this._errors.length !== 0;
}
/**
* Get a list of all errors that occurred
*
* @return {*[]}
*/
getErrorList() {
return this._errors.slice();
}
/**
* Returns the number of calendar-objects in parser
*
* @return {number}
*/
getItemCount() {
return 0;
}
/**
* Gets an option provided
*
* @param {string} name The name of the option to get
* @param {*} defaultValue The default value to return if option not provided
* @return {any}
* @protected
*/
_getOption(name, defaultValue) {
return Object.prototype.hasOwnProperty.call(this._options, name) ? this._options[name] : defaultValue;
}
/**
* Return list of supported mime types
*
* @static
*/
static getMimeTypes() {
throw new TypeError("Abstract method not implemented by subclass");
}
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class ModificationNotAllowedError extends Error {
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
function lockableTrait(baseClass) {
return class extends baseClass {
/**
* Constructor
*
* @param {...any} args
*/
constructor(...args) {
super(...args);
this._mutable = true;
}
/**
* Returns whether or not this object is locked
*
* @return {boolean}
*/
isLocked() {
return !this._mutable;
}
/**
* Marks this object is immutable
* locks it against further modification
*/
lock() {
this._mutable = false;
}
/**
* Marks this object as mutable
* allowing further modification
*/
unlock() {
this._mutable = true;
}
/**
* Check if modifications are allowed
*
* @throws {ModificationNotAllowedError} if this object is locked for modification
* @protected
*/
_modify() {
if (!this._mutable) {
throw new ModificationNotAllowedError();
}
}
/**
* Check if modification of content is allowed
*
* @throws {ModificationNotAllowedError} if this object is locked for modification
* @protected
*/
_modifyContent() {
this._modify();
}
};
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class ExpectedICalJSError extends Error {
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
function lc(str) {
return str.toLowerCase();
}
function uc(str) {
return str.toUpperCase();
}
function ucFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function startStringWith(str, startWith) {
if (!str.startsWith(startWith)) {
str = startWith + str;
}
return str;
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
const GLOBAL_CONFIG = /* @__PURE__ */ new Map();
function setConfig(key, value) {
GLOBAL_CONFIG.set(key, value);
}
function getConfig(key, defaultValue) {
return GLOBAL_CONFIG.get(key) || defaultValue;
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
function createComponent(componentName) {
return new ICAL.Component(lc(componentName));
}
function createProperty(propertyName) {
return new ICAL.Property(lc(propertyName));
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
function observerTrait(baseClass) {
return class extends baseClass {
/**
* Constructor
*
* @param {...any} args
*/
constructor(...args) {
super(...args);
this._subscribers = [];
}
/**
* Adds a new subscriber
*
* @param {Function} handler - Handler to be called when modification happens
*/
subscribe(handler) {
this._subscribers.push(handler);
}
/**
* Removes a subscriber
*
* @param {Function} handler - Handler to be no longer called when modification happens
*/
unsubscribe(handler) {
const index = this._subscribers.indexOf(handler);
if (index === -1) {
return;
}
this._subscribers.splice(index, 1);
}
/**
* Notify all subscribed handlers
*
* @param {...any} args
* @protected
*/
_notifySubscribers(...args) {
for (const handler of this._subscribers) {
handler(...args);
}
}
};
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class Parameter extends observerTrait(lockableTrait(class {
})) {
/**
* Constructor
*
* @param {string} name The name of the parameter
* @param {string|Array|null} value The value of the parameter
*/
constructor(name, value = null) {
super();
this._name = uc(name);
this._value = value;
}
/**
* Get parameter name
*
* @readonly
* @return {string}
*/
get name() {
return this._name;
}
/**
* Get parameter value
*
* @return {string | Array}
*/
get value() {
return this._value;
}
/**
* Set new parameter value
*
* @throws {ModificationNotAllowedError} if parameter is locked for modification
* @param {string | Array} value The new value to set
*/
set value(value) {
this._modifyContent();
this._value = value;
}
/**
* Gets the first value of this parameter
*
* @return {string | null}
*/
getFirstValue() {
if (!this.isMultiValue()) {
return this.value;
} else {
if (this.value.length > 0) {
return this.value[0];
}
}
return null;
}
/**
* Gets an iterator for all values
*/
*getValueIterator() {
if (this.isMultiValue()) {
yield* this.value.slice()[Symbol.iterator]();
} else {
yield this.value;
}
}
/**
* Returns whether or not the value is a multivalue
*
* @return {boolean}
*/
isMultiValue() {
return Array.isArray(this._value);
}
/**
* Creates a copy of this parameter
*
* @return {Parameter}
*/
clone() {
const parameter = new this.constructor(this._name);
if (this.isMultiValue()) {
parameter.value = this._value.slice();
} else {
parameter.value = this._value;
}
return parameter;
}
/**
* @inheritDoc
*/
_modifyContent() {
super._modifyContent();
this._notifySubscribers();
}
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class AbstractValue extends observerTrait(lockableTrait(class {
})) {
/**
* Constructor
*
* @param {ICAL.Binary|ICAL.Duration|ICAL.Period|ICAL.Recur|ICAL.Time|ICAL.UtcOffset} icalValue The ICAL.JS object to wrap
*/
constructor(icalValue) {
if (new.target === AbstractValue) {
throw new TypeError("Cannot instantiate abstract class AbstractValue");
}
super();
this._innerValue = icalValue;
}
/**
* Gets wrapped ICAL.JS object
*
* @return {*}
*/
toICALJs() {
return this._innerValue;
}
/**
* @inheritDoc
*/
_modifyContent() {
super._modifyContent();
this._notifySubscribers();
}
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class BinaryValue extends AbstractValue {
/**
* Sets the raw b64 encoded value
*
* @return {string}
*/
get rawValue() {
return this._innerValue.value;
}
/**
* Gets the raw b64 encoded value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {string} value - The new raw value
*/
set rawValue(value) {
this._modifyContent();
this._innerValue.value = value;
}
/**
* Gets the decoded value
*
* @return {string}
*/
get value() {
return this._innerValue.decodeValue();
}
/**
* Sets the decoded Value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {string} decodedValue - The new encoded value
*/
set value(decodedValue) {
this._modifyContent();
this._innerValue.setEncodedValue(decodedValue);
}
/**
* clones this value
*
* @return {BinaryValue}
*/
clone() {
return BinaryValue.fromRawValue(this._innerValue.value);
}
/**
* Create a new BinaryValue object from an ICAL.Binary object
*
* @param {ICAL.Binary} icalValue - The ICAL.Binary object
* @return {BinaryValue}
*/
static fromICALJs(icalValue) {
return new BinaryValue(icalValue);
}
/**
* Create a new BinaryValue object from a raw b64 encoded value
*
* @param {string} rawValue - The raw value
* @return {BinaryValue}
*/
static fromRawValue(rawValue) {
const icalBinary = new ICAL.Binary(rawValue);
return BinaryValue.fromICALJs(icalBinary);
}
/**
* Create a new BinaryValue object from decoded value
*
* @param {string} decodedValue - The encoded value
* @return {BinaryValue}
*/
static fromDecodedValue(decodedValue) {
const icalBinary = new ICAL.Binary();
icalBinary.setEncodedValue(decodedValue);
return BinaryValue.fromICALJs(icalBinary);
}
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class DurationValue extends AbstractValue {
/**
* Gets the weeks of the stored duration-value
*
* @return {number}
*/
get weeks() {
return this._innerValue.weeks;
}
/**
* Sets the weeks of the stored duration-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if value is negative
* @param {number} weeks Amount of weeks
*/
set weeks(weeks) {
this._modifyContent();
if (weeks < 0) {
throw new TypeError("Weeks cannot be negative, use isNegative instead");
}
this._innerValue.weeks = weeks;
}
/**
* Gets the days of the stored duration-value
*
* @return {number}
*/
get days() {
return this._innerValue.days;
}
/**
* Sets the days of the stored duration-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if value is negative
* @param {number} days Amount of days
*/
set days(days) {
this._modifyContent();
if (days < 0) {
throw new TypeError("Days cannot be negative, use isNegative instead");
}
this._innerValue.days = days;
}
/**
* Gets the hours of the stored duration-value
*
* @return {number}
*/
get hours() {
return this._innerValue.hours;
}
/**
* Sets the weeks of the stored duration-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if value is negative
* @param {number} hours Amount of hours
*/
set hours(hours) {
this._modifyContent();
if (hours < 0) {
throw new TypeError("Hours cannot be negative, use isNegative instead");
}
this._innerValue.hours = hours;
}
/**
* Gets the minutes of the stored duration-value
*
* @return {number}
*/
get minutes() {
return this._innerValue.minutes;
}
/**
* Sets the minutes of the stored duration-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if value is negative
* @param {number} minutes Amount of minutes
*/
set minutes(minutes) {
this._modifyContent();
if (minutes < 0) {
throw new TypeError("Minutes cannot be negative, use isNegative instead");
}
this._innerValue.minutes = minutes;
}
/**
* Gets the seconds of the stored duration-value
*
* @return {number}
*/
get seconds() {
return this._innerValue.seconds;
}
/**
* Sets the seconds of the stored duration-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if value is negative
* @param {number} seconds Amount of seconds
*/
set seconds(seconds) {
this._modifyContent();
if (seconds < 0) {
throw new TypeError("Seconds cannot be negative, use isNegative instead");
}
this._innerValue.seconds = seconds;
}
/**
* Gets the negative-indicator of the stored duration-value
*
* @return {boolean}
*/
get isNegative() {
return this._innerValue.isNegative;
}
/**
* Gets the negative-indicator of the stored duration-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {boolean} isNegative Whether or not the duration is negative
*/
set isNegative(isNegative) {
this._modifyContent();
this._innerValue.isNegative = !!isNegative;
}
/**
* Gets the amount of total seconds of the stored duration-value
*
* @return {* | number}
*/
get totalSeconds() {
return this._innerValue.toSeconds();
}
/**
* Sets the amount of total seconds of the stored duration-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {number} totalSeconds The total amounts of seconds to set
*/
set totalSeconds(totalSeconds) {
this._modifyContent();
this._innerValue.fromSeconds(totalSeconds);
}
/**
* Compares this duration to another one
*
* @param {DurationValue} otherDuration The duration to compare to
* @return {number} -1, 0 or 1 for less/equal/greater
*/
compare(otherDuration) {
return this._innerValue.compare(otherDuration.toICALJs());
}
/**
* Adds the value of another duration to this one
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {DurationValue} otherDuration The duration to add
*/
addDuration(otherDuration) {
this._modifyContent();
this.totalSeconds += otherDuration.totalSeconds;
this._innerValue.normalize();
}
/**
* Subtract the value of another duration from this one
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {DurationValue} otherDuration The duration to subtract
*/
subtractDuration(otherDuration) {
this._modifyContent();
this.totalSeconds -= otherDuration.totalSeconds;
this._innerValue.normalize();
}
/**
* clones this value
*
* @return {DurationValue}
*/
clone() {
return DurationValue.fromICALJs(this._innerValue.clone());
}
/**
* Create a new DurationValue object from an ICAL.Duration object
*
* @param {ICAL.Duration} icalValue The ical.js duration value
* @return {DurationValue}
*/
static fromICALJs(icalValue) {
return new DurationValue(icalValue);
}
/**
* Create a new DurationValue object from a number of seconds
*
* @param {number} seconds Total amount of seconds
* @return {DurationValue}
*/
static fromSeconds(seconds) {
const icalDuration = ICAL.Duration.fromSeconds(seconds);
return new DurationValue(icalDuration);
}
/**
* Create a new DurationValue object from data
*
* @param {object} data The destructuring object
* @param {number=} data.weeks Number of weeks to set
* @param {number=} data.days Number of days to set
* @param {number=} data.hours Number of hours to set
* @param {number=} data.minutes Number of minutes to set
* @param {number=} data.seconds Number of seconds to set
* @param {boolean=} data.isNegative Whether or not duration is negative
* @return {DurationValue}
*/
static fromData(data) {
const icalDuration = ICAL.Duration.fromData(data);
return new DurationValue(icalDuration);
}
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class DateTimeValue extends AbstractValue {
/**
* Gets the year of the stored date-time-value
*
* @return {number}
*/
get year() {
return this._innerValue.year;
}
/**
* Sets the year of the stored date-time-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {number} year Number of years to set
*/
set year(year) {
this._modifyContent();
this._innerValue.year = year;
}
/**
* Gets the month of the stored date-time-value
*
* @return {number}
*/
get month() {
return this._innerValue.month;
}
/**
* Sets the month of the stored date-time-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {number} month Number of months to set
*/
set month(month) {
this._modifyContent();
if (month < 1 || month > 12) {
throw new TypeError("Month out of range");
}
this._innerValue.month = month;
}
/**
* Gets the day of the stored date-time-value
*
* @return {number}
*/
get day() {
return this._innerValue.day;
}
/**
* Sets the day of the stored date-time-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if out of range
* @param {number} day Number of days to set
*/
set day(day) {
this._modifyContent();
if (day < 1 || day > 31) {
throw new TypeError("Day out of range");
}
this._innerValue.day = day;
}
/**
* Gets the hour of the stored date-time-value
*
* @return {number}
*/
get hour() {
return this._innerValue.hour;
}
/**
* Sets the hour of the stored date-time-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if out of range
* @param {number} hour Number of hours to set
*/
set hour(hour) {
this._modifyContent();
if (hour < 0 || hour > 23) {
throw new TypeError("Hour out of range");
}
this._innerValue.hour = hour;
}
/**
* Gets the minute of the stored date-time-value
*
* @return {number}
*/
get minute() {
return this._innerValue.minute;
}
/**
* Sets the minute of the stored date-time-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if out of range
* @param {number} minute Number of minutes to set
*/
set minute(minute) {
this._modifyContent();
if (minute < 0 || minute > 59) {
throw new TypeError("Minute out of range");
}
this._innerValue.minute = minute;
}
/**
* Gets the second of the stored date-time-value
*
* @return {number}
*/
get second() {
return this._innerValue.second;
}
/**
* Sets the second of the stored date-time-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if out of range
* @param {number} second Number of seconds to set
*/
set second(second) {
this._modifyContent();
if (second < 0 || second > 59) {
throw new TypeError("Second out of range");
}
this._innerValue.second = second;
}
/**
* Gets the timezone of this date-time-value
*
* @return {string | null}
*/
get timezoneId() {
if (this._innerValue.zone.tzid && this._innerValue.zone.tzid !== "floating" && this._innerValue.zone.tzid === "UTC") {
return this._innerValue.zone.tzid;
}
if (this._innerValue.timezone) {
return this._innerValue.timezone;
}
return this._innerValue.zone.tzid || null;
}
/**
* Gets whether this date-time-value is a date or date-time
*
* @return {boolean}
*/
get isDate() {
return this._innerValue.isDate;
}
/**
* Sets whether this date-time-value is a date or date-time
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {boolean} isDate Whether this is a date or date-time value
*/
set isDate(isDate) {
this._modifyContent();
this._innerValue.isDate = !!isDate;
if (isDate) {
this._innerValue.hour = 0;
this._innerValue.minute = 0;
this._innerValue.second = 0;
}
}
/**
* Gets the unix-time
*
* @return {number}
*/
get unixTime() {
return this._innerValue.toUnixTime();
}
/**
* returns vanilla javascript date object
*
* @return {Date}
*/
get jsDate() {
return this._innerValue.toJSDate();
}
/**
* Adds a duration to this date-time-value
*
* @param {DurationValue} duration The duration to ad
*/
addDuration(duration) {
this._innerValue.addDuration(duration.toICALJs());
}
/**
* Subtract another date excluding timezones
*
* @param {DateTimeValue} other The date-time value to subtract
* @return {DurationValue}
*/
subtractDateWithoutTimezone(other) {
const icalDuration = this._innerValue.subtractDate(other.toICALJs());
return DurationValue.fromICALJs(icalDuration);
}
/**
* Subtract another date, taking timezones into account
*
* @param {DateTimeValue} other The date-time value to subtract
* @return {DurationValue}
*/
subtractDateWithTimezone(other) {
const icalDuration = this._innerValue.subtractDateTz(other.toICALJs());
return DurationValue.fromICALJs(icalDuration);
}
/**
* Compares this DateTimeValue object with another one
*
* @param {DateTimeValue} other The date-time to compare to
* @return {number} -1, 0 or 1 for less/equal/greater
*/
compare(other) {
return this._innerValue.compare(other.toICALJs());
}
/**
* Compares only the date part in a given timezone
*
* @param {DateTimeValue} other The date-time to compare to
* @param {Timezone} timezone The timezone to compare in
* @return {number} -1, 0 or 1 for less/equal/greater
*/
compareDateOnlyInGivenTimezone(other, timezone) {
return this._innerValue.compareDateOnlyTz(other.toICALJs(), timezone.toICALTimezone());
}
/**
* Returns a clone of this object which was converted to a different timezone
*
* @param {Timezone} timezone TimezoneId to convert to
* @return {DateTimeValue}
*/
getInTimezone(timezone) {
const clonedICALTime = this._innerValue.convertToZone(timezone.toICALTimezone());
return DateTimeValue.fromICALJs(clonedICALTime);
}
/**
* Get the inner ICAL.Timezone
*
* @return {ICAL.Timezone}
* @package
*/
getICALTimezone() {
return this._innerValue.zone;
}
/**
* Returns a clone of this object which was converted to a different timezone
*
* @param {ICAL.Timezone} timezone TimezoneId to convert to
* @return {DateTimeValue}
* @package
*/
getInICALTimezone(timezone) {
const clonedICALTime = this._innerValue.convertToZone(timezone);
return DateTimeValue.fromICALJs(clonedICALTime);
}
/**
* Returns a clone of this object which was converted to UTC
*
* @return {DateTimeValue}
*/
getInUTC() {
const clonedICALTime = this._innerValue.convertToZone(ICAL.Timezone.utcTimezone);
return DateTimeValue.fromICALJs(clonedICALTime);
}
/**
* This silently replaces the inner timezone without converting the actual time
*
* @param {ICAL.Timezone} timezone The timezone to replace with
* @package
*/
silentlyReplaceTimezone(timezone) {
this._modify();
this._innerValue = new ICAL.Time({
year: this.year,
month: this.month,
day: this.day,
hour: this.hour,
minute: this.minute,
second: this.second,
isDate: this.isDate,
timezone
});
}
/**
* Replaces the inner timezone without converting the actual time
*
* @param {Timezone} timezone The timezone to replace with
*/
replaceTimezone(timezone) {
this._modifyContent();
this._innerValue = ICAL.Time.fromData({
year: this.year,
month: this.month,
day: this.day,
hour: this.hour,
minute: this.minute,
second: this.second,
isDate: this.isDate
}, timezone.toICALTimezone());
}
/**
* Calculates the UTC offset of the date-time-value in its timezone
*
* @return {number}
*/
utcOffset() {
return this._innerValue.utcOffset();
}
/**
* Check if this is an event with floating time
*
* @return {boolean}
*/
isFloatingTime() {
return this._innerValue.zone.tzid === "floating";
}
/**
* clones this value
*
* @return {DateTimeValue}
*/
clone() {
return DateTimeValue.fromICALJs(this._innerValue.clone());
}
/**
* Create a new DateTimeValue object from an ICAL.Time object
*
* @param {ICAL.Time} icalValue The ical.js Date value to initialise from
* @return {DateTimeValue}
*/
static fromICALJs(icalValue) {
return new DateTimeValue(icalValue);
}
/**
* Creates a new DateTimeValue object based on a vanilla javascript object
*
* @param {Date} jsDate The JavaScript date to initialise from
* @param {boolean=} useUTC Whether or not to treat it as UTC
* @return {DateTimeValue}
*/
static fromJSDate(jsDate, useUTC = false) {
const icalValue = ICAL.Time.fromJSDate(jsDate, useUTC);
return DateTimeValue.fromICALJs(icalValue);
}
/**
* Creates a new DateTimeValue object based on simple parameters
*
* @param {object} data The destructuring object
* @param {number=} data.year Amount of years to set
* @param {number=} data.month Amount of month to set (1-based)
* @param {number=} data.day Amount of days to set
* @param {number=} data.hour Amount of hours to set
* @param {number=} data.minute Amount of minutes to set
* @param {number=} data.second Amount of seconds to set
* @param {boolean=} data.isDate Whether this is a date or date-time
* @param {Timezone=} timezone The timezone of the DateTimeValue
* @return {DateTimeValue}
*/
static fromData(data, timezone) {
const icalValue = ICAL.Time.fromData(data, timezone ? timezone.toICALTimezone() : void 0);
return DateTimeValue.fromICALJs(icalValue);
}
}
DateTimeValue.SUNDAY = ICAL.Time.SUNDAY;
DateTimeValue.MONDAY = ICAL.Time.MONDAY;
DateTimeValue.TUESDAY = ICAL.Time.TUESDAY;
DateTimeValue.WEDNESDAY = ICAL.Time.WEDNESDAY;
DateTimeValue.THURSDAY = ICAL.Time.THURSDAY;
DateTimeValue.FRIDAY = ICAL.Time.FRIDAY;
DateTimeValue.SATURDAY = ICAL.Time.SATURDAY;
DateTimeValue.DEFAULT_WEEK_START = DateTimeValue.MONDAY;
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class PeriodValue extends AbstractValue {
/**
* @inheritDoc
*/
constructor(...args) {
super(...args);
this._start = DateTimeValue.fromICALJs(this._innerValue.start);
this._end = null;
this._duration = null;
}
/**
* Gets the start of the period-value
*
* @return {DateTimeValue}
*/
get start() {
return this._start;
}
/**
* Sets the start of the period-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {DateTimeValue} start The start of the period
*/
set start(start) {
this._modifyContent();
this._start = start;
this._innerValue.start = start.toICALJs();
}
/**
* Gets the end of the period-value
*
* @return {DateTimeValue}
*/
get end() {
if (!this._end) {
if (this._duration) {
this._duration.lock();
this._duration = null;
}
this._innerValue.end = this._innerValue.getEnd();
this._end = DateTimeValue.fromICALJs(this._innerValue.end);
this._innerValue.duration = null;
if (this.isLocked()) {
this._end.lock();
}
}
return this._end;
}
/**
* Sets the end of the period-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {DateTimeValue} end The end of the period
*/
set end(end) {
this._modifyContent();
this._innerValue.duration = null;
this._innerValue.end = end.toICALJs();
this._end = end;
}
/**
* Gets the duration of the period-value
* The value is automatically locked.
* If you want to edit the value, clone it and it as new duration
*
* @return {DurationValue}
*/
get duration() {
if (!this._duration) {
if (this._end) {
this._end.lock();
this._end = null;
}
this._innerValue.duration = this._innerValue.getDuration();
this._duration = DurationValue.fromICALJs(this._innerValue.duration);
this._innerValue.end = null;
if (this.isLocked()) {
this._duration.lock();
}
}
return this._duration;
}
/**
* Sets the duration of the period-value
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {DurationValue} duration The duration to set
*/
set duration(duration) {
this._modifyContent();
this._innerValue.end = null;
this._innerValue.duration = duration.toICALJs();
this._duration = duration;
}
/**
* @inheritDoc
*/
lock() {
super.lock();
this.start.lock();
if (this._end) {
this._end.lock();
}
if (this._duration) {
this._duration.lock();
}
}
/**
* @inheritDoc
*/
unlock() {
super.unlock();
this.start.unlock();
if (this._end) {
this._end.unlock();
}
if (this._duration) {
this._duration.unlock();
}
}
/**
* clones this value
*
* @return {PeriodValue}
*/
clone() {
return PeriodValue.fromICALJs(this._innerValue.clone());
}
/**
* Create a new PeriodValue object from a ICAL.Period object
*
* @param {ICAL.Period} icalValue The ical.js period value to initialise from
* @return {PeriodValue}
*/
static fromICALJs(icalValue) {
return new PeriodValue(icalValue);
}
/**
* Create a new PeriodValue object from start and end
*
* @param {object} data The destructuring object
* @param {DateTimeValue} data.start The start of the period
* @param {DateTimeValue} data.end The end of the period
* @return {PeriodValue}
*/
static fromDataWithEnd(data) {
const icalPeriod = ICAL.Period.fromData({
start: data.start.toICALJs(),
end: data.end.toICALJs()
});
return PeriodValue.fromICALJs(icalPeriod);
}
/**
* Create a new PeriodValue object from start and duration
*
* @param {object} data The destructuring object
* @param {DateTimeValue} data.start The start of the period
* @param {DurationValue} data.duration The duration of the period
* @return {PeriodValue}
*/
static fromDataWithDuration(data) {
const icalPeriod = ICAL.Period.fromData({
start: data.start.toICALJs(),
duration: data.duration.toICALJs()
});
return PeriodValue.fromICALJs(icalPeriod);
}
}
/**
* @copyright Copyright (c) 2019 Georg Ehrke
*
* @author Georg Ehrke <georg-nextcloud@ehrke.email>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
const ALLOWED_FREQ = ["SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY"];
class RecurValue extends AbstractValue {
/**
* Constructor
*
* @param {ICAL.Recur} icalValue The ical.js rrule value
* @param {DateTimeValue?} until The Until date
*/
constructor(icalValue, until) {
super(icalValue);
this._until = until;
}
/**
* Gets the stored interval of this recurrence rule
*
* @return {number}
*/
get interval() {
return this._innerValue.interval;
}
/**
* Sets the stored interval of this recurrence rule
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {number} interval New Interval to set
*/
set interval(interval) {
this._modifyContent();
this._innerValue.interval = parseInt(interval, 10);
}
/**
* Gets the weekstart used to calculate the recurrence expansion
*
* @return {number}
*/
get weekStart() {
return this._innerValue.wkst;
}
/**
* Sets the weekstart used to calculate the recurrence expansion
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if weekstart out of range
* @param {number} weekStart New start of week to set
*/
set weekStart(weekStart) {
this._modifyContent();
if (weekStart < DateTimeValue.SUNDAY || weekStart > DateTimeValue.SATURDAY) {
throw new TypeError("Weekstart out of range");
}
this._innerValue.wkst = weekStart;
}
/**
* Gets the until value if set
* The value is automatically locked.
* If you want to edit the value, clone it and it as new until
*
* @return {null|DateTimeValue}
*/
get until() {
if (!this._until && this._innerValue.until) {
this._until = DateTimeValue.fromICALJs(this._innerValue.until);
}
return this._until;
}
/**
* Sets the until value, automatically removes count
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {DateTimeValue} until New until date to set
*/
set until(until) {
this._modifyContent();
if (this._until) {
this._until.lock();
}
this._until = until;
this._innerValue.count = null;
this._innerValue.until = until.toICALJs();
}
/**
* Gets the count value if set
*
* @return {null | number}
*/
get count() {
return this._innerValue.count;
}
/**
* Sets the count value, automatically removes until
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {number} count New occurrence limit to set
*/
set count(count) {
this._modifyContent();
if (this._until) {
this._until.lock();
this._until = null;
}
this._innerValue.until = null;
this._innerValue.count = parseInt(count, 10);
}
/**
* Gets the frequency of the recurrence rule
*
* @return {string} see
*/
get frequency() {
return this._innerValue.freq;
}
/**
* Sets the frequency of the recurrence rule
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @throws {TypeError} if frequency is unknown
* @param {string} freq New frequency to set
*/
set frequency(freq) {
this._modifyContent();
if (!ALLOWED_FREQ.includes(freq)) {
throw new TypeError("Unknown frequency");
}
this._innerValue.freq = freq;
}
/**
* Modifies this recurrence-value to unset count and until
*/
setToInfinite() {
this._modifyContent();
if (this._until) {
this._until.lock();
this._until = null;
}
this._innerValue.until = null;
this._innerValue.count = null;
}
/**
* Checks whether the stored rule is finite
*
* @return {boolean}
*/
isFinite() {
return this._innerValue.isFinite();
}
/**
* Checks whether the recurrence rule is limited by count
*
* @return {boolean}
*/
isByCount() {
return this._innerValue.isByCount();
}
/**
* Adds a part to a component to the recurrence-rule
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {string} componentName The name of the recurrence-component to add
* @param {string | number} value The value to add
*/
addComponent(componentName, value) {
this._modifyContent();
this._innerValue.addComponent(componentName, value);
}
/**
* Sets / overwrites a component to the recurrence-rule
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {string} componentName The name of the component to set
* @param {number[] | string[]} value The value to set
*/
setComponent(componentName, value) {
this._modifyContent();
if (value.length === 0) {
delete this._innerValue.parts[componentName.toUpperCase()];
} else {
this._innerValue.setComponent(componentName, value);
}
}
/**
* Removes all parts of a component
*
* @throws {ModificationNotAllowedError} if value is locked for modification
* @param {string} componentName The name of the component to remove
*/
removeComponent(componentName) {
delete this._innerValu