Bug 1626739 - Upgrade ical.js to version 1.4.0. r=darktrojan
authorPhilipp Kewisch <mozilla@kewis.ch>
Sat, 11 Apr 2020 13:50:43 +0300
changeset 29233 de2bf949a75c5957685282c426ae4d88f36fd7d7
parent 29232 9361c493ce66bcc4bacac20f893e6c8beca893a9
child 29234 0f0c5a1c67f07885d4a38fc3d74c19ef6fc4d28f
push id17271
push usermkmelin@iki.fi
push dateSat, 11 Apr 2020 10:58:29 +0000
treeherdercomm-central@26b9bb2ef883 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdarktrojan
bugs1626739
Bug 1626739 - Upgrade ical.js to version 1.4.0. r=darktrojan
calendar/base/modules/Ical.jsm
--- a/calendar/base/modules/Ical.jsm
+++ b/calendar/base/modules/Ical.jsm
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * This is ical.js from <https://github.com/mozilla-comm/ical.js>.
  *
  * If you would like to change anything in ical.js, it is required to do so
  * upstream first.
  *
- * Current ical.js git revision: 3f219e04f238bcad6c964e8d93b21de49e5f5047 (v1.3.0)
+ * Current ical.js git revision: 7c99a434b38ad5d670a0e778ef055a2e17ccb3db (v1.4.0)
  */
 
 var EXPORTED_SYMBOLS = ["ICAL", "unwrap", "unwrapSetter", "unwrapSingle", "wrapGetter"];
 
 function wrapGetter(type, val) {
     return val ? new type(val) : null;
 }
 
