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;