1 /*
  2  * HanCal.js - Represent a Han Chinese Lunar calendar object.
  3  *
  4  * Copyright © 2014-2017, 2018, JEDLSoft
  5  *
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  *
 10  *     http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  *
 16  * See the License for the specific language governing permissions and
 17  * limitations under the License.
 18  */
 19 
 20 var ilib = require("../index.js");
 21 var MathUtils = require("./MathUtils.js");
 22 
 23 var Calendar = require("./Calendar.js");
 24 
 25 var Astro = require("./Astro.js");
 26 var RataDie = require("./RataDie.js");
 27 var GregorianDate = require("./GregorianDate.js");
 28 var GregRataDie = require("./GregRataDie.js");
 29 
 30 /**
 31  * @class
 32  * Construct a new Han algorithmic calendar object. This class encodes information about
 33  * a Han algorithmic calendar.<p>
 34  *
 35  *
 36  * @constructor
 37  * @param {Object=} params optional parameters to load the calendrical data
 38  * @extends Calendar
 39  */
 40 var HanCal = function(params) {
 41     this.type = "han";
 42     var sync = params && typeof(params.sync) === 'boolean' ? params.sync : true;
 43 
 44     Astro.initAstro(sync, params && params.loadParams, ilib.bind(this, function (x) {
 45         if (params && typeof(params.onLoad) === 'function') {
 46             params.onLoad(this);
 47         }
 48     }));
 49 };
 50 
 51 /**
 52  * @protected
 53  * @static
 54  * @param {number} year
 55  * @param {number=} cycle
 56  * @return {number}
 57  */
 58 HanCal._getElapsedYear = function(year, cycle) {
 59     var elapsedYear = year || 0;
 60     if (typeof(year) !== 'undefined' && year < 61 && typeof(cycle) !== 'undefined') {
 61         elapsedYear = 60 * cycle + year;
 62     }
 63     return elapsedYear;
 64 };
 65 
 66 /**
 67  * @protected
 68  * @static
 69  * @param {number} jd julian day to calculate from
 70  * @param {number} longitude longitude to seek
 71  * @returns {number} the julian day of the next time that the solar longitude
 72  * is a multiple of the given longitude
 73  */
 74 HanCal._hanNextSolarLongitude = function(jd, longitude) {
 75     var tz = HanCal._chineseTZ(jd);
 76     var uni = Astro._universalFromLocal(jd, tz);
 77     var sol = Astro._nextSolarLongitude(uni, longitude);
 78     return Astro._localFromUniversal(sol, tz);
 79 };
 80 
 81 /**
 82  * @protected
 83  * @static
 84  * @param {number} jd julian day to calculate from
 85  * @returns {number} the major solar term for the julian day
 86  */
 87 HanCal._majorSTOnOrAfter = function(jd) {
 88     var tz = HanCal._chineseTZ(jd);
 89     var uni = Astro._universalFromLocal(jd, tz);
 90     var next = Astro._fixangle(30 * Math.ceil(Astro._solarLongitude(uni)/30));
 91     return HanCal._hanNextSolarLongitude(jd, next);
 92 };
 93 
 94 /**
 95  * @protected
 96  * @static
 97  * @param {number} year the year for which the leap year information is being sought
 98  * @param {number=} cycle if the given year < 60, this can specify the cycle. If the
 99  * cycle is not given, then the year should be given as elapsed years since the beginning
100  * of the epoch
101  */
102 HanCal._solsticeBefore = function (year, cycle) {
103     var elapsedYear = HanCal._getElapsedYear(year, cycle);
104     var gregyear = elapsedYear - 2697;
105     var rd = new GregRataDie({
106         year: gregyear-1,
107         month: 12,
108         day: 15,
109         hour: 0,
110         minute: 0,
111         second: 0,
112         millisecond: 0
113     });
114     return HanCal._majorSTOnOrAfter(rd.getRataDie() + RataDie.gregorianEpoch);
115 };
116 
117 /**
118  * @protected
119  * @static
120  * @param {number} jd julian day to calculate from
121  * @returns {number} the current major solar term
122  */
123 HanCal._chineseTZ = function(jd) {
124     var year = GregorianDate._calcYear(jd - RataDie.gregorianEpoch);
125     return year < 1929 ? 465.6666666666666666 : 480;
126 };
127 
128 /**
129  * @protected
130  * @static
131  * @param {number} jd julian day to calculate from
132  * @returns {number} the julian day of next new moon on or after the given julian day date
133  */
134 HanCal._newMoonOnOrAfter = function(jd) {
135     var tz = HanCal._chineseTZ(jd);
136     var uni = Astro._universalFromLocal(jd, tz);
137     var moon = Astro._newMoonAtOrAfter(uni);
138     // floor to the start of the julian day
139     return Astro._floorToJD(Astro._localFromUniversal(moon, tz));
140 };
141 
142 /**
143  * @protected
144  * @static
145  * @param {number} jd julian day to calculate from
146  * @returns {number} the julian day of previous new moon before the given julian day date
147  */
148 HanCal._newMoonBefore = function(jd) {
149     var tz = HanCal._chineseTZ(jd);
150     var uni = Astro._universalFromLocal(jd, tz);
151     var moon = Astro._newMoonBefore(uni);
152     // floor to the start of the julian day
153     return Astro._floorToJD(Astro._localFromUniversal(moon, tz));
154 };
155 
156 /**
157  * @static
158  * @protected
159  * @param {number} year the year for which the leap year information is being sought
160  * @param {number=} cycle if the given year < 60, this can specify the cycle. If the
161  * cycle is not given, then the year should be given as elapsed years since the beginning
162  * of the epoch
163  */
164 HanCal._leapYearCalc = function(year, cycle) {
165     var ret = {
166         elapsedYear: HanCal._getElapsedYear(year, cycle)
167     };
168     ret.solstice1 = HanCal._solsticeBefore(ret.elapsedYear);
169     ret.solstice2 = HanCal._solsticeBefore(ret.elapsedYear+1);
170     // ceil to the end of the julian day
171     ret.m1 = HanCal._newMoonOnOrAfter(Astro._ceilToJD(ret.solstice1));
172     ret.m2 = HanCal._newMoonBefore(Astro._ceilToJD(ret.solstice2));
173 
174     return ret;
175 };
176 
177 /**
178  * @protected
179  * @static
180  * @param {number} jd julian day to calculate from
181  * @returns {number} the current major solar term
182  */
183 HanCal._currentMajorST = function(jd) {
184     var s = Astro._solarLongitude(Astro._universalFromLocal(jd, HanCal._chineseTZ(jd)));
185     return MathUtils.amod(2 + Math.floor(s/30), 12);
186 };
187 
188 /**
189  * @protected
190  * @static
191  * @param {number} jd julian day to calculate from
192  * @returns {boolean} true if there is no major solar term in the same year
193  */
194 HanCal._noMajorST = function(jd) {
195     return HanCal._currentMajorST(jd) === HanCal._currentMajorST(HanCal._newMoonOnOrAfter(jd+1));
196 };
197 
198 /**
199  * Return the number of months in the given year. The number of months in a year varies
200  * for some luni-solar calendars because in some years, an extra month is needed to extend the
201  * days in a year to an entire solar year. The month is represented as a 1-based number
202  * where 1=first month, 2=second month, etc.
203  *
204  * @param {number} year a year for which the number of months is sought
205  * @param {number=} cycle if the given year < 60, this can specify the cycle. If the
206  * cycle is not given, then the year should be given as elapsed years since the beginning
207  * of the epoch
208  * @return {number} The number of months in the given year
209  */
210 HanCal.prototype.getNumMonths = function(year, cycle) {
211     return this.isLeapYear(year, cycle) ? 13 : 12;
212 };
213 
214 /**
215  * Return the number of days in a particular month in a particular year. This function
216  * can return a different number for a month depending on the year because of things
217  * like leap years.
218  *
219  * @param {number} month the elapsed month for which the length is sought
220  * @param {number} year the elapsed year within which that month can be found
221  * @return {number} the number of days within the given month in the given year
222  */
223 HanCal.prototype.getMonLength = function(month, year) {
224     // distance between two new moons in Nanjing China
225     var calc = HanCal._leapYearCalc(year);
226     var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + month * 29);
227     var postNewMoon = HanCal._newMoonOnOrAfter(priorNewMoon + 1);
228     return postNewMoon - priorNewMoon;
229 };
230 
231 /**
232  * Return the equivalent year in the 2820 year cycle that begins on
233  * Far 1, 474. This particular cycle obeys the cycle-of-years formula
234  * whereas the others do not specifically. This cycle can be used as
235  * a proxy for other years outside of the cycle by shifting them into
236  * the cycle.
237  * @param {number} year year to find the equivalent cycle year for
238  * @returns {number} the equivalent cycle year
239  */
240 HanCal.prototype.equivalentCycleYear = function(year) {
241     var y = year - (year >= 0 ? 474 : 473);
242     return MathUtils.mod(y, 2820) + 474;
243 };
244 
245 /**
246  * Return true if the given year is a leap year in the Han calendar.
247  * If the year is given as a year/cycle combination, then the year should be in the
248  * range [1,60] and the given cycle is the cycle in which the year is located. If
249  * the year is greater than 60, then
250  * it represents the total number of years elapsed in the proleptic calendar since
251  * the beginning of the Chinese epoch in on 15 Feb, -2636 (Gregorian). In this
252  * case, the cycle parameter is ignored.
253  *
254  * @param {number} year the year for which the leap year information is being sought
255  * @param {number=} cycle if the given year < 60, this can specify the cycle. If the
256  * cycle is not given, then the year should be given as elapsed years since the beginning
257  * of the epoch
258  * @return {boolean} true if the given year is a leap year
259  */
260 HanCal.prototype.isLeapYear = function(year, cycle) {
261     var calc = HanCal._leapYearCalc(year, cycle);
262     return Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12;
263 };
264 
265 /**
266  * Return the month of the year that is the leap month. If the given year is
267  * not a leap year, then this method will return -1.
268  *
269  * @param {number} year the year for which the leap year information is being sought
270  * @param {number=} cycle if the given year < 60, this can specify the cycle. If the
271  * cycle is not given, then the year should be given as elapsed years since the beginning
272  * of the epoch
273  * @return {number} the number of the month that is doubled in this leap year, or -1
274  * if this is not a leap year
275  */
276 HanCal.prototype.getLeapMonth = function(year, cycle) {
277     var calc = HanCal._leapYearCalc(year, cycle);
278 
279     if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) != 12) {
280         return -1; // no leap month
281     }
282 
283     // search between rd1 and rd2 for the first month with no major solar term. That is our leap month.
284     var month = 0;
285     var m = HanCal._newMoonOnOrAfter(calc.m1+1);
286     while (!HanCal._noMajorST(m)) {
287         month++;
288         m = HanCal._newMoonOnOrAfter(m+1);
289     }
290 
291     // return the number of the month that is doubled
292     return month;
293 };
294 
295 /**
296  * Return the date of Chinese New Years in the given calendar year.
297  *
298  * @param {number} year the Chinese year for which the new year information is being sought
299  * @param {number=} cycle if the given year < 60, this can specify the cycle. If the
300  * cycle is not given, then the year should be given as elapsed years since the beginning
301  * of the epoch
302  * @return {number} the julian day of the beginning of the given year
303  */
304 HanCal.prototype.newYears = function(year, cycle) {
305     var calc = HanCal._leapYearCalc(year, cycle);
306     var m2 = HanCal._newMoonOnOrAfter(calc.m1+1);
307     if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12 &&
308             (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) {
309         return HanCal._newMoonOnOrAfter(m2+1);
310     }
311     return m2;
312 };
313 
314 /**
315  * Return the type of this calendar.
316  *
317  * @return {string} the name of the type of this calendar
318  */
319 HanCal.prototype.getType = function() {
320     return this.type;
321 };
322 
323 
324 /* register this calendar for the factory method */
325 Calendar._constructors["han"] = HanCal;
326 
327 module.exports = HanCal;
328