/*
* HanCal.js - Represent a Han Chinese Lunar calendar object.
*
* Copyright © 2014-2017, 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 ilib = require("../index.js");
var MathUtils = require("./MathUtils.js");
var Calendar = require("./Calendar.js");
var Astro = require("./Astro.js");
var RataDie = require("./RataDie.js");
var GregorianDate = require("./GregorianDate.js");
var GregRataDie = require("./GregRataDie.js");
/**
* @class
* Construct a new Han algorithmic calendar object. This class encodes information about
* a Han algorithmic calendar.<p>
*
*
* @constructor
* @param {Object=} params optional parameters to load the calendrical data
* @extends Calendar
*/
var HanCal = function(params) {
this.type = "han";
var sync = params && typeof(params.sync) === 'boolean' ? params.sync : true;
Astro.initAstro(sync, params && params.loadParams, ilib.bind(this, function (x) {
if (params && typeof(params.onLoad) === 'function') {
params.onLoad(this);
}
}));
};
/**
* @private
* @static
* @param {number} year
* @param {number=} cycle
* @return {number}
*/
HanCal._getElapsedYear = function(year, cycle) {
var elapsedYear = year || 0;
if (typeof(year) !== 'undefined' && year < 61 && typeof(cycle) !== 'undefined') {
elapsedYear = 60 * cycle + year;
}
return elapsedYear;
};
/**
* @private
* @static
* @param {number} jd julian day to calculate from
* @param {number} longitude longitude to seek
* @returns {number} the julian day of the next time that the solar longitude
* is a multiple of the given longitude
*/
HanCal._hanNextSolarLongitude = function(jd, longitude) {
var tz = HanCal._chineseTZ(jd);
var uni = Astro._universalFromLocal(jd, tz);
var sol = Astro._nextSolarLongitude(uni, longitude);
return Astro._localFromUniversal(sol, tz);
};
/**
* @private
* @static
* @param {number} jd julian day to calculate from
* @returns {number} the major solar term for the julian day
*/
HanCal._majorSTOnOrAfter = function(jd) {
var tz = HanCal._chineseTZ(jd);
var uni = Astro._universalFromLocal(jd, tz);
var next = Astro._fixangle(30 * Math.ceil(Astro._solarLongitude(uni)/30));
return HanCal._hanNextSolarLongitude(jd, next);
};
/**
* @private
* @static
* @param {number} year the year for which the leap year information is being sought
* @param {number=} cycle if the given year < 60, this can specify the cycle. If the
* cycle is not given, then the year should be given as elapsed years since the beginning
* of the epoch
*/
HanCal._solsticeBefore = function (year, cycle) {
var elapsedYear = HanCal._getElapsedYear(year, cycle);
var gregyear = elapsedYear - 2697;
var rd = new GregRataDie({
year: gregyear-1,
month: 12,
day: 15,
hour: 0,
minute: 0,
second: 0,
millisecond: 0
});
return HanCal._majorSTOnOrAfter(rd.getRataDie() + RataDie.gregorianEpoch);
};
/**
* @private
* @static
* @param {number} jd julian day to calculate from
* @returns {number} the current major solar term
*/
HanCal._chineseTZ = function(jd) {
var year = GregorianDate._calcYear(jd - RataDie.gregorianEpoch);
return year < 1929 ? 465.6666666666666666 : 480;
};
/**
* @private
* @static
* @param {number} jd julian day to calculate from
* @returns {number} the julian day of next new moon on or after the given julian day date
*/
HanCal._newMoonOnOrAfter = function(jd) {
var tz = HanCal._chineseTZ(jd);
var uni = Astro._universalFromLocal(jd, tz);
var moon = Astro._newMoonAtOrAfter(uni);
// floor to the start of the julian day
return Astro._floorToJD(Astro._localFromUniversal(moon, tz));
};
/**
* @private
* @static
* @param {number} jd julian day to calculate from
* @returns {number} the julian day of previous new moon before the given julian day date
*/
HanCal._newMoonBefore = function(jd) {
var tz = HanCal._chineseTZ(jd);
var uni = Astro._universalFromLocal(jd, tz);
var moon = Astro._newMoonBefore(uni);
// floor to the start of the julian day
return Astro._floorToJD(Astro._localFromUniversal(moon, tz));
};
/**
* @static
* @private
* @param {number} year the year for which the leap year information is being sought
* @param {number=} cycle if the given year < 60, this can specify the cycle. If the
* cycle is not given, then the year should be given as elapsed years since the beginning
* of the epoch
*/
HanCal._leapYearCalc = function(year, cycle) {
var ret = {
elapsedYear: HanCal._getElapsedYear(year, cycle)
};
ret.solstice1 = HanCal._solsticeBefore(ret.elapsedYear);
ret.solstice2 = HanCal._solsticeBefore(ret.elapsedYear+1);
// ceil to the end of the julian day
ret.m1 = HanCal._newMoonOnOrAfter(Astro._ceilToJD(ret.solstice1));
ret.m2 = HanCal._newMoonBefore(Astro._ceilToJD(ret.solstice2));
return ret;
};
/**
* @private
* @static
* @param {number} jd julian day to calculate from
* @returns {number} the current major solar term
*/
HanCal._currentMajorST = function(jd) {
var s = Astro._solarLongitude(Astro._universalFromLocal(jd, HanCal._chineseTZ(jd)));
return MathUtils.amod(2 + Math.floor(s/30), 12);
};
/**
* @private
* @static
* @param {number} jd julian day to calculate from
* @returns {boolean} true if there is no major solar term in the same year
*/
HanCal._noMajorST = function(jd) {
return HanCal._currentMajorST(jd) === HanCal._currentMajorST(HanCal._newMoonOnOrAfter(jd+1));
};
/**
* Return the number of months in the given year. The number of months in a year varies
* for some luni-solar calendars because in some years, an extra month is needed to extend the
* days in a year to an entire solar year. The month is represented as a 1-based number
* where 1=first month, 2=second month, etc.
*
* @param {number} year a year for which the number of months is sought
* @param {number=} cycle if the given year < 60, this can specify the cycle. If the
* cycle is not given, then the year should be given as elapsed years since the beginning
* of the epoch
* @return {number} The number of months in the given year
*/
HanCal.prototype.getNumMonths = function(year, cycle) {
return this.isLeapYear(year, cycle) ? 13 : 12;
};
/**
* Return the number of days in a particular month in a particular year. This function
* can return a different number for a month depending on the year because of things
* like leap years.
*
* @param {number} month the elapsed month for which the length is sought
* @param {number} year the elapsed year within which that month can be found
* @return {number} the number of days within the given month in the given year
*/
HanCal.prototype.getMonLength = function(month, year) {
// distance between two new moons in Nanjing China
var calc = HanCal._leapYearCalc(year);
var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + month * 29);
var postNewMoon = HanCal._newMoonOnOrAfter(priorNewMoon + 1);
return postNewMoon - priorNewMoon;
};
/**
* Return the equivalent year in the 2820 year cycle that begins on
* Far 1, 474. This particular cycle obeys the cycle-of-years formula
* whereas the others do not specifically. This cycle can be used as
* a proxy for other years outside of the cycle by shifting them into
* the cycle.
* @param {number} year year to find the equivalent cycle year for
* @returns {number} the equivalent cycle year
*/
HanCal.prototype.equivalentCycleYear = function(year) {
var y = year - (year >= 0 ? 474 : 473);
return MathUtils.mod(y, 2820) + 474;
};
/**
* Return true if the given year is a leap year in the Han calendar.
* If the year is given as a year/cycle combination, then the year should be in the
* range [1,60] and the given cycle is the cycle in which the year is located. If
* the year is greater than 60, then
* it represents the total number of years elapsed in the proleptic calendar since
* the beginning of the Chinese epoch in on 15 Feb, -2636 (Gregorian). In this
* case, the cycle parameter is ignored.
*
* @param {number} year the year for which the leap year information is being sought
* @param {number=} cycle if the given year < 60, this can specify the cycle. If the
* cycle is not given, then the year should be given as elapsed years since the beginning
* of the epoch
* @return {boolean} true if the given year is a leap year
*/
HanCal.prototype.isLeapYear = function(year, cycle) {
var calc = HanCal._leapYearCalc(year, cycle);
return Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12;
};
/**
* Return the month of the year that is the leap month. If the given year is
* not a leap year, then this method will return -1.
*
* @param {number} year the year for which the leap year information is being sought
* @param {number=} cycle if the given year < 60, this can specify the cycle. If the
* cycle is not given, then the year should be given as elapsed years since the beginning
* of the epoch
* @return {number} the number of the month that is doubled in this leap year, or -1
* if this is not a leap year
*/
HanCal.prototype.getLeapMonth = function(year, cycle) {
var calc = HanCal._leapYearCalc(year, cycle);
if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) != 12) {
return -1; // no leap month
}
// search between rd1 and rd2 for the first month with no major solar term. That is our leap month.
var month = 0;
var m = HanCal._newMoonOnOrAfter(calc.m1+1);
while (!HanCal._noMajorST(m)) {
month++;
m = HanCal._newMoonOnOrAfter(m+1);
}
// return the number of the month that is doubled
return month;
};
/**
* Return the date of Chinese New Years in the given calendar year.
*
* @param {number} year the Chinese year for which the new year information is being sought
* @param {number=} cycle if the given year < 60, this can specify the cycle. If the
* cycle is not given, then the year should be given as elapsed years since the beginning
* of the epoch
* @return {number} the julian day of the beginning of the given year
*/
HanCal.prototype.newYears = function(year, cycle) {
var calc = HanCal._leapYearCalc(year, cycle);
var m2 = HanCal._newMoonOnOrAfter(calc.m1+1);
if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12 &&
(HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) {
return HanCal._newMoonOnOrAfter(m2+1);
}
return m2;
};
/**
* Return the type of this calendar.
*
* @return {string} the name of the type of this calendar
*/
HanCal.prototype.getType = function() {
return this.type;
};
/* register this calendar for the factory method */
Calendar._constructors["han"] = HanCal;
module.exports = HanCal;
Source