/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* Defines {@link baja.RelTime}.
* @module baja/obj/RelTime
*/
define([
"bajaScript/sys",
"bajaScript/baja/obj/Simple",
"bajaScript/baja/obj/dateTimeUtil",
"bajaScript/baja/obj/objUtil",
"bajaPromises" ], function (
baja,
Simple,
dateTimeUtil,
objUtil,
Promise) {
"use strict";
var subclass = baja.subclass,
callSuper = baja.callSuper,
objectify = baja.objectify,
strictArg = baja.strictArg,
bajaDef = baja.def,
cacheDecode = objUtil.cacheDecode,
cacheEncode = objUtil.cacheEncode,
DAYS_IN_MONTH = 30,
DAYS_IN_YEAR = 365,
MILLIS_IN_SECOND = dateTimeUtil.MILLIS_IN_SECOND,
MILLIS_IN_MINUTE = dateTimeUtil.MILLIS_IN_MINUTE,
MILLIS_IN_HOUR = dateTimeUtil.MILLIS_IN_HOUR,
MILLIS_IN_DAY = dateTimeUtil.MILLIS_IN_DAY,
MILLIS_IN_MONTH = MILLIS_IN_DAY * DAYS_IN_MONTH,
MILLIS_IN_YEAR = MILLIS_IN_DAY * DAYS_IN_YEAR;
/**
* Represents a `baja:RelTime` in BajaScript.
*
* `RelTime` is a `Simple` type for managing a relative amount of time.
*
* When creating a `Simple`, always use the `make()` method instead of
* creating a new Object.
*
* @class
* @alias baja.RelTime
* @extends baja.Simple
*/
var RelTime = function RelTime(ms) {
callSuper(RelTime, this, arguments);
this.$ms = parseInt(ms, 10);
};
subclass(RelTime, Simple);
/**
* Make a `RelTime`.
*
* @param {Object|Number} [obj] the Object Literal or the number of milliseconds.
* @param {Number} [obj.days] the number of days.
* @param {Number} [obj.hours] the number of hours.
* @param {Number} [obj.minutes] the number of minutes.
* @param {Number} [obj.seconds] the number of seconds.
* @param {Number} [obj.ms] the number of milliseconds.
* @returns {baja.RelTime}
*
* @example
* //This method can take a number of milliseconds or an Object Literal as
* //method's argument...
* var rt1 = baja.RelTime.make(1000); // One second
*
* // ...or we can specify an Object Literal for more arguments...
*
* // Create a RelTime with 2 days + 2 hours + 2 minutes + 2 seconds + 2 milliseconds...
* var rt2 = baja.RelTime.make({
* days: 2,
* hours: 2,
* minutes: 2,
* seconds: 2,
* ms: 2
* });
*/
RelTime.make = function (obj) {
var ms = 0;
if (typeof obj === "number") {
ms = obj;
} else {
obj = objectify(obj, "ms");
ms = bajaDef(obj.ms, 0);
strictArg(ms, Number);
if (typeof obj.days === "number") {
ms += MILLIS_IN_DAY * obj.days;
}
if (typeof obj.hours === "number") {
ms += MILLIS_IN_HOUR * obj.hours;
}
if (typeof obj.minutes === "number") {
ms += MILLIS_IN_MINUTE * obj.minutes;
}
if (typeof obj.seconds === "number") {
ms += MILLIS_IN_SECOND * obj.seconds;
}
}
if (ms === 0) {
return RelTime.DEFAULT;
}
return new RelTime(ms);
};
/**
* Make a `RelTime`.
*
* @param {Object|Number} [obj] the Object Literal or the number of milliseconds.
* @param {Number} [obj.days] the number of days.
* @param {Number} [obj.hours] the number of hours.
* @param {Number} [obj.minutes] the number of minutes.
* @param {Number} [obj.seconds] the number of seconds.
* @param {Number} [obj.ms] the number of milliseconds.
* @returns {baja.RelTime}
*
* @example
* //This method can take a number of milliseconds of an Object Literal with
* //the method's argument...
* var rt1 = baja.$("baja:RelTime").make(1000); // One second
*
* // ...or we can specify an Object Literal for more arguments...
*
* // Create a RelTime with 2 days + 2 hours + 2 minutes + 2 seconds + 2 milliseconds...
* var rt2 = baja.$("baja:RelTime").make({
* days: 2,
* hours: 2,
* minutes: 2,
* seconds: 2,
* ms: 2
* });
*/
RelTime.prototype.make = function (obj) {
return RelTime.make.apply(RelTime, arguments);
};
/**
* Decode a `RelTime` from a `String`.
*
* @method
* @param {String} str
* @returns {baja.RelTime}
*/
RelTime.prototype.decodeFromString = cacheDecode(function (str) {
// Parse number
var n = Number(str);
// If still not a number then throw an error
if (isNaN(n)) {
throw new Error("Unable to create RelTime: " + str);
}
return RelTime.make(n);
});
/**
* Encode the `RelTime` to a `String`.
*
* @method
* @returns {String}
*/
RelTime.prototype.encodeToString = cacheEncode(function () {
return this.$ms.toString();
});
/**
* Default `RelTime` instance.
* @type {baja.RelTime}
*/
RelTime.DEFAULT = new RelTime(0);
/**
* Milliseconds in a second.
* @type {Number}
*/
RelTime.MILLIS_IN_SECOND = MILLIS_IN_SECOND;
/**
* Milliseconds in a minute.
* @type {Number}
*/
RelTime.MILLIS_IN_MINUTE = MILLIS_IN_MINUTE;
/**
* Milliseconds in an hour.
* @type {Number}
*/
RelTime.MILLIS_IN_HOUR = MILLIS_IN_HOUR;
/**
* Milliseconds in a day.
* @type {Number}
*/
RelTime.MILLIS_IN_DAY = MILLIS_IN_DAY;
/**
* `RelTime` instance for a second.
* @type {baja.RelTime}
*/
RelTime.SECOND = RelTime.make(MILLIS_IN_SECOND);
/**
* `RelTime` instance for a minute.
* @type {baja.RelTime}
*/
RelTime.MINUTE = RelTime.make(MILLIS_IN_MINUTE);
/**
* `RelTime` instance for an hour.
* @type {baja.RelTime}
*/
RelTime.HOUR = RelTime.make(MILLIS_IN_HOUR);
/**
* `RelTime` instance for a day.
* @type {baja.RelTime}
*/
RelTime.DAY = RelTime.make(MILLIS_IN_DAY);
/**
* Equality test.
*
* @param obj
* @returns {Boolean}
*/
RelTime.prototype.equals = function (obj) {
return objUtil.valueOfEquals(this, obj);
};
/**
* Return the data type symbol.
*
* @returns {String}
*/
RelTime.prototype.getDataTypeSymbol = function () {
return "r";
};
/**
* Return number of milliseconds.
*
* @returns {Number}
*/
RelTime.prototype.valueOf = function () {
return this.$ms;
};
/**
* Return a `String` representation of a `RelTime`.
*
* @param {Object} [cx] the context.
* @param {Boolean} [cx.showMilliseconds=true] set to false to hide milliseconds.
* @param {Boolean} [cx.showSeconds=true] set to false to hide seconds. If
* showSeconds is false then milliseconds will not be shown.
* @param {Boolean} [cx.showMinutes=true] set to false to hide minutes.
* @param {Boolean} [cx.showHours=true] set to false to hide hours.
* @param {Boolean} [cx.showDays=true] set to false to hide days.
*
* @example
* // IMPORTANT NOTE
* //
* // If a unit is greater than zero but the show flag for it is set to
* // false, then its value will get carried over to the leftmost shown
* // unit.
*
* var relTime = baja.RelTime.make({
* hours: 1, seconds: 1
* });
*
* // 1hour 1sec
* relTime.toString();
*
* // 60mins 1sec
* relTime.toString({
* showHours: false
* });
*
* // 3601secs
* relTime.toString({
* showHours: false,
* showMinutes: false
* });
*
* @returns {String|Promise.<string>} if context is given, a Promise that
* resolves to a localized string; otherwise an unlocalized string
*/
RelTime.prototype.toString = function (cx) {
var showHideContext = cx || {};
var numberContext = cx && { showSeparators: cx.showSeparators, precision: 0 };
var showMillis = showHideContext.showMilliseconds !== false,
showSeconds = showHideContext.showSeconds !== false,
showMinutes = showHideContext.showMinutes !== false,
showHours = showHideContext.showHours !== false,
//if either context.showDays or context.showDay === false then showDays will be set to false
showDays = showHideContext.showDays !== false,
fields = [],
prefix = '';
var ms = this.$ms;
if (ms < 0) {
prefix = '-';
ms = -ms;
}
if (ms < 1000) {
if (showSeconds) {
if (showMillis) {
fields.push(getText('ms', numberContext, ms));
} else {
fields.push(getText('seconds', numberContext, 0));
}
} else if (showMinutes) {
fields.push(getText('minutes', numberContext, 0));
} else if (showHours) {
fields.push(getText('hours', numberContext, 0));
} else if (showDays) {
fields.push(getText('days', numberContext, 0));
}
} else {
if (ms >= MILLIS_IN_DAY && showDays) {
var days = Math.abs(this.getDaysPart());
ms = ms % MILLIS_IN_DAY;
fields.push(getText(days === 1 ? 'day' : 'days', numberContext, days));
}
if (ms >= MILLIS_IN_HOUR && showHours) {
var hours;
if (!showDays) {
hours = Math.abs(this.getHours());
} else {
hours = Math.abs(this.getHoursPart());
}
ms = ms % MILLIS_IN_HOUR;
fields.push(getText(hours === 1 ? 'hour' : 'hours', numberContext, hours));
}
if (ms >= MILLIS_IN_MINUTE && showMinutes) {
var mins;
if (!showHours) {
mins = Math.abs(this.getMinutes());
} else {
mins = Math.abs(this.getMinutesPart());
}
ms = ms % MILLIS_IN_MINUTE;
fields.push(getText(mins === 1 ? 'minute' : 'minutes', numberContext, mins));
}
if (ms > 0 && showSeconds) {
fields.push(toSeconds(ms, showMillis, numberContext));
}
}
return joinFields(fields, prefix, cx);
};
/**
* Returns a friendly string indicating the time interval from the present
* moment. For example: "right now", "a few seconds ago", "3 months from now",
* "an hour ago".
* @returns {Promise.<string>} a human-readable string indicating the time interval, relative to the present moment
* @since Niagara 4.8
*/
RelTime.prototype.toFriendlyString = function () {
var that = this;
return baja.lex({ module: 'baja' })
.then(function (bajaLex) {
var millis = Math.abs(that.getMillis());
var tag;
if (millis === 0) {
return getFriendlyText(bajaLex, 'rightNow');
}
if (millis < MILLIS_IN_MINUTE) {
tag = getFriendlyText(bajaLex, 'aFewSeconds');
} else if (millis < MILLIS_IN_MINUTE * 2) {
tag = getFriendlyText(bajaLex, 'minute');
} else if (millis < MILLIS_IN_HOUR) {
tag = getFriendlyText(bajaLex, 'minutes', Math.abs(that.getMinutes()));
} else if (millis < MILLIS_IN_HOUR * 2) {
tag = getFriendlyText(bajaLex, 'hour');
} else if (millis < MILLIS_IN_DAY) {
tag = getFriendlyText(bajaLex, 'hours', Math.abs(that.getHours()));
} else if (millis < MILLIS_IN_DAY * 2) {
tag = getFriendlyText(bajaLex, 'day');
} else if (millis < MILLIS_IN_MONTH) {
tag = getFriendlyText(bajaLex, 'days', Math.abs(that.getDays()));
} else if (millis < MILLIS_IN_MONTH * 2) {
tag = getFriendlyText(bajaLex, 'month');
} else if (millis < MILLIS_IN_YEAR) {
tag = getFriendlyText(bajaLex, 'months', Math.floor(Math.abs(that.getDays()) / DAYS_IN_MONTH));
} else if (millis < MILLIS_IN_YEAR * 2) {
tag = getFriendlyText(bajaLex, 'year');
} else {
tag = getFriendlyText(bajaLex, 'years', Math.floor(Math.abs(that.getDays()) / DAYS_IN_YEAR));
}
return getFriendlyText(bajaLex, that.getMillis() < 0 ? 'inThePast' : 'inTheFuture', tag);
});
};
/**
* @param {string} suffix retrieve from baja lexicon as `relTime.{suffix}`
* @param {object} [cx]
* @param {*} arg
* @returns {Promise.<string>|string} promise if context is present, otherwise string
*/
function getText(suffix, cx, arg) {
if (cx) {
return Promise.all([
getBajaLex(),
arg.toString(cx)
])
.then(function (results) {
return results[0].get({
key: 'relTime.' + suffix,
def: '{0} ' + suffix,
args: [ results[1] ]
});
});
} else {
return arg + ' ' + suffix;
}
}
/**
* @param bajaLex the `baja` lexicon
* @param {string} suffix retrieve from baja lexicon as `relTime.friendly.{suffix}`
* @param {*} arg
* @returns {Promise.<string>}
*/
function getFriendlyText(bajaLex, suffix, arg) {
return bajaLex.get({
key: 'relTime.friendly.' + suffix,
def: '{0} ' + suffix,
args: [ arg ]
});
}
/**
* @param {Array.<Promise.<string>|string>} fields if context present will
* resolve these as promises, otherwise as synchronous strings
* @param {string} [prefix] prefix to prepend to result
* @param {object} [cx]
* @returns {Promise.<string>|string} promise if context is present, otherwise string
*/
function joinFields(fields, prefix, cx) {
if (cx) {
return Promise.all([
Promise.all(fields),
getBajaLex()
.then(function (lex) {
return lex.get({ key: 'relTime.separator', def: ',' });
})
])
.then(function (results) {
return prefix + results[0].join(results[1] + ' ');
});
} else {
return prefix + fields.join(', ');
}
}
/**
* @param {number} ms number of milliseconds, truncated to seconds
* @param {boolean} showMillis
* @param {object} [cx]
* @returns {Promise.<string>|string} promise if context is present, otherwise string
*/
function toSeconds(ms, showMillis, cx) {
var secs = ms / MILLIS_IN_SECOND;
var millis = ms % MILLIS_IN_SECOND;
var suffix = (secs === 1 && !millis) ? 'second' : 'seconds';
if (cx) {
return Promise.resolve(secs.toString({
showSeparators: cx.showSeparators,
precision: (millis && showMillis) ? 3 : 0
}))
.then(function (string) {
return getText(suffix, cx, string);
});
} else {
return getText(suffix, cx, millis ? secs.toFixed(3) : secs);
}
}
/** @returns {Promise} */
function getBajaLex() {
return baja.lex({ module: 'baja' });
}
/**
* Return number of milliseconds.
*
* @returns {Number}
*/
RelTime.prototype.getMillis = function () {
return this.$ms;
};
/**
* Return the milliseconds part of this duration.
*
* @returns {Number}
*/
RelTime.prototype.getMillisPart = function () {
return this.$ms % 1000;
};
function truncateToInteger(num) {
return Math[num < 0 ? 'ceil' : 'floor'](num);
}
/**
* Return number of seconds.
*
* @returns {Number}
*/
RelTime.prototype.getSeconds = function () {
return truncateToInteger(this.$ms / MILLIS_IN_SECOND);
};
/**
* Return the seconds part of this duration.
*
* @returns {Number}
*/
RelTime.prototype.getSecondsPart = function () {
return this.getSeconds() % 60;
};
/**
* Return number of minutes.
*
* @returns {Number}
*/
RelTime.prototype.getMinutes = function () {
return truncateToInteger(this.$ms / MILLIS_IN_MINUTE);
};
/**
* Return the minutes part of this duration.
*
* @returns {Number}
*/
RelTime.prototype.getMinutesPart = function () {
return this.getMinutes() % 60;
};
/**
* Return number of hours.
*
* @returns {Number}
*/
RelTime.prototype.getHours = function () {
return truncateToInteger(this.$ms / MILLIS_IN_HOUR);
};
/**
* Return the hours part of this duration.
*
* @returns {Number}
*/
RelTime.prototype.getHoursPart = function () {
return this.getHours() % 24;
};
/**
* Return number of days.
*
* @returns {Number}
*/
RelTime.prototype.getDays = function () {
return truncateToInteger(this.$ms / MILLIS_IN_DAY);
};
/**
* Return the days part of this duration.
*
* @returns {Number}
*/
RelTime.prototype.getDaysPart = function () {
return this.getDays();
};
return RelTime;
});