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("./ilib.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