1 /*
  2  * HebrewCal.js - Represent a Hebrew calendar object.
  3  *
  4  * Copyright © 2012-2015,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 MathUtils = require("./MathUtils.js");
 21 var Calendar = require("./Calendar.js");
 22 
 23 /**
 24  * @class
 25  * Construct a new Hebrew calendar object. This class encodes information about
 26  * the Hebrew (Jewish) calendar. The Hebrew calendar is a tabular hebrew
 27  * calendar where the dates are calculated by arithmetic rules. This differs from
 28  * the religious Hebrew calendar which is used to mark the beginning of particular
 29  * holidays. The religious calendar depends on the first sighting of the new
 30  * crescent moon to determine the first day of the new month. Because humans and
 31  * weather are both involved, the actual time of sighting varies, so it is not
 32  * really possible to precalculate the religious calendar. Certain groups, such
 33  * as the Hebrew Society of North America, decreed in in 2007 that they will use
 34  * a calendar based on calculations rather than observations to determine the
 35  * beginning of lunar months, and therefore the dates of holidays.<p>
 36  *
 37  * @param {Object=} options Options governing the construction of this instance
 38  * @constructor
 39  * @extends Calendar
 40  */
 41 var HebrewCal = function(options) {
 42     this.type = "hebrew";
 43 
 44     if (options && typeof(options.onLoad) === "function") {
 45         options.onLoad(this);
 46     }
 47 };
 48 
 49 /**
 50  * Return the number of days elapsed in the Hebrew calendar before the
 51  * given year starts.
 52  * @private
 53  * @param {number} year the year for which the number of days is sought
 54  * @return {number} the number of days elapsed in the Hebrew calendar before the
 55  * given year starts
 56  */
 57 HebrewCal.elapsedDays = function(year) {
 58     var months = Math.floor(((235*year) - 234)/19);
 59     var parts = 204 + 793 * MathUtils.mod(months, 1080);
 60     var hours = 11 + 12 * months + 793 * Math.floor(months/1080) +
 61         Math.floor(parts/1080);
 62     var days = 29 * months + Math.floor(hours/24);
 63     return (MathUtils.mod(3 * (days + 1), 7) < 3) ? days + 1 : days;
 64 };
 65 
 66 /**
 67  * Return the number of days that the New Year's (Rosh HaShanah) in the Hebrew
 68  * calendar will be corrected for the given year. Corrections are caused because New
 69  * Year's is not allowed to start on certain days of the week. To deal with
 70  * it, the start of the new year is corrected for the next year by adding a
 71  * day to the 8th month (Heshvan) and/or the 9th month (Kislev) in the current
 72  * year to make them 30 days long instead of 29.
 73  *
 74  * @private
 75  * @param {number} year the year for which the correction is sought
 76  * @param {number} elapsed number of days elapsed up to this year
 77  * @return {number} the number of days correction in the current year to make sure
 78  * Rosh HaShanah does not fall on undesirable days of the week
 79  */
 80 HebrewCal.newYearsCorrection = function(year, elapsed) {
 81     var lastYear = HebrewCal.elapsedDays(year-1),
 82         thisYear = elapsed,
 83         nextYear = HebrewCal.elapsedDays(year+1);
 84 
 85     return (nextYear - thisYear) == 356 ? 2 : ((thisYear - lastYear) == 382 ? 1 : 0);
 86 };
 87 
 88 /**
 89  * Return the rata die date of the new year for the given hebrew year.
 90  * @private
 91  * @param {number} year the year for which the new year is needed
 92  * @return {number} the rata die date of the new year
 93  */
 94 HebrewCal.newYear = function(year) {
 95     var elapsed = HebrewCal.elapsedDays(year);
 96 
 97     return elapsed + HebrewCal.newYearsCorrection(year, elapsed);
 98 };
 99 
