Bug 1408662 - Move date/time/timezone related functions into calDateTimeUtils.jsm - manual changes. r=MakeMyDay
authorPhilipp Kewisch <mozilla@kewis.ch>
Fri, 13 Oct 2017 20:27:54 +0200
changeset 30043 015d6cb840c4a0ed8db392a077aab10c2be6d0cd
parent 30042 ec015f717cb05fdf8d6057a6526d8cdce9378eb5
child 30044 18938fc0dd706992c6fd9fec8baddee302b2cd45
push id2108
push userclokep@gmail.com
push dateMon, 22 Jan 2018 17:53:55 +0000
treeherdercomm-beta@c44930d8ad9b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMakeMyDay
bugs1408662
Bug 1408662 - Move date/time/timezone related functions into calDateTimeUtils.jsm - manual changes. r=MakeMyDay MozReview-Commit-ID: Ks5ufwa5YhW
calendar/base/modules/calDateTimeUtils.jsm
calendar/base/modules/calUtils.jsm
calendar/base/modules/calUtilsCompat.jsm
calendar/base/modules/moz.build
calendar/base/src/calIcsParser.js
calendar/base/src/calUtils.js
calendar/providers/gdata/components/calGoogleCalendar.js
calendar/providers/gdata/content/gdata-calendar-event-dialog.js
calendar/providers/gdata/modules/calUtilsShim.jsm
calendar/providers/gdata/modules/gdataSession.jsm
calendar/providers/gdata/modules/gdataUtils.jsm
calendar/providers/gdata/moz.build
new file mode 100644
--- /dev/null
+++ b/calendar/base/modules/calDateTimeUtils.jsm
@@ -0,0 +1,252 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * date, time and timezone related functions via cal.dtz.*
+ *
+ * NOTE this module should never be imported directly. Instead, load
+ * calUtils.jsm and accss them via cal.dtz.*
+ */
+
+Components.utils.import("resource://gre/modules/Preferences.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "cal", "resource://calendar/modules/calUtils.jsm", "cal");
+
+this.EXPORTED_SYMBOLS = ["caldtz"]; /* exported caldtz */
+
+var caldtz = {
+    /**
+     * Shortcut to the timezone service's defaultTimezone
+     */
+    get defaultTimezone() {
+        return cal.getTimezoneService().defaultTimezone;
+    },
+
+    /**
+     * Shorcut to the UTC timezone
+     */
+    get UTC() {
+        return cal.getTimezoneService().UTC;
+    },
+
+    /**
+     * Shortcut to the floating (local) timezone
+     */
+    get floating() {
+        return cal.getTimezoneService().floating;
+    },
+
+    /**
+     * Makes sure the given timezone id is part of the list of recent timezones.
+     *
+     * @param aTzid     The timezone id to add
+     */
+    saveRecentTimezone: function(aTzid) {
+        let recentTimezones = caldtz.getRecentTimezones();
+        const MAX_RECENT_TIMEZONES = 5; // We don't need a pref for *everything*.
+
+        if (aTzid != caldtz.defaultTimezone.tzid &&
+            !recentTimezones.includes(aTzid)) {
+            // Add the timezone if its not already the default timezone
+            recentTimezones.unshift(aTzid);
+            recentTimezones.splice(MAX_RECENT_TIMEZONES);
+            Preferences.set("calendar.timezone.recent", JSON.stringify(recentTimezones));
+        }
+    },
+
+    /**
+     * Returns a calIDateTime that corresponds to the current time in the user's
+     * default timezone.
+     */
+    now: function() {
+        let date = caldtz.jsDateToDateTime(new Date());
+        return date.getInTimezone(caldtz.defaultTimezone);
+    },
+
+    /**
+     * Get the default event start date. This is the next full hour, or 23:00 if it
+     * is past 23:00.
+     *
+     * @param aReferenceDate    If passed, the time of this date will be modified,
+     *                            keeping the date and timezone intact.
+     */
+    getDefaultStartDate: function(aReferenceDate) {
+        let startDate = caldtz.now();
+        if (aReferenceDate) {
+            let savedHour = startDate.hour;
+            startDate = aReferenceDate;
+            if (!startDate.isMutable) {
+                startDate = startDate.clone();
+            }
+            startDate.isDate = false;
+            startDate.hour = savedHour;
+        }
+
+        startDate.second = 0;
+        startDate.minute = 0;
+        if (startDate.hour < 23) {
+            startDate.hour++;
+        }
+        return startDate;
+    },
+
+    /**
+     * Setup the default start and end hours of the given item. This can be a task
+     * or an event.
+     *
+     * @param aItem             The item to set up the start and end date for.
+     * @param aReferenceDate    If passed, the time of this date will be modified,
+     *                            keeping the date and timezone intact.
+     */
+    setDefaultStartEndHour: function(aItem, aReferenceDate) {
+        aItem[caldtz.startDateProp(aItem)] = caldtz.getDefaultStartDate(aReferenceDate);
+
+        if (cal.isEvent(aItem)) {
+            aItem.endDate = aItem.startDate.clone();
+            aItem.endDate.minute += Preferences.get("calendar.event.defaultlength", 60);
+        }
+    },
+
+    /**
+     * Returns the property name used for the start date of an item, ie either an
+     * event's start date or a task's entry date.
+     */
+    startDateProp: function(aItem) {
+        if (cal.isEvent(aItem)) {
+            return "startDate";
+        } else if (cal.isToDo(aItem)) {
+            return "entryDate";
+        }
+        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+    },
+
+    /**
+     * Returns the property name used for the end date of an item, ie either an
+     * event's end date or a task's due date.
+     */
+    endDateProp: function(aItem) {
+        if (cal.isEvent(aItem)) {
+            return "endDate";
+        } else if (cal.isToDo(aItem)) {
+            return "dueDate";
+        }
+        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+    },
+
+    /**
+     * Check if the two dates are on the same day (ignoring time)
+     *
+     * @param date1     The left date to compare
+     * @param date2     The right date to compare
+     * @return          True, if dates are on the same day
+     */
+    sameDay: function(date1, date2) {
+        if (date1 && date2) {
+            if ((date1.day == date2.day) &&
+                (date1.month == date2.month) &&
+                (date1.year == date2.year)) {
+                return true;
+            }
+        }
+        return false;
+    },
+
+    /**
+     * Many computations want to work only with date-times, not with dates.  This
+     * method will return a proper datetime (set to midnight) for a date object.  If
+     * the object is already a datetime, it will simply be returned.
+     *
+     * @param aDate  the date or datetime to check
+     */
+    ensureDateTime: function(aDate) {
+        if (!aDate || !aDate.isDate) {
+            return aDate;
+        }
+        let newDate = aDate.clone();
+        newDate.isDate = false;
+        return newDate;
+    },
+
+    /**
+     * Returns a calIDateTime corresponding to a javascript Date.
+     *
+     * @param aDate     a javascript date
+     * @param aTimezone (optional) a timezone that should be enforced
+     * @returns         a calIDateTime
+     *
+     * @warning  Use of this function is strongly discouraged.  calIDateTime should
+     *           be used directly whenever possible.
+     *           If you pass a timezone, then the passed jsDate's timezone will be ignored,
+     *           but only its local time portions are be taken.
+     */
+    jsDateToDateTime: function(aDate, aTimezone) {
+        let newDate = cal.createDateTime();
+        if (aTimezone) {
+            newDate.resetTo(aDate.getFullYear(),
+                            aDate.getMonth(),
+                            aDate.getDate(),
+                            aDate.getHours(),
+                            aDate.getMinutes(),
+                            aDate.getSeconds(),
+                            aTimezone);
+        } else {
+            newDate.nativeTime = aDate.getTime() * 1000;
+        }
+        return newDate;
+    },
+
+    /**
+     * Convert a calIDateTime to a Javascript date object. This is the
+     * replacement for the former .jsDate property.
+     *
+     * @param cdt       The calIDateTime instnace
+     * @return          The Javascript date equivalent.
+     */
+    dateTimeToJsDate: function(cdt) {
+        if (cdt.timezone.isFloating) {
+            return new Date(cdt.year, cdt.month, cdt.day,
+                            cdt.hour, cdt.minute, cdt.second);
+        } else {
+            return new Date(cdt.nativeTime / 1000);
+        }
+    },
+
+    /**
+     * Gets the list of recent timezones. Optionally retuns the list as
+     * calITimezones.
+     *
+     * @param aConvertZones     (optional) If true, return calITimezones instead
+     * @return                  An array of timezone ids or calITimezones.
+     */
+    getRecentTimezones: function(aConvertZones) {
+        let recentTimezones = JSON.parse(Preferences.get("calendar.timezone.recent", "[]") || "[]");
+        if (!Array.isArray(recentTimezones)) {
+            recentTimezones = [];
+        }
+
+        let tzService = cal.getTimezoneService();
+        if (aConvertZones) {
+            let oldZonesLength = recentTimezones.length;
+            for (let i = 0; i < recentTimezones.length; i++) {
+                let timezone = tzService.getTimezone(recentTimezones[i]);
+                if (timezone) {
+                    // Replace id with found timezone
+                    recentTimezones[i] = timezone;
+                } else {
+                    // Looks like the timezone doesn't longer exist, remove it
+                    recentTimezones.splice(i, 1);
+                    i--;
+                }
+            }
+
+            if (oldZonesLength != recentTimezones.length) {
+                // Looks like the one or other timezone dropped out. Go ahead and
+                // modify the pref.
+                Preferences.set("calendar.timezone.recent", JSON.stringify(recentTimezones));
+            }
+        }
+        return recentTimezones;
+    }
+};
--- a/calendar/base/modules/calUtils.jsm
+++ b/calendar/base/modules/calUtils.jsm
@@ -62,22 +62,16 @@ var cal = {
                                  Components.interfaces.calIFreeBusyService),
     getWeekInfoService: _service("@mozilla.org/calendar/weekinfo-service;1",
                                  Components.interfaces.calIWeekInfoService),
     getDateFormatter: _service("@mozilla.org/calendar/datetime-formatter;1",
                                Components.interfaces.calIDateTimeFormatter),
     getDragService: _service("@mozilla.org/widget/dragservice;1",
                              Components.interfaces.nsIDragService),
 
-    UTC: _cached(() => cal.getTimezoneService().UTC),
-    floating: _cached(() => cal.getTimezoneService().floating),
-    calendarDefaultTimezone: function() {
-        return cal.getTimezoneService().defaultTimezone;
-    },
-
     /**
      * Loads an array of calendar scripts into the passed scope.
      *
      * @param scriptNames an array of calendar script names
      * @param scope       scope to load into
      * @param baseDir     base dir; defaults to calendar-js/
      */
     loadScripts: function(scriptNames, scope, baseDir) {
@@ -206,23 +200,16 @@ var cal = {
      *
      * @param aCalendar
      */
     isTaskCalendar: function(aCalendar) {
         return (aCalendar.getProperty("capabilities.tasks.supported") !== false);
     },
 
     /**
-     * Checks whether a timezone lacks a definition.
-     */
-    isPhantomTimezone: function(timezone) {
-        return (!timezone.icalComponent && !timezone.isUTC && !timezone.isFloating);
-    },
-
-    /**
      * Shifts an item by the given timely offset.
      *
      * @param item an item
      * @param offset an offset (calIDuration)
      */
     shiftItem: function(item, offset) {
         // When modifying dates explicitly using the setters is important
         // since those may triggers e.g. calIRecurrenceInfo::onStartDateChange
@@ -640,37 +627,44 @@ var cal = {
             default:
                 return function(sortEntryA, sortEntryB) {
                     return 0;
                 };
         }
     },
 
     getItemSortKey: function(aItem, aKey, aStartTime) {
+        function nativeTime(calDateTime) {
+            if (calDateTime == null) {
+                return -62168601600000000; // ns value for (0000/00/00 00:00:00)
+            }
+            return calDateTime.nativeTime;
+        }
+
         switch (aKey) {
             case "priority":
                 return aItem.priority || 5;
 
             case "title":
                 return aItem.title || "";
 
             case "entryDate":
-                return cal.nativeTime(aItem.entryDate);
+                return nativeTime(aItem.entryDate);
 
             case "startDate":
-                return cal.nativeTime(aItem.startDate);
+                return nativeTime(aItem.startDate);
 
             case "dueDate":
-                return cal.nativeTime(aItem.dueDate);
+                return nativeTime(aItem.dueDate);
 
             case "endDate":
-                return cal.nativeTime(aItem.endDate);
+                return nativeTime(aItem.endDate);
 
             case "completedDate":
-                return cal.nativeTime(aItem.completedDate);
+                return nativeTime(aItem.completedDate);
 
             case "percentComplete":
                 return aItem.percentComplete;
 
             case "categories":
                 return aItem.getCategories({}).join(", ");
 
             case "location":
@@ -710,80 +704,16 @@ var cal = {
             case "percentComplete":
             case "status":
                 return "number";
             default:
                 return "unknown";
         }
     },
 
-    nativeTimeOrNow: function(calDateTime, sortStartedTime) {
-        // Treat null/0 as 'now' when sort started, so incomplete tasks stay current.
-        // Time is computed once per sort (just before sort) so sort is stable.
-        if (calDateTime == null) {
-            return sortStartedTime.nativeTime;
-        }
-        let nativeTime = calDateTime.nativeTime;
-        if (nativeTime == -62168601600000000) { // nativeTime value for (0000/00/00 00:00:00)
-            return sortStartedTime;
-        }
-        return nativeTime;
-    },
-
-    nativeTime: function(calDateTime) {
-        if (calDateTime == null) {
-            return -62168601600000000; // ns value for (0000/00/00 00:00:00)
-        }
-        return calDateTime.nativeTime;
-    },
-
-    /**
-     * Returns a calIDateTime corresponding to a javascript Date.
-     *
-     * @param aDate     a javascript date
-     * @param aTimezone (optional) a timezone that should be enforced
-     * @returns         a calIDateTime
-     *
-     * @warning  Use of this function is strongly discouraged.  calIDateTime should
-     *           be used directly whenever possible.
-     *           If you pass a timezone, then the passed jsDate's timezone will be ignored,
-     *           but only its local time portions are be taken.
-     */
-    jsDateToDateTime: function(aDate, aTimezone) {
-        let newDate = cal.createDateTime();
-        if (aTimezone) {
-            newDate.resetTo(aDate.getFullYear(),
-                            aDate.getMonth(),
-                            aDate.getDate(),
-                            aDate.getHours(),
-                            aDate.getMinutes(),
-                            aDate.getSeconds(),
-                            aTimezone);
-        } else {
-            newDate.nativeTime = aDate.getTime() * 1000;
-        }
-        return newDate;
-    },
-
-    /**
-     * Convert a calIDateTime to a Javascript date object. This is the
-     * replacement for the former .jsDate property.
-     *
-     * @param cdt       The calIDateTime instnace
-     * @return          The Javascript date equivalent.
-     */
-    dateTimeToJsDate: function(cdt) {
-        if (cdt.timezone.isFloating) {
-            return new Date(cdt.year, cdt.month, cdt.day,
-                            cdt.hour, cdt.minute, cdt.second);
-        } else {
-            return new Date(cdt.nativeTime / 1000);
-        }
-    },
-
     sortEntry: function(aItem) {
         let key = cal.getItemSortKey(aItem, this.mSortKey, this.mSortStartedDate);
         return { mSortKey: key, mItem: aItem };
     },
 
     sortEntryItem: function(sortEntry) {
         return sortEntry.mItem;
     },
@@ -1009,34 +939,18 @@ var cal = {
      *
      * @param obj    object
      * @param prop   property to be deleted on shutdown
      *               (if null, |object| will be deleted)
      */
     registerForShutdownCleanup: shutdownCleanup
 };
 
-// following functions are local to this module:
-
-/**
- * Returns a function that provides cached access to whatever the passed
- * function returns.
- *
- * @param {function} func   The function to call for the value
- * @return {function}       A function that returns the (possibly cached) value
- */
-function _cached(func) {
-    let thing;
-    return function() {
-        if (typeof thing == "undefined") {
-            thing = func();
-        }
-        return thing;
-    };
-}
+// Sub-modules for calUtils
+XPCOMUtils.defineLazyModuleGetter(cal, "dtz", "resource://calendar/modules/calDateTimeUtils.jsm", "caldtz");
 
 /**
  * Returns a function that provides access to the given service.
  *
  * @param cid           The contract id to create
  * @param iid           The interface id to create with
  * @return {function}   A function that returns the given service
  */
@@ -1087,8 +1001,12 @@ function shutdownCleanup(obj, prop) {
 
 // Interim import of all symbols into cal:
 // This should serve as a clean start for new code, e.g. new code could use
 // cal.createDatetime instead of plain createDatetime NOW.
 cal.loadScripts(["calUtils.js"], cal);
 // Some functions in calUtils.js refer to other in the same file, thus include
 // the code in global scope (although only visible to this module file), too:
 cal.loadScripts(["calUtils.js"], Components.utils.getGlobalForObject(cal));
+
+// Backwards compatibility for bug 905097. Please remove with Thunderbird 61.
+Components.utils.import("resource://calendar/modules/calUtilsCompat.jsm");
+injectCalUtilsCompat(cal);
new file mode 100644
--- /dev/null
+++ b/calendar/base/modules/calUtilsCompat.jsm
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Backwards compat for calUtils migration.
+ */
+
+Components.utils.import("resource://gre/modules/Deprecated.jsm");
+
+/* exported injectCalUtilsCompat */
+
+this.EXPORTED_SYMBOLS = ["injectCalUtilsCompat"];
+
+/**
+ * Migration data for backwards compatibility, will be used with
+ * injectCalUtilsCompat.
+ */
+var migrations = {
+    dtz: {
+        now: "now",
+        ensureDateTime: "ensureDateTime",
+        getRecentTimezones: "getRecentTimezones",
+        saveRecentTimezone: "saveRecentTimezone",
+        getDefaultStartDate: "getDefaultStartDate",
+        setDefaultStartEndHour: "setDefaultStartEndHour",
+        calGetStartDateProp: "startDateProp",
+        calGetEndDateProp: "endDateProp",
+        sameDay: "sameDay",
+        jsDateToDateTime: "jsDateToDateTime",
+        dateTimeToJsDate: "dateTimeToJsDate",
+
+        // The following are now getters
+        calendarDefaultTimezone: "defaultTimezone",
+        floating: "floating",
+        UTC: "UTC"
+    }
+};
+
+/**
+ * Generate a forward function on the given global, for the namespace from the
+ * migrations data.
+ *
+ * @param global        The global object to inject on.
+ * @param namespace     The new namespace in the cal object.
+ * @param from          The function/property name being migrated from
+ * @param to            The function/property name being migrated to
+ */
+function generateForward(global, namespace, from, to) {
+    // Protect from footguns
+    if (typeof global[from] != "undefined") {
+        throw new Error(from + " is already defined on the cal. namespace!");
+    }
+
+    global[from] = function(...args) {
+        let suffix = "";
+        let target = global[namespace][to];
+        if (typeof target == "function") {
+            target = target(...args);
+            suffix = "()";
+        }
+
+        Deprecated.warning(`calUtils' cal.${from}() has changed to cal.${namespace}.${to}${suffix}`,
+                           "https://bugzilla.mozilla.org/show_bug.cgi?id=905097",
+                           Components.stack.caller);
+
+        return target;
+    };
+}
+
+/**
+ * Inject the backwards compatibility functions using above migration data
+ *
+ * @param global        The global object to inject on.
+ */
+function injectCalUtilsCompat(global) {
+    for (let [namespace, nsdata] of Object.entries(migrations)) {
+        for (let [from, to] of Object.entries(nsdata)) {
+            generateForward(global, namespace, from, to);
+        }
+    }
+}
--- a/calendar/base/modules/moz.build
+++ b/calendar/base/modules/moz.build
@@ -2,22 +2,24 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXTRA_JS_MODULES += [
     'calAlarmUtils.jsm',
     'calAsyncUtils.jsm',
     'calAuthUtils.jsm',
+    'calDateTimeUtils.jsm',
     'calExtract.jsm',
     'calHashedArray.jsm',
     'calItemUtils.jsm',
     'calIteratorUtils.jsm',
     'calItipUtils.jsm',
     'calPrintUtils.jsm',
     'calProviderUtils.jsm',
     'calRecurrenceUtils.jsm',
     'calUtils.jsm',
+    'calUtilsCompat.jsm',
     'calViewUtils.jsm',
     'calXMLUtils.jsm',
     'ical.js',
 ]
 
--- a/calendar/base/src/calIcsParser.js
+++ b/calendar/base/src/calIcsParser.js
@@ -248,17 +248,21 @@ parserState.prototype = {
 
     /**
      * Checks if the timezones are missing and notifies the user via error console
      *
      * @param item      The item to check for
      * @param date      The datetime object to check with
      */
     checkTimezone: function(item, date) {
-        if (date && cal.isPhantomTimezone(date.timezone)) {
+        function isPhantomTimezone(timezone) {
+            return !timezone.icalComponent && !timezone.isUTC && !timezone.isFloating;
+        }
+
+        if (date && isPhantomTimezone(date.timezone)) {
             let tzid = date.timezone.tzid;
             let hid = item.hashId + "#" + tzid;
             if (!(hid in this.tzErrors)) {
                 // For now, publish errors to console and alert user.
                 // In future, maybe make them available through an interface method
                 // so this UI code can be removed from the parser, and caller can
                 // choose whether to alert, or show user the problem items and ask
                 // for fixes, or something else.
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -2,94 +2,38 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* This file contains commonly used functions in a centralized place so that
  * various components (and other js scopes) don't need to replicate them. Note
  * that loading this file twice in the same scope will throw errors.
  */
 
-/* exported saveRecentTimezone, getCalendarDirectory,
- *          isCalendarWritable, userCanAddItemsToCalendar,
- *          userCanDeleteItemsFromCalendar, attendeeMatchesAddresses,
- *          userCanRespondToInvitation, openCalendarWizard,
- *          openCalendarProperties, calPrint, calRadioGroupSelectItem,
- *          isItemSupported, getPrefCategoriesArray,
+/* exported getCalendarDirectory, isCalendarWritable,
+ *          userCanAddItemsToCalendar, userCanDeleteItemsFromCalendar,
+ *          attendeeMatchesAddresses, userCanRespondToInvitation,
+ *          openCalendarWizard, openCalendarProperties, calPrint,
+ *          calRadioGroupSelectItem, isItemSupported, getPrefCategoriesArray,
  *          setPrefCategoriesFromArray, compareItems, calTryWrappedJSObject,
- *          compareArrays, setDefaultStartEndHour, LOG, WARN, ERROR, showError,
- *          getContrastingTextColor, calGetEndDateProp, checkIfInRange,
- *          getProgressAtom, sendMailTo, sameDay, calSetProdidVersion,
- *          applyAttributeToMenuChildren, isPropertyValueSame,
- *          getParentNodeOrThis, getParentNodeOrThisByAttribute,
- *          setItemProperty, calIterateEmailIdentities, compareItemContent,
- *          binaryInsert, getCompositeCalendar, findItemWindow
+ *          compareArrays, LOG, WARN, ERROR, showError,
+ *          getContrastingTextColor, checkIfInRange, getProgressAtom,
+ *          sendMailTo, calSetProdidVersion, applyAttributeToMenuChildren,
+ *          isPropertyValueSame, getParentNodeOrThis,
+ *          getParentNodeOrThisByAttribute, setItemProperty,
+ *          calIterateEmailIdentities, compareItemContent, binaryInsert,
+ *          getCompositeCalendar, findItemWindow
  */
 
 Components.utils.import("resource:///modules/mailServices.js");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/Preferences.jsm");
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 
 /**
- * Makes sure the given timezone id is part of the list of recent timezones.
- *
- * @param aTzid     The timezone id to add
- */
-function saveRecentTimezone(aTzid) {
-    let recentTimezones = getRecentTimezones();
-    const MAX_RECENT_TIMEZONES = 5; // We don't need a pref for *everything*.
-
-    if (aTzid != calendarDefaultTimezone().tzid &&
-        !recentTimezones.includes(aTzid)) {
-        // Add the timezone if its not already the default timezone
-        recentTimezones.unshift(aTzid);
-        recentTimezones.splice(MAX_RECENT_TIMEZONES);
-        Preferences.set("calendar.timezone.recent", JSON.stringify(recentTimezones));
-    }
-}
-
-/**
- * Gets the list of recent timezones. Optionally retuns the list as
- * calITimezones.
- *
- * @param aConvertZones     (optional) If true, return calITimezones instead
- * @return                  An array of timezone ids or calITimezones.
- */
-function getRecentTimezones(aConvertZones) {
-    let recentTimezones = JSON.parse(Preferences.get("calendar.timezone.recent", "[]") || "[]");
-    if (!Array.isArray(recentTimezones)) {
-        recentTimezones = [];
-    }
-
-    let tzService = cal.getTimezoneService();
-    if (aConvertZones) {
-        let oldZonesLength = recentTimezones.length;
-        for (let i = 0; i < recentTimezones.length; i++) {
-            let timezone = tzService.getTimezone(recentTimezones[i]);
-            if (timezone) {
-                // Replace id with found timezone
-                recentTimezones[i] = timezone;
-            } else {
-                // Looks like the timezone doesn't longer exist, remove it
-                recentTimezones.splice(i, 1);
-                i--;
-            }
-        }
-
-        if (oldZonesLength != recentTimezones.length) {
-            // Looks like the one or other timezone dropped out. Go ahead and
-            // modify the pref.
-            Preferences.set("calendar.timezone.recent", JSON.stringify(recentTimezones));
-        }
-    }
-    return recentTimezones;
-}
-
-/**
  * Format the given string to work inside a CSS rule selector
  * (and as part of a non-unicode preference key).
  *
  * Replaces each space ' ' char with '_'.
  * Replaces each char other than ascii digits and letters, with '-uxHHH-'
  * where HHH is unicode in hexadecimal (variable length, terminated by the '-').
  *
  * Ensures: result only contains ascii digits, letters,'-', and '_'.
@@ -281,25 +225,16 @@ function calPrint(aWindow) {
                        "centerscreen,chrome,resizable");
 }
 
 /**
  * Other functions
  */
 
 /**
- * Returns a calIDateTime that corresponds to the current time in the user's
- * default timezone.
- */
-function now() {
-    let date = cal.jsDateToDateTime(new Date());
-    return date.getInTimezone(calendarDefaultTimezone());
-}
-
-/**
  * Selects an item with id aItemId in the radio group with id aRadioGroupId
  *
  * @param aRadioGroupId  the id of the radio group which contains the item
  * @param aItemId        the item to be selected
  */
 function calRadioGroupSelectItem(aRadioGroupId, aItemId) {
     let radioGroup = document.getElementById(aRadioGroupId);
     let items = radioGroup.getElementsByTagName("radio");
@@ -584,75 +519,16 @@ function compareArrays(aOne, aTwo, compa
     for (let i = 0; i < len; ++i) {
         if (!compareFunc(aOne[i], aTwo[i])) {
             return false;
         }
     }
     return true;
 }
 
-/**
- * Many computations want to work only with date-times, not with dates.  This
- * method will return a proper datetime (set to midnight) for a date object.  If
- * the object is already a datetime, it will simply be returned.
- *
- * @param aDate  the date or datetime to check
- */
-function ensureDateTime(aDate) {
-    if (!aDate || !aDate.isDate) {
-        return aDate;
-    }
-    let newDate = aDate.clone();
-    newDate.isDate = false;
-    return newDate;
-}
-
-/**
- * Get the default event start date. This is the next full hour, or 23:00 if it
- * is past 23:00.
- *
- * @param aReferenceDate    If passed, the time of this date will be modified,
- *                            keeping the date and timezone intact.
- */
-function getDefaultStartDate(aReferenceDate) {
-    let startDate = now();
-    if (aReferenceDate) {
-        let savedHour = startDate.hour;
-        startDate = aReferenceDate;
-        if (!startDate.isMutable) {
-            startDate = startDate.clone();
-        }
-        startDate.isDate = false;
-        startDate.hour = savedHour;
-    }
-
-    startDate.second = 0;
-    startDate.minute = 0;
-    if (startDate.hour < 23) {
-        startDate.hour++;
-    }
-    return startDate;
-}
-
-/**
- * Setup the default start and end hours of the given item. This can be a task
- * or an event.
- *
- * @param aItem             The item to set up the start and end date for.
- * @param aReferenceDate    If passed, the time of this date will be modified,
- *                            keeping the date and timezone intact.
- */
-function setDefaultStartEndHour(aItem, aReferenceDate) {
-    aItem[calGetStartDateProp(aItem)] = getDefaultStartDate(aReferenceDate);
-
-    if (isEvent(aItem)) {
-        aItem.endDate = aItem.startDate.clone();
-        aItem.endDate.minute += Preferences.get("calendar.event.defaultlength", 60);
-    }
-}
 
 /**
  * Helper used in the following log functions to actually log the message.
  * Should not be used outside of this file.
  */
 function _log(message, flag) {
     let frame = Components.stack.caller.caller;
     let filename = frame.filename ? frame.filename.split(" -> ").pop() : null;
@@ -785,54 +661,28 @@ function getContrastingTextColor(bgColor
     if (brightness < 144) {
         return "white";
     }
 
     return "black";
 }
 
 /**
- * Returns the property name used for the start date of an item, ie either an
- * event's start date or a task's entry date.
- */
-function calGetStartDateProp(aItem) {
-    if (isEvent(aItem)) {
-        return "startDate";
-    } else if (isToDo(aItem)) {
-        return "entryDate";
-    }
-    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-}
-
-/**
- * Returns the property name used for the end date of an item, ie either an
- * event's end date or a task's due date.
- */
-function calGetEndDateProp(aItem) {
-    if (isEvent(aItem)) {
-        return "endDate";
-    } else if (isToDo(aItem)) {
-        return "dueDate";
-    }
-    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-}
-
-/**
  * Checks whether the passed item fits into the demanded range.
  *
  * @param item               the item
  * @param rangeStart         (inclusive) range start or null (open range)
  * @param rangeStart         (exclusive) range end or null (open range)
  * @param returnDtstartOrDue returns item's start (or due) date in case
  *                           the item is in the specified Range; null otherwise.
  */
 function checkIfInRange(item, rangeStart, rangeEnd, returnDtstartOrDue) {
     let startDate;
     let endDate;
-    let queryStart = ensureDateTime(rangeStart);
+    let queryStart = cal.ensureDateTime(rangeStart);
     if (isEvent(item)) {
         startDate = item.startDate;
         if (!startDate) { // DTSTART mandatory
             // xxx todo: should we assert this case?
             return null;
         }
         endDate = item.endDate || startDate;
     } else {
@@ -841,28 +691,28 @@ function checkIfInRange(item, rangeStart
         if (!item.entryDate) {
             if (returnDtstartOrDue) { // DTSTART or DUE mandatory
                 return null;
             }
             // 3.6.2. To-do Component
             // A "VTODO" calendar component without the "DTSTART" and "DUE" (or
             // "DURATION") properties specifies a to-do that will be associated
             // with each successive calendar date, until it is completed.
-            let completedDate = ensureDateTime(item.completedDate);
-            dueDate = ensureDateTime(dueDate);
+            let completedDate = cal.ensureDateTime(item.completedDate);
+            dueDate = cal.ensureDateTime(dueDate);
             return !completedDate || !queryStart ||
                    completedDate.compare(queryStart) > 0 ||
                    (dueDate && dueDate.compare(queryStart) >= 0);
         }
         endDate = dueDate || startDate;
     }
 
-    let start = ensureDateTime(startDate);
-    let end = ensureDateTime(endDate);
-    let queryEnd = ensureDateTime(rangeEnd);
+    let start = cal.ensureDateTime(startDate);
+    let end = cal.ensureDateTime(endDate);
+    let queryEnd = cal.ensureDateTime(rangeEnd);
 
     if (start.compare(end) == 0) {
         if ((!queryStart || start.compare(queryStart) >= 0) &&
             (!queryEnd || start.compare(queryEnd) < 0)) {
             return startDate;
         }
     } else if ((!queryEnd || start.compare(queryEnd) < 0) &&
                (!queryStart || end.compare(queryStart) > 0)) {
@@ -1082,27 +932,16 @@ calOperationGroup.prototype = {
             this.mSubOperations = [];
             for (let operation of subOperations) {
                 operation.cancel(Components.interfaces.calIErrors.OPERATION_CANCELLED);
             }
         }
     }
 };
 
-function sameDay(date1, date2) {
-    if (date1 && date2) {
-        if ((date1.day == date2.day) &&
-            (date1.month == date2.month) &&
-            (date1.year == date2.year)) {
-            return true;
-        }
-    }
-    return false;
-}
-
 /**
  * Centralized funtions for accessing prodid and version
  */
 function calGetProductId() {
     return "-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN";
 }
 function calGetProductVersion() {
     return "2.0";
--- a/calendar/providers/gdata/components/calGoogleCalendar.js
+++ b/calendar/providers/gdata/components/calGoogleCalendar.js
@@ -6,16 +6,17 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/PromiseUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Components.utils.import("resource://calendar/modules/calAsyncUtils.jsm");
 Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 
+Components.utils.import("resource://gdata-provider/modules/calUtilsShim.jsm");
 Components.utils.import("resource://gdata-provider/modules/gdataLogging.jsm");
 Components.utils.import("resource://gdata-provider/modules/gdataRequest.jsm");
 Components.utils.import("resource://gdata-provider/modules/gdataSession.jsm");
 Components.utils.import("resource://gdata-provider/modules/gdataUtils.jsm");
 
 var cICL = Components.interfaces.calIChangeLog;
 var cIOL = Components.interfaces.calIOperationListener;
 
--- a/calendar/providers/gdata/content/gdata-calendar-event-dialog.js
+++ b/calendar/providers/gdata/content/gdata-calendar-event-dialog.js
@@ -1,14 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://gdata-provider/modules/gdataUtils.jsm");
 
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://gdata-provider/modules/calUtilsShim.jsm");
+
 (function() {
 
     // Older versions of Lightning don't have this variable.
     if (!("gOldEndTimezone" in window)) {
         window.gOldEndTimezone = null;
     }
 
     monkeyPatch(window, "updateCalendar", function(protofunc, ...args) {
new file mode 100644
--- /dev/null
+++ b/calendar/providers/gdata/modules/calUtilsShim.jsm
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
+this.EXPORTED_SYMBOLS = ["cal"];
+
+if (!cal.dtz) {
+    cal.dtz = {
+        get defaultTimezone() { return cal.calendarDefaultTimezone(); },
+        get floating() { return cal.floating(); },
+        get UTC() { return cal.UTC(); },
+
+        now: (...args) => cal.now(...args),
+        ensureDateTime: (...args) => cal.ensureDateTime(...args),
+        getRecentTimezones: (...args) => cal.getRecentTimezones(...args),
+        saveRecentTimezone: (...args) => cal.saveRecentTimezone(...args),
+        getDefaultStartDate: (...args) => cal.getDefaultStartDate(...args),
+        setDefaultStartEndHour: (...args) => cal.setDefaultStartEndHour(...args),
+        startDateProp: (...args) => cal.calGetStartDateProp(...args),
+        endDateProp: (...args) => cal.calGetEndDateProp(...args),
+        sameDay: (...args) => cal.sameDay(...args),
+        jsDateToDateTime: (...args) => cal.jsDateToDateTime(...args),
+        dateTimeToJsDate: (...args) => cal.dateTimeToJsDate(...args)
+    };
+}
--- a/calendar/providers/gdata/modules/gdataSession.jsm
+++ b/calendar/providers/gdata/modules/gdataSession.jsm
@@ -13,16 +13,18 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/PromiseUtils.jsm");
 Components.utils.import("resource://gre/modules/Timer.jsm");
 
 Components.utils.import("resource:///modules/iteratorUtils.jsm");
 
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
 
+Components.utils.import("resource://gdata-provider/modules/calUtilsShim.jsm");
+
 var cIFBI = Components.interfaces.calIFreeBusyInterval;
 var nIPM = Components.interfaces.nsIPermissionManager;
 
 var NOTIFY_TIMEOUT = 60 * 1000;
 
 var EXPORTED_SYMBOLS = ["getGoogleSessionManager"];
 
 var gdataSessionMap = new Map();
--- a/calendar/providers/gdata/modules/gdataUtils.jsm
+++ b/calendar/providers/gdata/modules/gdataUtils.jsm
@@ -9,16 +9,18 @@ Components.utils.import("resource://gdat
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/Preferences.jsm");
 Components.utils.import("resource://gre/modules/PromiseUtils.jsm");
 
 Components.utils.import("resource://calendar/modules/calAsyncUtils.jsm");
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
 
+Components.utils.import("resource://gdata-provider/modules/calUtilsShim.jsm");
+
 var cIE = Components.interfaces.calIErrors;
 
 var FOUR_WEEKS_IN_MINUTES = 40320;
 
 var EXPORTED_SYMBOLS = [
     "ItemToJSON", "JSONToItem", "ItemSaver",
     "checkResolveConflict", "getGoogleId",
     "getItemMetadata", "saveItemMetadata",
--- a/calendar/providers/gdata/moz.build
+++ b/calendar/providers/gdata/moz.build
@@ -7,16 +7,17 @@ XPI_NAME = 'gdata-provider'
 
 FINAL_TARGET_PP_FILES += ['install.rdf']
 
 DIRS += [
     'locales',
 ]
 
 EXTRA_JS_MODULES += [
+    'modules/calUtilsShim.jsm',
     'modules/gdataLogging.jsm',
     'modules/gdataRequest.jsm',
     'modules/gdataSession.jsm',
     'modules/gdataUtils.jsm',
     'modules/OAuth2.jsm',
     'modules/timezoneMap.jsm',
 ]