1 // ========================================================================== 2 // Project: The M-Project - Mobile HTML5 Application Framework 3 // Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved. 4 // (c) 2011 panacoda GmbH. All rights reserved. 5 // Creator: Sebastian 6 // Date: 11.11.2010 7 // License: Dual licensed under the MIT or GPL Version 2 licenses. 8 // http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE 9 // http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE 10 // ========================================================================== 11 12 m_require('core/foundation/object.js'); 13 14 /** 15 * A constant value for milliseconds. 16 * 17 * @type String 18 */ 19 M.MILLISECONDS = 'milliseconds'; 20 21 /** 22 * A constant value for seconds. 23 * 24 * @type String 25 */ 26 M.SECONDS = 'seconds'; 27 28 /** 29 * A constant value for minutes. 30 * 31 * @type String 32 */ 33 M.MINUTES = 'minutes'; 34 35 /** 36 * A constant value for hours. 37 * 38 * @type String 39 */ 40 M.HOURS = 'hours'; 41 42 /** 43 * A constant value for days. 44 * 45 * @type String 46 */ 47 M.DAYS = 'days'; 48 49 /** 50 * A constant array for day names. 51 * 52 * @type String 53 */ 54 M.DAY_NAMES = [ 55 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 56 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 57 ] 58 59 /** 60 * A constant array for month names. 61 * 62 * @type String 63 */ 64 M.MONTH_NAMES = [ 65 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 66 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 67 ]; 68 69 /** 70 * @class 71 * 72 * M.Date defines a prototype for creating, handling and computing dates. It is basically a wrapper 73 * to JavaScripts own date object that provides more convenient functionalities. 74 * 75 * @extends M.Object 76 */ 77 M.Date = M.Object.extend( 78 /** @scope M.Date.prototype */ { 79 80 /** 81 * The type of this object. 82 * 83 * @type String 84 */ 85 type: 'M.Date', 86 /** 87 * The native JavaScript date object. 88 * 89 * @type Object 90 */ 91 date: null, 92 93 /** 94 * Returns the current date, e.g. 95 * Thu Nov 11 2010 14:20:55 GMT+0100 (CET) 96 * 97 * @returns {M.Date} The current date. 98 */ 99 now: function() { 100 return this.extend({ 101 date: new Date() 102 }); 103 }, 104 105 /** 106 * This method returns the date, 24h in the future. 107 * 108 * @returns {M.Date} The current date, 24 hours in the future. 109 */ 110 tomorrow: function() { 111 return this.daysFromNow(1); 112 }, 113 114 /** 115 * This method returns the date, 24h in the past. 116 * 117 * @returns {M.Date} The current date, 24 hours in the past. 118 */ 119 yesterday: function() { 120 return this.daysFromNow(-1); 121 }, 122 123 /** 124 * This method returns a date for a given date string. It is based on JS Date's parse 125 * method. 126 * 127 * The following formats are accepted (time and timezone are optional): 128 * - 11/12/2010 129 * - 11/12/2010 15:28:15 130 * - 11/12/2010 13:28:15 GMT 131 * - 11/12/2010 15:28:15 GMT+0200 132 * - 12 November 2010 133 * - 12 November 2010 15:28:15 134 * - 12 November 2010 13:28:15 GMT 135 * - 12 November 2010 15:28:15 GMT+0200 136 * - 12 Nov 2010 137 * - 12 Nov 2010 15:28:15 138 * - 12 Nov 2010 13:28:15 GMT 139 * - 12 Nov 2010 15:28:15 GMT+0200 140 * 141 * If a wrong formatted date string is given, the method will return null. Otherwise a 142 * JS Date object will be returned. 143 * 144 * @param {String} dateString The date string specifying a certain date. 145 * @returns {M.Date} The date, specified by the given date string. 146 */ 147 create: function(dateString) { 148 var milliseconds = typeof(dateString) === 'number' ? dateString : null; 149 150 if(!milliseconds) { 151 var regexResult = /(\d{1,2})\.(\d{1,2})\.(\d{2,4})/.exec(dateString); 152 if(regexResult && regexResult[1] && regexResult[2] && regexResult[3]) { 153 var date = $.trim(dateString).split(' '); 154 dateString = regexResult[2] + '/' + regexResult[1] + '/' + regexResult[3] + (date[1] ? ' ' + date[1] : ''); 155 } 156 milliseconds = Date.parse(dateString); 157 } 158 159 if(dateString && !milliseconds) { 160 M.Logger.log('Invalid dateString \'' + dateString + '\'.', M.WARN); 161 return null; 162 } else if(!dateString) { 163 return this.now(); 164 } 165 166 return this.extend({ 167 date: new Date(milliseconds) 168 }); 169 }, 170 171 /** 172 * This method formats a given date object according to the passed 'format' property and 173 * returns it as a String. 174 * 175 * The following list defines the special characters that can be used in the 'format' property 176 * to format the given date: 177 * 178 * d Day of the month as digits; no leading zero for single-digit days. 179 * dd Day of the month as digits; leading zero for single-digit days. 180 * ddd Day of the week as a three-letter abbreviation. 181 * dddd Day of the week as its full name. 182 * D Day of the week as number. 183 * m Month as digits; no leading zero for single-digit months. 184 * mm Month as digits; leading zero for single-digit months. 185 * mmm Month as a three-letter abbreviation. 186 * mmmm Month as its full name. 187 * yy Year as last two digits; leading zero for years less than 10. 188 * yyyy Year represented by four digits. 189 * h Hours; no leading zero for single-digit hours (12-hour clock). 190 * hh Hours; leading zero for single-digit hours (12-hour clock). 191 * H Hours; no leading zero for single-digit hours (24-hour clock). 192 * HH Hours; leading zero for single-digit hours (24-hour clock). 193 * M Minutes; no leading zero for single-digit minutes. 194 * MM Minutes; leading zero for single-digit minutes. 195 * s Seconds; no leading zero for single-digit seconds. 196 * ss Seconds; leading zero for single-digit seconds. 197 * l or L Milliseconds. l gives 3 digits. L gives 2 digits. 198 * t Lowercase, single-character time marker string: a or p. 199 * tt Lowercase, two-character time marker string: am or pm. 200 * T Uppercase, single-character time marker string: A or P. 201 * TT Uppercase, two-character time marker string: AM or PM. 202 * Z US timezone abbreviation, e.g. EST or MDT. With non-US timezones or in the Opera browser, the GMT/UTC offset is returned, e.g. GMT-0500 203 * o GMT/UTC timezone offset, e.g. -0500 or +0230. 204 * S The date's ordinal suffix (st, nd, rd, or th). Works well with d. 205 * 206 * @param {String} format The format. 207 * @param {Boolean} utc Determines whether to convert to UTC time or not. Default: NO. 208 * @returns {String} The date, formatted with a given format. 209 */ 210 format: function(format, utc) { 211 if(isNaN(this.date)) { 212 M.Logger.log('Invalid date!', M.WARN); 213 } 214 215 var token = /d{1,4}|D{1}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g; 216 var timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g; 217 var timezoneClip = /[^-+\dA-Z]/g; 218 var pad = function (val, len) { 219 val = String(val); 220 len = len || 2; 221 while (val.length < len) val = "0" + val; 222 return val; 223 }; 224 225 if(arguments.length == 1 && Object.prototype.toString.call(this.date) == "[object String]" && !/\d/.test(this.date)) { 226 format = this.date; 227 date = undefined; 228 } 229 230 var _ = utc ? "getUTC" : "get"; 231 var d = this.date[_ + "Date"](); 232 var D = this.date[_ + "Day"](); 233 var m = this.date[_ + "Month"](); 234 var y = this.date[_ + "FullYear"](); 235 var H = this.date[_ + "Hours"](); 236 var Min = this.date[_ + "Minutes"](); 237 var s = this.date[_ + "Seconds"](); 238 var L = this.date[_ + "Milliseconds"](); 239 var o = utc ? 0 : this.date.getTimezoneOffset(); 240 var flags = { 241 d: d, 242 dd: pad(d), 243 ddd: M.DAY_NAMES[D], 244 dddd: M.DAY_NAMES[D + 7], 245 D: D, 246 m: m + 1, 247 mm: pad(m + 1), 248 mmm: M.MONTH_NAMES[m], 249 mmmm: M.MONTH_NAMES[m + 12], 250 yy: String(y).slice(2), 251 yyyy: y, 252 h: H % 12 || 12, 253 hh: pad(H % 12 || 12), 254 H: H, 255 HH: pad(H), 256 M: Min, 257 MM: pad(Min), 258 s: s, 259 ss: pad(s), 260 l: pad(L, 3), 261 L: pad(L > 99 ? Math.round(L / 10) : L), 262 t: H < 12 ? "a" : "p", 263 tt: H < 12 ? "am" : "pm", 264 T: H < 12 ? "A" : "P", 265 TT: H < 12 ? "AM" : "PM", 266 Z: utc ? "UTC" : (String(this.date).match(timezone) || [""]).pop().replace(timezoneClip, ""), 267 o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), 268 S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] 269 }; 270 271 return format.replace(token, function ($0) { 272 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); 273 }); 274 }, 275 276 /** 277 * This method returns a timestamp. 278 * 279 * @returns {Number} The current date as a timestamp. 280 */ 281 getTimestamp: function() { 282 if(this.date) { 283 return this.date.getTime(); 284 } 285 return null 286 }, 287 288 /** 289 * This method returns a date in the future or past, based on 'days'. Basically it adds or 290 * subtracts x times the milliseconds of a day, but also checks for clock changes and 291 * automatically includes these into the calculation of the future or past date. 292 * 293 * @param {Number} days The number of days to be added to or subtracted from the current date. 294 * @returns {M.Date} The current date, x days in the future (based on parameter 'days'). 295 */ 296 daysFromNow: function(days) { 297 var date = this.now(); 298 return date.daysFromDate(days); 299 }, 300 301 /** 302 * This method returns a date in the future or past, based on 'days' and a given date. Basically 303 * it adds or subtracts x days, but also checks for clock changes and automatically includes 304 * these into the calculation of the future or past date. 305 * 306 * @param {Number} days The number of days to be added to or subtracted from the current date. 307 * @returns {M.Date} The date, x days in the future (based on parameter 'days'). 308 */ 309 daysFromDate: function(days) { 310 return this.millisecondsFromDate(days * 24 * 60 * 60 * 1000); 311 }, 312 313 /** 314 * This method returns a date in the future or past, based on 'hours'. Basically it adds or 315 * subtracts x times the milliseconds of an hour, but also checks for clock changes and 316 * automatically includes these into the calculation of the future or past date. 317 * 318 * @param {Number} hours The number of hours to be added to or subtracted from the current date. 319 * @returns {M.Date} The current date, x hours in the future (based on parameter 'hours'). 320 */ 321 hoursFromNow: function(hours) { 322 var date = this.now(); 323 return date.hoursFromDate(hours); 324 }, 325 326 /** 327 * This method returns a date in the future or past, based on 'hours' and a given date. Basically 328 * it adds or subtracts x hours, but also checks for clock changes and automatically includes 329 * these into the calculation of the future or past date. 330 * 331 * @param {Number} hours The number of hours to be added to or subtracted from the current date. 332 * @returns {M.Date} The date, x hours in the future (based on parameter 'hours'). 333 */ 334 hoursFromDate: function(hours) { 335 return this.millisecondsFromDate(hours * 60 * 60 * 1000); 336 }, 337 338 /** 339 * This method returns a date in the future or past, based on 'minutes'. Basically it adds or 340 * subtracts x times the milliseconds of a minute, but also checks for clock changes and 341 * automatically includes these into the calculation of the future or past date. 342 * 343 * @param {Number} minutes The number of minutes to be added to or subtracted from the current date. 344 * @returns {M.Date} The current date, x minutes in the future (based on parameter 'minutes'). 345 */ 346 minutesFromNow: function(minutes) { 347 var date = this.now(); 348 return date.minutesFromDate(minutes); 349 }, 350 351 /** 352 * This method returns a date in the future or past, based on 'minutes' and a given date. Basically 353 * it adds or subtracts x minutes, but also checks for clock changes and automatically includes 354 * these into the calculation of the future or past date. 355 * 356 * @param {Number} minutes The number of minutes to be added to or subtracted from the current date. 357 * @returns {M.Date} The date, x minutes in the future (based on parameter 'minutes'). 358 */ 359 minutesFromDate: function(minutes) { 360 return this.millisecondsFromDate(minutes * 60 * 1000); 361 }, 362 363 /** 364 * This method returns a date in the future or past, based on 'seconds'. Basically it adds or 365 * subtracts x times the milliseconds of a second, but also checks for clock changes and 366 * automatically includes these into the calculation of the future or past date. 367 * 368 * @param {Number} seconds The number of seconds to be added to or subtracted from the current date. 369 * @returns {M.Date} The current date, x seconds in the future (based on parameter 'seconds'). 370 */ 371 secondsFromNow: function(seconds) { 372 var date = this.now(); 373 return date.secondsFromDate(seconds); 374 }, 375 376 /** 377 * This method returns a date in the future or past, based on 'seconds' and a given date. Basically 378 * it adds or subtracts x seconds, but also checks for clock changes and automatically includes 379 * these into the calculation of the future or past date. 380 * 381 * @param {Number} seconds The number of seconds to be added to or subtracted from the current date. 382 * @returns {M.Date} The date, x seconds in the future (based on parameter 'seconds'). 383 */ 384 secondsFromDate: function(seconds) { 385 return this.millisecondsFromDate(seconds * 1000); 386 }, 387 388 /** 389 * This method returns a date in the future or past, based on 'milliseconds'. Basically it adds or 390 * subtracts x milliseconds, but also checks for clock changes and automatically includes these 391 * into the calculation of the future or past date. 392 * 393 * @param {Number} milliseconds The number of milliseconds to be added to or subtracted from the current date. 394 * @returns {M.Date} The current date, x milliseconds in the future (based on parameter 'milliseconds'). 395 */ 396 millisecondsFromNow: function(milliseconds) { 397 var date = this.now(); 398 return date.millisecondsFromDate(milliseconds); 399 }, 400 401 /** 402 * This method returns a date in the future or past, based on 'milliseconds' and a given date. Basically 403 * it adds or subtracts x milliseconds, but also checks for clock changes and automatically includes 404 * these into the calculation of the future or past date. 405 * 406 * @param {Number} milliseconds The number of milliseconds to be added to or subtracted from the current date. 407 * @returns {M.Date} The date, x milliseconds in the future (based on parameter 'milliseconds'). 408 */ 409 millisecondsFromDate: function(milliseconds) { 410 if(!this.date) { 411 M.Logger.log('no date specified!', M.ERR); 412 } 413 414 return this.extend({ 415 date: new Date(this.getTimestamp() + milliseconds) 416 }); 417 }, 418 419 /** 420 * This method returns the time between two dates, based on the given returnType. 421 * 422 * Possible returnTypes are: 423 * - M.MILLISECONDS: milliseconds 424 * - M.SECONDS: seconds 425 * - M.MINUTES: minutes 426 * - M.HOURS: hours 427 * - M.DAYS: days 428 * 429 * @param {Object} date The date. 430 * @param {String} returnType The return type for the call. 431 * @returns {Number} The time between the two dates, computed as what is specified by the 'returnType' parameter. 432 */ 433 timeBetween: function(date, returnType) { 434 var firstDateInMilliseconds = this.date ? this.getTimestamp() : null; 435 var secondDateInMilliseconds = date.date ? date.getTimestamp() : null; 436 437 if(firstDateInMilliseconds && secondDateInMilliseconds) { 438 switch (returnType) { 439 case M.DAYS: 440 return (secondDateInMilliseconds - firstDateInMilliseconds) / (24 * 60 * 60 * 1000); 441 break; 442 case M.HOURS: 443 return (secondDateInMilliseconds - firstDateInMilliseconds) / (60 * 60 * 1000); 444 break; 445 case M.MINUTES: 446 return (secondDateInMilliseconds - firstDateInMilliseconds) / (60 * 1000); 447 break; 448 case M.SECONDS: 449 return (secondDateInMilliseconds - firstDateInMilliseconds) / 1000; 450 break; 451 case M.MILLISECONDS: 452 default: 453 return (secondDateInMilliseconds - firstDateInMilliseconds); 454 break; 455 } 456 } else if(firstDateInMilliseconds) { 457 M.Logger.log('invalid date passed.', M.ERR); 458 } else { 459 M.Logger.log('invalid date.', M.ERR); 460 } 461 }, 462 463 464 /** 465 * This method computes the calendar week of a date. It can either be executed on a M.Date object, 466 * to get the calendar week of that date, or you can pass parameters to get the calendar week 467 * for the specified date. 468 * 469 * @param {Number} year The year part of the date, e.g. 2011. Must be four digits. 470 * @param {Number} month The month part of the date: 0-11. Must be one/two digit. 471 * @param {Number} day The day part of the date: 1-31. Must be one/two digits. 472 * 473 * @returns {Number} The calendar week: 1-52. 474 */ 475 getCalendarWeek: function(year, month, day){ 476 if(!year) { 477 year = parseInt(this.format('yyyy')); 478 month = parseInt(this.format('m')); 479 day = parseInt(this.format('d')); 480 } else { 481 month += 1; 482 } 483 484 var a = Math.floor((14 - (month)) / 12); 485 var y = year + 4800 - a; 486 var m = (month) + (12 * a) - 3; 487 var jd = day + Math.floor(((153 * m) + 2) / 5) + (365 * y) + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045; 488 var d4 = (jd + 31741 - (jd % 7)) % 146097 % 36524 % 1461; 489 var L = Math.floor(d4 / 1460); 490 var d1 = ((d4 - L) % 365) + L; 491 var calendarWeek = Math.floor(d1 / 7) + 1; 492 493 return calendarWeek; 494 }, 495 496 /** 497 * This method returns an array containing all dates within one calendar week. If no parameters are given, 498 * the calendar week of the current date is taken. 499 * 500 * @param {Number} calendarWeek The calendar week. Note: Pass 'null' if you use this method on an existing M.Date object. 501 * @param {Boolean} startWeekOnMonday Determines whether a week starts on monday or sunday (optional, default is NO). 502 * @param {Number} year The year (optional, default is current year). 503 * 504 * @returns {Array} An array containing all dates within the specified calendar week. 505 */ 506 getDatesOfCalendarWeek: function(calendarWeek, startWeekOnMonday, year) { 507 year = year && !isNaN(year) ? year : (this.date ? this.format('yyyy') : M.Date.now().format('yyyy')); 508 var newYear = M.Date.create('01/01/' + year); 509 var newYearWeekDay = newYear.format('D'); 510 511 var firstWeek = null; 512 if(startWeekOnMonday) { 513 firstWeek = newYearWeekDay == 1 ? newYear : newYear.daysFromDate(8 - (newYearWeekDay == 0 ? 7 : newYearWeekDay)); 514 } else { 515 firstWeek = newYearWeekDay == 0 ? newYear : newYear.daysFromDate(7 - newYearWeekDay); 516 } 517 518 calendarWeek = calendarWeek ? calendarWeek : this.getCalendarWeek(); 519 520 var requiredWeek = firstWeek.daysFromDate((calendarWeek - 1) * 7); 521 522 var dates = []; 523 for(var i = 0; i < 7; i++) { 524 var date = requiredWeek.daysFromDate(i); 525 date = M.Date.create(date.format('mm') + '/' + date.format('dd') + '/' + date.format('yyyy')); 526 dates.push(date); 527 } 528 529 return dates; 530 }, 531 532 /** 533 * This method returns a date for a given calendar week and day of this week. 534 * 535 * @param {Number} calendarWeek The calendar week. 536 * @param {Number} dayOfWeek The day of the week (0 = sunday, ..., 7 = saturday). 537 * @param {Number} year The year (optional, default is current year). 538 * 539 * @returns {M.Date} The date. 540 */ 541 getDateByWeekdayAndCalendarWeek: function(calendarWeek, dayOfWeek, year) { 542 if(calendarWeek && !isNaN(calendarWeek) && ((dayOfWeek && !isNaN(dayOfWeek)) || dayOfWeek === 0)) { 543 var dates = M.Date.getDatesOfCalendarWeek(calendarWeek, NO, year); 544 if(dates && dates.length > 0 && dates[dayOfWeek]) { 545 return dates[dayOfWeek]; 546 } else { 547 M.Logger.log('Day ' + dayOfWeek + ' of calendar week ' + calendarWeek + ' could not be found!', M.ERR); 548 } 549 } else { 550 M.Logger.log('Please pass a valid calendarWeek and a valid day of the week!', M.ERR); 551 } 552 }, 553 554 /** 555 * This method is used for stringify an M.Date object, e.g. when persisting it into locale storage. 556 * 557 * @private 558 * @returns {String} The date as a string. 559 */ 560 toJSON: function() { 561 return String(this.date); 562 } 563 564 });