Fix bug 471973 - Make use of alarm interface in backend code. r=dbo
authorPhilipp Kewisch <mozilla@kewis.ch>
Fri, 16 Jan 2009 11:17:20 +0100
changeset 1658 a41dd9c8e51c03504ae72d24cf8e9d1767aa0e22
parent 1657 5d42e53ff9b12d9bccf8567d95b3e848d5ce6d6b
child 1659 926804509a09fa3cfeac157bae7163d1b8151242
push idunknown
push userunknown
push dateunknown
reviewersdbo
bugs471973
Fix bug 471973 - Make use of alarm interface in backend code. r=dbo
calendar/base/content/calendar-dialog-utils.js
calendar/base/content/calendar-dnd-listener.js
calendar/base/content/calendar-item-editing.js
calendar/base/content/calendar-summary-dialog.js
calendar/base/content/calendar-task-editing.js
calendar/base/content/calendar-task-tree.xml
calendar/base/content/calendar-unifinder.js
calendar/base/content/calendar-view-core.xml
calendar/base/content/calendar-views.js
calendar/base/content/dialogs/calendar-invitations-dialog.js
calendar/base/modules/Makefile.in
calendar/base/modules/calAlarmUtils.jsm
calendar/base/modules/calItipUtils.jsm
calendar/base/public/calIItemBase.idl
calendar/base/src/calAlarmService.js
calendar/base/src/calItemBase.js
calendar/base/src/calItipItem.js
calendar/base/src/calUtils.js
calendar/import-export/calOutlookCSVImportExport.js
calendar/providers/gdata/components/calGoogleUtils.js
calendar/providers/storage/calStorageCalendar.js
calendar/providers/wcap/calWcapCalendarItems.js
calendar/test/unit/head_consts.js
calendar/test/unit/test_alarm.js
--- a/calendar/base/content/calendar-dialog-utils.js
+++ b/calendar/base/content/calendar-dialog-utils.js
@@ -611,45 +611,53 @@ var gLastAlarmSelection = 0;
 
 /**
  * Load an item's reminders into the dialog
  *
  * @param item      The item to load.
  */
 function loadReminder(item) {
     // select 'no reminder' by default
-    var reminderPopup = document.getElementById("item-alarm");
+    let reminderPopup = document.getElementById("item-alarm");
     reminderPopup.selectedIndex = 0;
     gLastAlarmSelection = 0;
-    if (!item.alarmOffset) {
+
+    // TODO ALARMSUPPORT right now, just consider the first relative alarm. This
+    // should change as soon as the UI supports multiple alarms.
+    let alarms = item.getAlarms({})
+                     .filter(function(x) x.related != x.ALARM_RELATED_ABSOLUTE);
+    let alarm = alarms[0];
+    if (!alarm) {
         return;
     }
+    // END TODO ALARMSUPPORT
+        
 
     // try to match the reminder setting with the available popup items
-    var origin = "1";
-    if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_END) {
+    let origin = "1";
+    if (alarm.related == Components.interfaces.calIAlarm.ALARM_RELATED_END) {
         origin = "-1";
     }
-    var duration = item.alarmOffset.clone();
-    var relation = "END";
+    let duration = alarm.offset.clone();
+    let relation = "END";
     if (duration.isNegative) {
         duration.isNegative = false;
         duration.normalize();
         relation = "START";
     }
-    var matchingItem = null;
-    var menuItems = reminderPopup.getElementsByTagName("menuitem");
-    var numItems = menuItems.length;
-    for (var i=0; i<numItems; i++) {
-        var menuitem = menuItems[i];
+    let matchingItem = null;
+    let menuItems = reminderPopup.getElementsByTagName("menuitem");
+    let numItems = menuItems.length;
+    for (let i = 0; i < numItems; i++) {
+        let menuitem = menuItems[i];
         if (menuitem.hasAttribute("length")) {
             if (menuitem.getAttribute("origin") == origin &&
                 menuitem.getAttribute("relation") == relation) {
-                var unit = menuitem.getAttribute("unit");
-                var length = menuitem.getAttribute("length");
+                let unit = menuitem.getAttribute("unit");
+                let length = menuitem.getAttribute("length");
                 if (unit == "days") {
                     length = length * 60 * 60 * 24;
                 } else if (unit == "hours") {
                     length = length * 60 * 60;
                 } else if (unit == "minutes") {
                     length = length * 60;
                 } else {
                     continue;
@@ -671,31 +679,31 @@ function loadReminder(item) {
                 break;
             }
         }
     } else {
         reminderPopup.value = 'custom';
         var customReminder =
             document.getElementById("reminder-custom-menuitem");
         var reminder = {};
-        if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
+        if (alarm.related == Components.interfaces.calIAlarm.ALARM_RELATED_START) {
             reminder.origin = "1";
         } else {
             reminder.origin = "-1";
         }
-        var offset = item.alarmOffset.clone();
-        var relation = "END";
+        let offset = alarm.offset.clone();
+        relation = "END";
         if (offset.isNegative) {
             offset.isNegative = false;
             offset.normalize();
             relation = "START";
         }
         reminder.relation = relation;
         if (offset.minutes) {
-            var minutes = offset.minutes +
+            let minutes = offset.minutes +
                           offset.hours * 60 +
                           offset.days * 24 * 60 +
                           offset.weeks * 60 * 24 * 7;
             reminder.unit = 'minutes';
             reminder.length = minutes;
         } else if (offset.hours) {
             var hours = offset.hours + offset.days * 24 + offset.weeks * 24 * 7;
             reminder.unit = 'hours';
@@ -713,21 +721,20 @@ function loadReminder(item) {
 }
 
 /**
  * Save the selected reminder into the passed item.
  *
  * @param item      The item save the reminder into.
  */
 function saveReminder(item) {
+    item.clearAlarms();
     var reminderPopup = document.getElementById("item-alarm");
     if (reminderPopup.value == 'none') {
-        item.alarmOffset = null;
         item.alarmLastAck = null;
-        item.alarmRelated = null;
     } else {
         var menuitem = reminderPopup.selectedItem;
 
         // custom reminder entries carry their own reminder object
         // with them, pre-defined entries specify the necessary information
         // as attributes attached to the menuitem elements.
         var reminder = menuitem.reminder;
         if (!reminder) {
@@ -741,23 +748,26 @@ function saveReminder(item) {
         var duration = Components.classes["@mozilla.org/calendar/duration;1"]
                        .createInstance(Components.interfaces.calIDuration);
 
         duration[reminder.unit] = Number(reminder.length);
         if (reminder.relation != "END") {
             duration.isNegative = true;
         }
         duration.normalize();
-        item.alarmOffset = duration;
+        let alarm = cal.createAlarm();
 
         if (Number(reminder.origin) >= 0) {
-            item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
+            alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
         } else {
-            item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_END;
+            alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_END;
         }
+
+        alarm.offset = duration;
+        item.addAlarm(alarm);
     }
 }
 
 /**
  * Common update functions for both event dialogs. Called when a reminder has
  * been selected from the menulist.
  */
 function commonUpdateReminder() {
--- a/calendar/base/content/calendar-dnd-listener.js
+++ b/calendar/base/content/calendar-dnd-listener.js
@@ -32,33 +32,35 @@
  * 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://calendar/modules/calAlarmUtils.jsm");
+
 var itemConversion = {
 
     /**
      * Converts an email message to a calendar item.
      *
      * XXX Currently, only the title is taken from the passed message. Aside
      * from that, the currently visible message in the preview pane is used.
      *
      * @param aItem     The target calIItemBase.
      * @param aMessage  The message  to convert from
      */
     calendarItemFromMessage: function iC_calendarItemFromMessage(aItem, aMessage) {
         aItem.calendar = getSelectedCalendar();
         aItem.title = aMessage.mime2DecodedSubject;
 
         setDefaultStartEndHour(aItem);
-        setDefaultAlarmValues(aItem);
+        cal.alarms.setDefaultValues(aItem);
 
         // XXX It would be great if nsPlainTextParser could take care of this.
         function htmlToPlainText(html) {
           var texts = html.split(/(<\/?[^>]+>)/);
           var text = texts.map(function hTPT_map(string) {
               if (string.length > 0 && string[0] == '<') {
                   var regExpRes = string.match(/^<img.*?alt\s*=\s*['"](.*)["']/i)
                   if (regExpRes) {
@@ -151,20 +153,19 @@ var itemConversion = {
 
         // Dates and alarms
         if (!aEvent.startDate.isDate && !aEvent.endDate.isDate) {
             // Dates
             item.entryDate = aEvent.startDate.clone();
             item.dueDate = aEvent.endDate.clone();
 
             // Alarms
-            item.alarmOffset = (aEvent.alarmOffset ?
-                                aEvent.alarmOffset.clone() :
-                                null);
-            item.alarmRelated = aEvent.alarmRelated;
+            for each (let alarm in aEvent.getAlarms({})) {
+                item.addAlarm(alarm.clone());
+            }
             item.alarmLastAck = (aEvent.alarmLastAck ?
                                  aEvent.alarmLastAck.clone() :
                                  null);
         }
         return item;
     },
 
     /**
@@ -190,20 +191,19 @@ var itemConversion = {
         if (!item.endDate) {
             // Make the event be the default event length if no due date was
             // specified.
             item.endDate = item.startDate.clone();
             item.endDate.minute += getPrefSafe("calendar.event.defaultlength", 60);
         }
 
         // Alarms
-        item.alarmOffset = (aTask.alarmOffset ?
-                            aTask.alarmOffset.clone() :
-                            null);
-        item.alarmRelated = aTask.alarmRelated;
+        for each (let alarm in aEvent.getAlarms({})) {
+            item.addAlarm(alarm.clone());
+        }
         item.alarmLastAck = (aTask.alarmLastAck ?
                              aTask.alarmLastAck.clone() :
                              null);
         return item;
     }
 };
 
 /**
--- a/calendar/base/content/calendar-item-editing.js
+++ b/calendar/base/content/calendar-item-editing.js
@@ -32,16 +32,18 @@
  * 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://calendar/modules/calAlarmUtils.jsm");
+
 /**
  * Creates an event with the calendar event dialog.
  *
  * @param calendar      (optional) The calendar to create the event in
  * @param startDate     (optional) The event's start date.
  * @param endDate       (optional) The event's end date.
  * @param summary       (optional) The event's title.
  * @param event         (optional) A template event to show in the dialog
@@ -129,17 +131,17 @@ function createEventWithDialog(calendar,
         }
 
         event.calendar = calendar || getSelectedCalendar();
 
         if (summary) {
             event.title = summary;
         }
 
-        setDefaultAlarmValues(event);
+        cal.alarms.setDefaultValues(event);
     }
     openEventDialog(event, calendar, "new", onNewEvent, null);
 }
 
 /**
  * Creates a task with the calendar event dialog.
  *
  * @param calendar      (optional) The calendar to create the task in
@@ -178,17 +180,17 @@ function createTodoWithDialog(calendar, 
         todo.calendar = calendar || getSelectedCalendar();
 
         if (summary)
             todo.title = summary;
 
         if (dueDate)
             todo.dueDate = dueDate;
 
-        setDefaultAlarmValues(todo);
+        cal.alarms.setDefaultValues(todo);
     }
 
     openEventDialog(todo, calendar, "new", onNewItem, null);
 }
 
 
 
 /**
--- a/calendar/base/content/calendar-summary-dialog.js
+++ b/calendar/base/content/calendar-summary-dialog.js
@@ -33,16 +33,17 @@
  * 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://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calItipUtils.jsm");
+Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
 
 /**
  * Sets up the summary dialog, setting all needed fields on the dialog from the
  * item received in the window arguments.
  */
 function onLoad() {
     var args = window.arguments[0];
     var item = args.calendarEvent;
@@ -76,18 +77,19 @@ function onLoad() {
         }
     }
 
     window.readOnly = calendar.readOnly;
     if (!window.readOnly && calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
         var attendee = calendar.getInvitedAttendee(item);
         if (attendee) {
             // if this is an unresponded invitation, preset our default alarm values:
-            if (!item.alarmOffset && (attendee.participationStatus == "NEEDS-ACTION")) {
-                cal.setDefaultAlarmValues(item);
+            if (!item.getAlarms({}).length &&
+                (attendee.participationStatus == "NEEDS-ACTION")) {
+                cal.alarms.setDefaultValues(item);
             }
 
             window.attendee = attendee.clone();
             // Since we don't have API to update an attendee in place, remove
             // and add again. Also, this is needed if the attendee doesn't exist
             // (i.e REPLY on a mailing list)
             item.removeAttendee(attendee);
             item.addAttendee(window.attendee);
--- a/calendar/base/content/calendar-task-editing.js
+++ b/calendar/base/content/calendar-task-editing.js
@@ -30,16 +30,18 @@
  * 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://calendar/modules/calAlarmUtils.jsm");
+
 /**
  * Used by the "quick add" feature for tasks, for example in the task view or
  * the uniinder-todo.
  *
  * NOTE: many of the following methods are called without taskEdit being the
  * |this| object.
  */
 
@@ -156,17 +158,17 @@ var taskEdit = {
     onKeyPress: function tE_onKeyPress(aEvent) {
         if (aEvent.keyCode == Components.interfaces.nsIDOMKeyEvent.DOM_VK_RETURN) {
             var edit = aEvent.target;
             if (edit.value && edit.value.length > 0) {
                 var item = createTodo();
                 item.calendar = getSelectedCalendar();
                 item.title = edit.value;
                 edit.value = "";
-                setDefaultAlarmValues(item);
+                cal.alarms.setDefaultValues(item);
                 doTransaction('add', item, item.calendar, null, null);
             }
         }
     },
 
     /**
      * Window load function to set up all quick-add textboxes. The texbox must
      * have the class "task-edit-field".
--- a/calendar/base/content/calendar-task-tree.xml
+++ b/calendar/base/content/calendar-task-tree.xml
@@ -487,17 +487,17 @@
               aProps.AppendElement(getAtomFromService(calendarIdAtom));
 
               // Add item status atom
               if (item.status) {
                   aProps.AppendElement(getAtomFromService("status-" + item.status.toLowerCase()));
               }
 
               // Alarm status atom
-              if (item.alarmOffset) {
+              if (item.getAlarms({}).length) {
                   aProps.AppendElement(getAtomFromService("alarm"));
               }
 
               // Task categories
               var categories = item.getCategories({});
               categories.map(formatStringForCSSRule)
                         .map(getAtomFromService)
                         .forEach(aProps.AppendElement, aProps);
--- a/calendar/base/content/calendar-unifinder.js
+++ b/calendar/base/content/calendar-unifinder.js
@@ -699,17 +699,17 @@ var unifinderTreeView = {
         aProps.AppendElement(getAtomFromService(calendarAtom));
 
         // Add item status atom
         if (item.status) {
             aProps.AppendElement(getAtomFromService("status-" + item.status.toLowerCase()));
         }
 
         // Alarm status atom
-        if (item.alarmOffset) {
+        if (item.getAlarms({}).length) {
             aProps.AppendElement(getAtomFromService("alarm"));
         }
 
         // Task categories
         item.getCategories({}).map(formatStringForCSSRule)
                               .map(getAtomFromService)
                               .forEach(aProps.AppendElement, aProps);
     },
--- a/calendar/base/content/calendar-view-core.xml
+++ b/calendar/base/content/calendar-view-core.xml
@@ -186,17 +186,17 @@
           var item = this.mOccurrence;          
           this.setAttribute("item-calendar", item.calendar.uri.spec);
           var categoriesArray = item.getCategories({});
           if (categoriesArray.length > 0) {
             var cssClassesArray = categoriesArray.map(formatStringForCSSRule);
             this.setAttribute("categories", cssClassesArray.join(" "));
           }
 
-          if (item.alarmOffset && getPrefSafe("calendar.alarms.indicator.show", true)) {
+          if (item.getAlarms({}).length && getPrefSafe("calendar.alarms.indicator.show", true)) {
               var alarmImage = document.getAnonymousElementByAttribute(this, "anonid", "alarm-image");
               if (item.calendar.getProperty("suppressAlarms") &&
                   item.calendar.getProperty("capabilities.alarms.popup.supported") !== false) {
                   // Only show the suppressed icon if alarms are suppressed and
                   // popup alarms are supported. This keeps us from making the
                   // alarm look disabled when the server supports only email
                   // alarms (e.g. currently WCAP).
                   alarmImage.setAttribute("suppressed", "true");
@@ -218,17 +218,17 @@
               this.setAttribute("progress", getProgressAtom(item));
           }
 
           if (this.calendarView &&
               item.hashId in this.calendarView.mFlashingEvents) {
               this.setAttribute("flashing", "true");
           }
 
-          if (item.alarmOffset) {
+          if (item.getAlarms({}).length) {
               this.setAttribute("alarm", "true")
           }
 
           // priority
           if (item.priority > 0 && item.priority < 5) {
               this.setAttribute("priority", "high");
           } else if (item.priority > 5 && item.priority < 10) {
               this.setAttribute("priority", "low");
--- a/calendar/base/content/calendar-views.js
+++ b/calendar/base/content/calendar-views.js
@@ -37,16 +37,18 @@
  * 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://calendar/modules/calAlarmUtils.jsm");
+
 /**
  * Controller for the views
  * @see calIcalendarViewController
  */
 var calendarViewController = {
     QueryInterface: function(aIID) {
         if (!aIID.equals(Components.interfaces.calICalendarViewController) &&
             !aIID.equals(Components.interfaces.nsISupports)) {
@@ -67,17 +69,17 @@ var calendarViewController = {
         if (aStartTime && aEndTime && !aStartTime.isDate && !aEndTime.isDate) {
             var event = createEvent();
             event.startDate = aStartTime;
             event.endDate = aEndTime;
             var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                 .getService(Components.interfaces.nsIStringBundleService);
             var props = sbs.createBundle("chrome://calendar/locale/calendar.properties");
             event.title = props.GetStringFromName("newEvent");
-            setDefaultAlarmValues(event);
+            cal.alarms.setDefaultValues(event);
             doTransaction('add', event, aCalendar, null, null);
         } else {
             createEventWithDialog(aCalendar, aStartTime, null, null, null, aForceAllday);
         }
     },
 
     pendingJobs: [],
 
--- a/calendar/base/content/dialogs/calendar-invitations-dialog.js
+++ b/calendar/base/content/dialogs/calendar-invitations-dialog.js
@@ -15,32 +15,34 @@
  *
  * The Initial Developer of the Original Code is Sun Microsystems.
  * Portions created by the Initial Developer are Copyright (C) 2006
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Thomas Benisch <thomas.benisch@sun.com>
  *   Daniel Boelzle <daniel.boelzle@sun.com>
+ *   Philipp Kewisch <mozilla@kewis.ch>
  *
  * 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 ***** */
 
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
 
 /**
  * Sets up the invitations dialog from the window arguments, retrieves the
  * invitations from the invitations manager.
  */
 function onLoad() {
     var operationListener = {
         onOperationComplete: function oL_onOperationComplete(aCalendar,
@@ -132,18 +134,20 @@ function fillJobQueue(queue) {
         var newStatus = richListItem.participationStatus;
         var oldStatus = richListItem.initialParticipationStatus;
         if (newStatus != oldStatus) {
             var actionString = "modify";
             var oldCalendarItem = richListItem.calendarItem;
             var newCalendarItem = oldCalendarItem.clone();
 
             // set default alarm on unresponded items that have not been declined:
-            if (!newCalendarItem.alarmOffset && (oldStatus == "NEEDS-ACTION") && (newStatus != "DECLINED")) {
-                cal.setDefaultAlarmValues(newCalendarItem);
+            if (!newCalendarItem.getAlarms({}).length &&
+                (oldStatus == "NEEDS-ACTION") &&
+                (newStatus != "DECLINED")) {
+                cal.alarms.setDefaultValues(newCalendarItem);
             }
 
             richListItem.setCalendarItemParticipationStatus(newCalendarItem,
                 newStatus);
             var job = {
                 action: actionString,
                 oldItem: oldCalendarItem,
                 newItem: newCalendarItem
--- a/calendar/base/modules/Makefile.in
+++ b/calendar/base/modules/Makefile.in
@@ -15,16 +15,17 @@
 #
 # The Initial Developer of the Original Code is
 #   Sun Microsystems, Inc.
 # Portions created by the Initial Developer are Copyright (C) 2008
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Daniel Boelzle <daniel.boelzle@sun.com>
+#   Philipp Kewisch <mozilla@kewis.ch>
 #
 # 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
@@ -40,16 +41,17 @@ topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = calbase
 
 EXTRA_JS_MODULES = \
+    calAlarmUtils.jsm \
     calUtils.jsm \
     calIteratorUtils.jsm \
     calItipUtils.jsm \
     calProviderUtils.jsm \
     calAuthUtils.jsm \
     $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/calendar/base/modules/calAlarmUtils.jsm
@@ -0,0 +1,122 @@
+/* ***** 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 ***** */
+
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
+EXPORTED_SYMBOLS = ["cal"]; // even though it's defined in calUtils.jsm, import needs this
+cal.alarms = {
+    /**
+     * Read default alarm settings from user preferences and apply them to the
+     * event/todo passed in.
+     *
+     * @param aItem     The item to apply the default alarm values to.
+     */
+    setDefaultValues: function cal_alarm_setDefaultValues(aItem) {
+        let type = cal.isEvent(aItem) ? "event" : "todo";
+        if (cal.getPrefSafe("calendar.alarms.onfor" + type + "s", 0) == 1) {
+            let alarmOffset = cal.createDuration();
+            let alarm = cal.createAlarm();
+            let units = cal.getPrefSafe("calendar.alarms." + type + "alarmunit", "minute");
+            alarmOffset[units] = cal.getPrefSafe("calendar.alarms." + type + "alarmlen", 0);
+            alarmOffset.normalize();
+            alarmOffset.isNegative = true;
+            if (type == "todo" && !aItem.entryDate) {
+                // You can't have an alarm if the entryDate doesn't exist.
+                aItem.entryDate = cal.now();
+            }
+            alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
+            alarm.offset = alarmOffset;
+            aItem.addAlarm(alarm);
+        }
+    },
+
+    /**
+     * Calculate the alarm date for a calIAlarm.
+     *
+     * @param aItem     The item used to calculate the alarm date.
+     * @param aAlarm    The alarm to calculate the date for.
+     * @return          The alarm offset.
+     */
+    calculateAlarmDate: function cal_alarm_calculateAlarmDate(aItem, aAlarm) {
+        if (aAlarm.related == aAlarm.ALARM_RELATED_ABSOLUTE) {
+            return aAlarm.alarmDate;
+        } else {
+            let returnDate;
+            if (aAlarm.related == aAlarm.ALARM_RELATED_START) {
+                let startDate = aItem[cal.calGetStartDateProp(aItem)];
+                returnDate = startDate && startDate.clone();
+            } else if (aAlarm.related = aAlarm.ALARM_RELATED_END) {
+                let endDate = aItem[cal.calGetEndDateProp(aItem)];
+                returnDate = endDate && endDate.clone();
+            }
+
+            if (returnDate && aAlarm.offset) {
+                returnDate.addDuration(aAlarm.offset);
+                return returnDate;
+            }
+        }
+        return null;
+    },
+
+    /**
+     * Calculate the alarm offset for a calIAlarm. The resulting offset is
+     * related to either start or end of the event, depending on the aRelated
+     * parameter.
+     *
+     * @param aItem     The item to calculate the offset for.
+     * @param aAlarm    The alarm to calculate the offset for.
+     * @param aRelated  (optional) A relation constant from calIAlarm. If not
+     *                    passed, ALARM_RELATED_START will be assumed.
+     * @return          The alarm offset.
+     */
+    calculateAlarmOffset: function cal_alarms_calculateAlarmOffset(aItem, aAlarm, aRelated) {
+        if (aAlarm.related == aAlarm.ALARM_RELATED_ABSOLUTE) {
+            let returnDate;
+            if (aRelated === undefined || aRelated == aAlarm.ALARM_RELATED_START) {
+                returnDate = aItem[cal.calGetStartDateProp(aItem)];
+            } else if (aRelated == aAlarm.ALARM_RELATED_END) {
+                returnDate = aItem[cal.calGetEndDateProp(aItem)];
+            }
+            if (returnDate && aAlarm.alarmDate) {
+                return returnDate.subtractDate(aAlarm.alarmDate);
+            }
+                
+            return offset;
+        } else {
+            return aAlarm.offset;
+        }
+    }
+};
--- a/calendar/base/modules/calItipUtils.jsm
+++ b/calendar/base/modules/calItipUtils.jsm
@@ -34,16 +34,17 @@
  * 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://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
 Components.utils.import("resource://calendar/modules/calIteratorUtils.jsm");
 
 /*
  * Scheduling and iTIP helper code;
  * don't use deliberately, because it'll be moved into interfaces/components.
  *
  * May replace the current calItipProcessor.js code soon.
  */
@@ -429,17 +430,17 @@ function setReceivedInfo(item, itipItemI
 
 /**
  * Strips user specific data, e.g. categories and alarm settings and returns the stripped item.
  */
 function stripUserData(item_) {
     let item = item_.clone();
     let stamp = item.stampTime;
     let lastModified = item.lastModifiedTime;
-    item.alarmOffset = null;
+    item.clearAlarms();
     item.alarmLastAck = null;
     item.setCategories(0, []);
     item.deleteProperty("RECEIVED-SEQUENCE");
     item.deleteProperty("RECEIVED-DTSTAMP");
     let propEnum = item.propertyEnumerator;
     while (propEnum.hasMoreElements()) {
         let prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
         let pname = prop.name;
@@ -462,18 +463,20 @@ function stripUserData(item_) {
  *
  * @param item         the stored calendar item to update
  * @param itipItemItem the received item
  */
 function updateItem(item, itipItemItem) {
     function updateUserData(newItem, item) {
         // preserve user settings:
         newItem.generation = item.generation;
-        newItem.alarmOffset = item.alarmOffset;
-        newItem.alarmRelated = item.alarmRelated;
+        newItem.clearAlarms();
+        for each (let alarm in item.getAlarms({})) {
+            newItem.addAlarm(alarm);
+        }
         newItem.alarmLastAck = item.alarmLastAck;
         let cats = item.getCategories({});
         newItem.setCategories(cats.length, cats);
     }
 
     let newItem = item.clone();
     newItem.icalComponent = itipItemItem.icalComponent;
     setReceivedInfo(newItem, itipItemItem);
@@ -790,17 +793,17 @@ ItipFindItemListener.prototype = {
                     case "PUBLISH": {
                         let this_ = this;
                         let action = function(opListener, partStat) {
                             let newItem = itipItemItem.clone();
                             setReceivedInfo(newItem, itipItemItem);
                             newItem.parentItem.calendar = this_.mItipItem.targetCalendar;
                             if (partStat) {
                                 if (partStat != "DECLINED") {
-                                    cal.setDefaultAlarmValues(newItem);
+                                    cal.alarms.setDefaultValues(newItem);
                                 }
                                 let att = cal.getInvitedAttendee(newItem);
                                 if (!att) { // fall back to using configured organizer
                                     att = createOrganizer(newItem.calendar);
                                     if (att) {
                                         att.isOrganizer = false;
                                         newItem.addAttendee(att);
                                     }
--- a/calendar/base/public/calIItemBase.idl
+++ b/calendar/base/public/calIItemBase.idl
@@ -39,38 +39,33 @@
 
 #include "nsISupports.idl"
 
 interface nsISimpleEnumerator;
 interface nsIVariant;
 
 interface nsIPropertyBag;
 
+interface calIAlarm;
+interface calIAttachment;
+interface calIAttendee;
 interface calICalendar;
-
 interface calIDateTime;
-
 interface calIDuration;
-
+interface calIIcalComponent;
 interface calIRecurrenceInfo;
+interface calIRelation;
 
-interface calIAttendee;
-
-interface calIAttachment;
-
-interface calIIcalComponent;
-
-interface calIRelation;
 //
 // calIItemBase
 //
 // Base for Events, Todos, Journals, etc.
 //
 
-[scriptable, uuid(c2a21f8c-9de6-47d5-912b-d1c423ea5f2e)]
+[scriptable, uuid(9c988b8d-af45-4046-b05e-34417bba9058)]
 interface calIItemBase : nsISupports
 {
   // returns true if this thing is able to be modified;
   // if the item is not mutable, attempts to modify
   // any data will throw CAL_ERROR_ITEM_IS_IMMUTABLE
   readonly attribute boolean isMutable;
 
   // makes this item immutable
@@ -139,37 +134,41 @@ interface calIItemBase : nsISupports
   // item will not be reflected in the other.
   attribute calIIcalComponent icalComponent;
 
   //
   // alarms
   //
 
   /**
-   * The amount of time from the date (specified by alarmRelated) to offset
-   * the alarm's firing time by
+   * Get all alarms assigned to this item
+   *
+   * @param count       The number of alarms
+   * @param aAlarms     The array of calIAlarms
    */
-  attribute calIDuration alarmOffset;
+  void getAlarms(out PRUint32 count, [array, size_is(count), retval] out calIAlarm aAlarms);
 
-  /**
-   * One of the ALARM_RELATED constants below.  
+   /**
+   * Add an alarm to the item
+   *
+   * @param aAlarm      The calIAlarm to add
    */
-  attribute unsigned long alarmRelated;
+  void addAlarm(in calIAlarm aAlarm);
 
   /**
-   * Corresponds to an alarmOffset that should be based off of the startDate or
-   * entryDate (for events and tasks, respectively)
+   * Delete an alarm from the item
+   *
+   * @param aAlarm      The calIAlarm to delete
    */
-  const unsigned long ALARM_RELATED_START = 0;
+  void deleteAlarm(in calIAlarm aAlarm);
 
   /**
-   * Corresponds to an alarmOffset that should be based off of the endDate or
-   * dueDate (for events and tasks, respectively)
+   * Clear all alarms from the item
    */
-  const unsigned long ALARM_RELATED_END = 1;
+  void clearAlarms();
 
   // The last time this alarm was fired and acknowledged by the user; coerced to UTC.
   attribute calIDateTime alarmLastAck;
 
   //
   // recurrence
   //
   attribute calIRecurrenceInfo recurrenceInfo;
--- a/calendar/base/src/calAlarmService.js
+++ b/calendar/base/src/calAlarmService.js
@@ -370,39 +370,49 @@ calAlarmService.prototype = {
     unobserveCalendar: function cas_unobserveCalendar(calendar) {
         calendar.removeObserver(this.calendarObserver);
         this.disposeCalendarTimers([calendar]);
         this.notifyObservers("onRemoveAlarmsByCalendar", [calendar]);
     },
 
     getAlarmDate: function cas_getAlarmTime(aItem) {
         var alarmDate = null;
-        if (aItem.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
+        // TODO ALARMSUPPORT This will change as soon as we support multiple
+        // alarms.
+        let alarms = aItem.getAlarms({}).filter(function(x) x.related != x.ALARM_RELATED_ABSOLUTE);
+        let alarm = alarms[0]
+        if (!alarm) {
+            // No relative alarm available.
+            return null;
+        }
+        // END TODO ALARMSUPPORT
+
+        if (alarm.related == Components.interfaces.calIAlarm.ALARM_RELATED_START) {
             alarmDate = aItem.startDate || aItem.entryDate || aItem.dueDate;
         } else {
             alarmDate = aItem.endDate || aItem.dueDate || aItem.entryDate;
         }
 
-        if (!aItem.alarmOffset || !alarmDate) {
+        if (!alarmDate) {
             // If there is no alarm offset, or no date the alarm offset could be
             // relative to, then there is no valid alarm.
             return null;
         }
 
         alarmDate = alarmDate.clone();
 
         // Handle all day events.  This is kinda weird, because they don't have
         // a well defined startTime.  We just consider the start/end to be
         // midnight in the user's timezone.
         if (alarmDate.isDate) {
             alarmDate = alarmDate.getInTimezone(this.timezone);
             alarmDate.isDate = false;
         }
 
-        var offset = aItem.alarmOffset;
+        let offset = alarm.offset;
 
         alarmDate.addDuration(offset);
         alarmDate = alarmDate.getInTimezone(UTC());
 
         return alarmDate;
     },
 
     addAlarm: function cas_addAlarm(aItem) {
--- a/calendar/base/src/calItemBase.js
+++ b/calendar/base/src/calItemBase.js
@@ -49,23 +49,25 @@ Components.utils.import("resource://cale
 
 function calItemBase() {
     cal.ASSERT(false, "Inheriting objects call initItemBase()!");
 }
 
 calItemBase.prototype = {
     mPropertyParams: null,
     mIsProxy: false,
+    mAlarms: null,
 
     // initialize this class's members
     initItemBase: function cib_initItemBase() {
         this.wrappedJSObject = this;
         this.mProperties = new calPropertyBag();
         this.mPropertyParams = {};
         this.mProperties.setProperty("CREATED", jsDateToDateTime(new Date()));
+        this.mAlarms = [];
     },
 
     QueryInterface: function (aIID) {
         return doQueryInterface(this, calItemBase.prototype, aIID,
                                 [Components.interfaces.calIItemBase]);
     },
 
     mHashId: null,
@@ -166,19 +168,20 @@ calItemBase.prototype = {
 
         for each (let [propKey, propValue] in this.mProperties) {
             if (propValue instanceof Components.interfaces.calIDateTime &&
                 propValue.isMutable) {
                 propValue.makeImmutable();
             }
         }
 
-        if (this.mAlarmOffset) {
-            this.mAlarmOffset.makeImmutable();
+        for (let i = 0; i < this.mAlarms.length; i++) {
+            this.mAlarms[i].makeImmutable();
         }
+
         if (this.mAlarmLastAck) {
             this.mAlarmLastAck.makeImmutable();
         }
 
         this.ensureNotDirty();
         this.mImmutable = true;
     },
 
@@ -238,55 +241,39 @@ calItemBase.prototype = {
         // xxx todo: the below two need proper cloning,
         //           calIAttachment::item, calIRelation::item are wrong
         //           bug 466439
         m.mAttachments = this.getAttachments({});
         m.mRelations = this.getRelations({});
 
         m.mCategories = this.getCategories({});
 
-        // Clone any alarm info that exists, set it to null if it doesn't
-        let alarmOffset = this.alarmOffset;
-        if (alarmOffset) {
-            alarmOffset = alarmOffset.clone();
+        for each (let alarm in this.mAlarms) {
+            // Clone alarms into new item, assume the alarms from the old item
+            // are valid and don't need validation.
+            m.addAlarm(alarm.clone(), true);
         }
-        m.mAlarmOffset = alarmOffset;
 
         let alarmLastAck = this.alarmLastAck;
         if (alarmLastAck) {
             alarmLastAck = alarmLastAck.clone();
         }
         m.mAlarmLastAck = alarmLastAck;
 
-        m.alarmRelated = this.alarmRelated;
-
         m.mDirty = this.mDirty;
 
         return m;
     },
 
-    get alarmOffset() {
-        if (this.mIsProxy && (this.mAlarmOffset === undefined)) {
-            return this.parentItem.alarmOffset;
-        } else {
-            return this.mAlarmOffset;
-        }
-    },
-
-    set alarmOffset(aValue) {
-        this.modify();
-        return (this.mAlarmOffset = aValue);
-    },
-
     mAlarmLastAck: null,
-    get alarmLastAck cib_get_alarmLastAck() {
+    get alarmLastAck cIB_get_alarmLastAck() {
         return this.mAlarmLastAck;
     },
 
-    set alarmLastAck cib_set_alarmLastAck(aValue) {
+    set alarmLastAck cIB_set_alarmLastAck(aValue) {
         this.modify();
         if (aValue && !aValue.timezone.isUTC) {
             aValue = aValue.getInTimezone(UTC());
         }
         return (this.mAlarmLastAck = aValue);
     },
 
     get lastModifiedTime() {
@@ -693,45 +680,40 @@ calItemBase.prototype = {
                     rec = new calRecurrenceInfo();
                     rec.item = this;
                 }
                 rec.appendRecurrenceItem(ritem);
             }
         }
         this.mRecurrenceInfo = rec;
 
-        this.mAlarmOffset = null;
-        this.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
-        this.mAlarmLastAck = null;
-
-        let alarmComp = icalcomp.getFirstSubcomponent("VALARM");
-        if (alarmComp) {
-            let triggerProp = alarmComp.getFirstProperty("TRIGGER");
-            if (triggerProp) {
-                this.mAlarmOffset = cal.createDuration(triggerProp.valueAsIcalString);
-
-                let related = triggerProp.getParameter("RELATED");
-                if (related && related == "END") {
-                    this.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_END;
+        for (let alarmComp in cal.ical.subcomponentIterator(icalcomp, "VALARM")) {
+            let alarm = cal.createAlarm();
+            try {
+                alarm.icalComponent = alarmComp;
+                if (alarm.related != Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE) {
+                    // TODO ALARMSUPPORT unconditionally add alarm when we
+                    // support multiple alarms.
+                    this.addAlarm(alarm, true);
                 }
-
-                let email = alarmComp.getFirstProperty("X-EMAILADDRESS");
-                if (email) {
-                    this.setProperty("alarmEmailAddress", email.value);
-                }
-            } else {
-                // Really, really old Sunbird/Calendar versions didn't give us a
-                // trigger.
-                cal.ERROR("No trigger property for alarm on item: " + this.id);
+            } catch (e) {
+                Components.utils.reportError("Invalid alarm for item: " +
+                                             this.id + " (" +
+                                             alarmComp.serializeToICS() + ")");
             }
 
-            let lastAck = icalcomp.getFirstProperty("X-MOZ-LASTACK");
-            if (lastAck) {
-                this.mAlarmLastAck = cal.createDateTime(lastAck.value);
-            }
+            // TODO ALARMSUPPORT remove break when we fully support multiple
+            // alarms
+            break;
+        }
+
+        let lastAck = icalcomp.getFirstProperty("X-MOZ-LASTACK");
+        this.mAlarmLastAck = null;
+        if (lastAck) {
+            this.mAlarmLastAck = cal.createDateTime(lastAck.value);
         }
 
         this.mDirty = false;
     },
 
     importUnpromotedProperties: function (icalcomp, promoted) {
         for (let prop in cal.ical.propertyIterator(icalcomp)) {
             let propName = prop.propertyName;
@@ -761,16 +743,17 @@ calItemBase.prototype = {
         return (gen ? parseInt(gen, 10) : 0);
     },
     set generation(aValue) {
         return this.setProperty("X-MOZ-GENERATION", String(aValue));
     },
 
     fillIcalComponentFromBase: function (icalcomp) {
         this.ensureNotDirty();
+        let icssvc = cal.getIcsService();
 
         this.mapPropsToICS(icalcomp, this.icsBasePropMap);
 
         let org = this.organizer;
         if (org) {
             icalcomp.addProperty(org.icalProperty);
         }
 
@@ -788,65 +771,79 @@ calItemBase.prototype = {
 
         if (this.mRecurrenceInfo) {
             for each (let ritem in this.mRecurrenceInfo.getRecurrenceItems({})) {
                 icalcomp.addProperty(ritem.icalProperty);
             }
         }
 
         for each (let cat in this.getCategories({})) {
-            let catprop = getIcsService().createIcalProperty("CATEGORIES");
+            let catprop = icssvc.createIcalProperty("CATEGORIES");
             catprop.value = cat;
             icalcomp.addProperty(catprop);
         }
 
-        let alarmOffset = this.alarmOffset;
-        if (alarmOffset) {
-            let icssvc = cal.getIcsService();
-            let alarmComp = icssvc.createIcalComponent("VALARM");
-
-            let triggerProp = icssvc.createIcalProperty("TRIGGER");
-            triggerProp.valueAsIcalString = alarmOffset.icalString;
-
-            if (this.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_END) {
-                triggerProp.setParameter("RELATED", "END");
-            }
-
-            alarmComp.addProperty(triggerProp);
-
-            // We don't use this, but the ics-spec requires it
-            let descProp = icssvc.createIcalProperty("DESCRIPTION");
-            descProp.value = "Mozilla Alarm: "+ this.title;
-            alarmComp.addProperty(descProp);
-
-            let actionProp = icssvc.createIcalProperty("ACTION");
-            actionProp.value = "DISPLAY";
-
-            let alarmEmailAddress = this.getProperty("alarmEmailAddress");
-            if (alarmEmailAddress) {
-                let emailProp = icssvc.createIcalProperty("X-EMAILADDRESS");
-                emailProp.value = alarmEmailAddress;
-                actionProp.value = "EMAIL";
-                alarmComp.addProperty(emailProp);
-            }
-
-            alarmComp.addProperty(actionProp);
-
-            icalcomp.addSubcomponent(alarmComp);
+        for each (let alarm in this.mAlarms) {
+            icalcomp.addSubcomponent(alarm.icalComponent);
         }
 
         let alarmLastAck = this.alarmLastAck;
         if (alarmLastAck) {
             let lastAck = cal.getIcsService().createIcalProperty("X-MOZ-LASTACK");
             // - should we further ensure that those are UTC or rely on calAlarmService doing so?
             lastAck.value = alarmLastAck.icalString;
             icalcomp.addProperty(lastAck);
         }
     },
 
+    getAlarms: function cIB_getAlarms(aCount) {
+        if (typeof aCount != "object") {
+            throw Components.results.NS_ERROR_XPC_NEED_OUT_OBJECT;
+        }
+
+        aCount.value = this.mAlarms.length;
+        return this.mAlarms;
+    },
+
+    /**
+     * Adds an alarm. The second parameter is for internal use only, i.e not
+     * provided on the interface.
+     *
+     * @parm aDoNotValidate     Don't serialize the component to check for
+     *                            errors.
+     */
+    addAlarm: function cIB_addAlarm(aAlarm, aDoNotValidate) {
+        if (!aDoNotValidate) {
+            try {
+                // Trigger the icalComponent getter to make sure the alarm is valid.
+                aAlarm.icalComponent;
+            } catch (e) {
+                throw Components.results.NS_ERROR_INVALID_ARG;
+            }
+        }
+
+        this.modify();
+        this.mAlarms.push(aAlarm);
+    },
+
+    deleteAlarm: function cIB_deleteAlarm(aAlarm) {
+        this.modify();
+        for (let i = 0; i < this.mAlarms.length; i++) {
+            if (compareObjects(this.mAlarms[i], aAlarm, Components.interfaces.calIAlarm)) {
+                this.mAlarms.splice(i, 1);
+                break;
+            }
+        }
+    },
+
+    clearAlarms: function cIB_clearAlarms() {
+        this.modify();
+        this.mAlarms = [];
+    },
+
     getOccurrencesBetween: function cIB_getOccurrencesBetween(aStartDate, aEndDate, aCount) {
         if (this.recurrenceInfo) {
             return this.recurrenceInfo.getOccurrences(aStartDate, aEndDate, 0, aCount);
         }
 
         if (checkIfInRange(this, aStartDate, aEndDate)) {
             aCount.value = 1;
             return [this];
--- a/calendar/base/src/calItipItem.js
+++ b/calendar/base/src/calItipItem.js
@@ -146,17 +146,17 @@ calItipItem.prototype = {
         //   should not be sent out and should not be relevant for incoming messages
         // - faked master items
         // so clean them out:
 
         function cleanItem(item) {
             // the following changes will bump LAST-MODIFIED/DTSTAMP, we want to preserve the originals:
             let stamp = item.stampTime;
             let lastModified = item.lastModifiedTime;
-            item.alarmOffset = null;
+            item.clearAlarms();
             item.alarmLastAck = null;
             item.deleteProperty("RECEIVED-SEQUENCE");
             item.deleteProperty("RECEIVED-DTSTAMP");
             let propEnum = item.propertyEnumerator;
             while (propEnum.hasMoreElements()) {
                 let prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
                 let pname = prop.name;
                 if (pname != "X-MOZ-FAKED-MASTER" && pname.substr(0, "X-MOZ-".length) == "X-MOZ-") {
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -1832,64 +1832,16 @@ function binaryInsert(itemArray, item, c
                 comptor(itemArray[Math.min(newIndex, itemArray.length - 1)], item) != 0) {
         // Only add the item if duplicates should not be discarded, or if
         // they should and itemArray[newIndex] == item.
         itemArray.splice(newIndex, 0, item);
     }
     return newIndex;
 }
 
-/**
- * Read default alarm settings from user preferences and apply them to
- * the event/todo passed in.
- *
- * @param aItem   The event or todo the settings should be applied to.
- */
-function setDefaultAlarmValues(aItem)
-{
-    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
-                                .getService(Components.interfaces.nsIPrefService);
-    var alarmsBranch = prefService.getBranch("calendar.alarms.");
-
-    if (isEvent(aItem)) {
-        try {
-            if (alarmsBranch.getIntPref("onforevents") == 1) {
-                let alarmOffset = createDuration();
-                let units = alarmsBranch.getCharPref("eventalarmunit");
-                alarmOffset[units] = alarmsBranch.getIntPref("eventalarmlen");
-                alarmOffset.isNegative = true;
-                aItem.alarmOffset = alarmOffset;
-                aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
-            }
-        } catch (ex) {
-            Components.utils.reportError(
-                "Failed to apply default alarm settings to event: " + ex);
-        }
-    } else if (isToDo(aItem)) {
-        try {
-            if (alarmsBranch.getIntPref("onfortodos") == 1) {
-                // You can't have an alarm if the entryDate doesn't exist.
-                if (!aItem.entryDate) {
-                    aItem.entryDate = getSelectedDay() &&
-                                      getSelectedDay().clone() || now();
-                }
-                let alarmOffset = createDuration();
-                let units = alarmsBranch.getCharPref("todoalarmunit");
-                alarmOffset[units] = alarmsBranch.getIntPref("todoalarmlen");
-                alarmOffset.isNegative = true;
-                aItem.alarmOffset = alarmOffset;
-                aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
-            }
-        } catch (ex) {
-            Components.utils.reportError(
-                "Failed to apply default alarm settings to task: " + ex);
-        }
-    }
-}
-
 function getCompositeCalendar() {
     if (getCompositeCalendar.mObject === undefined) {
         getCompositeCalendar.mObject = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
                                                  .createInstance(Components.interfaces.calICompositeCalendar);
         getCompositeCalendar.mObject.prefPrefix = 'calendar-main';
 
         try {
             if (gCalendarStatusFeedback) {
--- a/calendar/import-export/calOutlookCSVImportExport.js
+++ b/calendar/import-export/calOutlookCSVImportExport.js
@@ -17,16 +17,17 @@
  * The Initial Developer of the Original Code is
  * Jussi Kukkonen <jussi.kukkonen@welho.com>.
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Michiel van Leeuwen <mvl@exedo.nl>
  *   Ernst Herbst <hb@calen.de>
+ *   Philipp Kewisch <mozilla@kewis.ch>
  *
  * 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
@@ -330,37 +331,30 @@ function csv_importFromStream(aStream, a
                 }
                 event.startDate = sDate;
                 event.endDate = eDate;
 
                 // Exists an alarm true/false column?
                 if ("alarmIndex" in args) {
                     // Is an alarm wanted for this event?
                     if (locale.valueTrue == eventFields[args.alarmIndex]) {
-                        var alarmDate =
+                        let alarmDate =
                                 parseDateTime(eventFields[args.alarmDateIndex],
                                               eventFields[args.alarmTimeIndex],
                                               locale);
-                        // Set to default if non valid alarmDate was achieved
+                        // Only set the alarm if a date was parsed
                         if (alarmDate) {
-                            event.alarmOffset = alarmDate.subtractDate(sDate);
+                            let alarm = cal.createAlarm();
+                            alarm.related = alarm.ALARM_RELATED_ABSOLUTE;
+                            alarm.alarmDate = alarmDate;
+                            event.addAlarm(alarm);
                         } else {
-                            var alarmOffset = Components
-                                              .classes["@mozilla.org/calendar/duration;1"]
-                                              .createInstance(Components
-                                              .interfaces.calIDuration);
-                            var units = getPrefSafe("calendar.alarms.eventalarmunit",
-                                                    "minutes");
-                            alarmOffset[units] = getPrefSafe("calendar.alarms.eventalarmlen",
-                                                             15);
-                            alarmOffset.isNegative = true;
-                            event.alarmOffset = alarmOffset;
+                            // XXX Is this really wanted here?
+                            cal.alarms.setDefaultValues(event);
                         }
-                        event.alarmRelated = Components.interfaces.calIItemBase
-                                                       .ALARM_RELATED_START;
                     }
                 }
 
                 // Using the "Private" field only for getting privacy status.
                 // "Sensitivity" is neglected for now.
                 if ("privateIndex" in args) {
                     if (locale.valueTrue == eventFields[args.privateIndex]) {
                         event.privacy = "PRIVATE";
@@ -508,31 +502,28 @@ function csv_exportToStream(aStream, aCo
         }
         var line = [];
         line.push(item.title);
         line.push(dateString(item.startDate));
         line.push(timeString(item.startDate));
         line.push(dateString(item.endDate));
         line.push(timeString(item.endDate));
         line.push(item.startDate.isDate ? localeEn.valueTrue : localeEn.valueFalse);
-        if (item.alarmOffset) {
-            line.push(localeEn.valueTrue);
-            var fireTime;
-            if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
-                fireTime = item.startDate.clone();
+        let alarms = item.getAlarms({});
+        if (alarms.length) {
+            let alarmDate = cal.alarms.calculateAlarmDate(item, alarms[0]);
+            if (alarmDate) {
+                line.push(localeEn.valueTrue);
+                line.push(dateString(alarmDate));
+                line.push(timeString(alarmDate));
             } else {
-                fireTime = item.endDate.clone();
+                line.push(localeEn.valueFalse);
+                line.push("");
+                line.push("");
             }
-            fireTime.addDuration(item.alarmOffset);
-            line.push(dateString(fireTime));
-            line.push(timeString(fireTime));
-        } else {
-            line.push(localeEn.valueFalse);
-            line.push("");
-            line.push("");
         }
         line.push(txtString(categoriesArrayToString(item.getCategories({})))); // xxx todo: what's the correct way to encode ',' in csv?, how are multi-values expressed?
         line.push(txtString(item.getProperty("DESCRIPTION")));
         line.push(txtString(item.getProperty("LOCATION")));
         line.push((item.privacy=="PRIVATE") ? localeEn.valueTrue : localeEn.valueFalse);
 
         line = line.map(function(v) {
             v = String(v).replace(/"/g,'""');
--- a/calendar/providers/gdata/components/calGoogleUtils.js
+++ b/calendar/providers/gdata/components/calGoogleUtils.js
@@ -493,44 +493,55 @@ function ItemToXMLEntry(aItem, aAuthorEm
     entry.gCal::sendEventNotifications.@value = (notify ? "true" : "false");
 
     // gd:when
     var duration = aItem.endDate.subtractDate(aItem.startDate);
     entry.gd::when.@startTime = toRFC3339(aItem.startDate);
     entry.gd::when.@endTime = toRFC3339(aItem.endDate);
 
     // gd:reminder
-    if (aItem.alarmOffset && selfIsOrganizer) {
-        var gdReminder = <gd:reminder xmlns:gd={gd}/>;
-        var alarmOffset = aItem.alarmOffset.clone();
-
-        if (aItem.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_END) {
-            // Google always uses an alarm offset related to the start time
-            alarmOffset.addDuration(duration);
-        }
-
-        gdReminder.@minutes = -aItem.alarmOffset.inSeconds / 60;
-        gdReminder.@method = "alert";
+    let alarms = aItem.getAlarms({});
+    let actionMap = {
+        DISPLAY: "alert",
+        EMAIL: "email",
+        SMS: "sms"
+    };
+    if (selfIsOrganizer) {
+        for (let i = 0; i < 5 && i < alarms.length; i++) {
+            let alarm = alarms[i];
+            let gdReminder = <gd:reminder xmlns:gd={gd}/>;
+            if (alarm.related == alarm.ALARM_RELATED_ABSOLUTE) {
+                // Setting an absolute date can be done directly. Google will take
+                // care of calculating the offset.
+                gdReminder.@absoluteTime = toRFC3339(alarm.alarmDate);
+            } else {
+                let alarmOffset = alarm.offset;
+                if (alarm.related == alarm.ALARM_RELATED_END) {
+                    // Google always uses an alarm offset related to the start time
+                    // for relative alarms.
+                    alarmOffset = alarmOffset.clone();
+                    alarmOffset.addDuration(duration);
+                }
 
-        if (aItem.recurrenceInfo) {
-            // On recurring items, set the reminder directly in the <entry> tag.
-            entry.gd::reminder += gdReminder;
-        } else {
-            // Otherwise, its a child of the gd:when element
-            entry.gd::when.gd::reminder += gdReminder;
+                gdReminder.@minutes = -alarmOffset.inSeconds / 60;
+                gdReminder.@method = actionMap[alarm.action] || "alert";
+            }
+
+
+            if (aItem.recurrenceInfo) {
+                // On recurring items, set the reminder directly in the <entry> tag.
+                entry.gd::reminder += gdReminder;
+            } else {
+                // Otherwise, its a child of the gd:when element
+                entry.gd::when.gd::reminder += gdReminder;
+            }
         }
-    } else if (aItem.alarmOffset) {
+    } else if (alarms.length) {
         // We need to reset this so the item gets returned correctly.
-        aItem.alarmOffset = null;
-    }
-
-    // saved alarms
-    var otherAlarms = aItem.getProperty("X-GOOGLE-OTHERALARMS");
-    for each (var alarm in otherAlarms) {
-        entry.gd::when.gd::reminder += new XML(alarm);
+        aItem.clearAlarms();
     }
 
     // gd:extendedProperty (alarmLastAck)
     addExtendedProperty("X-MOZ-LASTACK", toRFC3339(aItem.alarmLastAck));
 
     // XXX While Google now supports multiple alarms and alarm values, we still
     // need to fix bug 353492 first so we can better take care of finding out
     // what alarm is used for snoozing.
@@ -646,93 +657,114 @@ function relevantFieldsMatch(a, b) {
     if (a.id != b.id ||
         a.title != b.title ||
         a.status != b.status ||
         a.privacy != b.privacy) {
         return false;
     }
 
     function compareNotNull(prop) {
-        var ap = a[prop];
-        var bp = b[prop];
+        let ap = a[prop];
+        let bp = b[prop];
         return (ap && !bp || !ap && bp ||
                 (typeof(ap) == 'object' && ap && bp &&
                  ap.compare && ap.compare(bp)));
     }
 
     // Object flat values
-    if (compareNotNull("alarmOffset") ||
-        compareNotNull("alarmLastAck") ||
-        compareNotNull("recurrenceInfo") ||
+    if (compareNotNull("recurrenceInfo") ||
         /* Compare startDate and endDate */
         compareNotNull("startDate") ||
         compareNotNull("endDate") ||
         (a.startDate.isDate != b.startDate.isDate) ||
         (a.endDate.isDate != b.endDate.isDate)) {
         return false;
     }
 
     // Properties
     const kPROPERTIES = ["DESCRIPTION", "TRANSP", "X-GOOGLE-EDITURL",
                          "LOCATION", "X-MOZ-SNOOZE-TIME"];
 
-    for each (var p in kPROPERTIES) {
+    for each (let p in kPROPERTIES) {
         // null and an empty string should be handled as non-relevant
         if ((a.getProperty(p) || "") != (b.getProperty(p) || "")) {
             return false;
         }
     }
 
     // categories
-    var aCat = a.getCategories({});
-    var bCat = b.getCategories({});
+    let aCat = a.getCategories({});
+    let bCat = b.getCategories({});
     if ((aCat.length != bCat.length) ||
         aCat.some(function notIn(cat) { return (bCat.indexOf(cat) == -1); })) {
         return false;
     }
 
     // attendees and organzier
-    var aa = a.getAttendees({});
-    var ab = b.getAttendees({});
+    let aa = a.getAttendees({});
+    let ab = b.getAttendees({});
     if (aa.length != ab.length) {
         return false;
     }
 
     if ((a.organizer && !b.organizer) ||
         (!a.organizer && b.organizer) ||
         (a.organizer && b.organizer && a.organizer.id != b.organizer.id)) {
         return false;
     }
 
     // go through attendees in a, check if its id is in b
-    for each (var attendee in aa) {
-        var ba = b.getAttendeeById(attendee.id);
+    for each (let attendee in aa) {
+        let ba = b.getAttendeeById(attendee.id);
         if (!ba ||
             ba.participationStatus != attendee.participationStatus ||
             ba.commonName != attendee.commonName ||
             ba.isOrganizer != attendee.isOrganizer ||
             ba.role != attendee.role) {
             return false;
         }
     }
 
+    // Alarms
+    aa = a.getAlarms({});
+    ab = b.getAlarms({});
+
+    if (aa.length != ab.length) {
+        return false;
+    }
+
+    let alarmMap = {};
+    for each (let alarm in aa) {
+        alarmMap[alarm.icalString] = true;
+    }
+    let found = 0;
+    for each (let alarm in ab) {
+        if (alarm.icalString in alarmMap) {
+            found++;
+        }
+    }
+
+    if (found != ab.length) {
+        return false;
+    }
+
     // Recurrence Items
     if (a.recurrenceInfo) {
-        var ra = a.recurrenceInfo.getRecurrenceItems({});
-        var rb = b.recurrenceInfo.getRecurrenceItems({});
+        let ra = a.recurrenceInfo.getRecurrenceItems({});
+        let rb = b.recurrenceInfo.getRecurrenceItems({});
 
         // If we have more or less, it definitly changed.
         if (ra.length != rb.length) {
             return false;
         }
 
         // I assume that if the recurrence pattern has not changed, the order
         // of the recurrence items should not change. Anything more will be
         // very expensive.
-        for (var i=0; i < ra.length; i++) {
+        for (let i = 0; i < ra.length; i++) {
             if (ra[i].icalProperty.icalString !=
                 rb[i].icalProperty.icalString) {
                 return false;
             }
         }
     }
 
     return true;
@@ -833,86 +865,64 @@ function XMLEntryToItem(aXMLEntry, aTime
                          aXMLEntry.gd::transparency.@value.toString()
                                   .substring(39).toUpperCase());
 
         // gd:eventStatus
         item.status = aXMLEntry.gd::eventStatus.@value.toString()
                                .substring(39).toUpperCase();
 
         // gd:reminder (preparation)
-        // Google's alarms are always related to the start
-        item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
+        // If a reference item was passed, it may already contain alarms. Since
+        // we have no alarm id or such and the alarms are contained in every
+        // feed, we can go ahead and clear the alarms here.
+        item.clearAlarms();
 
         /**
-         * Helper function to parse all reminders in a tagset. This sets the
-         * item's alarm, and also saves all other alarms using the
-         * X-GOOGLE-OTHERALARMS property.
+         * Helper function to parse all reminders in a tagset.
          *
          * @param reminderTags      The tagset to parse.
          */
         function parseReminders(reminderTags) {
             if (aXMLEntry.gd::who.(@rel.substring(33) == "event.organizer")
                          .@email.toString() != aCalendar.googleCalendarName) {
                 // We are not the organizer, so its not smart to set alarms on
                 // this event.
                 return;
             }
-            var lastAlarm;
-            var otherAlarms = [];
-            // Go through all reminder tags given and pick the best alarm.
-            for each (var reminder in reminderTags) {
-                // We are only intrested in "alert" reminders. Other types
-                // include sms and email alerts, but thats not the point here.
-                if (reminder.@method == "alert") {
+            const actionMap = {
+                email: "EMAIL",
+                alert: "DISPLAY",
+                sms: "SMS"
+            };
+            for each (let reminderTag in reminderTags) {
+                let alarm = cal.createAlarm();
+                alarm.action = actionMap[reminderTag.@method] || "DISPLAY";
+                if (reminderTag.@absoluteTime.toString()) {
+                    alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE;
+                    let absolute = fromRFC3339(reminderTag.@absoluteTime,
+                                               aTimezone);
+                    alarm.alarmDate = absolute;
+                } else {
+                    alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
                     let alarmOffset = cal.createDuration();
-
-                    if (reminder.@absoluteTime.toString()) {
-                        var absolute = fromRFC3339(reminder.@absoluteTime,
-                                                   aTimezone);
-                        alarmOffset = startDate.subtractDate(absolute);
-                    } else if (reminder.@days.toString()) {
-                        alarmOffset.days = -reminder.@days;
-                    } else if (reminder.@hours.toString()) {
-                        alarmOffset.hours = -reminder.@hours;
-                    } else if (reminder.@minutes.toString()) {
-                        alarmOffset.minutes = -reminder.@minutes;
+                    if (reminderTag.@days.toString()) {
+                        alarmOffset.days = -reminderTag.@days;
+                    } else if (reminderTag.@hours.toString()) {
+                        alarmOffset.hours = -reminderTag.@hours;
+                    } else if (reminderTag.@minutes.toString()) {
+                        alarmOffset.minutes = -reminderTag.@minutes;
                     } else {
                         // Invalid alarm, skip it
                         continue;
                     }
                     alarmOffset.normalize();
-
-                    // If there is more than one alarm, we could either take the
-                    // alarm closest to the event or the alarm furthest to the
-                    // event. Let the user decide (use a property)
-                    var useClosest = getPrefSafe("calendar.google.alarmClosest",
-                                                 true);
-                    if (!item.alarmOffset ||
-                        (useClosest &&
-                         alarmOffset.compare(item.alarmOffset) > 0) ||
-                        (!useClosest &&
-                         alarmOffset.compare(item.alarmOffset) < 0)) {
-
-                        item.alarmOffset = alarmOffset;
-                        if (lastAlarm) {
-                            // If there was already an alarm, then it is now one
-                            // of the other alarms.
-                            otherAlarms.push(lastAlarm.toXMLString());
-                        }
-                        lastAlarm = reminder;
-                        // Don't push the reminder below, since we might be
-                        // keeping this one as our item's alarmOffset.
-                        continue;
-                    }
+                    alarm.offset = alarmOffset;
                 }
-                otherAlarms.push(reminder.toXMLString());
+                item.addAlarm(alarm);
             }
-
-            // Save other alarms that were set so we don't loose them
-            item.setProperty("X-GOOGLE-OTHERALARMS", otherAlarms);
         }
 
         // gd:when
         var recurrenceInfo = aXMLEntry.gd::recurrence.toString();
         if (recurrenceInfo.length == 0) {
             // If no recurrence information is given, then there will only be
             // one gd:when tag. Otherwise, we will be parsing the startDate from
             // the recurrence information.
@@ -1207,48 +1217,79 @@ function LOGitem(item) {
 
         rstr += "\tExceptions:\n";
         var exids = item.recurrenceInfo.getExceptionIds({});
         for each (var exc in exids) {
             rstr += "\t\t" + exc + "\n";
         }
     }
 
+    let astr = "\n";
+    let alarms = item.getAlarms({});
+    for each (let alarm in alarms) {
+        astr += "\t\t" + LOGalarm(alarm) + "\n";
+    }
+
+
     LOG("Logging calIEvent:" +
         "\n\tid:" + item.id +
         "\n\tediturl:" + item.getProperty("X-GOOGLE-EDITURL") +
         "\n\tcreated:" + item.getProperty("CREATED") +
         "\n\tupdated:" + item.getProperty("LAST-MODIFIED") +
         "\n\ttitle:" + item.title +
         "\n\tcontent:" + item.getProperty("DESCRIPTION") +
         "\n\ttransparency:" + item.getProperty("TRANSP") +
         "\n\tstatus:" + item.status +
         "\n\tstartTime:" + item.startDate.toString() +
         "\n\tendTime:" + item.endDate.toString() +
         "\n\tlocation:" + item.getProperty("LOCATION") +
         "\n\tprivacy:" + item.privacy +
-        "\n\talarmOffset:" + item.alarmOffset +
         "\n\talarmLastAck:" + item.alarmLastAck +
         "\n\tsnoozeTime:" + item.getProperty("X-MOZ-SNOOZE-TIME") +
         "\n\tisOccurrence: " + (item.recurrenceId != null) +
         "\n\tOrganizer: " + LOGattendee(item.organizer) +
         "\n\tAttendees: " + attendeeString +
-        "\n\trecurrence: " + (rstr.length > 1 ? "yes: " + rstr : "no"));
+        "\n\trecurrence: " + (rstr.length > 1 ? "yes: " + rstr : "no") +
+        "\n\talarms: " + (astr.length > 1 ? "yes: " + astr : "no"));
 }
 
 function LOGattendee(aAttendee, asString) {
     return aAttendee &&
         ("\n\t\tID: " + aAttendee.id +
          "\n\t\t\tName: " + aAttendee.commonName +
          "\n\t\t\tRsvp: " + aAttendee.rsvp +
          "\n\t\t\tIs Organizer: " +  (aAttendee.isOrganizer ? "yes" : "no") +
          "\n\t\t\tRole: " + aAttendee.role +
          "\n\t\t\tStatus: " + aAttendee.participationStatus);
 }
 
+function LOGalarm(aAlarm) {
+    if (!aAlarm) {
+        return "";
+    }
+
+    let enumerator = aAlarm.propertyEnumerator;
+    let xpropstr = "";
+    while (enumerator.hasMoreElements()) {
+        let el = enumerator.getNext();
+        xpropstr += "\n\t\t\t" + el.key + ":" + el.value;
+    }
+
+    return ("\n\t\tAction: " +  aAlarm.action +
+            "\n\t\tOffset: " + (aAlarm.offset && aAlarm.offset.toString()) +
+            "\n\t\talarmDate: " + (aAlarm.alarmDate && aAlarm.alarmDate.toString()) +
+            "\n\t\trelated: " + aAlarm.related +
+            "\n\t\trepeat: " + aAlarm.repeat +
+            "\n\t\trepeatOffset: " + (aAlarm.repeatOffset && aAlarm.repeatOffset.toString()) +
+            "\n\t\trepeatDate: " + (aAlarm.repeatDate && aAlarm.repeatDate.toString()) +
+            "\n\t\tdescription: " + aAlarm.description +
+            "\n\t\tsummary: " + aAlarm.summary +
+            "\n\t\tproperties: " + (xpropstr.length > 0 ? "yes:" + xpropstr : "no"));
+}
+
 function LOGinterval(aInterval) {
     const fbtypes = Components.interfaces.calIFreeBusyInterval;
     if (aInterval.freeBusyType == fbtypes.FREE) {
         type = "FREE";
     } else if (aInterval.freeBusyType == fbtypes.BUSY) {
         type = "BUSY";
     } else {
         type = aInterval.freeBusyType + "(UNKNOWN)";
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -40,16 +40,18 @@
  * 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://calendar/modules/calProviderUtils.jsm");
 
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
 const kStorageServiceContractID = "@mozilla.org/storage/service;1";
 const kStorageServiceIID = Components.interfaces.mozIStorageService;
 
 const kCalICalendar = Components.interfaces.calICalendar;
 
 const kCalAttendeeContractID = "@mozilla.org/calendar/attendee;1";
 const kCalIAttendee = Components.interfaces.calIAttendee;
 var CalAttendee;
@@ -1728,48 +1730,56 @@ calStorageCalendar.prototype = {
             item.status = row.ical_status;
 
         if (row.alarm_time) {
             // Old (schema version 4) data, need to convert this nicely to the
             // new alarm interface.  Eventually, we're going to want to be able
             // to deal with both types of data in a calIAlarm interface, but
             // not yet.  Leaving this column around though may help ease that
             // transition in the future.
-            var alarmTime = newDateTime(row.alarm_time, row.alarm_time_tz);
-            var time;
-            var related = Components.interfaces.calIItemBase.ALARM_RELATED_START;
+            let alarmTime = newDateTime(row.alarm_time, row.alarm_time_tz);
+            let time;
+            let related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
             if (isEvent(item)) {
                 time = newDateTime(row.event_start, row.event_start_tz);
             } else { //tasks
                 if (row.todo_entry) {
                     time = newDateTime(row.todo_entry, row.todo_entry_tz);
                 } else if (row.todo_due) {
-                    related = Components.interfaces.calIItemBase.ALARM_RELATED_END;
+                    related = Components.interfaces.calIAlarm.ALARM_RELATED_END;
                     time = newDateTime(row.todo_due, row.todo_due_tz);
                 }
             }
             if (time) {
-                var duration = alarmTime.subtractDate(time);
-                item.alarmOffset = duration;
-                item.alarmRelated = related;
+                // TODO ALARMSUPPORT for now just convert this to a relative
+                // alarm. Will change when supporting multiple alarms.
+                item.clearAlarms();
+                let alarm = cal.createAlarm();
+                let duration = alarmTime.subtractDate(time);
+                alarm.related = related;
+                alarm.offset = duration;
+                item.addAlarm(alarm);
             } else {
                 Components.utils.reportError("WARNING! Couldn't do alarm conversion for item:"+
                                              item.title+','+item.id+"!\n");
             }
         }
 
         // Alarm offset could be 0, but this is ok, so compare with null
         if (row.alarm_offset != null) {
-            var duration = Components.classes["@mozilla.org/calendar/duration;1"]
-                                     .createInstance(Components.interfaces.calIDuration);
+            let duration = cal.createDuration(); 
             duration.inSeconds = row.alarm_offset;
             duration.normalize();
 
-            item.alarmOffset = duration;
-            item.alarmRelated = row.alarm_related;
+            // TODO ALARMSUPPORT for now just use the one relative alarm. Will
+            // change when supporting multiple alarms.
+            item.clearAlarms();
+            let alarm = cal.createAlarm();
+            alarm.related = row.alarm_related + 1;
+            alarm.offset = duration;
         }
         if (row.alarm_last_ack) {
             // alarm acks are always in utc
             item.alarmLastAck = newDateTime(row.alarm_last_ack, "UTC");
         }
 
         if (row.recurrence_id)
             item.recurrenceId = newDateTime(row.recurrence_id, row.recurrence_id_tz);
@@ -2369,19 +2379,23 @@ calStorageCalendar.prototype = {
         if ((tmp = item.getProperty("LAST-MODIFIED")))
             ip.last_modified = tmp.nativeTime;
 
         ip.title = item.getProperty("SUMMARY");
         ip.priority = item.getProperty("PRIORITY");
         ip.privacy = item.getProperty("CLASS");
         ip.ical_status = item.getProperty("STATUS");
 
-        if (item.alarmOffset) {
-            ip.alarm_offset = item.alarmOffset.inSeconds;
-            ip.alarm_related = item.alarmRelated;
+        // TODO ALARMSUPPORT for now, just use the first relative alarm. This
+        // will change when supporting multiple alarms.
+        let alarms = item.getAlarms({})
+                         .filter(function(x) x.related != x.ALARM_RELATED_ABSOLUTE);
+        if (alarms.length) {
+            ip.alarm_offset = alarms[0].offset.inSeconds;
+            ip.alarm_related = alarms[0].related - 1;
         }
         if (item.alarmLastAck) {
             ip.alarm_last_ack = item.alarmLastAck.nativeTime;
         }
     },
 
     writeAttendees: function (item, olditem) {
         var attendees = item.getAttendees({});
--- a/calendar/providers/wcap/calWcapCalendarItems.js
+++ b/calendar/providers/wcap/calWcapCalendarItems.js
@@ -175,30 +175,37 @@ calWcapCalendar.prototype.encodeRecurren
     // rchange=1: expand recurrences,
     // or whether to replace the rrule, ambiguous documentation!!!
     // check with store(with no uid) upon adoptItem() which behaves strange
     // if rchange=0 is set!
 };
 
 calWcapCalendar.prototype.getAlarmParams =
 function calWcapCalendar_getAlarmParams(item) {
-    var params = null;
-    var alarmStart = item.alarmOffset;
-    if (alarmStart) {
-        if (item.alarmRelated == calIItemBase.ALARM_RELATED_END) {
+    let params = null;
+    // TODO ALARMSUPPORT This will change as soon as email alarms are supported
+    // by the UI.
+    let alarms = item.getAlarms({})
+                     .filter(function(x) x.related != x.ALARM_RELATED_ABSOLUTE);
+    let alarm = alarms[0];
+    // END TODO ALARMSUPPORT
+
+    if (alarm && alarm.offset) {
+        let alarmStart = alarm.offset;
+        if (alarm.related == alarm.ALARM_RELATED_END) {
             // cs does not support explicit RELATED=END when
             // both start|entry and end|due are written
-            var dur = item.duration;
+            let dur = item.duration;
             if (dur) { // both given
                 alarmStart = alarmStart.clone();
                 alarmStart.addDuration(dur);
             } // else only end|due is set, alarm makes little sense though
         }
         
-        var emails = "";
+        let emails = "";
         if (item.hasProperty("alarmEmailAddress")) {
             emails = encodeURIComponent(item.getProperty("alarmEmailAddress"));
         } else {
             emails = this.session.getDefaultAlarmEmails({}).map(encodeURIComponent).join(";");
         }
         if (emails.length > 0) {
             params = ("&alarmStart=" + alarmStart.icalString);
             params += ("&alarmEmails=" + emails);
@@ -365,17 +372,17 @@ function calWcapCalendar_storeItem(bAddI
             }
         } else { // calITodo
             // xxx todo: dtstart is mandatory for cs, so if this is
             //           undefined, assume an allDay todo???
             var dtstart = item.entryDate;
             var dtend = item.dueDate;
 
             // cs bug: enforce DUE (set to DTSTART) if alarm is set
-            if (!dtend && item.alarmOffset) {
+            if (!dtend && item.getAlarms({}).length) {
                 dtend = dtstart;
             }
 
             bIsAllDay = (dtstart && dtstart.isDate);
             if (!oldItem || !identicalDatetimes(dtstart, oldItem.entryDate)
                          || !identicalDatetimes(dtend, oldItem.dueDate)) {
                 params += ("&dtstart=" + getIcalUTC(dtstart)); // timezone will be set with tzid param
                 params += ("&due=" + getIcalUTC(dtend)); // timezone will be set with tzid param
@@ -639,27 +646,27 @@ function calWcapCalendar_storeItem(bAddI
 
 calWcapCalendar.prototype.tunnelXProps =
 function calWcapCalendar_tunnelXProps(destItem, srcItem) {
     // xxx todo: temp workaround for bug in calItemBase.js
     if (!isParent(srcItem)) {
         return;
     }
     // tunnel alarm X-MOZ-SNOOZE only if alarm is still set:
-    var alarmOffset = destItem.alarmOffset;
-    var enumerator = srcItem.propertyEnumerator;
+    let hasAlarms = destItem.getAlarms({}).length;
+    let enumerator = srcItem.propertyEnumerator;
     while (enumerator.hasMoreElements()) {
         try {
             var prop = enumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
             var name = prop.name;
             if (name.indexOf("X-MOZ-") == 0) {
                 switch (name) {
                     // keep snooze stamps for occurrences only and if alarm is still set:
                     case "X-MOZ-SNOOZE-TIME":
-                        if (!alarmOffset) {
+                        if (!hasAlarms) {
                             break; // alarm has been reset
                         }
                         // fallthru intended
                     default:
                         if (LOG_LEVEL > 1) {
                             log("tunneling " + name + "=" + prop.value, this);
                         }
                         destItem.setProperty(name, prop.value);
--- a/calendar/test/unit/head_consts.js
+++ b/calendar/test/unit/head_consts.js
@@ -132,17 +132,16 @@ function getStorageCal() {
 
 /**
  * Return an item property as string.
  * @param aItem
  * @param string aProp possible item properties: start, end, duration,
  *                     generation, title,
  *                     id, calendar, creationDate, lastModifiedTime,
  *                     stampTime, priority, privacy, status,
- *                     alarmOffset, alarmRelated,
  *                     alarmLastAck, recurrenceStartDate
  *                     and any property that can be obtained using getProperty()
  */
 function getProps(aItem, aProp) {
     var value = null;
     switch (aProp) {
         case "start":
             value = aItem.startDate || aItem.entryDate || null;
@@ -178,22 +177,16 @@ function getProps(aItem, aProp) {
             value = aItem.priority;
             break;
         case "privacy":
             value = aItem.privacy;
             break;
         case "status":
             value = aItem.status;
             break;
-        case "alarmOffset":
-            value = aItem.alarmOffset;
-            break;
-        case "alarmRelated":
-            value = aItem.alarmRelated;
-            break;
         case "alarmLastAck":
             value = aItem.alarmLastAck;
             break;
         case "recurrenceStartDate":
             value = aItem.recurrenceStartDate;
             break;
         default:
             value = aItem.getProperty(aProp);
@@ -206,17 +199,17 @@ function getProps(aItem, aProp) {
 }
 
 function compareItemsSpecific(aLeftItem, aRightItem, aPropArray) {
     if (!aPropArray) {
         // left out:  "id", "calendar", "lastModifiedTime", "generation",
         // "stampTime" as these are expected to change
         aPropArray = ["start", "end", "duration",
                       "title", "priority", "privacy", "creationDate",
-                      "status", "alarmOffset", "alarmRelated", "alarmLastAck",
+                      "status", "alarmLastAck",
                       "recurrenceStartDate"];
     }
     for (var i = 0; i < aPropArray.length; i++) {
         do_check_eq(getProps(aLeftItem, aPropArray[i]),
                     getProps(aRightItem,
                     aPropArray[i]));
     }
 }
--- a/calendar/test/unit/test_alarm.js
+++ b/calendar/test/unit/test_alarm.js
@@ -47,34 +47,34 @@ function run_test() {
     test_dates();
 
     test_clone();
     test_immutable();
 }
 
 function test_initial_creation() {
     dump("Testing initial creation...");
-    alarm = createAlarm();
+    alarm = cal.createAlarm();
 
     let passed;
     try {
         alarm.icalString;
         passed = false;
     } catch (e) {
         passed = true;
     }
     if (!passed) {
         do_throw("Fresh calIAlarm should not produce a valid icalString");
     }
     dump("Done\n");
 }
 
 function test_display_alarm() {
     dump("Testing DISPLAY alarms...");
-    let alarm = createAlarm();
+    let alarm = cal.createAlarm();
     // Set ACTION to DISPLAY, make sure this was not rejected
     alarm.action = "DISPLAY";
     do_check_eq(alarm.action, "DISPLAY");
 
     // Set a Description, REQUIRED for ACTION:DISPLAY
     alarm.description = "test";
     do_check_eq(alarm.description, "test");
 
@@ -83,17 +83,17 @@ function test_display_alarm() {
     do_check_eq(alarm.summary, null);
 
     // TODO No attendees
     dump("Done\n");
 }
 
 function test_email_alarm() {
     dump("Testing EMAIL alarms...");
-    let alarm = createAlarm();
+    let alarm = cal.createAlarm();
     // Set ACTION to DISPLAY, make sure this was not rejected
     alarm.action = "EMAIL";
     do_check_eq(alarm.action, "EMAIL");
 
     // Set a Description, REQUIRED for ACTION:EMAIL
     alarm.description = "description";
     do_check_eq(alarm.description, "description");
 
@@ -104,17 +104,17 @@ function test_email_alarm() {
     // TODO check for at least one attendee
 
     // TODO test attachments
     dump("Done\n");
 }
 
 function test_audio_alarm() {
     dump("Testing AUDIO alarms...");
-    let alarm = createAlarm();
+    let alarm = cal.createAlarm();
     // Set ACTION to AUDIO, make sure this was not rejected
     alarm.action = "AUDIO";
     do_check_eq(alarm.action, "AUDIO");
 
     // No Description for ACTION:AUDIO
     alarm.description = "description";
     do_check_eq(alarm.description, null);
 
@@ -124,17 +124,17 @@ function test_audio_alarm() {
 
     // TODO No attendees
     // TODO test for one attachment
     dump("Done\n");
 }
 
 function test_custom_alarm() {
     dump("Testing X-SMS (custom) alarms...");
-    let alarm = createAlarm();
+    let alarm = cal.createAlarm();
     // Set ACTION to a custom value, make sure this was not rejected
     alarm.action = "X-SMS"
     do_check_eq(alarm.action, "X-SMS");
 
     // There is no restriction on DESCRIPTION for custom alarms
     alarm.description = "description";
     do_check_eq(alarm.description, "description");
 
@@ -146,17 +146,17 @@ function test_custom_alarm() {
     // TODO test for attachments
     dump("Done\n");
 }
 
 // Check if any combination of REPEAT and DURATION work as expected.
 function test_repeat() {
     dump("Testing REPEAT and DURATION properties...");
     let message;
-    let alarm = createAlarm();
+    let alarm = cal.createAlarm();
 
     // Check initial value
     do_check_eq(alarm.repeat, 0);
     do_check_eq(alarm.repeatOffset, null);
     do_check_eq(alarm.repeatDate, null);
 
     // Should not be able to get REPEAT when DURATION is not set
     alarm.repeat = 1;
@@ -181,31 +181,31 @@ function test_repeat() {
     // Check final value
     do_check_eq(alarm.repeat, 0);
     do_check_eq(alarm.repeatOffset, null);
     dump("Done\n");
 }
 
 function test_xprop() {
     dump("Testing X-Props...");
-    let alarm = createAlarm();
+    let alarm = cal.createAlarm();
     alarm.setProperty("X-PROP", "X-VALUE");
     do_check_true(alarm.hasProperty("X-PROP"));
     do_check_eq(alarm.getProperty("X-PROP"), "X-VALUE");
     alarm.deleteProperty("X-PROP");
     do_check_false(alarm.hasProperty("X-PROP"));
     do_check_eq(alarm.getProperty("X-PROP"), null);
     dump("Done\n");
 }
 
 function test_dates() {
     dump("Testing alarm dates...");
     let passed;
     // Initial value
-    let alarm = createAlarm();
+    let alarm = cal.createAlarm();
     do_check_eq(alarm.alarmDate, null);
     do_check_eq(alarm.offset, null);
 
     // Set an offset and check it
     alarm.related = Ci.calIAlarm.ALARM_RELATED_START
     let offset = createDuration("-PT5M");
     alarm.offset = offset;
     do_check_eq(alarm.alarmDate, null);
@@ -297,17 +297,17 @@ function test_immutable() {
     if (!passed) {
         do_throw("setProperty succeeded while item was immutable");
     }
     dump("Done\n");
 }
 
 function test_clone() {
     dump("Testing cloning alarms...");
-    let alarm = createAlarm();
+    let alarm = cal.createAlarm();
     // Set up each attribute
     for (let prop in propMap) {
         alarm[prop] = propMap[prop];
     }
     // Make a copy
     let newAlarm = alarm.clone();
     newAlarm.makeImmutable();
     newAlarm = newAlarm.clone();