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;