Fix bug 463050 - Slight improvements to calIAlarm. r=dbo
authorPhilipp Kewisch <mozilla@kewis.ch>
Fri, 12 Dec 2008 17:46:55 +0100
changeset 1403 2247db027f7f0d691e780b41a80b4e7c44965cca
parent 1402 1818c8fa024fb8d73d693ce6d30ac3908634ecda
child 1404 ec152d198c34a188990c4cb74049762dda709499
push idunknown
push userunknown
push dateunknown
reviewersdbo
bugs463050
Fix bug 463050 - Slight improvements to calIAlarm. r=dbo
calendar/base/public/calIAlarm.idl
calendar/base/src/calAlarm.js
calendar/locales/en-US/chrome/calendar/calendar-alarms.properties
calendar/locales/jar.mn
--- a/calendar/base/public/calIAlarm.idl
+++ b/calendar/base/public/calIAlarm.idl
@@ -37,17 +37,17 @@
 #include "nsISupports.idl"
 
 interface nsIVariant;
 interface nsISimpleEnumerator;
 
 interface calIDateTime;
 interface calIDuration;
 interface calIItemBase;
-interface calIcalComponent;
+interface calIIcalComponent;
 
 [scriptable, uuid(b8db7c7f-c168-4e11-becb-f26c1c4f5f8f)]
 interface calIAlarm : nsISupports
 {
     /**
      * Returns true if this alarm is able to be modified
      */
     readonly attribute boolean isMutable;
@@ -58,16 +58,21 @@ interface calIAlarm : nsISupports
     void makeImmutable();
 
     /**
      * Make a copy of this alarm. The returned alarm will be mutable.
      */
     calIAlarm clone();
 
     /**
+     * A hash id to uniquely identify the alarm
+     */
+    readonly attribute AUTF8String hashId;
+
+    /**
      * The item this alarm is related to
      */
     attribute calIItemBase item;
 
     /**
      * How this alarm is shown. Special values as described in rfc2445 are
      * AUDIO, DISPLAY, EMAIL
      * In addition, custom actions may be defined as an X-Prop, i.e
@@ -97,31 +102,31 @@ interface calIAlarm : nsISupports
     /**
      * One of the ALARM_RELATED constants below.
      * If an alarmDate was set, changing this influences the offset, and vice
      * versa.
      */
     attribute unsigned long related;
 
     /**
+     * The alarm is absolute and is therefore not related to either.
+     */
+    const unsigned long ALARM_RELATED_ABSOLUTE = 0;
+
+    /**
      * The alarm's offset should be based off of the startDate or
      * entryDate (for events and tasks, respectively)
      */
-    const unsigned long ALARM_RELATED_START = 0;
+    const unsigned long ALARM_RELATED_START = 1;
 
     /**
      * the alarm's offset should be based off of the endDate or
      * dueDate (for events and tasks, respectively)
      */
-    const unsigned long ALARM_RELATED_END = 1;
-
-    /**
-     * The last time this alarm was fired and acknowledged by the user
-     */
-    attribute calIDateTime lastAck;
+    const unsigned long ALARM_RELATED_END = 2;
 
     /**
      * Times the alarm should be repeated. This value is the number of
      * ADDITIONAL alarms, aside from the actual alarm.
      *
      * For the alarm to be valid, if repeat is specified, the repeatOffset
      * attribute MUST also be specified.
      */
@@ -162,24 +167,29 @@ interface calIAlarm : nsISupports
      * For EMAIL alarms, more than one attachment can be specified.
      * For AUDIO alarms, one Attachment can be specified.
      * For DISPLAY alarms, attachments are invalid.
      */
     // TODO void addAttachment(in AUTF8String aAttachment);
     // TODO void deleteAttachment(in AUTF8String aAttachment);
 
     /**
+     * The human readable representation of this alarm. Uses locale strings.
+     */
+    AUTF8String toString();
+
+    /**
      * The ical representation of this VALARM
      */
     attribute AUTF8String icalString;
 
     /**
      * The ical component of this VALARM
      */
-    attribute calIcalComponent icalComponent;
+    attribute calIIcalComponent icalComponent;
 
     // Property bag
     boolean hasProperty(in AUTF8String name);
     nsIVariant getProperty(in AUTF8String name);
     void setProperty(in AUTF8String name, in nsIVariant value);
     void deleteProperty(in AUTF8String name);
 
     readonly attribute nsISimpleEnumerator propertyEnumerator;
--- a/calendar/base/src/calAlarm.js
+++ b/calendar/base/src/calAlarm.js
@@ -29,37 +29,43 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
 function calAlarm() {
     this.mProperties = new calPropertyBag();
     this.mPropertyParams = {};
+    this.mAttendees = [];
+    this.mAttachments = [];
 }
 
 calAlarm.prototype = {
 
     mProperties: null,
     mPropertyParams: null,
     mAction: null,
     mAbsoluteDate: null,
     mOffset: null,
     mDuration: null,
-    mAttendees: [],
-    mAttachments: [],
+    mAttendees: null,
+    mAttachments: null,
     mSummary: null,
     mDescription: null,
     mLastAck: null,
     mItem: null,
     mImmutable: false,
     mRelated: 0,
+    mRepeat: 0,
 
     QueryInterface: function cA_QueryInterface(aIID) {
         return doQueryInterface(this, calAlarm.__proto__, aIID, null, this);
     },
 
     /**
      * nsIClassInfo
      */
@@ -84,177 +90,162 @@ calAlarm.prototype = {
      */
 
     ensureMutable: function cA_ensureMutable() {
         if (this.mImmutable) {
             throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
         }
     },
 
-    get isMutable() {
+    get isMutable cA_get_isMutable() {
         return !this.mImmutable;
     },
 
     makeImmutable: function cA_makeImmutable() {
         if (this.mImmutable) {
             return;
         }
 
         const objectMembers = ["mAbsoluteDate",
                                "mOffset",
                                "mDuration",
-                               "mLastAck",
-                               "mItem"];
-        for each (var member in objectMembers) {
-            if (this[member]) {
+                               "mLastAck"];
+        for each (let member in objectMembers) {
+            if (this[member] && this[member].isMutable) {
                 this[member].makeImmutable();
             }
         }
 
         // Properties
-        var e = this.mProperties.enumerator;
+        let e = this.mProperties.enumerator;
         while (e.hasMoreElements()) {
-            var prop = e.getNext();
-            var val = prop.value;
+            let prop = e.getNext();
+            let val = prop.value;
 
             if (prop.value instanceof Components.interfaces.calIDateTime) {
                 if (prop.value.isMutable)
                     prop.value.makeImmutable();
             }
         }
 
         this.mImmutable = true;
     },
 
     clone: function cA_clone() {
-        var m = new calAlarm();
+        let m = new calAlarm();
 
         m.mImmutable = false;
 
         const simpleMembers = ["mAction",
                                "mSummary",
                                "mDescription",
                                "mRelated",
                                "mRepeat"];
 
         const arrayMembers = ["mAttendees",
                               "mAttachments"];
 
         const objectMembers = ["mAbsoluteDate",
                                "mOffset",
                                "mDuration",
-                               "mLastAck",
-                               "mItem"];
+                               "mLastAck"];
 
-        for each (var member in simpleMembers) {
+        for each (let member in simpleMembers) {
             m[member] = this[member];
         }
 
-        for each (var member in arrayMembers) {
+        for each (let member in arrayMembers) {
             m[member] = this[member].slice(0);
         }
 
-        for each (var member in objectMembers) {
+        for each (let member in objectMembers) {
             if (this[member] && this[member].clone) {
                 m[member] = this[member].clone();
             } else {
                 m[member] = this[member];
             }
         }
 
         // X-Props
         m.mProperties = new calPropertyBag();
-        var e = this.mProperties.enumerator;
-        while (e.hasMoreElements()) {
-            var prop = e.getNext();
-            var name = prop.name;
-            var val = prop.value;
-
-            if (val instanceof Components.interfaces.calIDateTime) {
-                val = val.clone();
+        for each (let [name, value] in this.mProperties) {
+            if (value instanceof Components.interfaces.calIDateTime) {
+                value = value.clone();
             }
 
-            m.mProperties.setProperty(name, val);
+            m.mProperties.setProperty(name, value);
 
-            var propBucket = this.mPropertyParams[name];
+            let propBucket = this.mPropertyParams[name];
             if (propBucket) {
-                var newBucket = {};
-                for (var param in propBucket) {
+                let newBucket = {};
+                for (let param in propBucket) {
                     newBucket[param] = propBucket[param];
                 }
                 m.mPropertyParams[name] = newBucket;
             }
         }
         return m;
     },
 
-    related: 0,
-    get related() {
+
+    get hashId cA_get_hashId() {
+        // TODO make the hash a bit more compact
+        return this.mItem.hashId + "#" + this.icalString;
+    },
+
+    get related cA_get_related() {
         return this.mRelated;
     },
-    set related(aValue) {
+    set related cA_set_related(aValue) {
         this.ensureMutable();
         return (this.mRelated = aValue);
     },
 
-    get lastAck() {
-        if (this.action == "AUDIO" ||
-            this.action == "EMAIL") {
-            return null;
-        }
-        return this.mLastAck;
-    },
-    set lastAck(aValue) {
-        this.ensureMutable();
-        // TODO check type
-        return (this.mLastAck = aValue);
-    },
-
-    get item() {
+    get item cA_get_item() {
         return this.mItem;
     },
-    set item(val) {
+    set item cA_set_item(val) {
         this.ensureMutable();
         return (this.mItem = val);
     },
 
-    get action() {
+    get action cA_get_action() {
         return this.mAction || "DISPLAY";
     },
-    set action(aValue) {
+    set action cA_set_action(aValue) {
         this.ensureMutable();
         return (this.mAction = aValue);
     },
 
     // TODO Do we really need to expose this?
-    get description() {
+    get description cA_get_description() {
         if (this.action == "AUDIO") {
             return null;
         }
         return this.mDescription;
     },
-    set description(aValue) {
+    set description cA_set_description(aValue) {
         this.ensureMutable();
         return (this.mDescription = aValue);
     },
 
-    get summary() {
+    get summary cA_get_summary() {
         if (this.mAction == "DISPLAY" ||
             this.mAction == "AUDIO") {
             return null;
         }
         return this.mSummary;
     },
-    set summary(aValue) {
+    set summary cA_set_summary(aValue) {
         this.ensureMutable();
         return (this.mSummary= aValue);
     },
 
-    _getAlarmDate: function cA_getAlarmDate() {
-        var itemAlarmDate;
+    _getAlarmDate: function cA__getAlarmDate() {
+        let itemAlarmDate;
         if (isEvent(this.mItem)) {
             switch (this.related) {
                 case Components.interfaces.calIAlarm.ALARM_RELATED_START:
                     itemAlarmDate = this.mItem.startDate;
                     break;
                 case Components.interfaces.calIAlarm.ALARM_RELATED_END:
                     itemAlarmDate = this.mItem.endDate;
                     break;
@@ -267,376 +258,451 @@ calAlarm.prototype = {
                 case Components.interfaces.calIAlarm.ALARM_RELATED_END:
                     itemAlarmDate = this.mItem.dueDate;
                     break;
             }
         }
         return itemAlarmDate;
     },
 
-    get offset() {
+    get offset cA_get_offset() {
         if (this.mOffset) {
             return this.mOffset;
         } else if (this.mItem && this.mAbsoluteDate) {
-            var itemAlarmDate = this._getAlarmDate();
+            let itemAlarmDate = this._getAlarmDate();
             if (itemAlarmDate) {
                 return this.mAbsoluteDate.subtractDate(itemAlarmDate);
             }
         }
         return null;
     },
-    set offset(aValue) {
+    set offset cA_set_offset(aValue) {
         this.ensureMutable();
         if (aValue && !(aValue instanceof Components.interfaces.calIDuration)) {
             throw Components.results.NS_ERROR_INVALID_ARG;
         }
         this.mAbsoluteDate = null;
         return (this.mOffset = aValue);
     },
 
-    get alarmDate() {
+    get alarmDate cA_get_alarmDate() {
         if (this.mAbsoluteDate) {
             return this.mAbsoluteDate;
         } else if (this.mOffset && this.mItem) {
-            var itemAlarmDate = this._getAlarmDate();
+            let itemAlarmDate = this._getAlarmDate();
             if (itemAlarmDate) {
                 itemAlarmDate = itemAlarmDate.clone();
                 itemAlarmDate.addDuration(this.mOffset);
                 return itemAlarmDate;
             }
         }
         return null;
 
     },
-    set alarmDate(aValue) {
+    set alarmDate cA_set_alarmDate(aValue) {
         this.ensureMutable();
         if (aValue && !(aValue instanceof Components.interfaces.calIDateTime)) {
             throw Components.results.NS_ERROR_INVALID_ARG;
         }
         this.mOffset = null;
         return (this.mAbsoluteDate = aValue);
     },
 
-    get repeat() {
+    get repeat cA_get_repeat() {
         if ((this.mRepeat != 0) ^ (this.mDuration != null)) {
             return 0;
         }
         return this.mRepeat || 0;
     },
-    set repeat(aValue) {
+    set repeat cA_set_repeat(aValue) {
         this.ensureMutable();
         if (aValue === null) {
             this.mRepeat = null;
         } else {
             this.mRepeat = parseInt(aValue);
             if (isNaN(this.mRepeat)) {
                 throw Components.results.NS_ERROR_INVALID_ARG;
             }
         }
         return aValue;
     },
 
-    get repeatOffset() {
+    get repeatOffset cA_get_repeatOffset() {
         if ((this.mRepeat != 0) ^ (this.mDuration != null)) {
             return null;
         }
         return this.mDuration;
     },
-    set repeatOffset(aValue) {
+    set repeatOffset cA_set_repeatOffset(aValue) {
         this.ensureMutable();
         if (aValue !== null &&
             !(aValue instanceof Components.interfaces.calIDuration)) {
             throw Components.results.NS_ERROR_INVALID_ARG;
         }
         return (this.mDuration = aValue);
     },
 
-    get repeatDate() {
-        var alarmDate = this._getAlarmDate();
+    get repeatDate cA_get_repeatDate() {
+        let alarmDate = this._getAlarmDate();
         if (!this.mRepeat || !this.mDuration || !alarmDate) {
             return null;
         }
 
         alarmDate = alarmDate.clone();
 
         // All Day events are handled as 00:00:00
         alarmDate.isDate = false;
         return alarmDate.addDuration(this.mDuration);
     },
 
-    get attendees() {
+    get attendees cA_get_attendees() {
         return this.mAttendees;
     },
-    set attendees(aValue) {
+    set attendees cA_set_attendees(aValue) {
         this.ensureMutable();
         // TODO Make add/update/deleteAttendee
         return (this.mAttendees = aValue);
     },
 
-    get attachments() {
+    get attachments cA_get_attachments() {
         if (this.action == "AUDIO") {
             return this.mAttachments.splice(1);
         } else if (this.action == "DISPLAY") {
             return [];
         }
         return this.mAttachments;
     },
-    set attachments(aValue) {
+    set attachments cA_set_attachments(aValue) {
         this.ensureMutable();
         // TODO Make add/update/deleteAttendee
         return (this.mAttachments = aValue);
     },
 
-    get icalString() {
-        var comp = this.icalComponent;
+    get icalString cA_get_icalString() {
+        let comp = this.icalComponent;
         return (comp ? comp.serializeToICS() : "");
     },
-    set icalString(val) {
+    set icalString cA_set_icalString(val) {
         this.ensureMutable();
         return (this.icalComponent = getIcsService().parseICS(val, null));
     },
 
     promotedProps: {
         "ACTION": "action",
         "TRIGGER": "offset",
         "REPEAT": "repeat",
         "DURATION": "duration",
         "SUMMARY": "summary",
         "DESCRIPTION": "description",
         "X-MOZ-LASTACK": "lastAck"
     },
 
-    get icalComponent() {
-        var icssvc = getIcsService();
-        var comp = icssvc.createIcalComponent("VALARM");
+    get icalComponent cA_get_icalComponent() {
+        let icssvc = getIcsService();
+        let comp = icssvc.createIcalComponent("VALARM");
 
         // Set up action (REQUIRED)
-        var actionProp = icssvc.createIcalProperty("ACTION");
+        let actionProp = icssvc.createIcalProperty("ACTION");
         actionProp.value = this.action;
         comp.addProperty(actionProp);
 
         // Set up trigger (REQUIRED)
-        var triggerProp = icssvc.createIcalProperty("TRIGGER");
-        if (this.mAbsoluteDate) {
+        let triggerProp = icssvc.createIcalProperty("TRIGGER");
+        if (this.related == Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE &&
+            this.mAbsoluteDate) {
             // Set the trigger to a specific datetime
             triggerProp.setParameter("VALUE", "DATE-TIME");
             triggerProp.valueAsDatetime = this.mAbsoluteDate.getInTimezone(UTC());
-        } else if (this.mOffset) {
+        } else if (this.related != Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE &&
+                   this.mOffset) {
             triggerProp.valueAsIcalString = this.mOffset.icalString;
         } else {
             // No offset or absolute date is not valid.
             throw Components.results.NS_ERROR_NOT_INITIALIZED;
         }
         comp.addProperty(triggerProp);
 
         // Set up repeat and duration (OPTIONAL, but if one exists, the other
         // MUST also exist)
         if (this.repeat && this.duration) {
-            var repeatProp = icssvc.createIcalProperty("REPEAT");
-            var durationProp = icssvc.createIcalProperty("DURATION");
+            let repeatProp = icssvc.createIcalProperty("REPEAT");
+            let durationProp = icssvc.createIcalProperty("DURATION");
 
             repeatProp.value = this.repeat;
             durationProp.valueAsIcalString = this.duration.icalString;
 
             comp.addProperty(repeatProp);
             comp.addProperty(durationProp);
         }
 
         // Set up attendees (REQUIRED for EMAIL action)
+        /* TODO add support for attendees
         if (this.action == "EMAIL" && !this.attendees.length) {
             throw Components.results.NS_ERROR_NOT_INITIALIZED;
-        }
-        for each (var attendee in this.attendees) {
-            var attendeeProp = icssvc.createIcalProperty("ATTENDEE");
+        } */
+        for each (let attendee in this.attendees) {
+            let attendeeProp = icssvc.createIcalProperty("ATTENDEE");
             attendeeProp.value = attendee;
             comp.addProperty(attendeeProp);
         }
 
         // Set up attachments (REQUIRED for AUDIO and EMAIL types, there MUST
         // NOT be more than one for AUDIO.
+        /* TODO add support for attachments
         if ((this.action == "EMAIL" || this.action == "AUDIO") &&
             !this.attachments.length) {
             throw Components.results.NS_ERROR_NOT_INITIALIZED;
-        }
+        } */
 
-        for (var i = 0; i < this.attachments.length; i++) {
-            var attachment = this.attachments[i];
-            var attachmentProp = icssvc.createIcalProperty("ATTACH");
+        for each (let attachment in attachments) {
+            let attachmentProp = icssvc.createIcalProperty("ATTACH");
             attachmentProp.value = attachment;
             comp.addProperty(attachmentProp);
         }
 
         // Set up summary (REQUIRED for EMAIL)
         if (this.summary || this.action == "EMAIL") {
-            var summaryProp = icssvc.createIcalProperty("SUMMARY");
-            summaryProp.value = this.summary || "";
+            let summaryProp = icssvc.createIcalProperty("SUMMARY");
+            // Summary needs to have a non-empty value
+            summaryProp.value = this.summary ||
+                calGetString("calendar", "alarmDefaultSummary");
             comp.addProperty(summaryProp);
         }
 
         // Set up the description (REQUIRED for DISPLAY and EMAIL)
         if (this.description ||
             this.action == "DISPLAY" ||
             this.action == "EMAIL") {
-            var descriptionProp = icssvc.createIcalProperty("DESCRIPTION");
-            descriptionProp.value = this.description || "";
+            let descriptionProp = icssvc.createIcalProperty("DESCRIPTION");
+            // description needs to have a non-empty value
+            descriptionProp.value = this.description ||
+                calGetString("calendar", "alarmDefaultDescription");
             comp.addProperty(descriptionProp);
         }
 
         // Set up lastAck
         if (this.lastAck) {
-            var lastAckProp = icssvc.createIcalProperty("X-MOZ-LASTACK");
+            let lastAckProp = icssvc.createIcalProperty("X-MOZ-LASTACK");
             lastAckProp.value = this.lastAck;
             comp.addProperty(lastAckProp);
         }
 
         // Set up X-Props. mProperties contains only non-promoted props
-        var e = this.mProperties.enumerator;
-        while (e.hasMoreElements()) {
-            var prop = e.getNext();
-            var icalprop = icssvc.createIcalProperty(prop.name);
-            icalprop.value = prop.value;
-            var propBucket = this.mPropertyParams[prop.name];
+        for (let propName in this.mProperties) {
+            let icalprop = icssvc.createIcalProperty(propName);
+            icalprop.value = this.mProperties.getProperty(propName);
+
+            // Add parameters
+            let propBucket = this.mPropertyParams[propName];
             if (propBucket) {
-                for (paramName in propBucket) {
+                for (let paramName in propBucket) {
                     icalprop.setParameter(paramName,
                                           propBucket[paramName]);
                 }
             }
-            icalcomp.addProperty(icalprop);
+            comp.addProperty(icalprop);
         }
         return comp;
     },
-    set icalComponent(aComp) {
+    set icalComponent cA_set_icalComponent(aComp) {
         this.ensureMutable();
         if (!aComp || aComp.componentType != "VALARM") {
             // Invalid Component
             throw Components.results.NS_ERROR_INVALID_ARG;
         }
 
-        var actionProp = aComp.getFirstProperty("ACTION");
-        var triggerProp = aComp.getFirstProperty("TRIGGER");
-        var repeatProp = aComp.getFirstProperty("REPEAT");
-        var durationProp = aComp.getFirstProperty("DURATION");
-        var summaryProp = aComp.getFirstProperty("SUMMARY");
-        var descriptionProp = aComp.getFirstProperty("DESCRIPTION");
-        var lastAckProp = aComp.getFirstProperty("X-MOZ-LASTACK");
+        let actionProp = aComp.getFirstProperty("ACTION");
+        let triggerProp = aComp.getFirstProperty("TRIGGER");
+        let repeatProp = aComp.getFirstProperty("REPEAT");
+        let durationProp = aComp.getFirstProperty("DURATION");
+        let summaryProp = aComp.getFirstProperty("SUMMARY");
+        let descriptionProp = aComp.getFirstProperty("DESCRIPTION");
+        let lastAckProp = aComp.getFirstProperty("X-MOZ-LASTACK");
 
         if (actionProp) {
             this.action = actionProp.value;
         } else {
             throw Components.results.NS_ERROR_INVALID_ARG;
         }
 
         if (triggerProp) {
             if (triggerProp.getParameter("VALUE") == "DATE-TIME")  {
                 this.mAbsoluteDate = triggerProp.valueAsDatetime;
             } else {
-                var offset = Components.classes["@mozilla.org/calendar/duration;1"]
-                                       .createInstance(Components.interfaces.calIDuration);
-                offset.icalString = triggerProp.valueAsIcalString;
-                this.mOffset = offset;
+                this.mOffset = cal.createDuration(triggerProp.valueAsIcalString);
             }
         } else {
             throw Components.results.NS_ERROR_INVALID_ARG;
         }
 
         // Set up alarm relation
-        var related = triggerProp.getParameter("RELATED");
+        let related = triggerProp.getParameter("RELATED");
         if (related && related == "END") {
             this.related = Components.interfaces.calIAlarm.ALARM_RELATED_END;
         } else {
             this.related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
         }
 
         if (durationProp && repeatProp) {
-            var duration = Components.classes["@mozilla.org/calendar/duration;1"]
-                                     .createInstance(Components.interfaces.calIDuration);
-            duration.icalString = durationProp.valueAsIcalString;
-            this.duration = duration;
+            this.duration = cal.createDuration(durationProp.valueAsIcalString);
             this.repeat = repeatProp.value;
-        } else if (!durationProp && !repeatProp) {
+        } else if (durationProp || repeatProp) {
             throw Components.results.NS_ERROR_INVALID_ARG;
         } else {
             this.duration = null;
             this.repeat = 0;
         }
 
         // Set up attendees
         this.attendees = [];
-        for (var attendeeProp = aComp.getFirstProperty("ATTENDEE");
-             attendeeProp;
-             attendeeProp = aComp.getNextProperty("ATTENDEE")) {
-            // XXX this.addAttendee(attendeeProp.value);
+        for each (let attendee in cal.ical.propertyIterator(aComp, "ATTENDEE")) {
+            // XXX this.addAttendee(attendee);
         }
 
         // Set up attachments
         this.attachments = [];
-        for (var attachmentProp = aComp.getFirstProperty("ATTACH");
-             attachmentProp;
-             attachmentProp = aComp.getNextProperty("ATTACH")) {
-            // XXX this.addAttachment(attachmentProp.value);
+        for each (let attach in cal.ical.propertyIterator(aComp, "ATTACH")) {
+            // XXX this.addAttachment(attach);
         }
 
         // Set up summary
         this.summary = (summaryProp ? summaryProp.value : null);
 
         // Set up description
         this.description = (descriptionProp ? descriptionProp.value : null);
 
         // Set up the alarm lastack
         this.lastAck = (lastAckProp ? lastAckProp.valueAsDatetime : null);
 
         this.mProperties = new calPropertyBag();
         this.mPropertyParams = {};
 
         // Other properties
-        for (var prop = aComp.getFirstProperty("ANY");
-             prop;
-             prop = aComp.getNextProperty("ANY")) {
+        for (let prop in cal.ical.propertyIterator(aComp)) {
             if (!this.promotedProps[prop.propertyName]) {
                 this.setProperty(prop.propertyName, prop.value);
-                var param = prop.getFristParameterName();
-                while (param) {
+
+                for (let paramName in cal.ical.paramIterator(prop)) {
                     if (!(prop.propertyName in this.mPropertyParams)) {
                         this.mPropertyParams[prop.propertyName] = {};
                     }
-                    this.mPropertyParams[prop.propertyName][param] = prop.getParameter(param);
-                    param = prop.getNextParameterName();
+                    let param = prop.getParameter(paramName);
+                    this.mPropertyParams[prop.propertyName][paramName] = param;
                 }
             }
         }
         return aComp;
     },
 
     hasProperty: function cA_hasProperty(aName) {
         return (this.getProperty(aName.toUpperCase()) != null);
     },
 
     getProperty: function cA_getProperty(aName) {
-        var name = aName.toUpperCase();
+        let name = aName.toUpperCase();
         if (name in this.promotedProps) {
             return this[this.promotedProps[name]];
         } else {
             return this.mProperties.getProperty(name);
         }
     },
 
     setProperty: function cA_setProperty(aName, aValue) {
         this.ensureMutable();
-        var name = aName.toUpperCase();
+        let name = aName.toUpperCase();
         if (name in this.promotedProps) {
             this[this.promotedProps[name]] = aValue;
         } else {
             this.mProperties.setProperty(name, aValue);
         }
         return aValue;
     },
 
     deleteProperty: function cA_deleteProperty(aName) {
         this.ensureMutable();
-        var name = aName.toUpperCase();
+        let name = aName.toUpperCase();
         if (name in this.promotedProps) {
             this[this.promotedProps[name]] = null;
         } else {
             this.mProperties.deleteProperty(name);
         }
+    },
+
+    get propertyEnumerator cA_get_propertyEnumerator() {
+        return this.mProperties.enumerator;
+    },
+
+    toString: function cA_toString() {
+        if (this.related == Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE &&
+            this.mAbsoluteDate) {
+            // this is an absolute alarm
+            let formatter = cal.getDateFormatter();
+            return formatter.formatDateTime(this.mAbsoluteDate);
+        } else if (this.related != Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE &&
+                   this.mOffset) {
+            function getItemBundleStringName(aPrefix) {
+                if (!this.mItem || isEvent(this.mItem)) {
+                    return aPrefix + "Event";
+                } else if (isToDo(this.mItem)) {
+                    return aPrefix + "Task";
+                }
+            }
+
+            // Relative alarm length
+            let alarmlen = Math.abs(this.mOffset.inSeconds / 60);
+            if (alarmlen == 0) {
+                // No need to get the other information if the alarm is at the start
+                // of the event/task.
+                if (this.related == Components.interfaces.calIAlarm.ALARM_RELATED_START) {
+                    return calGetString("calendar-alarms",
+                                        getItemBundleStringName("reminderTitleAtStart"));
+                } else if (this.related == Components.interfaces.calIAlarm.ALARM_RELATED_END) {
+                    return calGetString("calendar-alarms",
+                                        getItemBundleStringName("reminderTitleAtEnd"));
+                }
+            }
+
+            let unit;
+            if (alarmlen % 1440 == 0) {
+                // Alarm is in days
+                unit = "reminderCustomUnitDays";
+                alarmlen /= 1440;
+            } else if (alarmlen % 60 == 0) {
+                unit = "reminderCustomUnitHours";
+                alarmlen /= 60;
+            } else {
+                unit = "reminderCustomUnitMinutes";
+            }
+            let localeUnitString = calGetString("calendar-alarms", unit);
+            let unitString = PluralForm.get(alarmlen, localeUnitString)
+                                       .replace("#1", alarmlen);
+
+            // Origin
+            let originString;
+            switch (this.related) {
+                case Components.interfaces.calIAlarm.ALARM_RELATED_START:
+                    originString = calGetString("calendar-alarms",
+                                                getItemBundleStringName("reminderCustomOriginBegin"));
+                    break;
+                case Components.interfaces.calIAlarm.ALARM_RELATED_END:
+                    originString = calGetString("calendar-alarms",
+                                                getItemBundleStringName("reminderCustomOriginEnd"));
+
+                    break;
+            }
+
+            let relationString;
+            if (this.offset.isNegative) {
+                relationString = calGetString("calendar-alarms",
+                                              "reminderCustomRelationBefore");
+            } else {
+                relationString = calGetString("calendar-alarms",
+                                              "reminderCustomRelationAfter");
+            }
+
+            return calGetString("calendar-alarms",
+                                "reminderCustomTitle",
+                                [unitString,
+                                 relationString,
+                                 originString]);
+        }
     }
 };
new file mode 100644
--- /dev/null
+++ b/calendar/locales/en-US/chrome/calendar/calendar-alarms.properties
@@ -0,0 +1,58 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Sun Microsystems code.
+#
+# The Initial Developer of the Original Code is
+#   Philipp Kewisch <mozilla@kewis.ch>
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+# LOCALIZATION NOTE (reminderCustomTitle):
+# %1$S = reminderCustomUnit, %2$S = reminderCustomRelation %3$S = reminderCustomOrigin
+# Example: "3 minutes" "before" "the task starts"
+reminderCustomTitle=%1$S %2$S %3$S
+reminderTitleAtStartEvent=The moment the event starts
+reminderTitleAtStartTask=The moment the task starts
+reminderTitleAtEndEvent=The moment the event ends
+reminderTitleAtEndTask=The moment the task ends
+
+reminderCustomUnitMinutes=#1 minute;#1 minutes
+reminderCustomUnitHours=#1 hour;#1 hours 
+reminderCustomUnitDays=#1 day;#1 days
+
+reminderCustomRelationBefore=before
+reminderCustomRelationAfter=after
+reminderCustomOriginBeginEvent=the event starts
+reminderCustomOriginEndEvent=the event ends
+reminderCustomOriginBeginTask=the task starts
+reminderCustomOriginEndTask=the task ends
+
+reminderErrorMaxCountReachedEventN=The selected calendar has a limitation of #1 reminder per event.;The selected calendar has a limitation of #1 reminders per event.
+reminderErrorMaxCountReachedTaskN=The selected calendar has a limitation of #1 reminder per task.;The selected calendar has a limitation of #1 reminders per task.
--- a/calendar/locales/jar.mn
+++ b/calendar/locales/jar.mn
@@ -1,28 +1,29 @@
 #filter substitution
 
 calendar-@AB_CD@.jar:
 % locale calendar @AB_CD@ %locale/@AB_CD@/calendar/
-    locale/@AB_CD@/calendar/calendar.dtd         (%chrome/calendar/calendar.dtd)
-    locale/@AB_CD@/calendar/calendarCreation.dtd (%chrome/calendar/calendarCreation.dtd)
-    locale/@AB_CD@/calendar/calendar.properties  (%chrome/calendar/calendar.properties)
-    locale/@AB_CD@/calendar/calendar-occurrence-prompt.dtd  (%chrome/calendar/calendar-occurrence-prompt.dtd)
+    locale/@AB_CD@/calendar/calendar.dtd                   (%chrome/calendar/calendar.dtd)
+    locale/@AB_CD@/calendar/calendarCreation.dtd           (%chrome/calendar/calendarCreation.dtd)
+    locale/@AB_CD@/calendar/calendar.properties            (%chrome/calendar/calendar.properties)
+    locale/@AB_CD@/calendar/calendar-alarms.properties     (%chrome/calendar/calendar-alarms.properties)
+    locale/@AB_CD@/calendar/calendar-occurrence-prompt.dtd (%chrome/calendar/calendar-occurrence-prompt.dtd)
     locale/@AB_CD@/calendar/calendar-occurrence-prompt.properties  (%chrome/calendar/calendar-occurrence-prompt.properties)
-    locale/@AB_CD@/calendar/categories.properties  (%chrome/calendar/categories.properties)
-    locale/@AB_CD@/calendar/wcap.properties      (%chrome/calendar/providers/wcap/wcap.properties)
-    locale/@AB_CD@/calendar/dateFormat.properties  (%chrome/calendar/dateFormat.properties)
-    locale/@AB_CD@/calendar/global.dtd           (%chrome/calendar/global.dtd)
-    locale/@AB_CD@/calendar/menuOverlay.dtd      (%chrome/calendar/menuOverlay.dtd)
-    locale/@AB_CD@/calendar/migration.dtd        (%chrome/calendar/migration.dtd)
-    locale/@AB_CD@/calendar/migration.properties (%chrome/calendar/migration.properties)
-    locale/@AB_CD@/calendar/preferences/advanced.dtd  (%chrome/calendar/preferences/advanced.dtd)
-    locale/@AB_CD@/calendar/preferences/alarms.dtd  (%chrome/calendar/preferences/alarms.dtd)
-    locale/@AB_CD@/calendar/preferences/categories.dtd  (%chrome/calendar/preferences/categories.dtd)
-*   locale/@AB_CD@/calendar/preferences/connection.dtd  (%chrome/calendar/preferences/connection.dtd)
-    locale/@AB_CD@/calendar/preferences/general.dtd  (%chrome/calendar/preferences/general.dtd)
-    locale/@AB_CD@/calendar/preferences/preferences.dtd  (%chrome/calendar/preferences/preferences.dtd)
-    locale/@AB_CD@/calendar/preferences/timezones.dtd  (%chrome/calendar/preferences/timezones.dtd)
-    locale/@AB_CD@/calendar/preferences/views.dtd  (%chrome/calendar/preferences/views.dtd)
-    locale/@AB_CD@/calendar/calendar-event-dialog.dtd (%chrome/calendar/calendar-event-dialog.dtd)
-    locale/@AB_CD@/calendar/calendar-event-dialog.properties (%chrome/calendar/calendar-event-dialog.properties)
-    locale/@AB_CD@/calendar/calendar-invitations-dialog.dtd (%chrome/calendar/calendar-invitations-dialog.dtd)
-    locale/@AB_CD@/calendar/calendar-subscriptions-dialog.dtd (%chrome/calendar/calendar-subscriptions-dialog.dtd)
+    locale/@AB_CD@/calendar/categories.properties          (%chrome/calendar/categories.properties)
+    locale/@AB_CD@/calendar/wcap.properties                (%chrome/calendar/providers/wcap/wcap.properties)
+    locale/@AB_CD@/calendar/dateFormat.properties          (%chrome/calendar/dateFormat.properties)
+    locale/@AB_CD@/calendar/global.dtd                     (%chrome/calendar/global.dtd)
+    locale/@AB_CD@/calendar/menuOverlay.dtd                (%chrome/calendar/menuOverlay.dtd)
+    locale/@AB_CD@/calendar/migration.dtd                  (%chrome/calendar/migration.dtd)
+    locale/@AB_CD@/calendar/migration.properties           (%chrome/calendar/migration.properties)
+    locale/@AB_CD@/calendar/preferences/advanced.dtd       (%chrome/calendar/preferences/advanced.dtd)
+    locale/@AB_CD@/calendar/preferences/alarms.dtd         (%chrome/calendar/preferences/alarms.dtd)
+    locale/@AB_CD@/calendar/preferences/categories.dtd     (%chrome/calendar/preferences/categories.dtd)
+*   locale/@AB_CD@/calendar/preferences/connection.dtd     (%chrome/calendar/preferences/connection.dtd)
+    locale/@AB_CD@/calendar/preferences/general.dtd        (%chrome/calendar/preferences/general.dtd)
+    locale/@AB_CD@/calendar/preferences/preferences.dtd    (%chrome/calendar/preferences/preferences.dtd)
+    locale/@AB_CD@/calendar/preferences/timezones.dtd      (%chrome/calendar/preferences/timezones.dtd)
+    locale/@AB_CD@/calendar/preferences/views.dtd          (%chrome/calendar/preferences/views.dtd)
+    locale/@AB_CD@/calendar/calendar-event-dialog.dtd      (%chrome/calendar/calendar-event-dialog.dtd)
+    locale/@AB_CD@/calendar/calendar-event-dialog.properties  (%chrome/calendar/calendar-event-dialog.properties)
+    locale/@AB_CD@/calendar/calendar-invitations-dialog.dtd  (%chrome/calendar/calendar-invitations-dialog.dtd)
+    locale/@AB_CD@/calendar/calendar-subscriptions-dialog.dtd  (%chrome/calendar/calendar-subscriptions-dialog.dtd)