/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* Defines {@link baja.Time}.
* @module baja/obj/Time
*/
define([ "bajaScript/sys",
"bajaScript/baja/obj/Simple",
"bajaScript/baja/obj/TimeFormat",
"bajaScript/baja/obj/dateTimeUtil",
"bajaScript/baja/obj/numberUtil",
"bajaScript/baja/obj/objUtil" ], function (
baja,
Simple,
TimeFormat,
dateTimeUtil,
numberUtil,
objUtil) {
"use strict";
var subclass = baja.subclass,
callSuper = baja.callSuper,
strictArg = baja.strictArg,
strictAllArgs = baja.strictAllArgs,
objectify = baja.objectify,
bajaHasType = baja.hasType,
bajaDef = baja.def,
cacheDecode = objUtil.cacheDecode,
cacheEncode = objUtil.cacheEncode,
SHOW_DATE = TimeFormat.SHOW_DATE,
SHOW_TIME = TimeFormat.SHOW_TIME,
SHOW_SECONDS = TimeFormat.SHOW_SECONDS,
SHOW_MILLIS = TimeFormat.SHOW_MILLIS,
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,
toDateTimeString = dateTimeUtil.toDateTimeString,
toDateTimeStringSync = dateTimeUtil.toDateTimeStringSync,
addZeroPad = numberUtil.addZeroPad;
/**
* Represents a `baja:Time` in BajaScript.
*
* `Time` stores a time of day which is independent
* of any date in the past or future.
*
* When creating a `Simple`, always use the `make()` method instead of
* creating a new Object.
*
* @class
* @alias baja.Time
* @extends baja.Simple
*/
var Time = function Time(hour, min, sec, ms) {
// Constructor should be considered private
callSuper(Time, this, arguments);
this.$hour = hour;
this.$min = min;
this.$sec = sec;
this.$ms = ms;
};
subclass(Time, Simple);
function isNumber(num) {
return baja.hasType(num, 'baja:Number');
}
/**
* Make a `Time`.
*
* @param {Object|number} obj - the object literal used for the method's
* arguments, or number of milliseconds past the start of the day.
*
* @param {Number} [obj.hour] hours - (0-23).
*
* @param {Number} [obj.min] minutes - (0-59).
*
* @param {Number} [obj.sec] seconds - (0-59).
*
* @param {Number} [obj.ms] milliseconds - (0-999).
*
* @param {baja.RelTime} [obj.relTime] - if defined, this is the milliseconds
* since the start of the day. This overrides the other hour, min, sec and ms
* arguments.
*
* @param {number} [obj.milliseconds] - if defined, this is the milliseconds
* since the start of the day. This overrides the other hour, min, sec, ms
* and relTime arguments.
*
* @returns {baja.Time}
*
* @example
* // An object literal is used for the method's arguments...
* var t1 = baja.Time.make({
* hour: 23,
* min: 12,
* sec: 15,
* ms: 789
* });
*
* // ...or use a baja.RelTime to specify hour, min, sec and ms...
* var t2 = baja.Time.make({
* relTime: myRelTime
* });
*
* // ...or pass in milliseconds past midnight...
* var t3 = baja.Time.make(12345);
*/
Time.make = function (obj) {
obj = objectify(obj, "milliseconds");
var hour = bajaDef(obj.hour, 0),
min = bajaDef(obj.min, 0),
sec = bajaDef(obj.sec, 0),
ms = bajaDef(obj.ms, 0);
function processMillis(millis) {
strictArg(millis, Number);
millis = millis % MILLIS_IN_DAY;
hour = Math.floor(millis / MILLIS_IN_HOUR);
millis = millis % MILLIS_IN_HOUR;
min = Math.floor(millis / MILLIS_IN_MINUTE);
millis = millis % MILLIS_IN_MINUTE;
sec = Math.floor(millis / MILLIS_IN_SECOND);
ms = Math.floor(millis % MILLIS_IN_SECOND);
}
if (typeof obj.milliseconds === "number") {
// Create from a number of milliseconds...
processMillis(obj.milliseconds);
} else if (bajaHasType(obj.relTime, 'baja:RelTime')) {
// Build from rel time (overrides other hour, min, sec and ms on object)...
processMillis(obj.relTime.getMillis());
} else {
// Attempt to get time from Object Literal...
if (isNumber(hour)) { hour = hour.valueOf(); }
if (isNumber(min)) { min = min.valueOf(); }
if (isNumber(sec)) { sec = sec.valueOf(); }
if (isNumber(ms)) { ms = ms.valueOf(); }
}
// Ensure we're dealing with numbers
strictAllArgs([ hour, min, sec, ms ], [ Number, Number, Number, Number ]);
if (hour < 0 || hour > 23 ||
min < 0 || min > 59 ||
sec < 0 || sec > 59 ||
ms < 0 || ms > 999) {
throw new Error("Invalid time: " +
hour + ":" +
min + ":" +
sec + "." +
ms);
}
if (hour === 0 && min === 0 && sec === 0 && ms === 0) {
return Time.DEFAULT;
}
return new Time(hour, min, sec, ms);
};
/**
* Make a `Time`.
*
* @param {Object} obj - the object literal used for the method's arguments.
*
* @param {Number} [obj.hour] hours - (0-23).
*
* @param {Number} [obj.min] minutes - (0-59).
*
* @param {Number} [obj.sec] seconds - (0-59).
*
* @param {Number} [obj.ms] milliseconds - (0-999).
*
* @param {baja.RelTime} [obj.relTime] - if defined, this is the milliseconds
* since the start of the day. This overrides the other hour, min, sec and ms
* arguments.
*
* @returns {baja.Time}
*
* @example
* // An object literal is used for the method's arguments...
* var t1 = baja.$("baja:Time").make({
* hour: 23,
* min: 12,
* sec: 15,
* ms: 789
* });
*
* // ...or use a baja.RelTime to specify hour, min, sec and ms...
* var t2 = baja.$("baja:Time").make({
* relTime: timeOfDayMillis
* });
*
* // ...or pass in milliseconds past midnight...
* var t3 = baja.Time.make(12345);
*/
Time.prototype.make = function (obj) {
return Time.make.apply(Time, arguments);
};
/**
* Decode a `Time` from a `String`.
*
* @method
*
* @param {String} str
*
* @returns {baja.Time}
*/
Time.prototype.decodeFromString = cacheDecode(function (str) {
// Time ISO 8601 format hh:mm:ss.mmm
var res = /^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])\.([0-9]{3})$/
.exec(str);
if (res === null) {
throw new Error("Failed to decode time: " + str);
}
return Time.make({
hour: parseInt(res[1], 10),
min: parseInt(res[2], 10),
sec: parseInt(res[3], 10),
ms: parseInt(res[4], 10)
});
});
/**
* Encode a `Time` to a `String`.
*
* @method
*
* @returns {String}
*/
Time.prototype.encodeToString = cacheEncode(function () {
return addZeroPad(this.$hour, 2) + ":" +
addZeroPad(this.$min, 2) + ":" +
addZeroPad(this.$sec, 2) + "." +
addZeroPad(this.$ms, 3);
});
/**
* Equality test.
*
* @param obj
*
* @returns {Boolean}
*/
Time.prototype.equals = function (obj) {
if (bajaHasType(obj) && obj.getType().equals(this.getType())) {
return this.getTimeOfDayMillis() === obj.getTimeOfDayMillis();
}
return false;
};
/**
* Default `Time` instance.
* @type {baja.Time}
*/
Time.DEFAULT = new Time(0, 0, 0, 0);
/**
* Midnight `Time`.
* @type {baja.Time}
*/
Time.MIDNIGHT = Time.DEFAULT;
/**
* Return hours (0-23).
*
* @returns {Number}
*/
Time.prototype.getHour = function () {
return this.$hour;
};
/**
* Return minutes (0-59).
*
* @returns {Number}
*/
Time.prototype.getMinute = function () {
return this.$min;
};
/**
* Return seconds (0-59).
*
* @returns {Number}
*/
Time.prototype.getSecond = function () {
return this.$sec;
};
/**
* Return milliseconds (0-999).
*
* @returns {Number}
*/
Time.prototype.getMillisecond = function () {
return this.$ms;
};
/**
* Return the milliseconds since the start of the day.
*
* @returns {Number}
*/
Time.prototype.getTimeOfDayMillis = function () {
if (this.$timeOfDayMs === undefined) {
var ret = this.$hour * MILLIS_IN_HOUR;
ret += this.$min * MILLIS_IN_MINUTE;
ret += this.$sec * MILLIS_IN_SECOND;
ret += this.$ms;
this.$timeOfDayMs = ret;
}
return this.$timeOfDayMs;
};
/**
* Return a new time of day by adding the specified duration. If the result
* goes past midnight, then roll into the next day.
*
* @param {baja.RelTime|baja.Time|Number} duration - RelTime or number of
* millis
*
* @returns {baja.Time} the new time with the duration added on.
*/
Time.prototype.add = function (duration) {
strictArg(duration);
if (typeof duration.getMillis === 'function') {
duration = duration.getMillis();
} else if (typeof duration.getTimeOfDayMillis === 'function') {
duration = duration.getTimeOfDayMillis();
}
strictArg(duration, Number);
return Time.make(MILLIS_IN_DAY + this.getTimeOfDayMillis() + duration);
};
/**
* Return true if the specified time is before this time.
*
* @param {baja.Time} time
*
* @returns {Boolean}
*/
Time.prototype.isBefore = function (time) {
strictArg(time, Time);
return this.getTimeOfDayMillis() < time.getTimeOfDayMillis();
};
/**
* Return true if the specified time is after this time.
*
* @param {baja.Time} time
*
* @returns {Boolean}
*/
Time.prototype.isAfter = function (time) {
strictArg(time, Time);
return this.getTimeOfDayMillis() > time.getTimeOfDayMillis();
};
function doToTimeString(toStringFunc, time, obj) {
obj = objectify(obj);
var textPattern = obj.textPattern || baja.getTimeFormatPattern(),
show = calculateShow(obj) | SHOW_TIME;
// Filter out invalid flags
show &= ~SHOW_DATE;
return toStringFunc({
ok: obj.ok,
fail: obj.fail,
show: show,
textPattern: textPattern,
hour: time.$hour,
min: time.$min,
sec: time.$sec,
ms: time.$ms
}, obj.lex);
}
function calculateShow(obj) {
if (typeof obj.show === 'number') {
return obj.show;
}
var showSeconds = obj.showSeconds ? SHOW_SECONDS : 0,
showMilliseconds = obj.showMilliseconds ?
SHOW_MILLIS | SHOW_SECONDS : 0;
return SHOW_TIME | showSeconds | showMilliseconds;
}
/**
* Asynchronously get a `String` representation of the time.
*
* This method is invoked asynchronously. A `Function` callback or an object
* literal that contains a `Function` callback must be supplied.
*
* @param {Object|Function} [obj] - the Object Literal for the method's arguments
* or a Function that will be called with the formatting time String.
*
* @param {Function} [obj.ok] - (Deprecated: use Promise) the Function callback
* that will be invoked once the time has been formatted into a String.
*
* @param {Function} [obj.fail] - (Deprecated: use Promise) the Function
* callback that will be invoked if a fatal error occurs whilst formatting the
* String.
*
* @param {String} [obj.textPattern] - the text pattern to use for formatting.
* If not specified, then the user's default time format text pattern will be
* used.
*
* @param {Number} [obj.show] - flags used to format the time. For more
* information, please see {@link baja.TimeFormat}.
*
* @returns {Promise.<String>} promise to be resolved with the time string
*
* @example
* myTime.toTimeString().then(function(timeStr) {
* baja.outln("The time is: " + timeStr);
* });
*/
Time.prototype.toTimeString = function (obj) {
return doToTimeString(toDateTimeString, this, objectify(obj, "ok"));
};
/**
* Synchronously get a `String` representation of the time.
*
* This method is invoked synchronously. The string result will be returned
* directly from this function. Since building up `Time` string
* representations requires the `baja` lexicon, said lexicon *must* already be
* retrieved and passed into this method. Apart from that, the behavior is the
* same as the asynchronous {@link baja.Time#toTimeString}.
*
* @param {Object|Function} obj the Object Literal for the method's arguments
* or a Function that will be called with the formatting time String.
*
* @param {String} [obj.textPattern] the text pattern to use for formatting.
* If not specified, then the user's default time format text pattern will be
* used.
*
* @param {Number} [obj.show] flags used to format the time. For more
* information, please see {@link baja.TimeFormat}.
*
* @param obj.lex the `baja` lexicon
*
* @returns {String}
*
* @throws {Error} if the lexicon is not passed in, or is not the
* `baja` lexicon
*/
Time.prototype.toTimeStringSync = function (obj) {
return doToTimeString(toDateTimeStringSync, this, obj);
};
/**
* @see .toTimeStringSync
*/
Time.prototype.toString = function (obj) {
if (obj) {
return this.toTimeString(obj);
} else {
return this.toTimeStringSync();
}
};
/**
*
* @returns {Number} milliseconds since the beginning of the day.
*/
Time.prototype.valueOf = function () {
return this.getTimeOfDayMillis();
};
return Time;
});