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