/*
* IDate.js - Represent a date in any calendar. This class is subclassed for each
* calendar and includes some shared functionality.
*
* Copyright © 2012-2015, 2018, 2023 JEDLSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var LocaleInfo = require("./LocaleInfo.js");
/**
* @class
* Superclass for all the calendar date classes that contains shared
* functionality. This class is never instantiated on its own. Instead,
* you should use the {@link DateFactory} function to manufacture a new
* instance of a subclass of IDate. This class is called IDate for "ilib
* date" so that it does not conflict with the built-in Javascript Date
* class.
*
* @protected
* @constructor
* @param {Object=} options The date components to initialize this date with
*/
var IDate = function(options) {
};
/* place for the subclasses to put their constructors so that the factory method
* can find them. Do this to add your date after it's defined:
* IDate._constructors["mytype"] = IDate.MyTypeConstructor;
*/
IDate._constructors = {};
IDate.prototype = {
getType: function() {
return "date";
},
/**
* Return the unix time equivalent to this date instance. Unix time is
* the number of milliseconds since midnight on Jan 1, 1970 UTC (Gregorian). This
* method only returns a valid number for dates between midnight,
* Jan 1, 1970 UTC (Gregorian) and Jan 19, 2038 at 3:14:07am UTC (Gregorian) when
* the unix time runs out. If this instance encodes a date outside of that range,
* this method will return -1. For date types that are not Gregorian, the point
* in time represented by this date object will only give a return value if it
* is in the correct range in the Gregorian calendar as given previously.
*
* @return {number} a number giving the unix time, or -1 if the date is outside the
* valid unix time range
*/
getTime: function() {
return this.rd.getTime();
},
/**
* Return the extended unix time equivalent to this Gregorian date instance. Unix time is
* the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time
* (or the type "time_t" in C/C++) is only encoded with an unsigned 32 bit integer, and thus
* runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above
* 32 bits and the Date object allows you to encode up to 100 million days worth of time
* after Jan 1, 1970, and even more interestingly, 100 million days worth of time before
* Jan 1, 1970 as well. This method returns the number of milliseconds in that extended
* range. If this instance encodes a date outside of that range, this method will return
* NaN.
*
* @return {number} a number giving the extended unix time, or Nan if the date is outside
* the valid extended unix time range
*/
getTimeExtended: function() {
return this.rd.getTimeExtended();
},
/**
* Set the time of this instance according to the given unix time. Unix time is
* the number of milliseconds since midnight on Jan 1, 1970.
*
* @param {number} millis the unix time to set this date to in milliseconds
*/
setTime: function(millis) {
this.rd = this.newRd({
unixtime: millis,
cal: this.cal
});
this._calcDateComponents();
},
getDays: function() {
return this.day;
},
getMonths: function() {
return this.month;
},
getYears: function() {
return this.year;
},
getHours: function() {
return this.hour;
},
getMinutes: function() {
return this.minute;
},
getSeconds: function() {
return this.second;
},
getMilliseconds: function() {
return this.millisecond;
},
getEra: function() {
return (this.year < 1) ? -1 : 1;
},
setDays: function(day) {
this.day = parseInt(day, 10) || 1;
this.rd._setDateComponents(this);
},
setMonths: function(month) {
this.month = parseInt(month, 10) || 1;
this.rd._setDateComponents(this);
},
setYears: function(year) {
this.year = parseInt(year, 10) || 0;
this.rd._setDateComponents(this);
},
setHours: function(hour) {
this.hour = parseInt(hour, 10) || 0;
this.rd._setDateComponents(this);
},
setMinutes: function(minute) {
this.minute = parseInt(minute, 10) || 0;
this.rd._setDateComponents(this);
},
setSeconds: function(second) {
this.second = parseInt(second, 10) || 0;
this.rd._setDateComponents(this);
},
setMilliseconds: function(milli) {
this.millisecond = parseInt(milli, 10) || 0;
this.rd._setDateComponents(this);
},
/**
* Return a new date instance in the current calendar that represents the first instance
* of the given day of the week before the current date. The day of the week is encoded
* as a number where 0 = Sunday, 1 = Monday, etc.
*
* @param {number} dow the day of the week before the current date that is being sought
* @return {IDate} the date being sought
*/
before: function (dow) {
return new this.constructor({
rd: this.rd.before(dow, this.offset),
timezone: this.timezone
});
},
/**
* Return a new date instance in the current calendar that represents the first instance
* of the given day of the week after the current date. The day of the week is encoded
* as a number where 0 = Sunday, 1 = Monday, etc.
*
* @param {number} dow the day of the week after the current date that is being sought
* @return {IDate} the date being sought
*/
after: function (dow) {
return new this.constructor({
rd: this.rd.after(dow, this.offset),
timezone: this.timezone
});
},
/**
* Return a new Gregorian date instance that represents the first instance of the
* given day of the week on or before the current date. The day of the week is encoded
* as a number where 0 = Sunday, 1 = Monday, etc.
*
* @param {number} dow the day of the week on or before the current date that is being sought
* @return {IDate} the date being sought
*/
onOrBefore: function (dow) {
return new this.constructor({
rd: this.rd.onOrBefore(dow, this.offset),
timezone: this.timezone
});
},
/**
* Return a new Gregorian date instance that represents the first instance of the
* given day of the week on or after the current date. The day of the week is encoded
* as a number where 0 = Sunday, 1 = Monday, etc.
*
* @param {number} dow the day of the week on or after the current date that is being sought
* @return {IDate} the date being sought
*/
onOrAfter: function (dow) {
return new this.constructor({
rd: this.rd.onOrAfter(dow, this.offset),
timezone: this.timezone
});
},
/**
* Return a Javascript Date object that is equivalent to this date
* object.
*
* @return {Date|undefined} a javascript Date object
*/
getJSDate: function() {
var unix = this.rd.getTimeExtended();
return isNaN(unix) ? undefined : new Date(unix);
},
/**
* Return the Rata Die (fixed day) number of this date.
*
* @protected
* @return {number} the rd date as a number
*/
getRataDie: function() {
return this.rd.getRataDie();
},
/**
* Set the date components of this instance based on the given rd.
* @protected
* @param {number} rd the rata die date to set
*/
setRd: function (rd) {
this.rd = this.newRd({
rd: rd,
cal: this.cal
});
this._calcDateComponents();
},
/**
* Return the Julian Day equivalent to this calendar date as a number.
*
* @return {number} the julian date equivalent of this date
*/
getJulianDay: function() {
return this.rd.getJulianDay();
},
/**
* Set the date of this instance using a Julian Day.
* @param {number|JulianDay} date the Julian Day to use to set this date
*/
setJulianDay: function (date) {
this.rd = this.newRd({
julianday: (typeof(date) === 'object') ? date.getDate() : date,
cal: this.cal
});
this._calcDateComponents();
},
/**
* Return the time zone associated with this date, or
* undefined if none was specified in the constructor.
*
* @return {string|undefined} the name of the time zone for this date instance
*/
getTimeZone: function() {
return this.timezone || "local";
},
/**
* Set the time zone associated with this date.
* @param {string=} tzName the name of the time zone to set into this date instance,
* or "undefined" to unset the time zone
*/
setTimeZone: function (tzName) {
if (!tzName || tzName === "") {
// same as undefining it
this.timezone = undefined;
this.tz = undefined;
} else if (typeof(tzName) === 'string') {
this.timezone = tzName;
this.tz = undefined;
// assuming the same UTC time, but a new time zone, now we have to
// recalculate what the date components are
this._calcDateComponents();
}
},
/**
* Return the rd number of the first Sunday of the given ISO year.
* @protected
* @param {number} year the year for which the first Sunday is being sought
* @return {number} the rd of the first Sunday of the ISO year
*/
firstSunday: function (year) {
var firstDay = this.newRd({
year: year,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
cal: this.cal
});
var firstThu = this.newRd({
rd: firstDay.onOrAfter(4),
cal: this.cal
});
return firstThu.before(0);
},
/**
* Return the ISO 8601 week number in the current year for the current date. The week
* number ranges from 0 to 55, as some years have 55 weeks assigned to them in some
* calendars.
*
* @return {number} the week number for the current date
*/
getWeekOfYear: function() {
var rd = Math.floor(this.rd.getRataDie());
var year = this._calcYear(rd + this.offset);
var yearStart = this.firstSunday(year);
var nextYear;
// if we have a January date, it may be in this ISO year or the previous year
if (rd < yearStart) {
yearStart = this.firstSunday(year-1);
} else {
// if we have a late December date, it may be in this ISO year, or the next year
nextYear = this.firstSunday(year+1);
if (rd >= nextYear) {
yearStart = nextYear;
}
}
return Math.floor((rd-yearStart)/7) + 1;
},
/**
* Return the ordinal number of the week within the month. The first week of a month is
* the first one that contains 4 or more days in that month. If any days precede this
* first week, they are marked as being in week 0. This function returns values from 0
* through 6.<p>
*
* The locale is a required parameter because different locales that use the same
* Gregorian calendar consider different days of the week to be the beginning of
* the week. This can affect the week of the month in which some days are located.
*
* @param {Locale|string} locale the locale or locale spec to use when figuring out
* the first day of the week
* @return {number} the ordinal number of the week within the current month
*/
getWeekOfMonth: function(locale) {
var li = new LocaleInfo(locale);
var first = this.newRd({
year: this._calcYear(this.rd.getRataDie()+this.offset),
month: this.getMonths(),
day: 1,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
cal: this.cal
});
var weekStart = first.onOrAfter(li.getFirstDayOfWeek());
if (weekStart - first.getRataDie() > 3) {
// if the first week has 4 or more days in it of the current month, then consider
// that week 1. Otherwise, it is week 0. To make it week 1, move the week start
// one week earlier.
weekStart -= 7;
}
return Math.floor((this.rd.getRataDie() - weekStart) / 7) + 1;
}
};
module.exports = IDate;
Source