Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

update ical.js for tests add commander.js as dev dep

  • Loading branch information...
commit ae75f140a6263b78aa096534db68f4b53805c36a 1 parent 13966eb
James Lal authored
1  Makefile
@@ -58,7 +58,6 @@ test-node:
58 58
 		--reporter $(REPORTER) \
59 59
 		--growl test/helper.js \
60 60
 		test/caldav/sax/*_test.js \
61  
-		test/caldav/templates/*_test.js \
62 61
 		test/caldav/request/*_test.js \
63 62
 		test/caldav/*_test.js
64 63
 
3  lib/caldav/request/calendar_query.js
@@ -50,7 +50,8 @@
50 50
         content += this.filter.toString();
51 51
       }
52 52
 
53  
-      return this.template.render(content);
  53
+      var out = this.template.render(content);
  54
+      return out;
54 55
     }
55 56
 
56 57
   };
2  lib/caldav/request/resources.js
@@ -29,9 +29,7 @@
29 29
 
30 30
         for (url in root) {
31 31
           collection = root[url];
32  
-
33 32
           resources = collection.resourcetype;
34  
-
35 33
           if (resources.value.forEach) {
36 34
 
37 35
             resources.value.forEach(function(type) {
4  package.json
@@ -18,10 +18,12 @@
18 18
     "xmlhttprequest": "1.4.2"
19 19
   },
20 20
   "devDependencies": {
  21
+    "debug": "~0.7",
21 22
     "mocha": "~1.1",
22 23
     "chai": "~1.0",
23 24
     "test-agent": "~0.6",
24  
-    "http-proxy": "~0.8"
  25
+    "http-proxy": "~0.8",
  26
+    "commander": "~1.0"
25 27
   },
26 28
   "license": "MIT",
27 29
   "engine": {
2  test-agent/config.json
... ...
@@ -1,3 +1,3 @@
1 1
 {"tests": [
2  
-"/test/caldav/connection_test.js","/test/caldav/index_test.js","/test/caldav/request/abstract_test.js","/test/caldav/request/calendar_home_test.js","/test/caldav/request/calendar_query_test.js","/test/caldav/request/propfind_test.js","/test/caldav/request/resources_test.js","/test/caldav/resources/calendar_test.js","/test/caldav/sax/base_test.js","/test/caldav/sax/calendar_data_handler_test.js","/test/caldav/sax/dav_response_test.js","/test/caldav/sax_test.js","/test/caldav/template_test.js","/test/caldav/templates/calendar_data_test.js","/test/caldav/templates/calendar_filter_test.js","/test/caldav/xhr_test.js"
  2
+"/test/caldav/connection_test.js","/test/caldav/index_test.js","/test/caldav/query_builder_test.js","/test/caldav/request/abstract_test.js","/test/caldav/request/asset_test.js","/test/caldav/request/calendar_home_test.js","/test/caldav/request/calendar_query_test.js","/test/caldav/request/propfind_test.js","/test/caldav/request/resources_test.js","/test/caldav/resources/calendar_test.js","/test/caldav/sax/base_test.js","/test/caldav/sax/calendar_data_handler_test.js","/test/caldav/sax/dav_response_test.js","/test/caldav/sax_test.js","/test/caldav/template_test.js","/test/caldav/xhr_test.js","/test/servers/calendar_home_test.js"
3 3
   ]}
2  test/helper.js
@@ -175,8 +175,6 @@
175 175
     testSupport.lib('sax/dav_response');
176 176
     testSupport.lib('request/abstract');
177 177
     testSupport.lib('template');
178  
-    testSupport.lib('templates/calendar_data');
179  
-    testSupport.lib('templates/calendar_filter');
180 178
     testSupport.helper('fake_xhr');
181 179
     testSupport.lib('request/propfind');
182 180
 
1,798  test/support/ical.js
@@ -29,6 +29,66 @@ ICAL.helpers = {
29 29
     };
30 30
   },
31 31
 
  32
+  /**
  33
+   * Creates or returns a class instance
  34
+   * of a given type with the initialization
  35
+   * data if the data is not already an instance
  36
+   * of the given type.
  37
+   *
  38
+   *
  39
+   * Example:
  40
+   *
  41
+   *    var time = new ICAL.icaltime(...);
  42
+   *    var result = ICAL.helpers.formatClassType(time, ICAL.icaltime);
  43
+   *
  44
+   *    (result instanceof ICAL.icaltime)
  45
+   *    // => true
  46
+   *
  47
+   *    result = ICAL.helpers.formatClassType({}, ICAL.icaltime);
  48
+   *    (result isntanceof ICAL.icaltime)
  49
+   *    // => true
  50
+   *
  51
+   *
  52
+   * @param {Object} data object initialization data.
  53
+   * @param {Object} type object type (like ICAL.icaltime).
  54
+   */
  55
+  formatClassType: function formatClassType(data, type) {
  56
+    if (typeof(data) === 'undefined')
  57
+      return undefined;
  58
+
  59
+    if (data instanceof type) {
  60
+      return data;
  61
+    }
  62
+    return new type(data);
  63
+  },
  64
+
  65
+  binsearchInsert: function(list, seekVal, cmpfunc) {
  66
+    if (!list.length)
  67
+      return 0;
  68
+
  69
+    var low = 0, high = list.length - 1,
  70
+        mid, cmpval;
  71
+
  72
+    while (low <= high) {
  73
+      mid = low + Math.floor((high - low) / 2);
  74
+      cmpval = cmpfunc(seekVal, list[mid]);
  75
+
  76
+      if (cmpval < 0)
  77
+        high = mid - 1;
  78
+      else if (cmpval > 0)
  79
+        low = mid + 1;
  80
+      else
  81
+        break;
  82
+    }
  83
+
  84
+    if (cmpval < 0)
  85
+      return mid; // insertion is displacing, so use mid outright.
  86
+    else if (cmpval > 0)
  87
+      return mid + 1;
  88
+    else
  89
+      return mid;
  90
+  },
  91
