      1 var string = require("./string").string;
      2
      3 /**
      4  * @ignore
      5  * Based on DOJO Date Implementation
      6  *
      7  * Dojo is available under *either* the terms of the modified BSD license *or* the
      8  * Academic Free License version 2.1. As a recipient of Dojo, you may choose which
      9  * license to receive this code under (except as noted in per-module LICENSE
     10  * files). Some modules may not be the copyright of the Dojo Foundation. These
     11  * modules contain explicit declarations of copyright in both the LICENSE files in
     12  * the directories in which they reside and in the code itself. No external
     13  * contributions are allowed under licenses which are fundamentally incompatible
     14  * with the AFL or BSD licenses that Dojo is distributed under.
     15  *
     16  */
     17
     18 var floor = Math.floor, round = Math.round, min = Math.min, pow = Math.pow, ceil = Math.ceil, abs = Math.abs;
     19 var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
     20 var monthAbbr = ["Jan.", "Feb.", "Mar.", "Apr.", "May.", "Jun.", "Jul.", "Aug.", "Sep.", "Oct.", "Nov.", "Dec."];
     21 var monthLetter = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
     22 var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
     23 var dayAbbr = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
     24 var dayLetter = ["S", "M", "T", "W", "T", "F", "S"];
     25 var eraNames = ["Before Christ", "Anno Domini"];
     26 var eraAbbr = ["BC", "AD"];
     27 var eraLetter = ["B", "A"];
     28
     29
     30 var comb = exports, date;
     31 /**
     32  * Determines if obj is a Date
     33  *
     34  * @param {Anything} obj the thing to test if it is a Date
     35  * @memberOf comb
     36  * @returns {Boolean} true if it is a Date false otherwise
     37  */
     38 comb.isDate = function(obj) {
     39     var undef;
     40     return (obj !== undef && typeof obj === "object" && obj instanceof Date);
     41 };
     42
     43 function getDayOfYear(/*Date*/dateObject, utc) {
     44     // summary: gets the day of the year as represented by dateObject
     45     return date.difference(new Date(dateObject.getFullYear(), 0, 1, dateObject.getHours()), dateObject, null, utc) + 1; // Number
     46 }
     47
     48 function getWeekOfYear(/*Date*/dateObject, /*Number*/firstDayOfWeek, utc) {
     49     firstDayOfWeek = firstDayOfWeek || 0;
     50     var fullYear = dateObject[utc ? "getUTCFullYear" : "getFullYear"]();
     51     var firstDayOfYear = new Date(fullYear, 0, 1).getDay(),
     52             adj = (firstDayOfYear - firstDayOfWeek + 7) % 7,
     53             week = floor((getDayOfYear(dateObject) + adj - 1) / 7);
     54
     55     // if year starts on the specified day, start counting weeks at 1
     56     if (firstDayOfYear == firstDayOfWeek) {
     57         week++;
     58     }
     59
     60     return week; // Number
     61 }
     62
     63 function buildDateEXP(pattern, tokens) {
     64     return pattern.replace(/([a-z])\1*/ig,
     65             function(match) {
     66                 // Build a simple regexp.  Avoid captures, which would ruin the tokens list
     67                 var s,
     68                         c = match.charAt(0),
     69                         l = match.length,
     70                         p2 = '', p3 = '';
     71                 p2 = '0?';
     72                 p3 = '0{0,2}';
     73                 switch (c) {
     74                     case 'y':
     75                         s = '\\d{2,4}';
     76                         break;
     77                     case 'M':
     78                         s = (l > 2) ? '\\S+?' : p2 + '[1-9]|1[0-2]';
     79                         break;
     80                     case 'D':
     81                         s = p2 + '[1-9]|' + p3 + '[1-9][0-9]|[12][0-9][0-9]|3[0-5][0-9]|36[0-6]';
     82                         break;
     83                     case 'd':
     84                         s = '[12]\\d|' + p2 + '[1-9]|3[01]';
     85                         break;
     86                     case 'w':
     87                         s = p2 + '[1-9]|[1-4][0-9]|5[0-3]';
     88                         break;
     89                     case 'E':
     90                         s = '\\S+';
     91                         break;
     92                     case 'h': //hour (1-12)
     93                         s = p2 + '[1-9]|1[0-2]';
     94                         break;
     95                     case 'K': //hour (1-24)
     96                         s = p2 + '\\d|1[01]';
     97                         break;
     98                     case 'H': //hour (0-23)
     99                         s = p2 + '\\d|1\\d|2[0-3]';
    100                         break;
    101                     case 'k': //hour (1-24)
    102                         s = p2 + '[1-9]|1\\d|2[0-4]';
    103                         break;
    104                     case 'm':
    105                     case 's':
    106                         s = '[0-5]\\d';
    107                         break;
    108                     case 'S':
    109                         s = '\\d{' + l + '}';
    110                         break;
    111                     case 'a':
    112                         var am = 'AM', pm = 'PM';
    113
    114                         s = am + '|' + pm;
    115                         if (am != am.toLowerCase()) {
    116                             s += '|' + am.toLowerCase();
    117                         }
    118                         if (pm != pm.toLowerCase()) {
    119                             s += '|' + pm.toLowerCase();
    120                         }
    121                         s = s.replace(/\./g, "\\.");
    122                         break;
    123                     default:
    124                         // case 'v':
    125                         // case 'z':
    126                         // case 'Z':
    127                         s = ".*";
    128 //				console.log("parse of date format, pattern=" + pattern);
    129                 }
    130
    131                 if (tokens) {
    132                     tokens.push(match);
    133                 }
    134
    135                 return "(" + s + ")"; // add capture
    136             }).replace(/[\xa0 ]/g, "[\\s\\xa0]"); // normalize whitespace.  Need explicit handling of \xa0 for IE.
    137 }
    138
    139
    140 /**
    141  * @namespace Utilities for Dates
    142  */
    143 comb.date = {
    144
    145     /**
    146      * Returns the number of days in the month of a date
    147      *
    148      * @example
    149      *
    150      *  comb.date.getDaysInMonth(new Date(2006, 1, 1)) => 28
    151      *  comb.date.getDaysInMonth(new Date(2004, 1, 1)) => 29
    152      *  comb.date.getDaysInMonth(new Date(2006, 2, 1)) => 31
    153      *  comb.date.getDaysInMonth(new Date(2006, 3, 1)) => 30
    154      *  comb.date.getDaysInMonth(new Date(2006, 4, 1)) => 31
    155      *  comb.date.getDaysInMonth(new Date(2006, 5, 1)) => 30
    156      *  comb.date.getDaysInMonth(new Date(2006, 6, 1)) => 31
    157      * @param {Date} dateObject the date containing the month
    158      * @return {Number} the number of days in the month
    159      */
    160     getDaysInMonth : function(/*Date*/dateObject) {
    161         //	summary:
    162         //		Returns the number of days in the month used by dateObject
    163         var month = dateObject.getMonth();
    164         var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    165         if (month == 1 && date.isLeapYear(dateObject)) {
    166             return 29;
    167         } // Number
    168         return days[month]; // Number
    169     },
    170
    171     /**
    172      * Determines if a date is a leap year
    173      *
    174      * @example
    175      *
    176      *  comb.date.isLeapYear(new Date(1600, 0, 1)) => true
    177      *  comb.date.isLeapYear(new Date(2004, 0, 1)) => true
    178      *  comb.date.isLeapYear(new Date(2000, 0, 1)) => true
    179      *  comb.date.isLeapYear(new Date(2006, 0, 1)) => false
    180      *  comb.date.isLeapYear(new Date(1900, 0, 1)) => false
    181      *  comb.date.isLeapYear(new Date(1800, 0, 1)) => false
    182      *  comb.date.isLeapYear(new Date(1700, 0, 1)) => false
    183      *
    184      * @param {Date} dateObject
    185      * @returns {Boolean} true if it is a leap year false otherwise
    186      */
    187     isLeapYear : function(/*Date*/dateObject, utc) {
    188         var year = dateObject[utc ? "getUTCFullYear" : "getFullYear"]();
    189         return !(year % 400) || (!(year % 4) && !!(year % 100)); // Boolean
    190     },
    191
    192     /**
    193      * Determines if a date is on a weekend
    194      *
    195      * @example
    196      *
    197      * var thursday = new Date(2006, 8, 21);
    198      * var saturday = new Date(2006, 8, 23);
    199      * var sunday = new Date(2006, 8, 24);
    200      * var monday = new Date(2006, 8, 25);
    201      * comb.date.isWeekend(thursday)) => false
    202      * comb.date.isWeekend(saturday) => true
    203      * comb.date.isWeekend(sunday) => true
    204      * comb.date.isWeekend(monday)) => false
    205      *
    206      * @param {Date} dateObject the date to test
    207      *
    208      * @returns {Boolean} true if the date is a weekend
    209      */
    210     isWeekend : function(/*Date?*/dateObject, utc) {
    211         // summary:
    212         //	Determines if the date falls on a weekend, according to local custom.
    213         var day = (dateObject || new Date())[utc ? "getUTCDay" : "getDay"]();
    214         return day == 0 || day == 6;
    215     },
    216
    217     /**
    218      * Get the timezone of a date
    219      *
    220      * @example
    221      *  //just setting the strLocal to simulate the toString() of a date
    222      *  dt.str = 'Sun Sep 17 2006 22:25:51 GMT-0500 (CDT)';
    223      *  //just setting the strLocal to simulate the locale
    224      *  dt.strLocale = 'Sun 17 Sep 2006 10:25:51 PM CDT';
    225      *  comb.date.getTimezoneName(dt) => 'CDT'
    226      *  dt.str = 'Sun Sep 17 2006 22:57:18 GMT-0500 (CDT)';
    227      *  dt.strLocale = 'Sun Sep 17 22:57:18 2006';
    228      *  comb.date.getTimezoneName(dt) => 'CDT'
    229      * @param dateObject the date to get the timezone from
    230      *
    231      * @returns {String} the timezone of the date
    232      */
    233     getTimezoneName : function(/*Date*/dateObject) {
    234         var str = dateObject.toString();
    235         var tz = '';
    236         var pos = str.indexOf('(');
    237         if (pos > -1) {
    238             tz = str.substring(++pos, str.indexOf(')'));
    239         }
    240         return tz; // String
    241     },
    242
    243
    244     /**
    245      * Compares two dates
    246      *
    247      * @example
    248      *
    249      * var d1 = new Date();
    250      * d1.setHours(0);
    251      * comb.date.compare(d1, d1) => 0
    252      *
    253      *  var d1 = new Date();
    254      *  d1.setHours(0);
    255      *  var d2 = new Date();
    256      *  d2.setFullYear(2005);
    257      *  d2.setHours(12);
    258      *  comb.date.compare(d1, d2, "date") => 1
    259      *  comb.date.compare(d1, d2, "datetime") => 1
    260      *
    261      *  var d1 = new Date();
    262      *  d1.setHours(0);
    263      *  var d2 = new Date();
    264      *  d2.setFullYear(2005);
    265      *  d2.setHours(12);
    266      *  comb.date.compare(d2, d1, "date"), -1);
    267      *  comb.date.compare(d1, d2, "time"), -1);
    268      *
    269      * @param {Date|String} date1 the date to comapare
    270      * @param {Date|String} [date2=new Date()] the date to compare date1 againse
    271      * @param {"date"|"time"|"datetime"} portion compares the portion specified
    272      *
    273      * @returns -1 if date1 is < date2 0 if date1 === date2  1 if date1 > date2
    274      */
    275     compare : function(/*Date*/date1, /*Date*/date2, /*String*/portion) {
    276         date1 = new Date(date1);
    277         date2 = new Date((date2 || new Date()));
    278
    279         if (portion == "date") {
    280             // Ignore times and compare dates.
    281             date1.setHours(0, 0, 0, 0);
    282             date2.setHours(0, 0, 0, 0);
    283         } else if (portion == "time") {
    284             // Ignore dates and compare times.
    285             date1.setFullYear(0, 0, 0);
    286             date2.setFullYear(0, 0, 0);
    287         }
    288         var ret = 0;
    289         date1 > date2 && (ret = 1);
    290         date1 < date2 && (ret = -1);
    291         return ret; // int
    292     },
    293
    294
    295     /**
    296      * Adds a specified interval and amount to a date
    297      *
    298      * @example
    299      *  var dtA = new Date(2005, 11, 27);
    300      *  comb.date.add(dtA, "year", 1) => new Date(2006, 11, 27);
    301      *  comb.date.add(dtA, "years", 1) => new Date(2006, 11, 27);
    302      *
    303      *  dtA = new Date(2000, 0, 1);
    304      *  comb.date.add(dtA, "quarter", 1) => new Date(2000, 3, 1);
    305      *  comb.date.add(dtA, "quarters", 1) => new Date(2000, 3, 1);
    306      *
    307      *  dtA = new Date(2000, 0, 1);
    308      *  comb.date.add(dtA, "month", 1) => new Date(2000, 1, 1);
    309      *  comb.date.add(dtA, "months", 1) => new Date(2000, 1, 1);
    310      *
    311      *  dtA = new Date(2000, 0, 31);
    312      *  comb.date.add(dtA, "month", 1) => new Date(2000, 1, 29);
    313      *  comb.date.add(dtA, "months", 1) => new Date(2000, 1, 29);
    314      *
    315      *  dtA = new Date(2000, 0, 1);
    316      *  comb.date.add(dtA, "week", 1) => new Date(2000, 0, 8);
    317      *  comb.date.add(dtA, "weeks", 1) => new Date(2000, 0, 8);
    318      *
    319      *  dtA = new Date(2000, 0, 1);
    320      *  comb.date.add(dtA, "day", 1) => new Date(2000, 0, 2);
    321      *
    322      *  dtA = new Date(2000, 0, 1);
    323      *  comb.date.add(dtA, "weekday", 1) => new Date(2000, 0, 3);
    324      *
    325      *  dtA = new Date(2000, 0, 1, 11);
    326      *  comb.date.add(dtA, "hour", 1) => new Date(2000, 0, 1, 12);
    327      *
    328      *  dtA = new Date(2000, 11, 31, 23, 59);
    329      *  comb.date.add(dtA, "minute", 1) => new Date(2001, 0, 1, 0, 0);
    330      *
    331      *  dtA = new Date(2000, 11, 31, 23, 59, 59);
    332      *  comb.date.add(dtA, "second", 1) => new Date(2001, 0, 1, 0, 0, 0);
    333      *
    334      *  dtA = new Date(2000, 11, 31, 23, 59, 59, 999);
    335      *  comb.date.add(dtA, "millisecond", 1) => new Date(2001, 0, 1, 0, 0, 0, 0);
    336      *
    337      * @param {Date} date
    338      * @param {String} interval the interval to add
    339      *  <ul>
    340      *      <li>day | days</li>
    341      *      <li>weekday | weekdays</li>
    342      *      <li>year | years</li>
    343      *      <li>week | weeks</li>
    344      *      <li>quarter | quarters</li>
    345      *      <li>months | months</li>
    346      *      <li>hour | hours</li>
    347      *      <li>minute | minutes</li>
    348      *      <li>second | seconds</li>
    349      *      <li>millisecond | milliseconds</li>
    350      *  </ul>
    351      * @param {Number} [amount=0] the amount to add
    352      */
    353     add : function(/*Date*/date, /*String*/interval, /*int*/amount) {
    354         amount = amount | 0
    355         var sum = new Date(date);
    356         var fixOvershoot = false;
    357         var property = "Date";
    358
    359         //noinspection FallthroughInSwitchStatementJS
    360         switch (interval) {
    361             case "day":
    362             case "days" :
    363                 break;
    364             case "weekday":
    365             case "weekdays":
    366                 // Divide the increment time span into weekspans plus leftover days
    367                 // e.g., 8 days is one 5-day weekspan / and two leftover days
    368                 // Can't have zero leftover days, so numbers divisible by 5 get
    369                 // a days value of 5, and the remaining days make up the number of weeks
    370                 var days, weeks, mod = amount % 5, strt = date.getDay(), adj = 0;
    371                 if (!mod) {
    372                     days = (amount > 0) ? 5 : -5;
    373                     weeks = (amount > 0) ? ((amount - 5) / 5) : ((amount + 5) / 5);
    374                 } else {
    375                     days = mod;
    376                     weeks = parseInt(amount / 5);
    377                 }
    378                 if (strt == 6 && amount > 0) {
    379                     adj = 1;
    380                 } else if (strt == 0 && amount < 0) {
    381                     // Orig date is Sun / negative increment
    382                     // Jump back over Sat
    383                     adj = -1;
    384                 }
    385                 // Get weekday val for the new date
    386                 var trgt = strt + days;
    387                 // New date is on Sat or Sun
    388                 if (trgt == 0 || trgt == 6) {
    389                     adj = (amount > 0) ? 2 : -2;
    390                 }
    391                 // Increment by number of weeks plus leftover days plus
    392                 // weekend adjustments
    393                 amount = (7 * weeks) + days + adj;
    394                 break;
    395             case "year":
    396             case "years":
    397                 property = "FullYear";
    398                 fixOvershoot = true;
    399                 break;
    400             case "week":
    401             case "weeks":
    402                 amount *= 7;
    403                 break;
    404             case "quarter":
    405             case "quarters" :
    406                 // Naive quarter is just three months
    407                 amount *= 3;
    408             case "month":
    409             case "months":
    410                 // Reset to last day of month if you overshoot
    411                 fixOvershoot = true;
    412                 property = "Month";
    413                 break;
    414             default:
    415                 interval = interval.replace(/s$/, "");
    416                 property = "UTC" + interval.charAt(0).toUpperCase() + interval.substring(1) + "s";
    417         }
    418
    419         if (property) {
    420             sum["set" + property](sum["get" + property]() + amount);
    421         }
    422
    423         if (fixOvershoot && (sum.getDate() < date.getDate())) {
    424             sum.setDate(0);
    425         }
    426
    427         return sum; // Date
    428     },
    429
    430     /**
    431      * Finds the difference between two dates based on the specified interval
    432      *
    433      * @example
    434      *
    435      * var dtA, dtB;
    436      *
    437      * dtA = new Date(2005, 11, 27);
    438      * dtB = new Date(2006, 11, 27);
    439      * comb.date.difference(dtA, dtB, "year") => 1
    440      *
    441      * dtA = new Date(2000, 1, 29);
    442      * dtB = new Date(2001, 2, 1);
    443      * comb.date.difference(dtA, dtB, "quarter") => 4
    444      * comb.date.difference(dtA, dtB, "month") => 13
    445      *
    446      * dtA = new Date(2000, 1, 1);
    447      * dtB = new Date(2000, 1, 8);
    448      * comb.date.difference(dtA, dtB, "week") => 1
    449      *
    450      * dtA = new Date(2000, 1, 29);
    451      * dtB = new Date(2000, 2, 1);
    452      * comb.date.difference(dtA, dtB, "day") => 1
    453      *
    454      * dtA = new Date(2006, 7, 3);
    455      * dtB = new Date(2006, 7, 11);
    456      * comb.date.difference(dtA, dtB, "weekday") => 6
    457      *
    458      * dtA = new Date(2000, 11, 31, 23);
    459      * dtB = new Date(2001, 0, 1, 0);
    460      * comb.date.difference(dtA, dtB, "hour") => 1
    461      *
    462      * dtA = new Date(2000, 11, 31, 23, 59);
    463      * dtB = new Date(2001, 0, 1, 0, 0);
    464      * comb.date.difference(dtA, dtB, "minute") => 1
    465      *
    466      * dtA = new Date(2000, 11, 31, 23, 59, 59);
    467      * dtB = new Date(2001, 0, 1, 0, 0, 0);
    468      * comb.date.difference(dtA, dtB, "second") => 1
    469      *
    470      * dtA = new Date(2000, 11, 31, 23, 59, 59, 999);
    471      * dtB = new Date(2001, 0, 1, 0, 0, 0, 0);
    472      * comb.date.difference(dtA, dtB, "millisecond") => 1
    473      *
    474      *
    475      * @param {Date} date1
    476      * @param {Date} [date2 = new Date()]
    477      * @param {String} [interval = "day"] the intercal to find the difference of.
    478      *   <ul>
    479      *      <li>day | days</li>
    480      *      <li>weekday | weekdays</li>
    481      *      <li>year | years</li>
    482      *      <li>week | weeks</li>
    483      *      <li>quarter | quarters</li>
    484      *      <li>months | months</li>
    485      *      <li>hour | hours</li>
    486      *      <li>minute | minutes</li>
    487      *      <li>second | seconds</li>
    488      *      <li>millisecond | milliseconds</li>
    489      *  </ul>
    490      */
    491     difference : function(/*Date*/date1, /*Date?*/date2, /*String*/interval, utc) {
    492         date2 = date2 || new Date();
    493         interval = interval || "day";
    494         var yearDiff = date2.getFullYear() - date1.getFullYear();
    495         var delta = 1; // Integer return value
    496
    497         switch (interval) {
    498             case "quarter":
    499             case "quarters":
    500                 var m1 = date1[utc ? "getUTCMonth" : "getMonth"]();
    501                 var m2 = date2[utc ? "getUTCMonth" : "getMonth"]();
    502                 // Figure out which quarter the months are in
    503                 var q1 = floor(m1 / 3) + 1;
    504                 var q2 = floor(m2 / 3) + 1;
    505                 // Add quarters for any year difference between the dates
    506                 q2 += (yearDiff * 4);
    507                 delta = q2 - q1;
    508                 break;
    509             case "weekday":
    510             case "weekdays":
    511                 var days = round(date.difference(date1, date2, "day", utc));
    512                 var weeks = parseInt(date.difference(date1, date2, "week", utc));
    513                 var mod = days % 7;
    514
    515                 // Even number of weeks
    516                 if (mod == 0) {
    517                     days = weeks * 5;
    518                 } else {
    519                     // Weeks plus spare change (< 7 days)
    520                     var adj = 0;
    521                     var aDay = date1[utc ? "getUTCDay" : "getDay"]();
    522                     var bDay = date2[utc ? "getUTCDay" : "getDay"]();
    523
    524                     weeks = parseInt(days / 7);
    525                     mod = days % 7;
    526                     // Mark the date advanced by the number of
    527                     // round weeks (may be zero)
    528                     var dtMark = new Date(date1);
    529                     dtMark.setDate(dtMark[utc ? "getUTCDate" : "getDate"]() + (weeks * 7));
    530                     var dayMark = dtMark[utc ? "getUTCDay" : "getDay"]();
    531
    532                     // Spare change days -- 6 or less
    533                     if (days > 0) {
    534                         switch (true) {
    535                             // Range starts on Sat
    536                             case (aDay == 6 || bDay == 6):
    537                                 adj = -1;
    538                                 break;
    539                             // Range starts on Sun
    540                             case aDay == 0:
    541                                 adj = 0;
    542                                 break;
    543                             // Range ends on Sun
    544                             case bDay == 0:
    545                                 adj = -2;
    546                                 break;
    547                             // Range contains weekend
    548                             case (dayMark + mod) > 5:
    549                                 adj = -2;
    550                         }
    551                     } else if (days < 0) {
    552                         switch (true) {
    553                             // Range starts on Sat
    554                             case aDay == 6:
    555                                 adj = 0;
    556                                 break;
    557                             // Range starts on Sun
    558                             case aDay == 0:
    559                                 adj = 1;
    560                                 break;
    561                             // Range ends on Sat
    562                             case bDay == 6:
    563                                 adj = 2;
    564                                 break;
    565                             // Range ends on Sun
    566                             case bDay == 0:
    567                                 adj = 1;
    568                                 break;
    569                             // Range contains weekend
    570                             case (dayMark + mod) < 0:
    571                                 adj = 2;
    572                         }
    573                     }
    574                     days += adj;
    575                     days -= (weeks * 2);
    576                 }
    577                 delta = days;
    578                 break;
    579             case "year":
    580             case "years":
    581                 delta = yearDiff;
    582                 break;
    583             case "month":
    584             case "months":
    585                 var m1 = date1[utc ? "getUTCMonth" : "getMonth"]();
    586                 var m2 = date2[utc ? "getUTCMonth" : "getMonth"]();
    587                 delta = (m2 - m1) + (yearDiff * 12);
    588                 break;
    589             case "week":
    590             case "weeks":
    591                 delta = parseInt(date.difference(date1, date2, "day", utc) / 7);
    592                 break;
    593             case "day":
    594             case "days":
    595                 delta /= 24;
    596             case "hour":
    597             case "hours":
    598                 delta /= 60;
    599             case "minute":
    600             case "minutes":
    601                 delta /= 60;
    602             case "second":
    603             case "seconds":
    604                 delta /= 1000;
    605             case "millisecond":
    606             case "milliseconds":
    607                 delta *= date2.getTime() - date1.getTime();
    608         }
    609
    610         // Round for fractional values and DST leaps
    611         return round(delta); // Number (integer)
    612     },
    613
    614
    615     /**
    616      * Parses a date string into a date object
    617      *
    618      * @example
    619      *  var aug_11_2006 = new Date(2006, 7, 11, 0);
    620      *  comb.date.parse("08/11/06", "MM/dd/yy") => aug_11_2006
    621      *  comb.date.parse("11Aug2006", 'ddMMMyyyy') => aug_11_2006
    622      *  comb.date.parse("Aug2006", 'MMMyyyy') => new Date(2006, 7, 1)
    623      *  comb.date.parse("Aug 11, 2006", "MMM dd, yyyy") => aug_11_2006
    624      *  comb.date.parse("August 11, 2006", "MMMM dd, yyyy") => aug_11_2006
    625      *  comb.date.parse("Friday, August 11, 2006", "EEEE, MMMM dd, yyyy") => aug_11_2006
    626      *
    627      * @param {String} dateStr The string to parse
    628      * @param {String} format the format of the date composed of the following options
    629      * <ul>
    630      *                  <li> G    Era designator    Text    AD</li>
    631      *                  <li> y    Year    Year    1996; 96</li>
    632      *                  <li> M    Month in year    Month    July; Jul; 07</li>
    633      *                  <li> w    Week in year    Number    27</li>
    634      *                  <li> W    Week in month    Number    2</li>
    635      *                  <li> D    Day in year    Number    189</li>
    636      *                  <li> d    Day in month    Number    10</li>
    637      *                  <li> E    Day in week    Text    Tuesday; Tue</li>
    638      *                  <li> a    Am/pm marker    Text    PM</li>
    639      *                  <li> H    Hour in day (0-23)    Number    0</li>
    640      *                  <li> k    Hour in day (1-24)    Number    24</li>
    641      *                  <li> K    Hour in am/pm (0-11)    Number    0</li>
    642      *                  <li> h    Hour in am/pm (1-12)    Number    12</li>
    643      *                  <li> m    Minute in hour    Number    30</li>
    644      *                  <li> s    Second in minute    Number    55</li>
    645      *                  <li> S    Millisecond    Number    978</li>
    646      *                  <li> z    Time zone    General time zone    Pacific Standard Time; PST; GMT-08:00</li>
    647      *                  <li> Z    Time zone    RFC 822 time zone    -0800 </li>
    648      * </ul>
    649      *
    650      * @returns {Date} the parsed date
    651      *
    652      *
    653      */
    654     parse : function(dateStr, format) {
    655         if (!format) throw new Error('format required when calling comb.date.parse');
    656         var tokens = [], regexp = buildDateEXP(format, tokens),
    657                 re = new RegExp("^" + regexp + "$", "i"),
    658                 match = re.exec(dateStr);
    659         if (!match) {
    660             return null;
    661         } // null
    662
    663         var result = [1970,0,1,0,0,0,0],// will get converted to a Date at the end
    664                 amPm = "",
    665                 valid = match.every(function(v, i) {
    666                     if (!i) {
    667                         return true;
    668                     }
    669                     var token = tokens[i - 1];
    670                     var l = token.length;
    671                     switch (token.charAt(0)) {
    672                         case 'y':
    673                             if (v < 100) {
    674                                 v = parseInt(v, 10);
    675                                 //choose century to apply, according to a sliding window
    676                                 //of 80 years before and 20 years after present year
    677                                 var year = '' + new Date().getFullYear(),
    678                                         century = year.substring(0, 2) * 100,
    679                                         cutoff = min(year.substring(2, 4) + 20, 99);
    680                                 result[0] = (v < cutoff) ? century + v : century - 100 + v;
    681                             } else {
    682                                 result[0] = v;
    683                             }
    684                             break;
    685                         case 'M':
    686                             if (l > 2) {
    687                                 var months = monthNames;
    688                                 if (l === 3) {
    689                                     months = monthAbbr;
    690                                 }
    691                                 //Tolerate abbreviating period in month part
    692                                 //Case-insensitive comparison
    693                                 v = v.replace(".", "").toLowerCase();
    694                                 months = months.map(function(s) {
    695                                     return s.replace(".", "").toLowerCase();
    696                                 });
    697                                 if ((v = months.indexOf(v)) == -1) {
    698                                     return false;
    699                                 }
    700                             } else {
    701                                 v--;
    702                             }
    703                             result[1] = v;
    704                             break;
    705                         case 'E':
    706                         case 'e':
    707                             var days = dayNames;
    708                             if (l == 3) {
    709                                 days = dayAbbr;
    710                             }
    711                             //Case-insensitive comparison
    712                             v = v.toLowerCase();
    713                             days = days.map(function(d) {
    714                                 return d.toLowerCase();
    715                             });
    716                             var d = days.indexOf(v);
    717                             if (d == -1) {
    718                                 v = parseInt(v);
    719                                 if(isNaN(v) || v > days.length){
    720                                     return false;
    721                                 }
    722                             }else{
    723                                 v = d;
    724                             }
    725                             break;
    726                         case 'D':
    727                             result[1] = 0;
    728                         case 'd':
    729                             result[2] = v;
    730                             break;
    731                         case 'a': //am/pm
    732                             var am = "am";
    733                             var pm = "pm";
    734                             var period = /\./g;
    735                             v = v.replace(period, '').toLowerCase();
    736                             // we might not have seen the hours field yet, so store the state and apply hour change later
    737                             amPm = (v == pm) ? 'p' : (v == am) ? 'a' : '';
    738                             break;
    739                         case 'k': //hour (0-11)
    740                             if (v == 24) {
    741                                 v = 0;
    742                             }
    743                         // fallthrough...
    744                         case 'h': //hour (1-12)
    745                         case 'H': //hour (0-23)
    746                         case 'K': //hour (0-11)
    747                             //in the 12-hour case, adjusting for am/pm requires the 'a' part
    748                             //which could come before or after the hour, so we will adjust later
    749                             result[3] = v;
    750                             break;
    751                         case 'm': //minutes
    752                             result[4] = v;
    753                             break;
    754                         case 's': //seconds
    755                             result[5] = v;
    756                             break;
    757                         case 'S': //milliseconds
    758                             result[6] = v;
    759                     }
    760                     return true;
    761                 });
    762         if (valid) {
    763             var hours = +result[3];
    764             //account for am/pm
    765             if (amPm === 'p' && hours < 12) {
    766                 result[3] = hours + 12; //e.g., 3pm -> 15
    767             } else if (amPm === 'a' && hours == 12) {
    768                 result[3] = 0; //12am -> 0
    769             }
    770             var dateObject = new Date(result[0], result[1], result[2], result[3], result[4], result[5], result[6]); // Date
    771             var dateToken = (tokens.indexOf('d') != -1),
    772                     monthToken = (tokens.indexOf('M') != -1),
    773                     month = result[1],
    774                     day = result[2],
    775                     dateMonth = dateObject.getMonth(),
    776                     dateDay = dateObject.getDate();
    777             if ((monthToken && dateMonth > month) || (dateToken && dateDay > day)) {
    778                 return null;
    779             }
    780             return dateObject; // Date
    781         } else {
    782             return null;
    783         }
    784     },
    785
    786     /**
    787      * Formats a date to the specidifed format string
    788      *
    789      * @example
    790      *
    791      * var date = new Date(2006, 7, 11, 0, 55, 12, 345);
    792      * comb.date.format(date, "EEEE, MMMM dd, yyyy") => "Friday, August 11, 2006"
    793      * comb.date.format(date, "M/dd/yy") => "8/11/06"
    794      * comb.date.format(date, "E") => "6"
    795      * comb.date.format(date, "h:m a") => "12:55 AM"
    796      * comb.date.format(date, 'h:m:s') => "12:55:12"
    797      * comb.date.format(date, 'h:m:s.SS') => "12:55:12.35"
    798      * comb.date.format(date, 'k:m:s.SS') => "24:55:12.35"
    799      * comb.date.format(date, 'H:m:s.SS') => "0:55:12.35"
    800      * comb.date.format(date, "ddMMyyyy") => "11082006"
    801      *
    802      * @param date the date to format
    803      * @param {String} format the format of the date composed of the following options
    804      * <ul>
    805      *                  <li> G    Era designator    Text    AD</li>
    806      *                  <li> y    Year    Year    1996; 96</li>
    807      *                  <li> M    Month in year    Month    July; Jul; 07</li>
    808      *                  <li> w    Week in year    Number    27</li>
    809      *                  <li> W    Week in month    Number    2</li>
    810      *                  <li> D    Day in year    Number    189</li>
    811      *                  <li> d    Day in month    Number    10</li>
    812      *                  <li> E    Day in week    Text    Tuesday; Tue</li>
    813      *                  <li> a    Am/pm marker    Text    PM</li>
    814      *                  <li> H    Hour in day (0-23)    Number    0</li>
    815      *                  <li> k    Hour in day (1-24)    Number    24</li>
    816      *                  <li> K    Hour in am/pm (0-11)    Number    0</li>
    817      *                  <li> h    Hour in am/pm (1-12)    Number    12</li>
    818      *                  <li> m    Minute in hour    Number    30</li>
    819      *                  <li> s    Second in minute    Number    55</li>
    820      *                  <li> S    Millisecond    Number    978</li>
    821      *                  <li> z    Time zone    General time zone    Pacific Standard Time; PST; GMT-08:00</li>
    822      *                  <li> Z    Time zone    RFC 822 time zone    -0800 </li>
    823      * </ul>
    824      */
    825     format : function(date, format, utc) {
    826         utc = utc || false;
    827         var fullYear, month, day, d, hour, minute, second, millisecond;
    828         if (utc) {
    829             fullYear = date.getUTCFullYear(),
    830                     month = date.getUTCMonth(),
    831                     day = date.getUTCDay(),
    832                     d = date.getUTCDate(),
    833                     hour = date.getUTCHours(),
    834                     minute = date.getUTCMinutes(),
    835                     second = date.getUTCSeconds(),
    836                     millisecond = date.getUTCMilliseconds();
    837         } else {
    838             fullYear = date.getFullYear(),
    839                     month = date.getMonth(),
    840                     d = date.getDate(),
    841                     day = date.getDay(),
    842                     hour = date.getHours(),
    843                     minute = date.getMinutes(),
    844                     second = date.getSeconds(),
    845                     millisecond = date.getMilliseconds();
    846         }
    847         return format.replace(/([a-z])\1*/ig, function(match, options) {
    848             var s, pad, h,
    849                     c = match.charAt(0),
    850                     l = match.length;
    851
    852             switch (c) {
    853                 case 'd':
    854                     s = "" + d;
    855                 case 'H':
    856                     !s && (s = "" + hour);
    857                 case 'm':
    858                     !s && (s = "" + minute);
    859                 case 's':
    860                     !s && (s = "" + second);
    861                     pad = true;
    862                     break;
    863                 case 'G':
    864                     s = ((l < 4) ? eraAbbr : eraNames)[fullYear < 0 ? 0 : 1];
    865                     break;
    866                 case 'y':
    867                     s = fullYear;
    868                     if (l > 1) {
    869                         l == 2 ? s = string.truncate("" + s, 2, true) : pad = true;
    870                     }
    871                     break;
    872                 case 'Q':
    873                 case 'q':
    874                     s = ceil((month + 1) / 3);
    875                     pad = true;
    876                     break;
    877                 case 'M':
    878                     if (l < 3) {
    879                         s = month + 1;
    880                         pad = true;
    881                     } else {
    882                         s = (l == 3 ? monthAbbr : monthNames)[month];
    883                     }
    884                     break;
    885                 case 'w':
    886                     s = getWeekOfYear(date, 0, utc),pad = true;
    887                     break;
    888                 case 'D':
    889                     s = getDayOfYear(date, utc),pad = true;
    890                     break;
    891                 case 'E':
    892                     if (l < 3) {
    893                         s = day + 1;
    894                         pad = true;
    895                     } else {
    896                         s = (l == 3 ? dayAbbr : dayNames)[day];
    897                     }
    898                     break;
    899                 case 'a':
    900                     s = (hour < 12) ? 'AM' : 'PM';
    901                     break;
    902                 case 'h':
    903                     s = (hour % 12) || 12,pad = true;
    904                     break;
    905                 case 'K':
    906                     s = (hour % 12),pad = true;
    907                     break;
    908                 case 'k':
    909                     s = hour || 24,pad = true;
    910                     break;
    911                 case 'S':
    912                     s = round(millisecond * pow(10, l - 3)),pad = true;
    913                     break;
    914                 case 'v':
    915                 case 'z':
    916                     s = comb.date.getTimezoneName(date);
    917                     if (s) {
    918                         break;
    919                     }
    920                     l = 4;
    921                 // fallthrough... use GMT if tz not available
    922                 case 'Z':
    923                     var offset = date.getTimezoneOffset();
    924                     var tz = [
    925                         (offset >= 0 ? "-" : "+"),
    926                         string.pad(floor(abs(offset) / 60), 2, "0"),
    927                         string.pad(abs(offset) % 60, 2, "0")
    928                     ];
    929                     if (l == 4) {
    930                         tz.splice(0, 0, "GMT");
    931                         tz.splice(3, 0, ":");
    932                     }
    933                     s = tz.join("");
    934                     break;
    935 //				case 'Y': case 'u': case 'W': case 'F': case 'g': case 'A': case 'e':
    936 //					console.log(match+" modifier unimplemented");
    937                 default:
    938                     s = match;
    939                 //  throw new Error("comb.date.format: invalid pattern char: " + match);
    940             }
    941             if (pad) {
    942                 s = string.pad(s, l, '0');
    943             }
    944             return s;
    945         });
    946     }
    947
    948 };
    949
    950 date = comb.date;