100 /**
101  * Return the number of days in the given year. Years contain a variable number of
102  * days because the date of Rosh HaShanah (New Year's) changes so that it doesn't
103  * fall on particular days of the week. Days are added to the months of Heshvan
104  * and/or Kislev in the previous year in order to prevent the current year's New
105  * Year from being on Sunday, Wednesday, or Friday.
106  *
107  * @param {number} year the year for which the length is sought
108  * @return {number} number of days in the given year
109  */
110 HebrewCal.daysInYear = function(year) {
111     return HebrewCal.newYear(year+1) - HebrewCal.newYear(year);
112 };
113 
114 /**
115  * Return true if the given year contains a long month of Heshvan. That is,
116  * it is 30 days instead of 29.
117  *
118  * @private
119  * @param {number} year the year in which that month is questioned
120  * @return {boolean} true if the given year contains a long month of Heshvan
121  */
122 HebrewCal.longHeshvan = function(year) {
123     return MathUtils.mod(HebrewCal.daysInYear(year), 10) === 5;
124 };
125 
126 /**
127  * Return true if the given year contains a long month of Kislev. That is,
128  * it is 30 days instead of 29.
129  *
130  * @private
131  * @param {number} year the year in which that month is questioned
132  * @return {boolean} true if the given year contains a short month of Kislev
133  */
134 HebrewCal.longKislev = function(year) {
135     return MathUtils.mod(HebrewCal.daysInYear(year), 10) !== 3;
136 };
137 
138 /**
139  * Return the date of the last day of the month for the given year. The date of
140  * the last day of the month is variable because a number of months gain an extra
141  * day in leap years, and it is variable which months gain a day for each leap
142  * year and which do not.
143  *
144  * @param {number} month the month for which the number of days is sought
145  * @param {number} year the year in which that month is
146  * @return {number} the number of days in the given month and year
147  */
148 HebrewCal.prototype.lastDayOfMonth = function(month, year) {
149     switch (month) {
150         case 2:
151         case 4:
152         case 6:
153         case 10:
154             return 29;
155         case 13:
156             return this.isLeapYear(year) ? 29 : 0;
157         case 8:
158             return HebrewCal.longHeshvan(year) ? 30 : 29;
159         case 9:
160             return HebrewCal.longKislev(year) ? 30 : 29;
161         case 12:
162         case 1:
163         case 3:
164         case 5:
165         case 7:
166         case 11:
167             return 30;
168         default:
169             return 0;
170     }
171 };
172 
173 /**
174  * Return the number of months in the given year. The number of months in a year varies
175  * for luni-solar calendars because in some years, an extra month is needed to extend the
176  * days in a year to an entire solar year. The month is represented as a 1-based number
177  * where 1=first month, 2=second month, etc.
178  *
179  * @param {number} year a year for which the number of months is sought
180  */
181 HebrewCal.prototype.getNumMonths = function(year) {
182     return this.isLeapYear(year) ? 13 : 12;
183 };
184 
185 /**
186  * Return the number of days in a particular month in a particular year. This function
187  * can return a different number for a month depending on the year because of leap years.
188  *
189  * @param {number} month the month for which the length is sought
190  * @param {number} year the year within which that month can be found
191  * @returns {number} the number of days within the given month in the given year, or
192  * 0 for an invalid month in the year
193  */
194 HebrewCal.prototype.getMonLength = function(month, year) {
195     if (month < 1 || month > 13 || (month == 13 && !this.isLeapYear(year))) {
196         return 0;
197     }
198     return this.lastDayOfMonth(month, year);
199 };
200 
201 /**
202  * Return true if the given year is a leap year in the Hebrew calendar.
203  * The year parameter may be given as a number, or as a HebrewDate object.
204  * @param {number|Object} year the year for which the leap year information is being sought
205  * @returns {boolean} true if the given year is a leap year
206  */
207 HebrewCal.prototype.isLeapYear = function(year) {
208     var y = (typeof(year) == 'number') ? year : year.year;
209     return (MathUtils.mod(1 + 7 * y, 19) < 7);
210 };
211 
212 /**
213  * Return the type of this calendar.
214  *
215  * @returns {string} the name of the type of this calendar
216  */
217 HebrewCal.prototype.getType = function() {
218     return this.type;
219 };
220 
221 
222 /*register this calendar for the factory method */
223 Calendar._constructors["hebrew"] = HebrewCal;
224 
225 module.exports = HebrewCal;
226