summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/src/CalRecurrenceRule.jsm
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/calendar/base/src/CalRecurrenceRule.jsm268
1 files changed, 268 insertions, 0 deletions
diff --git a/comm/calendar/base/src/CalRecurrenceRule.jsm b/comm/calendar/base/src/CalRecurrenceRule.jsm
new file mode 100644
index 0000000000..7d713f2ecf
--- /dev/null
+++ b/comm/calendar/base/src/CalRecurrenceRule.jsm
@@ -0,0 +1,268 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ["CalRecurrenceRule"];
+
+const { ICAL, unwrapSetter, unwrapSingle, wrapGetter } = ChromeUtils.import(
+ "resource:///modules/calendar/Ical.jsm"
+);
+const { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
+
+const lazy = {};
+ChromeUtils.defineModuleGetter(lazy, "CalDateTime", "resource:///modules/CalDateTime.jsm");
+ChromeUtils.defineModuleGetter(lazy, "CalIcalProperty", "resource:///modules/CalICSService.jsm");
+
+function CalRecurrenceRule(innerObject) {
+ this.innerObject = innerObject || new ICAL.Recur();
+ this.wrappedJSObject = this;
+}
+
+var calRecurrenceRuleInterfaces = [Ci.calIRecurrenceRule, Ci.calIRecurrenceItem];
+var calRecurrenceRuleClassID = Components.ID("{df19281a-5389-4146-b941-798cb93a7f0d}");
+CalRecurrenceRule.prototype = {
+ QueryInterface: cal.generateQI(["calIRecurrenceRule", "calIRecurrenceItem"]),
+ classID: calRecurrenceRuleClassID,
+ classInfo: cal.generateCI({
+ contractID: "@mozilla.org/calendar/recurrence-rule;1",
+ classDescription: "Calendar Recurrence Rule",
+ classID: calRecurrenceRuleClassID,
+ interfaces: calRecurrenceRuleInterfaces,
+ }),
+
+ innerObject: null,
+
+ isMutable: true,
+ makeImmutable() {
+ this.isMutable = false;
+ },
+ ensureMutable() {
+ if (!this.isMutable) {
+ throw Components.Exception("", Cr.NS_ERROR_OBJECT_IS_IMMUTABLE);
+ }
+ },
+ clone() {
+ return new CalRecurrenceRule(new ICAL.Recur(this.innerObject));
+ },
+
+ isNegative: false, // We don't support EXRULE anymore
+ get isFinite() {
+ return this.innerObject.isFinite();
+ },
+
+ /**
+ * Tests whether the "FREQ" value for this rule is supported or not. A warning
+ * is logged if an unsupported value ("SECONDLY"|"MINUTELY") is encountered.
+ *
+ * @returns {boolean}
+ */
+ freqSupported() {
+ let { freq } = this.innerObject;
+ if (freq == "SECONDLY" || freq == "MINUTELY") {
+ cal.WARN(
+ `The frequency value "${freq}" is currently not supported. No occurrences will be generated.`
+ );
+ return false;
+ }
+ return true;
+ },
+
+ getNextOccurrence(aStartTime, aRecId) {
+ if (!this.freqSupported()) {
+ return null;
+ }
+ aStartTime = unwrapSingle(ICAL.Time, aStartTime);
+ aRecId = unwrapSingle(ICAL.Time, aRecId);
+ return wrapGetter(lazy.CalDateTime, this.innerObject.getNextOccurrence(aStartTime, aRecId));
+ },
+
+ getOccurrences(aStartTime, aRangeStart, aRangeEnd, aMaxCount) {
+ if (!this.freqSupported()) {
+ return [];
+ }
+ aStartTime = unwrapSingle(ICAL.Time, aStartTime);
+ aRangeStart = unwrapSingle(ICAL.Time, aRangeStart);
+ aRangeEnd = unwrapSingle(ICAL.Time, aRangeEnd);
+
+ if (!aMaxCount && !aRangeEnd && this.count == 0 && this.until == null) {
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ let occurrences = [];
+ let rangeStart = aRangeStart.clone();
+ rangeStart.isDate = false;
+
+ let dtend = null;
+
+ if (aRangeEnd) {
+ dtend = aRangeEnd.clone();
+ dtend.isDate = false;
+
+ // If the start of the recurrence is past the end, we have no dates
+ if (aStartTime.compare(dtend) >= 0) {
+ return [];
+ }
+ }
+
+ let iter = this.innerObject.iterator(aStartTime);
+
+ for (let next = iter.next(); next; next = iter.next()) {
+ let dtNext = next.clone();
+ dtNext.isDate = false;
+
+ if (dtNext.compare(rangeStart) < 0) {
+ continue;
+ }
+
+ if (dtend && dtNext.compare(dtend) >= 0) {
+ break;
+ }
+
+ next = next.clone();
+
+ if (aStartTime.zone) {
+ next.zone = aStartTime.zone;
+ }
+
+ occurrences.push(new lazy.CalDateTime(next));
+
+ if (aMaxCount && occurrences.length >= aMaxCount) {
+ break;
+ }
+ }
+
+ return occurrences;
+ },
+
+ get icalString() {
+ return "RRULE:" + this.innerObject.toString() + ICAL.newLineChar;
+ },
+ set icalString(val) {
+ this.ensureMutable();
+ this.innerObject = ICAL.Recur.fromString(val.replace(/^RRULE:/i, ""));
+ },
+
+ get icalProperty() {
+ let prop = new ICAL.Property("rrule");
+ prop.setValue(this.innerObject);
+ return new lazy.CalIcalProperty(prop);
+ },
+ set icalProperty(rawval) {
+ this.ensureMutable();
+ unwrapSetter(
+ ICAL.Property,
+ rawval,
+ function (val) {
+ this.innerObject = val.getFirstValue();
+ },
+ this
+ );
+ },
+
+ get type() {
+ return this.innerObject.freq;
+ },
+ set type(val) {
+ this.ensureMutable();
+ this.innerObject.freq = val;
+ },
+
+ get interval() {
+ return this.innerObject.interval;
+ },
+ set interval(val) {
+ this.ensureMutable();
+ this.innerObject.interval = val;
+ },
+
+ get count() {
+ if (!this.isByCount) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+ return this.innerObject.count || -1;
+ },
+ set count(val) {
+ this.ensureMutable();
+ this.innerObject.count = val && val > 0 ? val : null;
+ },
+
+ get untilDate() {
+ if (this.innerObject.until) {
+ return new lazy.CalDateTime(this.innerObject.until);
+ }
+ return null;
+ },
+ set untilDate(rawval) {
+ this.ensureMutable();
+ unwrapSetter(
+ ICAL.Time,
+ rawval,
+ function (val) {
+ if (
+ val.timezone != ICAL.Timezone.utcTimezone &&
+ val.timezone != ICAL.Timezone.localTimezone
+ ) {
+ val = val.convertToZone(ICAL.Timezone.utcTimezone);
+ }
+
+ this.innerObject.until = val;
+ },
+ this
+ );
+ },
+
+ get isByCount() {
+ return this.innerObject.isByCount();
+ },
+
+ get weekStart() {
+ return this.innerObject.wkst - 1;
+ },
+ set weekStart(val) {
+ this.ensureMutable();
+ this.innerObject.wkst = val + 1;
+ },
+
+ getComponent(aType) {
+ let values = this.innerObject.getComponent(aType);
+ if (aType == "BYDAY") {
+ // BYDAY values are alphanumeric: SU, MO, TU, etc..
+ for (let i = 0; i < values.length; i++) {
+ let match = /^([+-])?(5[0-3]|[1-4][0-9]|[1-9])?(SU|MO|TU|WE|TH|FR|SA)$/.exec(values[i]);
+ if (!match) {
+ cal.ERROR("Malformed BYDAY rule\n" + cal.STACK(10));
+ return [];
+ }
+ values[i] = ICAL.Recur.icalDayToNumericDay(match[3]);
+ if (match[2]) {
+ // match[2] is the week number for this value.
+ values[i] += 8 * match[2];
+ }
+ if (match[1] == "-") {
+ // Week numbers are counted back from the end of the period.
+ values[i] *= -1;
+ }
+ }
+ }
+
+ return values;
+ },
+
+ setComponent(aType, aValues) {
+ let values = aValues;
+ if (aType == "BYDAY") {
+ // BYDAY values are alphanumeric: SU, MO, TU, etc..
+ for (let i = 0; i < values.length; i++) {
+ let absValue = Math.abs(values[i]);
+ if (absValue > 7) {
+ let ordinal = Math.trunc(values[i] / 8);
+ let day = ICAL.Recur.numericDayToIcalDay(absValue % 8);
+ values[i] = ordinal + day;
+ } else {
+ values[i] = ICAL.Recur.numericDayToIcalDay(values[i]);
+ }
+ }
+ }
+ this.innerObject.setComponent(aType, values);
+ },
+};