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