+
32 92
   dumpn: function() {
33 93
     if (!ICAL.debug) {
34 94
       return null;
@@ -77,7 +137,7 @@ ICAL.helpers = {
77 137
       var result = {};
78 138
       for (var name in aSrc) {
79 139
         if (aSrc.hasOwnProperty(name)) {
80  
-          dump("Cloning " + name + "\n");
  140
+          this.dumpn("Cloning " + name + "\n");
81 141
           if (aDeep) {
82 142
             result[name] = ICAL.helpers.clone(aSrc[name], true);
83 143
           } else {
@@ -253,9 +313,12 @@ ICAL.helpers = {
253 313
     constructor: ParserError
254 314
   };
255 315
 
256  
-  var parser = {};
  316
+  var parser = {
  317
+    Error: ParserError
  318
+  };
257 319
   ICAL.icalparser = parser;
258 320
 
  321
+
259 322
   parser.lexContentLine = function lexContentLine(aState) {
260 323
     // contentline   = name *(";" param ) ":" value CRLF
261 324
     // The corresponding json object will be:
@@ -422,8 +485,12 @@ ICAL.helpers = {
422 485
     }
423 486
 
424 487
     if ("parameters" in aLineData && "VALUE" in aLineData.parameters) {
425  
-      ICAL.helpers.dumpn("VAAAA: " + aLineData.parameters.VALUE.toString());
426  
-      valueType = aLineData.parameters.VALUE.value.toUpperCase();
  488
+      var valueParam = aLineData.parameters.VALUE;
  489
+      if (typeof(valueParam) === 'string') {
  490
+        valueType = aLineData.parameters.VALUE.toUpperCase();
  491
+      } else if(typeof(valueParam) === 'object') {
  492
+        valueType = valueParam.value.toUpperCase();
  493
+      }
427 494
     }
428 495
 
429 496
     if (!(valueType in ICAL.design.value)) {
@@ -505,7 +572,7 @@ ICAL.helpers = {
505 572
       for (var param in reqParam) {
506 573
         if (!("parameters" in aLineData) ||
507 574
             !(param in aLineData.parameters) ||
508  
-            aLineData.parameters[param].value != reqParam[param]) {
  575
+            aLineData.parameters[param] != reqParam[param]) {
509 576
 
510 577
           throw new ParserError(aLineData, "Value requires " + param + "=" +
511 578
                                 valueData.requireParam[param]);
@@ -1624,6 +1691,31 @@ ICAL.design = {
1624 1691
       return props;
1625 1692
     },
1626 1693
 
  1694
+    /**
  1695
+     * Adds or replaces a property with a given value.
  1696
+     * Suitable for use when updating properties which
  1697
+     * are expected to only have a single value (like DTSTART, SUMMARY, etc..)
  1698
+     *
  1699
+     * @param {String} aName property name.
  1700
+     * @param {Object} aValue property value.
  1701
+     */
  1702
+    updatePropertyWithValue: function updatePropertyWithValue(aName, aValue) {
  1703
+      if (!this.hasProperty(aName)) {
  1704
+        return this.addPropertyWithValue(aName, aValue);
  1705
+      }
  1706
+
  1707
+      var prop = this.getFirstProperty(aName);
  1708
+
  1709
+      var lineData = ICAL.icalparser.detectValueType({
  1710
+        name: aName.toUpperCase(),
  1711
+        value: aValue
  1712
+      });
  1713
+
  1714
+      prop.setValues(lineData.value, lineData.type);
  1715
+
  1716
+      return prop;
  1717
+    },
  1718
+
1627 1719
     addPropertyWithValue: function addStringProperty(aName, aValue) {
1628 1720
       var ucName = aName.toUpperCase();
1629 1721
       var lineData = ICAL.icalparser.detectValueType({
@@ -1725,7 +1817,7 @@ ICAL.design = {
1725 1817
   };
1726 1818
 
1727 1819
   ICAL.icalcomponent.fromString = function icalcomponent_from_string(str) {
1728  
-    return ICAL.toJSON(str, true);
  1820
+    return ICAL.icalcomponent.fromData(ICAL.parse(str));
1729 1821
   };
1730 1822
 
1731 1823
   ICAL.icalcomponent.fromData = function icalcomponent_from_data(aData) {
@@ -2757,7 +2849,7 @@ ICAL.design = {
2757 2849
         this.reset();
2758 2850
       } else {
2759 2851
         if (useUTC) {
2760  
-          this.zone = ICAL.icaltimzone.utc_timezone;
  2852
+          this.zone = ICAL.icaltimezone.utc_timezone;
2761 2853
           this.year = aDate.getUTCFullYear();
2762 2854
           this.month = aDate.getUTCMonth() + 1;
2763 2855
           this.day = aDate.getUTCDate();
@@ -2803,9 +2895,21 @@ ICAL.design = {
2803 2895
         this.isDate = aData.isDate;
2804 2896
       }
2805 2897
 
2806  
-      if (aData && "timezone" in aData && aData.timezone == "Z") {
2807  
-        this.zone = ICAL.icaltimezone.utc_timezone;
  2898
+      if (aData && "timezone" in aData) {
  2899
+        var timezone = aData.timezone;
  2900
+
  2901
+        //TODO: replace with timezone service
  2902
+        switch (timezone) {
  2903
+          case 'Z':
  2904
+          case ICAL.icaltimezone.utc_timezone.tzid:
  2905
+            this.zone = ICAL.icaltimezone.utc_timezone;
  2906
+            break;
  2907
+          case ICAL.icaltimezone.local_timezone.tzid:
  2908
+            this.zone = ICAL.icaltimezone.local_timezone;
  2909
+            break;
  2910
+        }
2808 2911
       }
  2912
+
2809 2913
       if (aData && "zone" in aData) {
2810 2914
         this.zone = aData.zone;
2811 2915
       }
@@ -2825,7 +2929,7 @@ ICAL.design = {
2825 2929
       return this;
2826 2930
     },
2827 2931
 
2828  
-    day_of_week: function icaltime_day_of_week() {
  2932
+    dayOfWeek: function icaltime_dayOfWeek() {
2829 2933
       // Using Zeller's algorithm
2830 2934
       var q = this.day;
2831 2935
       var m = this.month + (this.month < 3 ? 12 : 0);
@@ -2843,21 +2947,21 @@ ICAL.design = {
2843 2947
       return h;
2844 2948
     },
2845 2949
 
2846  
-    day_of_year: function icaltime_day_of_year() {
  2950
+    dayOfYear: function icaltime_dayOfYear() {
2847 2951
       var is_leap = (ICAL.icaltime.is_leap_year(this.year) ? 1 : 0);
2848 2952
       var diypm = ICAL.icaltime._days_in_year_passed_month;
2849 2953
       return diypm[is_leap][this.month - 1] + this.day;
2850 2954
     },
2851 2955
 
2852  
-    start_of_week: function start_of_week() {
  2956
+    startOfWeek: function startOfWeek() {
2853 2957
       var result = this.clone();
2854  
-      result.day -= this.day_of_week() - 1;
  2958
+      result.day -= this.dayOfWeek() - 1;
2855 2959
       return result.normalize();
2856 2960
     },
2857 2961
 
2858 2962
     end_of_week: function end_of_week() {
2859 2963
       var result = this.clone();
2860  
-      result.day += 7 - this.day_of_week();
  2964
+      result.day += 7 - this.dayOfWeek();
2861 2965
       return result.normalize();
2862 2966
     },
2863 2967
 
@@ -2873,7 +2977,7 @@ ICAL.design = {
2873 2977
 
2874 2978
     end_of_month: function end_of_month() {
2875 2979
       var result = this.clone();
2876  
-      result.day = ICAL.icaltime.days_in_month(result.month, result.year);
  2980
+      result.day = ICAL.icaltime.daysInMonth(result.month, result.year);
2877 2981
       result.isDate = true;
2878 2982
       result.hour = 0;
2879 2983
       result.minute = 0;
@@ -2905,62 +3009,138 @@ ICAL.design = {
2905 3009
 
2906 3010
     start_doy_week: function start_doy_week(aFirstDayOfWeek) {
2907 3011
       var firstDow = aFirstDayOfWeek || ICAL.icaltime.SUNDAY;
2908  
-      var delta = this.day_of_week() - firstDow;
  3012
+      var delta = this.dayOfWeek() - firstDow;
2909 3013
       if (delta < 0) delta += 7;
2910  
-      return this.day_of_year() - delta;
  3014
+      return this.dayOfYear() - delta;
2911 3015
     },
2912 3016
 
2913  
-    nth_weekday: function icaltime_nth_weekday(aDayOfWeek, aPos) {
2914  
-      var days_in_month = ICAL.icaltime.days_in_month(this.month, this.year);
  3017
+    /**
  3018
+     * Finds the nthWeekDay relative to the current month (not day).
  3019
+     * The returned value is a day relative the month that this
  3020
+     * month belongs to so 1 would indicate the first of the month
  3021
+     * and 40 would indicate a day in the following month.
  3022
+     *
  3023
+     * @param {Numeric} aDayOfWeek day of the week see the day name constants.
  3024
+     * @param {Numeric} aPos nth occurrence of a given week day
  3025
+     *                       values of 1 and 0 both indicate the first
  3026
+     *                       weekday of that type. aPos may be either positive
  3027
+     *                       or negative.
  3028
+     *
  3029
+     * @return {Numeric} numeric value indicating a day relative
  3030
+     *                   to the current month of this time object.
  3031
+     */
  3032
+    nthWeekDay: function icaltime_nthWeekDay(aDayOfWeek, aPos) {
  3033
+      var daysInMonth = ICAL.icaltime.daysInMonth(this.month, this.year);
2915 3034
       var weekday;
2916 3035
       var pos = aPos;
2917 3036
 
2918  
-      var otherday = this.clone();
  3037
+      var start = 0;
  3038
+
  3039
+      var otherDay = this.clone();
2919 3040
 
2920 3041
       if (pos >= 0) {
2921  
-        otherday.day = 1;
2922  
-        var start_dow = otherday.day_of_week();
  3042
+        otherDay.day = 1;
2923 3043
 
  3044
+        // because 0 means no position has been given
  3045
+        // 1 and 0 indicate the same day.
2924 3046
         if (pos != 0) {
  3047
+          // remove the extra numeric value
2925 3048
           pos--;
2926 3049
         }
2927 3050
 
2928  
-        weekday = aDayOfWeek - start_dow + 1;
  3051
+        // set current start offset to current day.
  3052
+        start = otherDay.day;
2929 3053
 
2930  
-        if (weekday <= 0) {
2931  
-          weekday += 7;
2932  
-        }
  3054
+        // find the current day of week
  3055
+        var startDow = otherDay.dayOfWeek();
  3056
+
  3057
+        // calculate the difference between current
  3058
+        // day of the week and desired day of the week
  3059
+        var offset = aDayOfWeek - startDow;
  3060
+
  3061
+
  3062
+        // if the offset goes into the past
  3063
+        // week we add 7 so its goes into the next
  3064
+        // week. We only want to go forward in time here.
  3065
+        if (offset < 0)
  3066
+          // this is really important otherwise we would
  3067
+          // end up with dates from in the past.
  3068
+          offset += 7;
  3069
+
  3070
+        // add offset to start so start is the same
  3071
+        // day of the week as the desired day of week.
  3072
+        start += offset;
  3073
+
  3074
+        // because we are going to add (and multiply)
  3075
+        // the numeric value of the day we subtract it
  3076
+        // from the start position so not to add it twice.
  3077
+        start -= aDayOfWeek;
  3078
+
  3079
+        // set week day
  3080
+        weekday = aDayOfWeek;
2933 3081
       } else {
2934  
-        otherday.day = days_in_month;
2935  
-        var end_dow = otherday.day_of_week();
  3082
+
  3083
+        // then we set it to the last day in the current month
  3084
+        otherDay.day = daysInMonth;
  3085
+
  3086
+        // find the ends weekday
  3087
+        var endDow = otherDay.dayOfWeek();
2936 3088
 
2937 3089
         pos++;
2938 3090
 
2939  
-        weekday = (end_dow - dow);
  3091
+        weekday = (endDow - aDayOfWeek);
2940 3092
 
2941 3093
         if (weekday < 0) {
2942 3094
           weekday += 7;
2943 3095
         }
2944 3096
 
2945  
-        weekday = days_in_month - weekday;
  3097
+        weekday = daysInMonth - weekday;
2946 3098
       }
2947 3099
 
2948 3100
       weekday += pos * 7;
2949 3101
 
2950  
-      return weekday;
  3102
+      return start + weekday;
  3103
+    },
  3104
+
  3105
+    /**
  3106
+     * Checks if current time is the nthWeekDay.
  3107
+     * Relative to the current month.
  3108
+     *
  3109
+     * Will always return false when rule resolves
  3110
+     * outside of current month.
  3111
+     *
  3112
+     * @param {Numeric} aDayOfWeek day of week.
  3113
+     * @param {Numeric} aPos position.
  3114
+     * @param {Numeric} aMax maximum valid day.
  3115
+     */
  3116
+    isNthWeekDay: function(aDayOfWeek, aPos) {
  3117
+      var dow = this.dayOfWeek();
  3118
+
  3119
+      if (aPos === 0 && dow === aDayOfWeek) {
  3120
+        return true;
  3121
+      }
  3122
+
  3123
+      // get pos
  3124
+      var day = this.nthWeekDay(aDayOfWeek, aPos);
  3125
+
  3126
+      if (day === this.day) {
  3127
+        return true;
  3128
+      }
  3129
+
  3130
+      return false;
2951 3131
     },
2952 3132
 
2953 3133
     week_number: function week_number(aWeekStart) {
2954 3134
       // This function courtesty of Julian Bucknall, published under the MIT license
2955 3135
       // http://www.boyet.com/articles/publishedarticles/calculatingtheisoweeknumb.html
2956  
-      var doy = this.day_of_year();
2957  
-      var dow = this.day_of_week();
  3136
+      var doy = this.dayOfYear();
  3137
+      var dow = this.dayOfWeek();
2958 3138
       var year = this.year;
2959 3139
       var week1;
2960 3140
 
2961 3141
       var dt = this.clone();
2962 3142
       dt.isDate = true;
2963  
-      var first_dow = dt.day_of_week();
  3143
+      var first_dow = dt.dayOfWeek();
2964 3144
       var isoyear = this.year;
2965 3145
 
2966 3146
       if (dt.month == 12 && dt.day > 28) {
@@ -3014,23 +3194,23 @@ ICAL.design = {
3014 3194
       dur.hours = this.hour - aDate.hour;
3015 3195
 
3016 3196
       if (this.year == aDate.year) {
3017  
-        var this_doy = this.day_of_year();
3018  
-        var that_doy = aDate.day_of_year();
  3197
+        var this_doy = this.dayOfYear();
  3198
+        var that_doy = aDate.dayOfYear();
3019 3199
         dur.days = this_doy - that_doy;
3020 3200
       } else if (this.year < aDate.year) {
3021 3201
         var days_left_thisyear = 365 +
3022 3202
           (ICAL.icaltime.is_leap_year(this.year) ? 1 : 0) -
3023  
-          this.day_of_year();
  3203
+          this.dayOfYear();
3024 3204
 
3025  
-        dur.days -= days_left_thisyear + aDate.day_of_year();
  3205
+        dur.days -= days_left_thisyear + aDate.dayOfYear();
3026 3206
         dur.days -= leap_years_between(this.year + 1, aDate.year);
3027 3207
         dur.days -= 365 * (aDate.year - this.year - 1);
3028 3208
       } else {
3029 3209
         var days_left_thatyear = 365 +
3030 3210
           (ICAL.icaltime.is_leap_year(aDate.year) ? 1 : 0) -
3031  
-          aDate.day_of_year();
  3211
+          aDate.dayOfYear();
3032 3212
 
3033  
-        dur.days += days_left_thatyear + this.day_of_year();
  3213
+        dur.days += days_left_thatyear + this.dayOfYear();
3034 3214
         dur.days += leap_years_between(aDate.year + 1, this.year);
3035 3215
         dur.days += 365 * (this.year - aDate.year - 1);
3036 3216
       }
@@ -3169,7 +3349,7 @@ ICAL.design = {
3169 3349
       var second, minute, hour, day;
3170 3350
       var minutes_overflow, hours_overflow, days_overflow = 0,
3171 3351
         years_overflow = 0;
3172  
-      var days_in_month;
  3352
+      var daysInMonth;
3173 3353
 
3174 3354
       if (!this.isDate) {
3175 3355
         second = this.second + aExtraSeconds;
@@ -3212,8 +3392,8 @@ ICAL.design = {
3212 3392
       day = this.day + aExtraDays + days_overflow;
3213 3393
       if (day > 0) {
3214 3394
         for (;;) {
3215  
-          var days_in_month = ICAL.icaltime.days_in_month(this.month, this.year);
3216  
-          if (day <= days_in_month) {
  3395
+          var daysInMonth = ICAL.icaltime.daysInMonth(this.month, this.year);
  3396
+          if (day <= daysInMonth) {
3217 3397
             break;
3218 3398
           }
3219 3399
 
@@ -3223,7 +3403,7 @@ ICAL.design = {
3223 3403
             this.month = 1;
3224 3404
           }
3225 3405
 
3226  
-          day -= days_in_month;
  3406
+          day -= daysInMonth;
3227 3407
         }
3228 3408
       } else {
3229 3409
         while (day <= 0) {
@@ -3234,7 +3414,7 @@ ICAL.design = {
3234 3414
             this.month--;
3235 3415
           }
3236 3416
 
3237  
-          day += ICAL.icaltime.days_in_month(this.month, this.year);
  3417
+          day += ICAL.icaltime.daysInMonth(this.month, this.year);
3238 3418
         }
3239 3419
       }
3240 3420
 
@@ -3252,7 +3432,52 @@ ICAL.design = {
3252 3432
     toUnixTime: function toUnixTime() {
3253 3433
       var dur = this.subtractDate(ICAL.icaltime.epoch_time);
3254 3434
       return dur.toSeconds();
  3435
+    },
  3436
+
  3437
+    /**
  3438
+     * Converts time to into Object
  3439
+     * which can be serialized then re-created
  3440
+     * using the constructor.
  3441
+     *
  3442
+     * Example:
  3443
+     *
  3444
+     *    // toJSON will automatically be called
  3445
+     *    var json = JSON.stringify(mytime);
  3446
+     *
  3447
+     *    var deserialized = JSON.parse(json);
  3448
+     *
  3449
+     *    var time = new ICAL.icaltime(deserialized);
  3450
+     *
  3451
+     */
  3452
+    toJSON: function() {
  3453
+      var copy = [
  3454
+        'year',
  3455
+        'month',
  3456
+        'day',
  3457
+        'hour',
  3458
+        'minute',
  3459
+        'second',
  3460
+        'isDate'
  3461
+      ];
  3462
+
  3463
+      var result = Object.create(null);
  3464
+
  3465
+      var i = 0;
  3466
+      var len = copy.length;
  3467
+      var prop;
  3468
+
  3469
+      for (; i < len; i++) {
  3470
+        prop = copy[i];
  3471
+        result[prop] = this[prop];
  3472
+      }
  3473
+
  3474
+      if (this.zone) {
  3475
+        result.timezone = this.zone.tzid;
  3476
+      }
  3477
+
  3478
+      return result;
3255 3479
     }
  3480
+
3256 3481
   };
3257 3482
 
3258 3483
   (function setupNormalizeAttributes() {
@@ -3291,13 +3516,13 @@ ICAL.design = {
3291 3516
     }
3292 3517
   })();
3293 3518
 
3294  
-  ICAL.icaltime.days_in_month = function icaltime_days_in_month(month, year) {
3295  
-    var _days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  3519
+  ICAL.icaltime.daysInMonth = function icaltime_daysInMonth(month, year) {
  3520
+    var _daysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
3296 3521
     var days = 30;
3297 3522
 
3298 3523
     if (month < 1 || month > 12) return days;
3299 3524
 
3300  
-    days = _days_in_month[month];
  3525
+    days = _daysInMonth[month];
3301 3526
 
3302 3527
     if (month == 2) {
3303 3528
       days += ICAL.icaltime.is_leap_year(year);
@@ -3314,7 +3539,7 @@ ICAL.design = {
3314 3539
     }
3315 3540
   };
3316 3541
 
3317  
-  ICAL.icaltime.from_day_of_year = function icaltime_from_day_of_year(aDayOfYear, aYear) {
  3542
+  ICAL.icaltime.fromDayOfYear = function icaltime_fromDayOfYear(aDayOfYear, aYear) {
3318 3543
     var year = aYear;
3319 3544
     var doy = aDayOfYear;
3320 3545
     var tt = new ICAL.icaltime();
@@ -3373,7 +3598,7 @@ ICAL.design = {
3373 3598
       isDate: true
3374 3599
     });
3375 3600
 
3376  
-    var fourth_dow = t.day_of_week();
  3601
+    var fourth_dow = t.dayOfWeek();
3377 3602
     t.day += (1 - fourth_dow) + ((aWeekStart || ICAL.icaltime.SUNDAY) - 1);
3378 3603
     return t;
3379 3604
   };
@@ -3417,6 +3642,17 @@ ICAL.design = {
3417 3642
 
3418 3643
 (typeof(ICAL) === 'undefined')? ICAL = {} : '';
3419 3644
 (function() {
  3645
+
  3646
+  var DOW_MAP = {
  3647
+    SU: 1,
  3648
+    MO: 2,
  3649
+    TU: 3,
  3650
+    WE: 4,
  3651
+    TH: 5,
  3652
+    FR: 6,
  3653
+    SA: 7
  3654
+  };
  3655
+
3420 3656
   ICAL.icalrecur = function icalrecur(data) {
3421 3657
     this.wrappedJSObject = this;
3422 3658
     this.parts = {};
@@ -3436,7 +3672,10 @@ ICAL.design = {
3436 3672
     icaltype: "RECUR",
3437 3673
 
3438 3674
     iterator: function(aStart) {
3439  
-      return new icalrecur_iterator(this, aStart);
  3675
+      return new ICAL.icalrecur_iterator({
  3676
+        rule: this,
  3677
+        dtstart: aStart
  3678
+      });
3440 3679
     },
3441 3680
 
3442 3681
     clone: function clone() {
@@ -3444,12 +3683,12 @@ ICAL.design = {
3444 3683
       //return ICAL.icalrecur.fromIcalProperty(this.toIcalProperty());
3445 3684
     },
3446 3685
 
3447  
-    is_finite: function isfinite() {
3448  
-      return (this.count || this.until);
  3686
+    isFinite: function isfinite() {
  3687
+      return !!(this.count || this.until);
3449 3688
     },
3450 3689
 
3451  
-    is_by_count: function isbycount() {
3452  
-      return (this.count && !this.until);
  3690
+    isByCount: function isbycount() {
  3691
+      return !!(this.count && !this.until);
3453 3692
     },
3454 3693
 
3455 3694
     addComponent: function addPart(aType, aValue) {
@@ -3489,8 +3728,37 @@ ICAL.design = {
3489 3728
       return next;
3490 3729
     },
3491 3730
 
  3731
+    toJSON: function() {
  3732
+      //XXX: extract this list up to proto?
  3733
+      var propsToCopy = [
  3734
+        "freq",
  3735
+        "count",
  3736
+        "until",
  3737
+        "wkst",
  3738
+        "interval",
  3739
+        "parts"
  3740
+      ];
  3741
+
  3742
+      var result = Object.create(null);
  3743
+
  3744
+      var i = 0;
  3745
+      var len = propsToCopy.length;
  3746
+      var prop;
  3747
+
  3748
+      for (; i < len; i++) {
  3749
+        var prop = propsToCopy[i];
  3750
+        result[prop] = this[prop];
  3751
+      }
  3752
+
  3753
+      if (result.until instanceof ICAL.icaltime) {
  3754
+        result.until = result.until.toJSON();
  3755
+      }
  3756
+
  3757
+      return result;
  3758
+    },
  3759
+
3492 3760
     fromData: function fromData(aData) {
3493  
-      var propsToCopy = ["freq", "count", "wkst", "interval"];
  3761
+      var propsToCopy = ["freq", "count", "until", "wkst", "interval"];
3494 3762
       for (var key in propsToCopy) {
3495 3763
         var prop = propsToCopy[key];
3496 3764
         if (aData && prop.toUpperCase() in aData) {
@@ -3502,8 +3770,19 @@ ICAL.design = {
3502 3770
         }
3503 3771
       }
3504 3772
 
3505  
-      if (aData && "until" in aData && aData.until) {
3506  
-        this.until = aData.until.clone();
  3773
+      // wkst is usually in SU, etc.. format we need
  3774
+      // to convert it from the string
  3775
+      if (typeof(this.wkst) === 'string') {
  3776
+        this.wkst = ICAL.icalrecur.icalDayToNumericDay(this.wkst);
  3777
+      }
  3778
+
  3779
+      // Another hack for multiple construction of until value.
  3780
+      if (this.until) {
  3781
+        if (this.until instanceof ICAL.icaltime) {
  3782
+          this.until = this.until.clone();
  3783
+        } else {
  3784
+          this.until = ICAL.icaltime.fromData(this.until);
  3785
+        }
3507 3786
       }
3508 3787
 
3509 3788
       var partsToCopy = ["BYSECOND", "BYMINUTE", "BYHOUR", "BYDAY",
@@ -3594,40 +3873,110 @@ ICAL.design = {
3594 3873
     return recur;
3595 3874
   };
3596 3875
 
3597  
-  function icalrecur_iterator(aRule, aStart) {
3598  
-    this.rule = aRule;
3599  
-    this.dtstart = aStart;
3600  
-    this.by_data = ICAL.helpers.clone(aRule.parts, true);
3601  
-    this.days = [];
3602  
-    this.init();
  3876
+  /**
  3877
+   * Convert an ical representation of a day (SU, MO, etc..)
  3878
+   * into a numeric value of that day.
  3879
+   *
  3880
+   * @param {String} day ical day.
  3881
+   * @return {Numeric} numeric value of given day.
  3882
+   */
  3883
+  ICAL.icalrecur.icalDayToNumericDay = function toNumericDay(string) {
  3884
+    //XXX: this is here so we can deal
  3885
+    //     with possibly invalid string values.
  3886
+
  3887
+    return DOW_MAP[string];
  3888
+  };
  3889
+
  3890
+})();
  3891
+ICAL.icalrecur_iterator = (function() {
  3892
+
  3893
+  /**
  3894
+   * Options:
  3895
+   *  - rule: (ICAL.icalrecur) instance
  3896
+   *  - dtstart: (ICAL.icaltime) start date of recurrence rule
  3897
+   *  - initialized: (Boolean) when true will assume options
  3898
+   *                           are from previously constructed
  3899
+   *                           iterator and will not re-initialize
  3900
+   *                           iterator but resume its state from given data.
  3901
+   *
  3902
+   *  - by_data: (for iterator de-serialization)
  3903
+   *  - days: "
  3904
+   *  - last: "
  3905
+   *  - by_indices: "
  3906
+   */
  3907
+  function icalrecur_iterator(options) {
  3908
+    this.fromData(options);
3603 3909
   }
3604 3910
 
3605 3911
   icalrecur_iterator.prototype = {
3606 3912
 
  3913
+    /**
  3914
+     * True when iteration is finished.
  3915
+     */
  3916
+    completed: false,
  3917
+
3607 3918
     rule: null,
3608 3919
     dtstart: null,
3609 3920
     last: null,
3610 3921
     occurrence_number: 0,
3611 3922
     by_indices: null,
  3923
+    initialized: false,
3612 3924
     by_data: null,
3613 3925
 
3614 3926
     days: null,
3615 3927
     days_index: 0,
3616 3928
 
  3929
+    fromData: function(options) {
  3930
+      this.rule = ICAL.helpers.formatClassType(options.rule, ICAL.icalrecur);
  3931
+
  3932
+      if (!this.rule) {
  3933
+        throw new Error('iterator requires a (ICAL.icalrecur) rule');
  3934
+      }
  3935
+
  3936
+      this.dtstart = ICAL.helpers.formatClassType(options.dtstart, ICAL.icaltime);
  3937
+
  3938
+      if (!this.dtstart) {
  3939
+        throw new Error('iterator requires a (ICAL.icaltime) dtstart');
  3940
+      }
  3941
+
  3942
+      if (options.by_data) {
  3943
+        this.by_data = options.by_data;
  3944
+      } else {
  3945
+        this.by_data = ICAL.helpers.clone(this.rule.parts, true);
  3946
+      }
  3947
+
  3948
+      if (options.occurrence_number)
  3949
+        this.occurrence_number = options.occurrence_number;
  3950
+
  3951
+      this.days = options.days || [];
  3952
+      this.last = ICAL.helpers.formatClassType(options.last, ICAL.icaltime);
  3953
+
  3954
+      this.by_indices = options.by_indices;
  3955
+
  3956
+      if (!this.by_indices) {
  3957
+        this.by_indices = {
  3958
+          "BYSECOND": 0,
  3959
+          "BYMINUTE": 0,
  3960
+          "BYHOUR": 0,
  3961
+          "BYDAY": 0,
  3962
+          "BYMONTH": 0,
  3963
+          "BYWEEKNO": 0,
  3964
+          "BYMONTHDAY": 0
  3965
+        };
  3966
+      }
  3967
+
  3968
+      this.initialized = options.initialized || false;
  3969
+
  3970
+      if (!this.initialized) {
  3971
+        this.init();
  3972
+      }
  3973
+    },
  3974
+
3617 3975
     init: function icalrecur_iterator_init() {
  3976
+      this.initialized = true;
3618 3977
       this.last = this.dtstart.clone();
3619 3978
       var parts = this.by_data;
3620 3979
 
3621  
-      this.by_indices = {
3622  
-        "BYSECOND": 0,
3623  
-        "BYMINUTE": 0,
3624  
-        "BYHOUR": 0,
3625  
-        "BYDAY": 0,
3626  
-        "BYMONTH": 0,
3627  
-        "BYWEEKNO": 0,
3628  
-        "BYMONTHDAY": 0
3629  
-      };
3630  
-
3631 3980
       if ("BYDAY" in parts) {
3632 3981
         // libical does this earlier when the rule is loaded, but we postpone to
3633 3982
         // now so we can preserve the original order.
@@ -3674,17 +4023,17 @@ ICAL.design = {
3674 4023
 
3675 4024
       if (this.rule.freq == "WEEKLY") {
3676 4025
         if ("BYDAY" in parts) {
3677  
-          var parts = this.rule_day_of_week(parts.BYDAY[0]);
  4026
+          var parts = this.ruleDayOfWeek(parts.BYDAY[0]);
3678 4027
           var pos = parts[0];
3679 4028
           var rule_dow = parts[1];
3680  
-          var dow = rule_dow - this.last.day_of_week();
3681  
-          if ((this.last.day_of_week() < rule_dow && dow >= 0) || dow < 0) {
  4029
+          var dow = rule_dow - this.last.dayOfWeek();
  4030
+          if ((this.last.dayOfWeek() < rule_dow && dow >= 0) || dow < 0) {
3682 4031
             // Initial time is after first day of BYDAY data
3683 4032
             this.last.day += dow;
3684 4033
             this.last.normalize();
3685 4034
           }
3686 4035
         } else {
3687  
-          var wkMap = icalrecur_iterator._wkdayMap[this.dtstart.day_of_week()];
  4036
+          var wkMap = icalrecur_iterator._wkdayMap[this.dtstart.dayOfWeek()];
3688 4037
           parts.BYDAY = [wkMap];
3689 4038
         }
3690 4039
       }
@@ -3698,24 +4047,25 @@ ICAL.design = {
3698 4047
           this.increment_year(this.rule.interval);
3699 4048
         }
3700 4049
 
3701  
-        var next = ICAL.icaltime.from_day_of_year(this.days[0], this.last.year);
  4050
+        var next = ICAL.icaltime.fromDayOfYear(this.days[0], this.last.year);
3702 4051
 
3703 4052
         this.last.day = next.day;
3704 4053
         this.last.month = next.month;
3705 4054
       }
3706 4055
 
3707 4056
       if (this.rule.freq == "MONTHLY" && this.has_by_data("BYDAY")) {
  4057
+
3708 4058
         var coded_day = this.by_data.BYDAY[this.by_indices.BYDAY];
3709  
-        var parts = this.rule_day_of_week(coded_day);
  4059
+        var parts = this.ruleDayOfWeek(coded_day);
3710 4060
         var pos = parts[0];
3711 4061
         var dow = parts[1];
3712 4062
 
3713  
-        var days_in_month = ICAL.icaltime.days_in_month(this.last.month, this.last.year);
  4063
+        var daysInMonth = ICAL.icaltime.daysInMonth(this.last.month, this.last.year);
3714 4064
         var poscount = 0;
3715 4065
 
3716 4066
         if (pos >= 0) {
3717  
-          for (this.last.day = 1; this.last.day <= days_in_month; this.last.day++) {
3718  
-            if (this.last.day_of_week() == dow) {
  4067
+          for (this.last.day = 1; this.last.day <= daysInMonth; this.last.day++) {
  4068
+            if (this.last.dayOfWeek() == dow) {
3719 4069
               if (++poscount == pos || pos == 0) {
3720 4070
                 break;
3721 4071
               }
@@ -3723,8 +4073,8 @@ ICAL.design = {
3723 4073
           }
3724 4074
         } else {
3725 4075
           pos = -pos;
3726  
-          for (this.last.day = days_in_month; this.last.day != 0; this.last.day--) {
3727  
-            if (this.last.day_of_week() == dow) {
  4076
+          for (this.last.day = daysInMonth; this.last.day != 0; this.last.day--) {
  4077
+            if (this.last.dayOfWeek() == dow) {
3728 4078
               if (++poscount == pos) {
3729 4079
                 break;
3730 4080
               }
@@ -3732,14 +4082,23 @@ ICAL.design = {
3732 4082
           }
3733 4083
         }
3734 4084
 
3735  
-        if (this.last.day > days_in_month || this.last.day == 0) {
  4085
+        //XXX: This feels like a hack, but we need to initialize
  4086
+        //     the BYMONTHDAY case correctly and byDayAndMonthDay handles
  4087
+        //     this case. It accepts a special flag which will avoid incrementing
  4088
+        //     the initial value without the flag days that match the start time
  4089
+        //     would be missed.
  4090
+        if (this.has_by_data('BYMONTHDAY')) {
  4091
+          this._byDayAndMonthDay(true);
  4092
+        }
  4093
+
  4094
+        if (this.last.day > daysInMonth || this.last.day == 0) {
3736 4095
           throw new Error("Malformed values in BYDAY part");
3737 4096
         }
3738 4097
 
3739 4098
       } else if (this.has_by_data("BYMONTHDAY")) {
3740 4099
         if (this.last.day < 0) {
3741  
-          var days_in_month = ICAL.icaltime.days_in_month(this.last.month, this.last.year);
3742  
-          this.last.day = days_in_month + this.last.day + 1;
  4100
+          var daysInMonth = ICAL.icaltime.daysInMonth(this.last.month, this.last.year);
  4101
+          this.last.day = daysInMonth + this.last.day + 1;
3743 4102
         }
3744 4103
 
3745 4104
         this.last.normalize();
@@ -3751,6 +4110,11 @@ ICAL.design = {
3751 4110
 
3752 4111
       if ((this.rule.count && this.occurrence_number >= this.rule.count) ||
3753 4112
           (this.rule.until && this.last.compare(this.rule.until) > 0)) {
  4113
+
  4114
+        //XXX: right now this is just a flag and has no impact
  4115
+        //     we can simplify the above case to check for completed later.
  4116
+        this.completed = true;
  4117
+
3754 4118
         return null;
3755 4119
       }
3756 4120
 
@@ -3776,7 +4140,6 @@ ICAL.design = {
3776 4140
         case "DAILY":
3777 4141
           this.next_day();
3778 4142
           break;
3779  
-
3780 4143
         case "WEEKLY":
3781 4144
           this.next_week();
3782 4145
           break;
@@ -3801,6 +4164,7 @@ ICAL.design = {
3801 4164
       }
3802 4165
 
3803 4166
       if (this.rule.until && this.last.compare(this.rule.until) > 0) {
  4167
+        this.completed = true;
3804 4168
         return null;
3805 4169
       } else {
3806 4170
         this.occurrence_number++;
@@ -3886,51 +4250,195 @@ ICAL.design = {
3886 4250
       return end_of_data;
3887 4251
     },
3888 4252
 
3889  
-    next_month: function next_month() {
3890  
-      var this_freq = (this.rule.freq == "MONTHLY");
3891  
-      var data_valid = 1;
  4253
+    /**
  4254
+     * normalize each by day rule for a given year/month.
  4255
+     * Takes into account ordering and negative rules
  4256
+     *
  4257
+     * @param {Numeric} year current year.
  4258
+     * @param {Numeric} month current month.
  4259
+     * @param {Array} rules array of rules.
  4260
+     *
  4261
+     * @return {Array} sorted and normalized rules.
  4262
+     *                 Negative rules will be expanded to their
  4263
+     *                 correct positive values for easier processing.
  4264
+     */
  4265
+    normalizeByMonthDayRules: function(year, month, rules) {
  4266
+      var daysInMonth = ICAL.icaltime.daysInMonth(month, year);
  4267
+
  4268
+      // XXX: This is probably bad for performance to allocate
  4269
+      //      a new array for each month we scan, if possible
  4270
+      //      we should try to optimize this...
  4271
+      var newRules = [];
  4272
+
  4273
+      var ruleIdx = 0;
  4274
+      var len = rules.length;
  4275
+      var rule;
  4276
+
  4277
+      for (; ruleIdx < len; ruleIdx++) {
  4278
+        rule = rules[ruleIdx];
  4279
+
  4280
+        // if this rule falls outside of given
  4281
+        // month discard it.
  4282
+        if (Math.abs(rule) > daysInMonth) {
  4283
+          continue;
  4284
+        }
  4285
+
  4286
+        // negative case
  4287
+        if (rule < 0) {
  4288
+          // we add (not subtract its a negative number)
  4289
+          // one from the rule because 1 === last day of month
  4290
+          rule = daysInMonth + (rule + 1);
  4291
+        } else if (rule === 0) {
  4292
+          // skip zero its invalid.
  4293
+          continue;
  4294
+        }
  4295
+
  4296
+        // only add unique items...
  4297
+        if (newRules.indexOf(rule) === -1) {
  4298
+          newRules.push(rule);
  4299
+        }
3892 4300
 
3893  
-      if (this.next_hour() == 0) {
3894  
-        return data_valid;
3895 4301
       }
3896 4302
 
3897  
-      if (this.has_by_data("BYDAY") && this.has_by_data("BYMONTHDAY")) {
3898  
-        var days_in_month = ICAL.icaltime.days_in_month(this.last.month, this.last.year);
3899  
-        var notFound = true;
3900  
-        var day;
3901  
-
3902  
-        for (day = last.day + 1; notFound && day <= days_in_month; day++) {
3903  
-          for (var dayIdx = 0; dayIdx < this.by_data.BYDAY.length; dayIdx++) {
3904  
-            for (var mdIdx = 0; mdIdx < this.by_data.BYMONTHDAY.length; mdIdx++) {
3905  
-              var parts = this.rule_day_of_week(this.by_data.BYDAY[dayIdx]);
3906  
-              var pos = parts[0];
3907  
-              var dow = parts[1];
3908  
-              var mday = this.by_data.BYMONTHDAY[mdIdx];
  4303
+      // unique and sort
  4304
+      return newRules.sort();
  4305
+    },
3909 4306
 
3910  
-              this.last.day = day;
3911  
-              var this_dow = this.last.day_of_week();
  4307
+    /**
  4308
+     * NOTES:
  4309
+     * We are given a list of dates in the month (BYMONTHDAY) (23, etc..)
  4310
+     * Also we are given a list of days (BYDAY) (MO, 2SU, etc..) when
  4311
+     * both conditions match a given date (this.last.day) iteration stops.
  4312
+     *
  4313
+     * @param {Boolean} [isInit] when given true will not
  4314
+     *                           increment the current day (this.last).
  4315
+     */
  4316
+    _byDayAndMonthDay: function(isInit) {
  4317
+     var byMonthDay; // setup in initMonth
  4318
+      var byDay = this.by_data.BYDAY;
3912 4319
 
3913  
-              if ((pos == 0 && dow == this_dow && mday == day) ||
3914  
-                  (this.last.nth_weekday(dow, pos))) {
3915  
-                notFound = false;
3916  
-              }
3917  
-            }
  4320
+      var date;
  4321
+      var dateIdx = 0;
  4322
+      var dateLen; // setup in initMonth
  4323
+      var dayIdx = 0;
  4324
+      var dayLen = byDay.length;
  4325
+
  4326
+      // we are not valid by default
  4327
+      var dataIsValid = 0;
  4328
+
  4329
+      var daysInMonth;
  4330
+      var self = this;
  4331
+
  4332
+      function initMonth() {
  4333
+        daysInMonth = ICAL.icaltime.daysInMonth(
  4334
+          self.last.month, self.last.year
  4335
+        );
  4336
+
  4337
+        byMonthDay = self.normalizeByMonthDayRules(
  4338
+          self.last.year,
  4339
+          self.last.month,
  4340
+          self.by_data.BYMONTHDAY
  4341
+        );
  4342
+
  4343
+        dateLen = byMonthDay.length;
  4344
+      }
  4345
+
  4346
+      function nextMonth() {
  4347
+        self.last.day = 1;
  4348
+        self.increment_month();
  4349
+        initMonth();
  4350
+
  4351
+        dateIdx = 0;
  4352
+        dayIdx = 0;
  4353
+      }
  4354
+
  4355
+      initMonth();
  4356
+
  4357
+      // should come after initMonth
  4358
+      if (isInit) {
  4359
+        this.last.day -= 1;
  4360
+      }
  4361
+
  4362
+      while (!dataIsValid) {
  4363
+        // find next date
  4364
+        var next = byMonthDay[dateIdx++];
  4365
+
  4366
+        // increment the current date. This is really
  4367
+        // important otherwise we may fall into the infinite
  4368
+        // loop trap. The initial date takes care of the case
  4369
+        // where the current date is the date we are looking
  4370
+        // for.
  4371
+        date = this.last.day + 1;
  4372
+
  4373
+        if (date > daysInMonth) {
  4374
+          nextMonth();
  4375
+          continue;
  4376
+        }
  4377
+
  4378
+        // after verify that the next date
  4379
+        // is in the current month we can increment
  4380
+        // it permanently.
  4381
+        this.last.day = date;
  4382
+
  4383
+        // this logic is dependant on the BYMONTHDAYS
  4384
+        // being in order (which is done by #normalizeByMonthDayRules)
  4385
+        if (next >= this.last.day) {
  4386
+          // if the next month day is in the future jump to it.
  4387
+          this.last.day = next;
  4388
+        } else {
  4389
+          // in this case the 'next' monthday has past
  4390
+          // we must move to the month.
  4391
+          nextMonth();
  4392
+          continue;
  4393
+        }
  4394
+
  4395
+        // Now we can loop through the day rules to see
  4396
+        // if one matches the current month date.
  4397
+        for (dayIdx = 0; dayIdx < dayLen; dayIdx++) {
  4398
+          var parts = this.ruleDayOfWeek(byDay[dayIdx]);
  4399
+          var pos = parts[0];
  4400
+          var dow = parts[1];
  4401
+
  4402
+          if (this.last.isNthWeekDay(dow, pos)) {
  4403
+            // when we find the valid one we can mark
  4404
+            // the conditions as met and break the loop.
  4405
+            // (Because we have this condition above
  4406
+            //  it will also break the parent loop).
  4407
+            dataIsValid = 1;
  4408
+            break;
3918 4409
           }
3919 4410
         }
3920  
-        if (day > days_in_month) {
3921  
-          this.last.day = 1;
3922  
-          this.increment_month();
3923  
-          this.last.day--;
3924  
-          data_valid = 0;
  4411
+
  4412
+        // Its completely possible that the combination
  4413
+        // cannot be matched in the current month.
  4414
+        // When we reach the end of possible combinations
  4415
+        // in the current month we iterate to the next one.
  4416
+        if (!dataIsValid && dateIdx === (dateLen - 1)) {
  4417
+          nextMonth();
  4418
+          continue;
3925 4419
         }