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;