/* * HanDate.js - Represent a date in the Han algorithmic calendar * * Copyright © 2014-2015, 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. */ /* !depends ilib.js IDate.js GregorianDate.js HanCal.js Astro.js JSUtils.js MathUtils.js LocaleInfo.js Locale.js TimeZone.js HanRataDie.js RataDie.js */ var ilib = require("./ilib.js"); var JSUtils = require("./JSUtils.js"); var MathUtils = require("./MathUtils.js"); var Locale = require("./Locale.js"); var LocaleInfo = require("./LocaleInfo.js"); var IDate = require("./IDate.js"); var TimeZone = require("./TimeZone.js"); var Calendar = require("./Calendar.js"); var Astro = require("./Astro.js"); var HanCal = require("./HanCal.js"); var GregorianDate = require("./GregorianDate.js"); var HanRataDie = require("./HanRataDie.js"); var RataDie = require("./RataDie.js"); /** * @class * * Construct a new Han date object. The constructor parameters can * contain any of the following properties: * *
* * If the constructor is called with no arguments at all or if none of the * properties listed above * from unixtime through millisecond are present, then the date * components are * filled in with the current date at the time of instantiation. Note that if * you do not give the time zone when defaulting to the current time and the * time zone for all of ilib was not set with ilib.setTimeZone(), then the * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich * Mean Time").
* * If any of the properties from year through millisecond are not * specified in the params, it is assumed that they have the smallest possible * value in the range for the property (zero or one).
* * * @constructor * @extends Date * @param {Object=} params parameters that govern the settings and behaviour of this Han date */ var HanDate = function(params) { this.timezone = "local"; if (params) { if (params.locale) { this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; var li = new LocaleInfo(this.locale); this.timezone = li.getTimeZone(); } if (params.timezone) { this.timezone = params.timezone; } } new HanCal({ sync: params && typeof(params) === 'boolean' ? params.sync : true, loadParams: params && params.loadParams, callback: ilib.bind(this, function (cal) { this.cal = cal; if (params && (params.year || params.month || params.day || params.hour || params.minute || params.second || params.millisecond || params.cycle || params.cycleYear)) { if (typeof(params.cycle) !== 'undefined') { /** * Cycle number in the Han calendar. * @type number */ this.cycle = parseInt(params.cycle, 10) || 0; var year = (typeof(params.year) !== 'undefined' ? parseInt(params.year, 10) : parseInt(params.cycleYear, 10)) || 0; /** * Year in the Han calendar. * @type number */ this.year = HanCal._getElapsedYear(year, this.cycle); } else { if (typeof(params.year) !== 'undefined') { this.year = parseInt(params.year, 10) || 0; this.cycle = Math.floor((this.year - 1) / 60); } else { this.year = this.cycle = 0; } } /** * The month number, ranging from 1 to 13 * @type number */ this.month = parseInt(params.month, 10) || 1; /** * The day of the month. This ranges from 1 to 30. * @type number */ this.day = parseInt(params.day, 10) || 1; /** * The hour of the day. This can be a number from 0 to 23, as times are * stored unambiguously in the 24-hour clock. * @type number */ this.hour = parseInt(params.hour, 10) || 0; /** * The minute of the hours. Ranges from 0 to 59. * @type number */ this.minute = parseInt(params.minute, 10) || 0; /** * The second of the minute. Ranges from 0 to 59. * @type number */ this.second = parseInt(params.second, 10) || 0; /** * The millisecond of the second. Ranges from 0 to 999. * @type number */ this.millisecond = parseInt(params.millisecond, 10) || 0; // derived properties /** * Year in the cycle of the Han calendar * @type number */ this.cycleYear = MathUtils.amod(this.year, 60); /** * The day of the year. Ranges from 1 to 384. * @type number */ this.dayOfYear = parseInt(params.dayOfYear, 10); if (typeof(params.dst) === 'boolean') { this.dst = params.dst; } this.newRd({ cal: this.cal, cycle: this.cycle, year: this.year, month: this.month, day: this.day, hour: this.hour, minute: this.minute, second: this.second, millisecond: this.millisecond, sync: params && typeof(params.sync) === 'boolean' ? params.sync : true, loadParams: params && params.loadParams, callback: ilib.bind(this, function (rd) { if (rd) { this.rd = rd; // add the time zone offset to the rd to convert to UTC if (!this.tz) { this.tz = new TimeZone({id: this.timezone}); } // getOffsetMillis requires that this.year, this.rd, and this.dst // are set in order to figure out which time zone rules apply and // what the offset is at that point in the year this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; if (this.offset !== 0) { this.rd = this.newRd({ cal: this.cal, rd: this.rd.getRataDie() - this.offset }); this._calcLeap(); } else { // re-use the derived properties from the RD calculations this.leapMonth = this.rd.leapMonth; this.priorLeapMonth = this.rd.priorLeapMonth; this.leapYear = this.rd.leapYear; } } if (!this.rd) { this.rd = this.newRd(JSUtils.merge(params || {}, { cal: this.cal })); this._calcDateComponents(); } if (params && typeof(params.onLoad) === 'function') { params.onLoad(this); } }) }); } else { if (!this.rd) { this.rd = this.newRd(JSUtils.merge(params || {}, { cal: this.cal })); this._calcDateComponents(); } if (params && typeof(params.onLoad) === 'function') { params.onLoad(this); } } }) }); }; HanDate.prototype = new IDate({noinstance: true}); HanDate.prototype.parent = IDate; HanDate.prototype.constructor = HanDate; /** * Return a new RD for this date type using the given params. * @protected * @param {Object=} params the parameters used to create this rata die instance * @returns {RataDie} the new RD instance for the given params */ HanDate.prototype.newRd = function (params) { return new HanRataDie(params); }; /** * Return the year for the given RD * @protected * @param {number} rd RD to calculate from * @returns {number} the year for the RD */ HanDate.prototype._calcYear = function(rd) { var gregdate = new GregorianDate({ rd: rd, timezone: this.timezone }); var hanyear = gregdate.year + 2697; var newYears = this.cal.newYears(hanyear); return hanyear - ((rd + RataDie.gregorianEpoch < newYears) ? 1 : 0); }; /** * @private * Calculate the leap year and months from the RD. */ HanDate.prototype._calcLeap = function() { var jd = this.rd.getRataDie() + RataDie.gregorianEpoch; var calc = HanCal._leapYearCalc(this.year); var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; var newYears = (this.leapYear && (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? HanCal._newMoonOnOrAfter(m2+1) : m2; var m = HanCal._newMoonBefore(jd + 1); this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); }; /** * @private * Calculate date components for the given RD date. */ HanDate.prototype._calcDateComponents = function () { var remainder, jd = this.rd.getRataDie() + RataDie.gregorianEpoch; // console.log("HanDate._calcDateComponents: calculating for jd " + jd); if (typeof(this.offset) === "undefined") { // now offset the jd by the time zone, then recalculate in case we were // near the year boundary if (!this.tz) { this.tz = new TimeZone({id: this.timezone}); } this.offset = this.tz.getOffsetMillis(this) / 86400000; } if (this.offset !== 0) { jd += this.offset; } // use the Gregorian calendar objects as a convenient way to short-cut some // of the date calculations var gregyear = GregorianDate._calcYear(this.rd.getRataDie()); this.year = gregyear + 2697; var calc = HanCal._leapYearCalc(this.year); var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; var newYears = (this.leapYear && (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? HanCal._newMoonOnOrAfter(m2+1) : m2; // See if it's between Jan 1 and the Chinese new years of that Gregorian year. If // so, then the Han year is actually the previous one if (jd < newYears) { this.year--; calc = HanCal._leapYearCalc(this.year); m2 = HanCal._newMoonOnOrAfter(calc.m1+1); this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; newYears = (this.leapYear && (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? HanCal._newMoonOnOrAfter(m2+1) : m2; } // month is elapsed month, not the month number + leap month boolean var m = HanCal._newMoonBefore(jd + 1); this.month = Math.round((m - calc.m1) / 29.530588853000001); this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); this.cycle = Math.floor((this.year - 1) / 60); this.cycleYear = MathUtils.amod(this.year, 60); this.day = Astro._floorToJD(jd) - m + 1; /* console.log("HanDate._calcDateComponents: year is " + this.year); console.log("HanDate._calcDateComponents: isLeapYear is " + this.leapYear); console.log("HanDate._calcDateComponents: cycle is " + this.cycle); console.log("HanDate._calcDateComponents: cycleYear is " + this.cycleYear); console.log("HanDate._calcDateComponents: month is " + this.month); console.log("HanDate._calcDateComponents: isLeapMonth is " + this.leapMonth); console.log("HanDate._calcDateComponents: day is " + this.day); */ // floor to the start of the julian day remainder = jd - Astro._floorToJD(jd); // console.log("HanDate._calcDateComponents: time remainder is " + remainder); // now convert to milliseconds for the rest of the calculation remainder = Math.round(remainder * 86400000); this.hour = Math.floor(remainder/3600000); remainder -= this.hour * 3600000; this.minute = Math.floor(remainder/60000); remainder -= this.minute * 60000; this.second = Math.floor(remainder/1000); remainder -= this.second * 1000; this.millisecond = remainder; }; /** * Return the year within the Chinese cycle of this date. Cycles are 60 * years long, and the value returned from this method is the number of the year * within this cycle. The year returned from getYear() is the total elapsed * years since the beginning of the Chinese epoch and does not include * the cycles. * * @return {number} the year within the current Chinese cycle */ HanDate.prototype.getCycleYears = function() { return this.cycleYear; }; /** * Return the Chinese cycle number of this date. Cycles are 60 years long, * and the value returned from getCycleYear() is the number of the year * within this cycle. The year returned from getYear() is the total elapsed * years since the beginning of the Chinese epoch and does not include * the cycles. * * @return {number} the current Chinese cycle */ HanDate.prototype.getCycles = function() { return this.cycle; }; /** * Return whether the year of this date is a leap year in the Chinese Han * calendar. * * @return {boolean} true if the year of this date is a leap year in the * Chinese Han calendar. */ HanDate.prototype.isLeapYear = function() { return this.leapYear; }; /** * Return whether the month of this date is a leap month in the Chinese Han * calendar. * * @return {boolean} true if the month of this date is a leap month in the * Chinese Han calendar. */ HanDate.prototype.isLeapMonth = function() { return this.leapMonth; }; /** * Return the day of the week of this date. The day of the week is encoded * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. * * @return {number} the day of the week */ HanDate.prototype.getDayOfWeek = function() { var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); return MathUtils.mod(rd, 7); }; /** * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and * December 31st is 365 in regular years, or 366 in leap years. * @return {number} the ordinal day of the year */ HanDate.prototype.getDayOfYear = function() { var newYears = this.cal.newYears(this.year); var priorNewMoon = HanCal._newMoonOnOrAfter(newYears + (this.month -1) * 29); return priorNewMoon - newYears + this.day; }; /** * Return the era for this date as a number. The value for the era for Han * calendars is -1 for "before the han era" (BP) and 1 for "the han era" (anno * persico or AP). * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Han calendar, * there is a year 0, so any years that are negative or zero are BP. * @return {number} 1 if this date is in the common era, -1 if it is before the * common era */ HanDate.prototype.getEra = function() { return (this.year < 1) ? -1 : 1; }; /** * Return the name of the calendar that governs this date. * * @return {string} a string giving the name of the calendar */ HanDate.prototype.getCalendar = function() { return "han"; }; // register with the factory method IDate._constructors["han"] = HanDate; module.exports = HanDate;