Source

HebrewCal.js

/*
 * HebrewCal.js - Represent a Hebrew calendar object.
 *
 * Copyright © 2012-2015,2018, 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 MathUtils = require("./MathUtils.js");
var Calendar = require("./Calendar.js");

/**
 * @class
 * Construct a new Hebrew calendar object. This class encodes information about
 * the Hebrew (Jewish) calendar. The Hebrew calendar is a tabular hebrew
 * calendar where the dates are calculated by arithmetic rules. This differs from
 * the religious Hebrew calendar which is used to mark the beginning of particular
 * holidays. The religious calendar depends on the first sighting of the new
 * crescent moon to determine the first day of the new month. Because humans and
 * weather are both involved, the actual time of sighting varies, so it is not
 * really possible to precalculate the religious calendar. Certain groups, such
 * as the Hebrew Society of North America, decreed in in 2007 that they will use
 * a calendar based on calculations rather than observations to determine the
 * beginning of lunar months, and therefore the dates of holidays.<p>
 *
 * @param {Object=} options Options governing the construction of this instance
 * @constructor
 * @extends Calendar
 */
var HebrewCal = function(options) {
    this.type = "hebrew";

    if (options && typeof(options.onLoad) === "function") {
        options.onLoad(this);
    }
};

/**
 * Return the number of days elapsed in the Hebrew calendar before the
 * given year starts.
 * @private
 * @param {number} year the year for which the number of days is sought
 * @return {number} the number of days elapsed in the Hebrew calendar before the
 * given year starts
 */
HebrewCal.elapsedDays = function(year) {
    var months = Math.floor(((235*year) - 234)/19);
    var parts = 204 + 793 * MathUtils.mod(months, 1080);
    var hours = 11 + 12 * months + 793 * Math.floor(months/1080) +
        Math.floor(parts/1080);
    var days = 29 * months + Math.floor(hours/24);
    return (MathUtils.mod(3 * (days + 1), 7) < 3) ? days + 1 : days;
};

/**
 * Return the number of days that the New Year's (Rosh HaShanah) in the Hebrew
 * calendar will be corrected for the given year. Corrections are caused because New
 * Year's is not allowed to start on certain days of the week. To deal with
 * it, the start of the new year is corrected for the next year by adding a
 * day to the 8th month (Heshvan) and/or the 9th month (Kislev) in the current
 * year to make them 30 days long instead of 29.
 *
 * @private
 * @param {number} year the year for which the correction is sought
 * @param {number} elapsed number of days elapsed up to this year
 * @return {number} the number of days correction in the current year to make sure
 * Rosh HaShanah does not fall on undesirable days of the week
 */
HebrewCal.newYearsCorrection = function(year, elapsed) {
    var lastYear = HebrewCal.elapsedDays(year-1),
        thisYear = elapsed,
        nextYear = HebrewCal.elapsedDays(year+1);

    return (nextYear - thisYear) == 356 ? 2 : ((thisYear - lastYear) == 382 ? 1 : 0);
};

/**
 * Return the rata die date of the new year for the given hebrew year.
 * @private
 * @param {number} year the year for which the new year is needed
 * @return {number} the rata die date of the new year
 */
HebrewCal.newYear = function(year) {
    var elapsed = HebrewCal.elapsedDays(year);

    return elapsed + HebrewCal.newYearsCorrection(year, elapsed);
};

/**
 * Return the number of days in the given year. Years contain a variable number of
 * days because the date of Rosh HaShanah (New Year's) changes so that it doesn't
 * fall on particular days of the week. Days are added to the months of Heshvan
 * and/or Kislev in the previous year in order to prevent the current year's New
 * Year from being on Sunday, Wednesday, or Friday.
 *
 * @param {number} year the year for which the length is sought
 * @return {number} number of days in the given year
 */
HebrewCal.daysInYear = function(year) {
    return HebrewCal.newYear(year+1) - HebrewCal.newYear(year);
};

/**
 * Return true if the given year contains a long month of Heshvan. That is,
 * it is 30 days instead of 29.
 *
 * @private
 * @param {number} year the year in which that month is questioned
 * @return {boolean} true if the given year contains a long month of Heshvan
 */
HebrewCal.longHeshvan = function(year) {
    return MathUtils.mod(HebrewCal.daysInYear(year), 10) === 5;
};

/**
 * Return true if the given year contains a long month of Kislev. That is,
 * it is 30 days instead of 29.
 *
 * @private
 * @param {number} year the year in which that month is questioned
 * @return {boolean} true if the given year contains a short month of Kislev
 */
HebrewCal.longKislev = function(year) {
    return MathUtils.mod(HebrewCal.daysInYear(year), 10) !== 3;
};

/**
 * Return the date of the last day of the month for the given year. The date of
 * the last day of the month is variable because a number of months gain an extra
 * day in leap years, and it is variable which months gain a day for each leap
 * year and which do not.
 *
 * @param {number} month the month for which the number of days is sought
 * @param {number} year the year in which that month is
 * @return {number} the number of days in the given month and year
 */
HebrewCal.prototype.lastDayOfMonth = function(month, year) {
    switch (month) {
        case 2:
        case 4:
        case 6:
        case 10:
            return 29;
        case 13:
            return this.isLeapYear(year) ? 29 : 0;
        case 8:
            return HebrewCal.longHeshvan(year) ? 30 : 29;
        case 9:
            return HebrewCal.longKislev(year) ? 30 : 29;
        case 12:
        case 1:
        case 3:
        case 5:
        case 7:
        case 11:
            return 30;
        default:
            return 0;
    }
};

/**
 * Return the number of months in the given year. The number of months in a year varies
 * for 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
 */
HebrewCal.prototype.getNumMonths = function(year) {
    return this.isLeapYear(year) ? 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 leap years.
 *
 * @param {number} month the month for which the length is sought
 * @param {number} year the year within which that month can be found
 * @returns {number} the number of days within the given month in the given year, or
 * 0 for an invalid month in the year
 */
HebrewCal.prototype.getMonLength = function(month, year) {
    if (month < 1 || month > 13 || (month == 13 && !this.isLeapYear(year))) {
        return 0;
    }
    return this.lastDayOfMonth(month, year);
};

/**
 * Return true if the given year is a leap year in the Hebrew calendar.
 * The year parameter may be given as a number, or as a HebrewDate object.
 * @param {number|Object} year the year for which the leap year information is being sought
 * @returns {boolean} true if the given year is a leap year
 */
HebrewCal.prototype.isLeapYear = function(year) {
    var y = (typeof(year) == 'number') ? year : year.year;
    return (MathUtils.mod(1 + 7 * y, 19) < 7);
};

/**
 * Return the type of this calendar.
 *
 * @returns {string} the name of the type of this calendar
 */
HebrewCal.prototype.getType = function() {
    return this.type;
};


/*register this calendar for the factory method */
Calendar._constructors["hebrew"] = HebrewCal;

module.exports = HebrewCal;