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;