/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* Defines {@link baja.AbsTime}.
*
* @module baja/obj/AbsTime
*/
define([ "bajaScript/sys",
"bajaScript/baja/obj/Date",
"bajaScript/baja/obj/RelTime",
"bajaScript/baja/obj/Simple",
"bajaScript/baja/obj/Time",
"bajaScript/baja/obj/TimeFormat",
"bajaScript/baja/obj/TimeZone",
"bajaScript/baja/obj/dateTimeUtil",
"bajaScript/baja/obj/numberUtil",
"bajaScript/baja/obj/objUtil" ], function (
baja,
BDate,
RelTime,
Simple,
Time,
TimeFormat,
TimeZone,
dateTimeUtil,
numberUtil,
objUtil) {
"use strict";
var subclass = baja.subclass,
callSuper = baja.callSuper,
strictAllArgs = baja.strictAllArgs,
strictNumber = baja.strictNumber,
objectify = baja.objectify,
bajaHasType = baja.hasType,
bajaDef = baja.def,
cacheDecode = objUtil.cacheDecode,
cacheEncode = objUtil.cacheEncode,
addZeroPad = numberUtil.addZeroPad;
const { SHOW_DATE, SHOW_MILLIS, SHOW_SECONDS, SHOW_TIME, SHOW_ZONE } = TimeFormat;
const {
toDateTimeString,
toDateTimeStringSync,
toDateTimeStringWithLongYear,
toNullDateTimeString,
toNullDateTimeStringSync } = dateTimeUtil;
/**
* Represents a `baja:AbsTime` in BajaScript.
*
* `AbsTime` encapsulates an absolute point in time relative to a given
* time zone.
*
* When creating a `Simple`, always use the `make` method instead of
* creating a new Object.
*
* @class
* @alias baja.AbsTime
* @extends baja.Simple
*/
var AbsTime = function AbsTime(date, time, offset) {
// Constructor should be considered private
callSuper(AbsTime, this, arguments);
this.$date = date;
this.$time = time;
this.$offset = offset;
};
subclass(AbsTime, Simple);
/**
* Make an `AbsTime`.
*
* @param {Object} [obj] the Object literal used for the method's arguments.
* @param {baja.Date} [obj.date]
* @param {baja.Time} [obj.time]
* @param {Number|baja.Simple} [obj.offset] UTC offset in milliseconds (any
* `baja:Number`). This should be negative for negative time zones, e.g.
* `-04:00` corresponds to `MILLIS_IN_HOUR * -4`. Additionally, if timeZone is
* set, this provided offset will be ignored.
* @param {Date} [obj.jsDate] if defined, this date is used for the date and
* time. The offset will be read directly from the JS date - the `offset`
* argument, if provided, will be ignored.
* @param {baja.TimeZone} [obj.timeZone] the timezone to base this date's
* offset upon. If obj.jsDate is set, this timezone will be ignored and the
* jsDate's offset will be used instead.
* @returns {baja.AbsTime}
*
* @example
* // An Object literal is used for the method's arguments...
* var at1 = baja.AbsTime.make({
* date: baja.Date.make({year: 1981, month: 5, day: 17}),
* time: baja.Time.make({hour: 15, min: 30}),
* offset: 0
* });
*
* // ...or from a JavaScript Date:
* var date = new Date();
* var at2 = baja.AbsTime.make({ jsDate: date });
* // the offset will be converted from JS Date style to BAbsTime style.
* baja.outln(at2.getOffset() === date.getTimezoneOffset() * -60000);
* baja.outln(at2.getMillis() === date.getTime());
*/
AbsTime.make = function (obj) {
obj = objectify(obj);
var date = bajaDef(obj.date, BDate.DEFAULT),
time = bajaDef(obj.time, Time.DEFAULT),
offset = bajaDef(obj.offset, 0),
jsDate;
if (obj.jsDate !== undefined) {
// Get information from JavaScript Date
jsDate = obj.jsDate;
if (!(jsDate instanceof Date)) {
throw new Error("jsDate must be a JavaScript Date");
}
date = BDate.make({
"year": jsDate.getFullYear(),
"month": jsDate.getMonth(),
"day": jsDate.getDate()
});
time = baja.Time.make({
"hour": jsDate.getHours(),
"min": jsDate.getMinutes(),
"sec": jsDate.getSeconds(),
"ms": jsDate.getMilliseconds()
});
offset = jsDate.getTimezoneOffset() * -60000;
} else if (obj.timeZone) {
var targetDate = baja.AbsTime.make({
date: date,
time: time,
offset: obj.timeZone.getUtcOffset()
});
if (dateTimeUtil.isDstActive(targetDate.getJsDate(), obj.timeZone)) {
offset = obj.timeZone.getUtcOffset() + obj.timeZone.getDaylightAdjustment();
} else {
offset = obj.timeZone.getUtcOffset();
}
}
// The year, month and day must always be specified for this to be valid
strictAllArgs([ date, time ], [ BDate, Time ]);
offset = strictNumber(offset);
if (date === BDate.DEFAULT && time === Time.DEFAULT && offset === 0) {
return AbsTime.DEFAULT;
}
return new AbsTime(date, time, offset);
};
/**
* Make an AbsTime.
*
* @param {Object} [obj] the Object Literal used for the method's arguments.
* @param {baja.Date} [obj.date]
* @param {baja.Time} [obj.time]
* @param {Number|baja.Simple} [obj.offset] (any `baja:Number`)
* @param {Date} [obj.jsDate] if defined, this date is used for the date and time.
* @returns baja.AbsTime
*
* @example
* // An Object Literal is used for the method's arguments...
* var at1 = baja.$("baja:AbsTime").make({
* date: baja.$("baja:Date").make({year: 1981, month: 5, day: 17}),
* time: baja.$("baja:Time").make({hour: 15, min: 30}),
* offset: 0
* });
*
* // ...or from a JavaScript Date...
* var at2 = baja.$("baja:AbsTime").make({jsDate: new Date()});
*/
AbsTime.prototype.make = function (obj) {
return AbsTime.make.apply(AbsTime, arguments);
};
/**
* Decode an `AbsTime` from a String.
*
* @method
* @param {String} str
* @returns {baja.AbsTime}
*/
AbsTime.prototype.decodeFromString = cacheDecode(function (str) {
// Decode ISO 8601 encoding that BAbsTime creates
var res = /^([0-9]{4,})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})\.([0-9]+)(?:(Z)|(?:([-+])([0-9]{2}):([0-9]{2})))$/.exec(str),
date,
time,
offset = 0;
if (res === null) {
throw new Error("Could not decode AbsTime: " + str);
}
function parse(s) {
return parseInt(s, 10);
}
date = BDate.make({
"year": parse(res[1]),
"month": parse(res[2]) - 1, // Zero indexed based
"day": parse(res[3])
});
time = Time.make({
"hour": parse(res[4]),
"min": parse(res[5]),
"sec": parse(res[6]),
"ms": parse(res[7])
});
if (res[8] !== "Z") {
// Parse hour and minutes and convert to millis
offset = parse(res[10]) * (60 * 60 * 1000) + parse(res[11]) * (60 * 1000);
// Apply sign
if (res[9] === "-") {
offset *= -1;
}
}
return AbsTime.make({
"date": date,
"time": time,
"offset": offset
});
});
/**
* Encode the AbsTime to a String.
*
* @method
* @returns {String}
*/
AbsTime.prototype.encodeToString = cacheEncode(function () {
var s = this.$date.encodeToString() + "T" + this.$time.encodeToString(),
hrOff,
minOff;
if (this.$offset === 0) {
s += "Z";
} else {
hrOff = Math.floor(Math.abs(this.$offset / (1000 * 60 * 60)));
minOff = Math.floor(Math.abs((this.$offset % (1000 * 60 * 60)) / (1000 * 60)));
if (this.$offset < 0) {
s += "-";
} else {
s += "+";
}
s += addZeroPad(hrOff, 2) + ":";
s += addZeroPad(minOff, 2);
}
return s;
});
/**
* Equality test.
*
* @param obj
* @returns {Boolean}
*/
AbsTime.prototype.equals = function (obj) {
if (bajaHasType(obj, 'baja:AbsTime')) {
return obj.getMillis() === this.getMillis();
}
return false;
};
/**
* Default AbsTime instance - maps to Java Epoch.
* @type {baja.AbsTime}
*/
AbsTime.DEFAULT = new AbsTime(BDate.DEFAULT, Time.DEFAULT, 0);
/**
* Return the data type symbol.
*
* @returns {String}
*/
AbsTime.prototype.getDataTypeSymbol = function () {
return "a";
};
/**
* Return the time.
*
* @returns {baja.Time}
*/
AbsTime.prototype.getTime = function () {
return this.$time;
};
/**
* Return the date.
*
* @returns {baja.Date}
*/
AbsTime.prototype.getDate = function () {
return this.$date;
};
/**
* Returns the day of the week.
*
* @see baja.Date#getWeekday
*
* @returns {baja.FrozenEnum} a `baja:Weekday` `FrozenEnum`.
*/
AbsTime.prototype.getWeekday = function () {
return this.getDate().getWeekday();
};
/**
* Return the UTC offset.
*
* @returns {Number}
*/
AbsTime.prototype.getOffset = function () {
return this.$offset;
};
/**
* Return the month.
*
* When invoking this method, please ensure the `baja:Month` Type has been
* imported.
*
* @see baja.importTypes
*
* @returns {baja.FrozenEnum} a `baja:Month` `FrozenEnum`
*/
AbsTime.prototype.getMonth = function () {
return this.getDate().getMonth();
};
/**
* Return the year.
*
* @returns {Number}
*/
AbsTime.prototype.getYear = function () {
return this.getDate().getYear();
};
/**
* Return the day (1-31).
*
* @returns {Number}
*/
AbsTime.prototype.getDay = function () {
return this.getDate().getDay();
};
/**
* Return the hour (0-23)
*
* @returns {Number}
*/
AbsTime.prototype.getHour = function () {
return this.getTime().getHour();
};
/**
* Return minutes (0-59).
*
* @returns {Number}
*/
AbsTime.prototype.getMinute = function () {
return this.getTime().getMinute();
};
/**
* Return seconds (0-59).
*
* @returns {Number}
*/
AbsTime.prototype.getSecond = function () {
return this.getTime().getSecond();
};
/**
* Returns milliseconds from the time (0-999)
*
* @returns {Number}
*/
AbsTime.prototype.getMillisecond = function () {
return this.getTime().getMillisecond();
};
/**
* Make an AbsTime with the current date and time.
*
* @returns {baja.AbsTime}
*/
AbsTime.now = function () {
return AbsTime.make({ jsDate: new Date() });
};
/**
* Return a new JavaScript Date based on this AbsTime. The Date will represent
* the same instant as this AbsTime (as returned by `getMillis()`) but will
* use the browser's local time zone.
*
* @returns {Date}
*/
AbsTime.prototype.getJsDate = function () {
// Create a JavaScript Date and return it (warning has no timezone offset)
return this.$jsDate || (this.$jsDate = new Date(this.getMillis()));
};
/**
* Get the number of milliseconds past the epoch represented by this AbsTime.
*
* @returns {Number}
*/
AbsTime.prototype.getMillis = function () {
var millis = this.$millis;
if (millis === undefined) {
var date = this.$date,
time = this.$time,
year = date.getYear(),
month = date.getMonth().getOrdinal(),
day = date.getDay(),
hour = time.getHour(),
min = time.getMinute(),
sec = time.getSecond(),
ms = time.getMillisecond();
millis = this.$millis =
Date.UTC(year, month, day, hour, min, sec, ms) - this.$offset;
}
return millis;
};
function doToDateTimeString(toStringFunc, absTime, obj) {
obj = objectify(obj);
var textPattern = obj.textPattern || baja.getTimeFormatPattern(),
show = calculateShow(obj),
offset = absTime.$offset,
timezone = obj.TimeZone || obj.timezone,
millis = absTime.getMillis(),
isDst = baja.TimeZone.isDstActive(absTime.getJsDate(), timezone);
if (!(timezone instanceof baja.TimeZone)) {
if (offset === 0) {
timezone = TimeZone.UTC;
} else {
var offsetDisplayName = "UTC" + dateTimeUtil.formatOffset(offset);
timezone = baja.TimeZone.make("UTC", offset, 0, null, null, {
shortDisplayName: offsetDisplayName,
displayName: offsetDisplayName,
shortDstDisplayName: offsetDisplayName,
dstDisplayName: offsetDisplayName
});
}
}
//if time zone is known, apply offset to AbsTime.$millis
var millisInTimeZone = millis + timezone.getUtcOffset();
if (isDst) {
millisInTimeZone += timezone.getDaylightAdjustment();
}
var jsDate = new Date(millisInTimeZone);
return toStringFunc({
ok: obj.ok,
fail: obj.fail,
show: show,
textPattern: textPattern,
year: jsDate.getUTCFullYear(),
month: baja.$('baja:Month', jsDate.getUTCMonth()),
day: jsDate.getUTCDate(),
hour: jsDate.getUTCHours(),
min: jsDate.getUTCMinutes(),
sec: jsDate.getUTCSeconds(),
ms: jsDate.getUTCMilliseconds(),
timezone: timezone,
isDst: isDst
}, obj.lex);
}
function calculateShow(obj) {
if (typeof obj.show === 'number') {
return obj.show;
}
// date, time, and time zone all default to true
var showDate = obj.showDate !== false,
showTime = obj.showTime !== false,
showSeconds = obj.showSeconds,
showMilliseconds = obj.showMilliseconds,
showTimeZone = obj.showTimeZone !== false;
return (showDate ? SHOW_DATE : 0) |
(showTime ? SHOW_TIME : 0) |
(showTime && showSeconds ? SHOW_SECONDS : 0) |
(showTime && showMilliseconds ? SHOW_MILLIS | SHOW_SECONDS : 0) |
(showTime && showTimeZone ? SHOW_ZONE : 0);
}
/**
* Asynchronously get a `String` representation of the `AbsTime`.
*
* This method is invoked asynchronously. A `Function` callback or an `Object`
* literal that contains a `Function` callback must be supplied.
*
* @param {Object} [obj] the Object Literal for the method's arguments.
*
* @param {Function} [obj.ok] (Deprecated: use Promise) the Function callback
* that will be invoked once the time has been formatted.
*
* @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 {baja.TimeZone} [obj.TimeZone] timezone to use for formatting.
* If not specified, then the system default
* timezone will try to be determined,
* otherwise UTC be used. (As of Niagara
* 4.9, the name "TimeZone" is supported to
* match `BFacets.TIME_ZONE`. The old key
* "timezone" is still supported.)
*
* @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 formatted string
*
* @example
* absTime.toDateTimeString().then(function (dateTimeStr) {
* baja.outln("The date time is: " + dateTimeStr);
* });
*/
AbsTime.prototype.toDateTimeString = function (obj) {
var absTime = this;
obj = objectify(obj);
return dateTimeUtil.resolveTimezone(obj).then(function (tz) {
obj.TimeZone = tz;
return doToDateTimeString(toDateTimeString, absTime, objectify(obj, "ok"));
});
};
/**
* Asynchronously get a `String` representation of the `AbsTime` always showing the long year
* format if year part is available.
*
* This method is invoked asynchronously. A `Function` callback or an `Object`
* literal that contains a `Function` callback must be supplied.
*
* @param {Object} [obj] the Object Literal for the method's arguments.
*
* @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 {baja.TimeZone} [obj.TimeZone] timezone to use for formatting.
* If not specified, then the system default
* timezone will try to be determined,
* otherwise UTC be used. (As of Niagara
* 4.9, the name "TimeZone" is supported to
* match `BFacets.TIME_ZONE`. The old key
* "timezone" is still supported.)
*
* @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 formatted string
*
* @since Niagara 4.13
*
* @example
* absTime.toDateTimeStringWithLongYear().then(function (dateTimeStr) {
* baja.outln("The date time with long year is: " + dateTimeStr);
* });
*/
AbsTime.prototype.toDateTimeStringWithLongYear = function (obj) {
var absTime = this;
obj = objectify(obj);
return dateTimeUtil.resolveTimezone(obj).then(function (tz) {
obj.TimeZone = tz;
return doToDateTimeString(toDateTimeStringWithLongYear, absTime, objectify(obj, "ok"));
});
};
/**
* Synchronously get a `String` representation of the `AbsTime`.
*
* This method is invoked synchronously. The string result will be returned
* directly from this function.
*
* **Notes on lexicons:**
*
* * A lexicon will be used if it is passed in.
* * If no lexicon is passed in, the baja lexicon will be used if it has been
* cached locally.
* * If the baja lexicon has not been cached, strings units will be
* represented by their internal tag names (which are in English).
*
* @param {Object} [obj] the Object Literal for the method's arguments.
*
* @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}
*/
AbsTime.prototype.toDateTimeStringSync = function (obj) {
return doToDateTimeString(toDateTimeStringSync, this, obj);
};
/**
* @see .toDateTimeString if obj is defined
* @see .toDateTimeStringSync if obj is undefined
* @returns {String|Promise.<String>} a String if an undefined parameter object is passed, otherwise a Promise.<String>
*/
AbsTime.prototype.toString = function (obj) {
if (this.isNull()) {
return toNullString(obj);
}
if (obj) {
return this.toDateTimeString(obj);
} else {
return this.toDateTimeStringSync();
}
};
/**
* Gets the date string for this baja.AbsTime
* @see baja.Date#toDateString
* @param {Object} obj the Object Literal for the method's arguments.
* @returns {Promise.<String>}
*/
AbsTime.prototype.toDateString = function (obj) {
if (this.isNull()) {
return toNullString(obj || {});
}
return this.getDate().toDateString(obj);
};
/**
* Gets the time string for this baja.AbsTime
* @see baja.Time#toTimeString
* @param {Object} obj the Object Literal for the method's arguments.
* @returns {Promise.<String>}
*/
AbsTime.prototype.toTimeString = function (obj) {
if (this.isNull()) {
return toNullString(obj || {});
}
return this.getTime().toTimeString(obj);
};
/**
*
* @param {Object} obj the Object Literal for the method's arguments.
* @returns {Promise.<String>|String}
*/
function toNullString(obj) {
return obj ? toNullDateTimeString(obj) : toNullDateTimeStringSync();
}
/**
* @since Niagara 4.14
* @returns {boolean} if this represents the null AbsTime.
*/
AbsTime.prototype.isNull = function () {
return this.getMillis() === 0;
};
/**
*
* @returns {Number} result of getMillis
*/
AbsTime.prototype.valueOf = function () {
return this.getMillis();
};
//region prev and next - day, month, year functions
/**
*
* @param {number} year
* @returns {boolean}
* @since Niagara 4.12
*/
AbsTime.isLeapYear = function (year) {
return baja.Date.isLeapYear(year);
};
/**
*
* @returns {boolean}
* @since Niagara 4.12
*/
AbsTime.prototype.isLeapDay = function () {
return (this.$date.isLeapDay());
};
/**
*
* @param {number} year
* @param {number|baja.FrozenEnum} month either a `baja:Month` `FrozenEnum` or an integer (January = 0)
* @returns {number} days in month
* @since Niagara 4.12
*/
AbsTime.getDaysInMonth = function (year, month) {
return baja.Date.getDaysInMonth(year, month);
};
/**
* returns a `baja.AbsTime` object that is 1 day after this `baja.AbsTime`
* @returns {baja.AbsTime}
* @since Niagara 4.12
*/
AbsTime.prototype.nextDay = function () {
return AbsTime.make({
date: this.$date.nextDay(),
time: this.getTime(),
offset: this.getOffset()
});
};
/**
* returns a `baja.AbsTime` object that is 1 day before this `baja.AbsTime`
* @returns {baja.AbsTime}
* @since Niagara 4.12
*/
AbsTime.prototype.prevDay = function () {
return AbsTime.make({
date: this.$date.prevDay(),
time: this.getTime(),
offset: this.getOffset()
});
};
/**
* returns a `baja.AbsTime` object that is 1 month after this `baja.AbsTime`
* @returns {baja.AbsTime}
* @since Niagara 4.12
*/
AbsTime.prototype.nextMonth = function () {
return AbsTime.make({
date: this.$date.nextMonth(),
time: this.getTime(),
offset: this.getOffset()
});
};
/**
* returns a `baja.AbsTime` object that is 1 month before this `baja.AbsTime`
* @returns {baja.AbsTime}
* @since Niagara 4.12
*/
AbsTime.prototype.prevMonth = function () {
return AbsTime.make({
date: this.$date.prevMonth(),
time: this.getTime(),
offset: this.getOffset()
});
};
/**
* returns a `baja.AbsTime` object that is 1 year after this `baja.AbsTime`
* @returns {baja.AbsTime}
* @since Niagara 4.12
*/
AbsTime.prototype.nextYear = function () {
return AbsTime.make({
date: this.$date.nextYear(),
time: this.getTime(),
offset: this.getOffset()
});
};
/**
* returns a `baja.AbsTime` object that is 1 year before this `baja.AbsTime`
* @returns {baja.AbsTime}
* @since Niagara 4.12
*/
AbsTime.prototype.prevYear = function () {
return AbsTime.make({
date: this.$date.prevYear(),
time: this.getTime(),
offset: this.getOffset()
});
};
//endregion prev and next - day, month, year functions
return AbsTime;
});