/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* Defines {@link baja.Date}.
* @module baja/obj/Date
*/
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,
strictNumber = baja.strictNumber,
objectify = baja.objectify,
bajaHasType = baja.hasType,
cacheDecode = objUtil.cacheDecode,
cacheEncode = objUtil.cacheEncode,
SHOW_DATE = TimeFormat.SHOW_DATE,
SHOW_MILLIS = TimeFormat.SHOW_MILLIS,
SHOW_SECONDS = TimeFormat.SHOW_SECONDS,
SHOW_TIME = TimeFormat.SHOW_TIME,
toDateTimeString = dateTimeUtil.toDateTimeString,
toDateTimeStringSync = dateTimeUtil.toDateTimeStringSync,
addZeroPad = numberUtil.addZeroPad;
/**
* Represents a `baja:Date` in BajaScript.
*
* `Date` represents a specific day, month, and year.
*
* When creating a `Simple`, always use the `make()` method instead of
* creating a new Object.
*
* @class
* @alias baja.Date
* @extends baja.Simple
*/
var BDate = function BDate(year, month, day) {
// Constructor should be considered private
callSuper(BDate, this, arguments);
this.$year = year;
this.$month = month; // Zero indexed
this.$day = day;
};
subclass(BDate, Simple);
/**
* Make a `Date`.
*
* @param {Object} obj - the Object Literal.
*
* @param {Number|baja.Simple} obj.year (any `baja:Number` type)
*
* @param {Number|baja.Simple|baja.FrozenEnum} obj.month - (any `baja:Number`
* type) (0-11) or a `baja:Month` `FrozenEnum` for the month of the year.
*
* @param {Number|baja.Simple} obj.day - (1-31). (any `baja:Number` type)
*
* @param {Date} [obj.jsDate] - A JavaScript `Date` used to specify the year,
* month and day. If defined, this will override the year, month and day
* arguments.
*
* @returns {baja.Date}
*
* @example
* //An Object Literal is used for the method's arguments...
* var d1 = baja.Date.make({
* year: 2008,
* month: baja.$("baja:Month").get("december"),
* day: 24
* });
*
* // ...or from a JavaScript Date...
* var d2 = baja.Date.make({
* jsDate: date
* });
*/
BDate.make = function (obj) {
obj = objectify(obj);
var year,
month,
day,
d;
// Create baja.Date from a JavaScript date
if (obj.jsDate && obj.jsDate instanceof Date) {
year = obj.jsDate.getFullYear();
month = obj.jsDate.getMonth(); // zero index based
day = obj.jsDate.getDate();
} else {
year = obj.year;
month = obj.month; // If a number, this should be zero index based
day = obj.day;
// If the month is a baja:Month then get its ordinal as zero index based
if (bajaHasType(month) && month.getType().toString() === "baja:Month") {
month = month.getOrdinal();
}
}
// Validate we have these specified
year = strictNumber(year);
month = strictNumber(month);
day = strictNumber(day);
if (year < 0 || month < 0 || month > 11 || day < 1 || day > 31) {
throw new Error("Invalid date range");
}
// Check to see if we should return the default instance
d = BDate.DEFAULT;
if (year === d.$year && month === d.$month && day === d.$day) {
return d;
}
return new BDate(year, month, day);
};
/**
* Make a Date.
*
* @param {Object} obj - the Object Literal.
*
* @param {Number|baja.Simple} [obj.year] (any `baja:Number` type)
*
* @param {Number|baja.Simple|baja.FrozenEnum} [obj.month] - (any `baja:Number`
* type) (0-11) or a `baja:Month` `FrozenEnum` for the month of the year.
*
* @param {Number|baja.Simple} [obj.day] - (1-31). (any `baja:Number` type)
*
* @param {Date} [obj.jsDate] A JavaScript `Date` used to specify the year,
* month and day. If defined, this will override the year, month and day
* arguments.
*
* @returns {baja.Date}
*
* @example
* // An Object Literal is used to for the method's arguments...
* var d1 = baja.$("baja:Date").make({
* year: 2008,
* month: baja.$("baja:Month").get("december"),
* day: 24
* });
* // ...or from a JavaScript Date...
* var d2 = baja.$("baja:Date").make({
* jsDate: date
* });
*/
BDate.prototype.make = function (obj) {
return BDate.make.apply(BDate, arguments);
};
/**
* Decode a `Date` from a `String`. Expects ISO 8601 encoding (`yyyy-mm-dd`).
*
* @method
*
* @param {String} str
*
* @returns {baja.Date}
*/
BDate.prototype.decodeFromString = cacheDecode(function (str) {
// Decode ISO 8601 encoding yyyy-mm-dd
var res = /^([0-9]{4,})-([0-9]{2})-([0-9]{2})$/.exec(str),
d;
if (res === null) {
throw new Error("Could not decode baja.Date: " + str);
}
function parse(s) {
return parseInt(s, 10);
}
d = BDate.make({
year: parse(res[1]),
month: parse(res[2]) - 1,
day: parse(res[3])
});
return d;
});
/**
* Encode the `Date` to a String. Uses ISO 8601 encoding (`yyyy-mm-dd`).
*
* @method
*
* @returns {String}
*/
BDate.prototype.encodeToString = cacheEncode(function () {
var s = addZeroPad(this.$year, 4) + "-" +
addZeroPad((this.$month + 1), 2) + "-" +
addZeroPad(this.$day, 2);
return s;
});
function dateCompareTo(date1, date2) {
strictArg(date2, BDate);
if (date1.$year !== date2.$year) {
return date1.$year - date2.$year;
}
if (date1.$month !== date2.$month) {
return date1.$month - date2.$month;
}
if (date1.$day !== date2.$day) {
return date1.$day - date2.$day;
}
return 0;
}
/**
* Equality test.
*
* @param obj
*
* @returns {Boolean}
*/
BDate.prototype.equals = function (obj) {
if (bajaHasType(obj) && obj.getType().equals(this.getType())) {
return dateCompareTo(this, obj) === 0;
}
return false;
};
/**
* Default Date instance.
* @type {baja.Date}
*/
BDate.DEFAULT = new BDate(1970, 0, 1);
/**
* Return a `Date` that maps to the current day.
*
* @returns {baja.Date}
*/
BDate.today = function () {
return BDate.make({ jsDate: new Date() });
};
/**
* Return the year.
*
* @returns {Number}
*/
BDate.prototype.getYear = function () {
return this.$year;
};
/**
* 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`
*/
BDate.prototype.getMonth = function () {
return baja.$("baja:Month").get(this.$month);
};
/**
* Return the day (1-31).
*
* @returns {Number}
*/
BDate.prototype.getDay = function () {
return this.$day;
};
/**
* Return a new JavaScript `Date` using this date's year, month and day.
*
* @returns {Date}
*/
BDate.prototype.getJsDate = function () {
// JavaScript Date is mutable therefore we have to return a new instance of
// Date each time
return new Date(this.$year,
this.$month,
this.$day,
/*hours*/0,
/*minutes*/0,
/*seconds*/0,
/*ms*/0);
};
function getCachedJsDate(date) {
// Lazily create and return an immutable cached version of the JavaScript
// Date
if (date.$jsDate === undefined) {
date.$jsDate = date.getJsDate();
}
return date.$jsDate;
}
/**
* Return the weekday as a `baja:Weekday` `FrozenEnum`.
*
* When invoking this method, please ensure the `baja:Weekday` Type has been
* imported.
*
* @see baja.importTypes
*
* @returns {baja.FrozenEnum} a `baja:Weekday` `FrozenEnum`.
*/
BDate.prototype.getWeekday = function () {
return baja.$("baja:Weekday").get(getCachedJsDate(this).getDay());
};
/**
* Return true if the specified date is before this date.
*
* @param {baja.Date} date.
*
* @returns {Boolean}
*/
BDate.prototype.isBefore = function (date) {
return dateCompareTo(this, date) < 0;
};
/**
* Return true if the specified date is after this date.
*
* @param {baja.Date} date.
*
* @returns {Boolean}
*/
BDate.prototype.isAfter = function (date) {
return dateCompareTo(this, date) > 0;
};
function doToDateTimeString(toStringFunc, date, obj) {
obj = objectify(obj, "ok");
var textPattern = obj.textPattern || baja.getTimeFormatPattern(),
show = (obj.show || 0) | SHOW_DATE;
// Filter out invalid flags
show &= ~SHOW_TIME;
show &= ~SHOW_SECONDS;
show &= ~SHOW_MILLIS;
return toStringFunc({
ok: obj.ok,
fail: obj.fail,
show: show,
textPattern: textPattern,
year: date.getYear(),
month: date.getMonth(),
day: date.getDay()
}, obj.lex);
}
/**
* Asynchronously get a String representation of the `Date`.
*
* 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 date 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 date string
*
* @example
* myDate.toDateString().then(function (dateStr) {
* baja.outln("The date is: " + dateStr);
* });
*/
BDate.prototype.toDateString = function (obj) {
return doToDateTimeString(toDateTimeString, this, objectify(obj, "ok"));
};
/**
* Synchronously get a `String` representation of the `Date`.
*
* 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}
*/
BDate.prototype.toDateStringSync = function (obj) {
return doToDateTimeString(toDateTimeStringSync, this, obj);
};
/**
* @see .toDateStringSync
*/
BDate.prototype.toString = function (obj) {
return this.toDateStringSync(obj);
};
/**
* @returns {Number} a number that can be compared for sorting (same as JSDate.valeuOf)
*/
BDate.prototype.valueOf = function () {
return this.getJsDate().valueOf();
};
//region prev and next - day, month, year functions
/**
*
* @param {number} year
* @returns {boolean}
* @since Niagara 4.12
*/
BDate.isLeapYear = function (year) {
return ((year >= 1582) && (year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0));
};
/**
*
* @returns {boolean}
* @since Niagara 4.12
*/
BDate.prototype.isLeapDay = function () {
return ((this.getMonth().$ordinal === 1) && (this.getDay() === 29));
};
/**
*
* @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
*/
BDate.getDaysInMonth = function (year, month) {
const daysInMonth = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
// If the month is a baja:Month then get its ordinal as zero index based
if (bajaHasType(month, "baja:Month")) {
month = month.getOrdinal();
}
if (month === 1) {
return this.isLeapYear(year) ? 29 : 28;
} else {
return daysInMonth[month];
}
};
/**
* returns a `baja.Date` object that is 1 day after this `baja.Date`
* @returns {baja.Date}
* @since Niagara 4.12
*/
BDate.prototype.nextDay = function () {
let year = this.getYear(),
month = this.getMonth().$ordinal,
day = this.getDay();
day++;
if (day > baja.Date.getDaysInMonth(year, month)) {
day = 1;
month++;
}
if (month > 11) {
month = 0;
year++;
}
return baja.Date.make({ year, month, day });
};
/**
* returns a `baja.Date` object that is 1 day before this `baja.Date`
* @returns {baja.Date}
* @since Niagara 4.12
*/
BDate.prototype.prevDay = function () {
let year = this.getYear(),
month = this.getMonth().$ordinal,
day = this.getDay();
day--;
if (day < 1) {
month--;
if (month < 0) {
month = 11;
year--;
}
day = baja.Date.getDaysInMonth(year, month);
}
return baja.Date.make({ year, month, day });
};
/**
* returns a `baja.Date` object that is 1 month after this `baja.Date`
* @returns {baja.Date}
* @since Niagara 4.12
*/
BDate.prototype.nextMonth = function () {
let year = this.getYear(),
month = this.getMonth().$ordinal,
day = this.getDay();
if (month === 11) {
month = 0;
year++;
} else if (day === baja.Date.getDaysInMonth(year, month)) {
month++;
day = baja.Date.getDaysInMonth(year, month);
} else {
month++;
if (day > baja.Date.getDaysInMonth(year, month)) {
day = baja.Date.getDaysInMonth(year, month);
}
}
return baja.Date.make({ year, month, day });
};
/**
* returns a `baja.Date` object that is 1 month before this `baja.Date`
* @returns {baja.Date}
* @since Niagara 4.12
*/
BDate.prototype.prevMonth = function () {
let year = this.getYear(),
month = this.getMonth().$ordinal,
day = this.getDay();
if (month === 0) {
month = 11;
year--;
} else if (day === baja.Date.getDaysInMonth(year, month)) {
month--;
day = baja.Date.getDaysInMonth(year, month);
} else {
month--;
if (day > baja.Date.getDaysInMonth(year, month)) {
day = baja.Date.getDaysInMonth(year, month);
}
}
return baja.Date.make({ year, month, day });
};
/**
* returns a `baja.Date` object that is 1 year after this `baja.Date`
* @returns {baja.Date}
* @since Niagara 4.12
*/
BDate.prototype.nextYear = function () {
let year = this.getYear(),
month = this.getMonth().$ordinal,
day = this.getDay();
year++;
if (this.isLeapDay()) {
day = 28;
}
return baja.Date.make({ year, month, day });
};
/**
* returns a `baja.Date` object that is 1 year before this `baja.Date`
* @returns {baja.Date}
* @since Niagara 4.12
*/
BDate.prototype.prevYear = function () {
let year = this.getYear(),
month = this.getMonth().$ordinal,
day = this.getDay();
year--;
if (this.isLeapDay()) {
day = 28;
}
return baja.Date.make({ year, month, day });
};
//endregion prev and next - day, month, year functions
return BDate;
});