      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 });