@@ -717,93 +717,117 @@ ICAL.design = (function() {
         return aBinary.toString();
       }
     },
     "cal-address": {
       // needs to be an uri
     },
     "date": {
       decorate: function(aValue, aProp) {
-        return ICAL.Time.fromDateString(aValue, aProp);
+        if (design.strict) {
+          return ICAL.Time.fromDateString(aValue, aProp);
+        } else {
+          return ICAL.Time.fromString(aValue, aProp);
+        }
       },
 
       /**
        * undecorates a time object.
        */
       undecorate: function(aValue) {
         return aValue.toString();
       },
 
       fromICAL: function(aValue) {
         // from: 20120901
         // to: 2012-09-01
-        return aValue.substr(0, 4) + '-' +
-               aValue.substr(4, 2) + '-' +
-               aValue.substr(6, 2);
+        if (!design.strict && aValue.length >= 15) {
+          // This is probably a date-time, e.g. 20120901T130000Z
+          return icalValues["date-time"].fromICAL(aValue);
+        } else {
+          return aValue.substr(0, 4) + '-' +
+                 aValue.substr(4, 2) + '-' +
+                 aValue.substr(6, 2);
+        }
       },
 
       toICAL: function(aValue) {
         // from: 2012-09-01
         // to: 20120901
-
-        if (aValue.length > 11) {
+        var len = aValue.length;
+
+        if (len == 10) {
+          return aValue.substr(0, 4) +
+                 aValue.substr(5, 2) +
+                 aValue.substr(8, 2);
+        } else if (len >= 19) {
+          return icalValues["date-time"].toICAL(aValue);
+        } else {
           //TODO: serialize warning?
           return aValue;
         }
 
-        return aValue.substr(0, 4) +
-               aValue.substr(5, 2) +
-               aValue.substr(8, 2);
       }
     },
     "date-time": {
       fromICAL: function(aValue) {
         // from: 20120901T130000
         // to: 2012-09-01T13:00:00
-        var result = aValue.substr(0, 4) + '-' +
-                     aValue.substr(4, 2) + '-' +
-                     aValue.substr(6, 2) + 'T' +
-                     aValue.substr(9, 2) + ':' +
-                     aValue.substr(11, 2) + ':' +
-                     aValue.substr(13, 2);
-
-        if (aValue[15] && aValue[15] === 'Z') {
-          result += 'Z';
-        }
-
-        return result;
+        if (!design.strict && aValue.length == 8) {
+          // This is probably a date, e.g. 20120901
+          return icalValues.date.fromICAL(aValue);
+        } else {
+          var result = aValue.substr(0, 4) + '-' +
+                       aValue.substr(4, 2) + '-' +
+                       aValue.substr(6, 2) + 'T' +
+                       aValue.substr(9, 2) + ':' +
+                       aValue.substr(11, 2) + ':' +
+                       aValue.substr(13, 2);
+
+          if (aValue[15] && aValue[15] === 'Z') {
+            result += 'Z';
+          }
+
+          return result;
+        }
       },
 
       toICAL: function(aValue) {
         // from: 2012-09-01T13:00:00
         // to: 20120901T130000
-
-        if (aValue.length < 19) {
+        var len = aValue.length;
+
+        if (len == 10 && !design.strict) {
+          return icalValues.date.toICAL(aValue);
+        } else if (len >= 19) {
+          var result = aValue.substr(0, 4) +
+                       aValue.substr(5, 2) +
+                       // grab the (DDTHH) segment
+                       aValue.substr(8, 5) +
+                       // MM
+                       aValue.substr(14, 2) +
+                       // SS
+                       aValue.substr(17, 2);
+
+          if (aValue[19] && aValue[19] === 'Z') {
+            result += 'Z';
+          }
+          return result;
+        } else {
           // TODO: error
           return aValue;
         }
-
-        var result = aValue.substr(0, 4) +
-                     aValue.substr(5, 2) +
-                     // grab the (DDTHH) segment
-                     aValue.substr(8, 5) +
-                     // MM
-                     aValue.substr(14, 2) +
-                     // SS
-                     aValue.substr(17, 2);
-
-        if (aValue[19] && aValue[19] === 'Z') {
-          result += 'Z';
-        }
-
-        return result;
       },
 
       decorate: function(aValue, aProp) {
-        return ICAL.Time.fromDateTimeString(aValue, aProp);
+        if (design.strict) {
+          return ICAL.Time.fromDateTimeString(aValue, aProp);
+        } else {
+          return ICAL.Time.fromString(aValue, aProp);
+        }
       },
 
       undecorate: function(aValue) {
         return aValue.toString();
       }
     },
     duration: {
       decorate: function(aValue) {
@@ -822,27 +846,35 @@ ICAL.design = (function() {
         if (!ICAL.Duration.isValueString(parts[1])) {
           parts[1] = icalValues['date-time'].fromICAL(parts[1]);
         }
 
         return parts;
       },
 
       toICAL: function(parts) {
-        parts[0] = icalValues['date-time'].toICAL(parts[0]);
+        if (!design.strict && parts[0].length == 10) {
+          parts[0] = icalValues.date.toICAL(parts[0]);
+        } else {
+          parts[0] = icalValues['date-time'].toICAL(parts[0]);
+        }
 
         if (!ICAL.Duration.isValueString(parts[1])) {
-          parts[1] = icalValues['date-time'].toICAL(parts[1]);
+          if (!design.strict && parts[1].length == 10) {
+            parts[1] = icalValues.date.toICAL(parts[1]);
+          } else {
+            parts[1] = icalValues['date-time'].toICAL(parts[1]);
+          }
         }
 
         return parts.join("/");
       },
 
       decorate: function(aValue, aProp) {
-        return ICAL.Period.fromJSON(aValue, aProp);
+        return ICAL.Period.fromJSON(aValue, aProp, !design.strict);
       },
 
       undecorate: function(aValue) {
         return aValue.toJSON();
       }
     },
     recur: {
       fromICAL: function(string) {
@@ -1321,16 +1353,20 @@ ICAL.design = (function() {
      *
      * @typedef {Object} designSet
      * @memberOf ICAL.design
      * @property {Object} value       Definitions for value types, keys are type names
      * @property {Object} param       Definitions for params, keys are param names
      * @property {Object} property    Defintions for properties, keys are property names
      */
 
+    /**
+     * Can be set to false to make the parser more lenient.
+     */
+    strict: true,
 
     /**
      * The default set for new properties and components if none is specified.
      * @type {ICAL.design.designSet}
      */
     defaultSet: icalSet,
 
     /**
@@ -2045,16 +2081,18 @@ ICAL.parse = (function() {
     while ((pos !== false) &&
            (pos = helpers.unescapedIndexOf(line, delim, pos + 1)) !== -1) {
 
       name = line.substr(lastParam + 1, pos - lastParam - 1);
       if (name.length == 0) {
         throw new ParserError("Empty parameter name in '" + line + "'");
       }
       lcname = name.toLowerCase();
+      mvdelim = false;
+      multiValue = false;
 
       if (lcname in designSet.param && designSet.param[lcname].valueType) {
         type = designSet.param[lcname].valueType;
       } else {
         type = DEFAULT_PARAM_TYPE;
       }
 
       if (lcname in designSet.param) {
@@ -2112,19 +2150,32 @@ ICAL.parse = (function() {
         }
 
         value = line.substr(valuePos, nextPos - valuePos);
       }
 
       value = parser._rfc6868Escape(value);
       if (multiValue) {
         var delimiter = mvdelim || multiValue;
-        result[lcname] = parser._parseMultiValue(value, delimiter, type, [], null, designSet);
+        value = parser._parseMultiValue(value, delimiter, type, [], null, designSet);
       } else {
-        result[lcname] = parser._parseValue(value, type, designSet);
+        value = parser._parseValue(value, type, designSet);
+      }
+
+      if (multiValue && (lcname in result)) {
+        if (Array.isArray(result[lcname])) {
+          result[lcname].push(value);
+        } else {
+          result[lcname] = [
+            result[lcname],
+            value
+          ];
+        }
+      } else {
+        result[lcname] = value;
       }
     }
     return [result, value, valuePos];
   };
 
   /**
    * Internal helper for rfc6868. Exposing this on ICAL.parse so that
    * hackers can disable the rfc6868 parsing if the really need to.
@@ -3758,28 +3809,37 @@ ICAL.Binary = (function() {
 
   /**
    * Returns a new period instance from the given jCal data array. The first
    * member is always the start date string, the second member is either a
    * duration or end date string.
    *
    * @param {Array<String,String>} aData    The jCal data array
    * @param {ICAL.Property} aProp           The property this jCal data is on
+   * @param {Boolean} aLenient              If true, data value can be both date and date-time
    * @return {ICAL.Period}                  The period instance
    */
-  ICAL.Period.fromJSON = function(aData, aProp) {
+  ICAL.Period.fromJSON = function(aData, aProp, aLenient) {
+    function fromDateOrDateTimeString(aValue, aProp) {
+      if (aLenient) {
+        return ICAL.Time.fromString(aValue, aProp);
+      } else {
+        return ICAL.Time.fromDateTimeString(aValue, aProp);
+      }
+    }
+
     if (ICAL.Duration.isValueString(aData[1])) {
       return ICAL.Period.fromData({
-        start: ICAL.Time.fromDateTimeString(aData[0], aProp),
+        start: fromDateOrDateTimeString(aData[0], aProp),
         duration: ICAL.Duration.fromString(aData[1])
       });
     } else {
       return ICAL.Period.fromData({
-        start: ICAL.Time.fromDateTimeString(aData[0], aProp),
-        end: ICAL.Time.fromDateTimeString(aData[1], aProp)
+        start: fromDateOrDateTimeString(aData[0], aProp),
+        end: fromDateOrDateTimeString(aData[1], aProp)
       });
     }
   };
 })();
 /* 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/.
  * Portions Copyright (C) Philipp Kewisch, 2011-2015 */
@@ -4576,17 +4636,17 @@ ICAL.Binary = (function() {
     return 0;
   };
 
   /**
    * Convert the date/time from one zone to the next.
    *
    * @param {ICAL.Time} tt                  The time to convert
    * @param {ICAL.Timezone} from_zone       The source zone to convert from
-   * @param {ICAL.Timezone} to_zone         The target zone to conver to
+   * @param {ICAL.Timezone} to_zone         The target zone to convert to
    * @return {ICAL.Time}                    The converted date/time object
    */
   ICAL.Timezone.convert_time = function icaltimezone_convert_time(tt, from_zone, to_zone) {
     if (tt.isDate ||
         from_zone.tzid == to_zone.tzid ||
         from_zone == ICAL.Timezone.localTimezone ||
         to_zone == ICAL.Timezone.localTimezone) {
       tt.zone = to_zone;
@@ -4684,16 +4744,20 @@ ICAL.TimezoneService = (function() {
    * Singleton class to contain timezones.  Right now its all manual registry in
    * the future we may use this class to download timezone information or handle
    * loading pre-expanded timezones.
    *
    * @namespace
    * @alias ICAL.TimezoneService
    */
   var TimezoneService = {
+    get count() {
+      return Object.keys(zones).length;
+    },
+
     reset: function() {
       zones = Object.create(null);
       var utc = ICAL.Timezone.utcTimezone;
 
       zones.Z = utc;
       zones.UTC = utc;
       zones.GMT = utc;
     },
@@ -4980,20 +5044,23 @@ ICAL.TimezoneService = (function() {
       }
 
       this._cachedUnixTime = null;
       return this;
     },
 
     /**
      * Calculate the day of week.
+     * @param {ICAL.Time.weekDay=} aWeekStart
+     *        The week start weekday, defaults to SUNDAY
      * @return {ICAL.Time.weekDay}
      */
-    dayOfWeek: function icaltime_dayOfWeek() {
-      var dowCacheKey = (this.year << 9) + (this.month << 5) + this.day;
+    dayOfWeek: function icaltime_dayOfWeek(aWeekStart) {
+      var firstDow = aWeekStart || ICAL.Time.SUNDAY;
+      var dowCacheKey = (this.year << 12) + (this.month << 8) + (this.day << 3) + firstDow;
       if (dowCacheKey in ICAL.Time._dowCache) {
         return ICAL.Time._dowCache[dowCacheKey];
       }
 
       // Using Zeller's algorithm
       var q = this.day;
       var m = this.month + (this.month < 3 ? 12 : 0);
       var Y = this.year - (this.month < 3 ? 1 : 0);
@@ -5001,18 +5068,18 @@ ICAL.TimezoneService = (function() {
       var h = (q + Y + ICAL.helpers.trunc(((m + 1) * 26) / 10) + ICAL.helpers.trunc(Y / 4));
       /* istanbul ignore else */
       if (true /* gregorian */) {
         h += ICAL.helpers.trunc(Y / 100) * 6 + ICAL.helpers.trunc(Y / 400);
       } else {
         h += 5;
       }
 
-      // Normalize to 1 = sunday
-      h = ((h + 6) % 7) + 1;
+      // Normalize to 1 = wkst
+      h = ((h + 7 - firstDow) % 7) + 1;
       ICAL.Time._dowCache[dowCacheKey] = h;
       return h;
     },
 
     /**
      * Calculate the day of year.
      * @return {Number}
      */
@@ -5898,21 +5965,22 @@ ICAL.TimezoneService = (function() {
 
     return time;
   };
 
   /**
    * Returns a new ICAL.Time instance from a date or date-time string,
    *
    * @param {String} aValue         The string to create from
+   * @param {ICAL.Property=} prop   The property the date belongs to
    * @return {ICAL.Time}            The date/time instance
    */
-  ICAL.Time.fromString = function fromString(aValue) {
+  ICAL.Time.fromString = function fromString(aValue, aProperty) {
     if (aValue.length > 10) {
-      return ICAL.Time.fromDateTimeString(aValue);
+      return ICAL.Time.fromDateTimeString(aValue, aProperty);
     } else {
       return ICAL.Time.fromDateString(aValue);
     }
   };
 
   /**
    * Creates a new ICAL.Time instance from the given Javascript Date.
    *
@@ -6632,36 +6700,45 @@ ICAL.TimezoneService = (function() {
     return result;
   }
 
   /**
    * Convert an ical representation of a day (SU, MO, etc..)
    * into a numeric value of that day.
    *
    * @param {String} string     The iCalendar day name
+   * @param {ICAL.Time.weekDay=} aWeekStart
+   *        The week start weekday, defaults to SUNDAY
    * @return {Number}           Numeric value of given day
    */
-  ICAL.Recur.icalDayToNumericDay = function toNumericDay(string) {
+  ICAL.Recur.icalDayToNumericDay = function toNumericDay(string, aWeekStart) {
     //XXX: this is here so we can deal
     //     with possibly invalid string values.
-
-    return DOW_MAP[string];
+    var firstDow = aWeekStart || ICAL.Time.SUNDAY;
+    return ((DOW_MAP[string] - firstDow + 7) % 7) + 1;
   };
 
   /**
    * Convert a numeric day value into its ical representation (SU, MO, etc..)
    *
    * @param {Number} num        Numeric value of given day
+   * @param {ICAL.Time.weekDay=} aWeekStart
+   *        The week start weekday, defaults to SUNDAY
    * @return {String}           The ICAL day value, e.g SU,MO,...
    */
-  ICAL.Recur.numericDayToIcalDay = function toIcalDay(num) {
+  ICAL.Recur.numericDayToIcalDay = function toIcalDay(num, aWeekStart) {
     //XXX: this is here so we can deal with possibly invalid number values.
     //     Also, this allows consistent mapping between day numbers and day
     //     names for external users.
-    return REVERSE_DOW_MAP[num];
+    var firstDow = aWeekStart || ICAL.Time.SUNDAY;
+    var dow = (num + firstDow - ICAL.Time.SUNDAY);
+    if (dow > 7) {
+      dow -= 7;
+    }
+    return REVERSE_DOW_MAP[dow];
   };
 
   var VALID_DAY_NAMES = /^(SU|MO|TU|WE|TH|FR|SA)$/;
   var VALID_BYDAY_PART = /^([+-])?(5[0-3]|[1-4][0-9]|[1-9])?(SU|MO|TU|WE|TH|FR|SA)$/;
 
   /**
    * Possible frequency values for the FREQ part
    * (YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY)
@@ -6991,17 +7068,17 @@ ICAL.RecurIterator = (function() {
     init: function icalrecur_iterator_init() {
       this.initialized = true;
       this.last = this.dtstart.clone();
       var parts = this.by_data;
 
       if ("BYDAY" in parts) {
         // libical does this earlier when the rule is loaded, but we postpone to
         // now so we can preserve the original order.
-        this.sort_byday_rules(parts.BYDAY, this.rule.wkst);
+        this.sort_byday_rules(parts.BYDAY);
       }
 
       // If the BYYEARDAY appares, no other date rule part may appear
       if ("BYYEARDAY" in parts) {
         if ("BYMONTH" in parts || "BYWEEKNO" in parts ||
             "BYMONTHDAY" in parts || "BYDAY" in parts) {
           throw new Error("Invalid BYYEARDAY rule");
         }
@@ -7034,21 +7111,21 @@ ICAL.RecurIterator = (function() {
       this.last.second = this.setup_defaults("BYSECOND", "SECONDLY", this.dtstart.second);
       this.last.minute = this.setup_defaults("BYMINUTE", "MINUTELY", this.dtstart.minute);
       this.last.hour = this.setup_defaults("BYHOUR", "HOURLY", this.dtstart.hour);
       this.last.day = this.setup_defaults("BYMONTHDAY", "DAILY", this.dtstart.day);
       this.last.month = this.setup_defaults("BYMONTH", "MONTHLY", this.dtstart.month);
 
       if (this.rule.freq == "WEEKLY") {
         if ("BYDAY" in parts) {
-          var bydayParts = this.ruleDayOfWeek(parts.BYDAY[0]);
+          var bydayParts = this.ruleDayOfWeek(parts.BYDAY[0], this.rule.wkst);
           var pos = bydayParts[0];
           var dow = bydayParts[1];
-          var wkdy = dow - this.last.dayOfWeek();
-          if ((this.last.dayOfWeek() < dow && wkdy >= 0) || wkdy < 0) {
+          var wkdy = dow - this.last.dayOfWeek(this.rule.wkst);
+          if ((this.last.dayOfWeek(this.rule.wkst) < dow && wkdy >= 0) || wkdy < 0) {
             // Initial time is after first day of BYDAY data
             this.last.day += wkdy;
           }
         } else {
           var dayName = ICAL.Recur.numericDayToIcalDay(this.dtstart.dayOfWeek());
           parts.BYDAY = [dayName];
         }
       }
@@ -7658,21 +7735,26 @@ ICAL.RecurIterator = (function() {
             doy += 1;
             year += 1;
         }
         var next = ICAL.Time.fromDayOfYear(doy, year);
         this.last.day = next.day;
         this.last.month = next.month;
     },
 
-    ruleDayOfWeek: function ruleDayOfWeek(dow) {
+    /**
+     * @param dow (eg: '1TU', '-1MO')
+     * @param {ICAL.Time.weekDay=} aWeekStart The week start weekday
+     * @return [pos, numericDow] (eg: [1, 3]) numericDow is relative to aWeekStart
+     */
+    ruleDayOfWeek: function ruleDayOfWeek(dow, aWeekStart) {
       var matches = dow.match(/([+-]?[0-9])?(MO|TU|WE|TH|FR|SA|SU)/);
       if (matches) {
         var pos = parseInt(matches[1] || 0, 10);
-        dow = ICAL.Recur.icalDayToNumericDay(matches[2]);
+        dow = ICAL.Recur.icalDayToNumericDay(matches[2], aWeekStart);
         return [pos, dow];
       } else {
         return [0, 0];
       }
     },
 
     next_generic: function next_generic(aRuleType, aInterval, aDateAttr,
                                         aFollowingAttr, aPreviousIncr) {
@@ -8115,25 +8197,21 @@ ICAL.RecurIterator = (function() {
       if (this.has_by_data('BYSETPOS')) {
         var idx = this.by_data.BYSETPOS.indexOf(aPos);
         // negative numbers are not false-y
         return idx !== -1;
       }
       return false;
     },
 
-    sort_byday_rules: function icalrecur_sort_byday_rules(aRules, aWeekStart) {
+    sort_byday_rules: function icalrecur_sort_byday_rules(aRules) {
       for (var i = 0; i < aRules.length; i++) {
         for (var j = 0; j < i; j++) {
-          var one = this.ruleDayOfWeek(aRules[j])[1];
-          var two = this.ruleDayOfWeek(aRules[i])[1];
-          one -= aWeekStart;
-          two -= aWeekStart;
-          if (one < 0) one += 7;
-          if (two < 0) two += 7;
+          var one = this.ruleDayOfWeek(aRules[j], this.rule.wkst)[1];
+          var two = this.ruleDayOfWeek(aRules[i], this.rule.wkst)[1];
 
           if (one > two) {
             var tmp = aRules[i];
             aRules[i] = aRules[j];
             aRules[j] = tmp;
           }
         }
       }
@@ -9132,17 +9210,17 @@ ICAL.Event = (function() {
      * The duration. This can be the result directly from the property, or the
      * duration calculated from start date and end date. Setting the property
      * will remove any `dtend` properties.
      * @type {ICAL.Duration}
      */
     get duration() {
       var duration = this._firstProp('duration');
       if (!duration) {
-        return this.endDate.subtractDate(this.startDate);
+        return this.endDate.subtractDateTz(this.startDate);
       }
       return duration;
     },
 
     set duration(value) {
       if (this.component.hasProperty('dtend')) {
         this.component.removeProperty('dtend');
       }
@@ -9228,17 +9306,17 @@ ICAL.Event = (function() {
      * The recurrence id for this event. See {@tutorial terminology} for details.
      * @type {ICAL.Time}
      */
     get recurrenceId() {
       return this._firstProp('recurrence-id');
     },
 
     set recurrenceId(value) {
-      this._setProp('recurrence-id', value);
+      this._setTime('recurrence-id', value);
     },
 
     /**
      * Set/update a time property's value.
      * This will also update the TZID of the property.
      *
      * TODO: this method handles the case where we are switching
      * from a known timezone to an implied timezone (one without TZID).