hebcal-fixed
Version:
An easy to use, richly-featured, perpetual Jewish Calendar API
472 lines (401 loc) • 12.3 kB
JavaScript
/*
Hebcal - A Jewish Calendar Generator
Copyright (C) 1994-2004 Danny Sadinoff
Portions Copyright (c) 2002 Michael J. Radwin. All Rights Reserved.
https://github.com/hebcal/hebcal-js
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Danny Sadinoff can be reached at danny@sadinoff.com
Michael Radwin has made significant contributions as a result of
maintaining hebcal.com.
The JavaScript code was completely rewritten in 2014 by Eyal Schachter.
*/
var c = require('./common'),
greg = require('./greg'),
suncalc = require('suncalc'),
cities = require('./cities'),
gematriya = require('gematriya');
suncalc.addTime(-16.1, 'alot_hashachar', 0);
suncalc.addTime(-11.5, 'misheyakir', 0);
suncalc.addTime(-10.2, 'misheyakir_machmir', 0);
suncalc.addTime(-8.5, 0, 'tzeit');
suncalc.addTime(-5.879999999999995, 0, 'tzeitstars');
// for minifying optimizations
var getFullYear = 'getFullYear',
getMonth = 'getMonth',
getDate = 'getDate',
getTime = 'getTime',
abs = 'abs',
hour = 'hour',
months = c.months,
TISHREI = months.TISHREI,
MONTH_CNT = c.MONTH_CNT,
daysInMonth = c.daysInMonth,
dayOnOrBefore = c.dayOnOrBefore,
prototype = HDate.prototype;
function HDate(day, month, year) {
var me = this;
switch (arguments.length) {
case 0:
return new HDate(new Date());
case 1:
if (typeof day == 'undefined') {
return new HDate();
} else if (day instanceof Date) {
// we were passed a Gregorian date, so convert it
var d = abs2hebrew(greg.greg2abs(day));
/*if (d.sunset() < day) {
d = d.next();
}*/
return d;
} else if (day instanceof HDate) {
var d = new HDate(day[getDate](), day[getMonth](), day[getFullYear]());
d.il = day.il;
d.setLocation(d.lat, d.long);
return d;
} else if (typeof day == 'string') {
switch (day.toLowerCase().trim()) {
case 'today':
return new HDate();
case 'yesterday':
return new HDate().prev();
case 'tomorrow':
return new HDate().next();
}
if (/\s/.test(day)) {
var s = day.split(/\s+/);
if (s.length == 2) {
return new HDate(s[0], s[1]);
} else if (s.length == 3) {
return new HDate(s[0], s[1], s[2]);
} else if (s.length == 4) { // should only be if s[1] is Adar
if (/i/i.test(s[2])) { // Using I[I] syntax
s[2] = s[2].length;
} // otherwise using 1|2 syntax
return new HDate(s[0], s[1] + s[2], s[3]);
}
}
} else if (typeof day == 'number') { // absolute date
return abs2hebrew(day);
}
throw new TypeError('HDate called with bad argument');
case 2:
return new HDate(day, month, (new HDate)[getFullYear]());
case 3:
me.day = me.month = 1;
me.year = c.dayYearNum(year);
me.setMonth(c.monthNum(month));
me.setDate(c.dayYearNum(day));
break;
default:
throw new TypeError('HDate called with bad arguments');
}
return me.setLocation.apply(me, HDate.defaultLocation);
}
HDate.defaultLocation = [0, 0];
Object.defineProperty(HDate, 'defaultCity', {
enumerable: true,
configurable: true,
get: function() {
return cities.nearest(HDate.defaultLocation[0], HDate.defaultLocation[1]);
},
set: function(city) {
HDate.defaultLocation = cities.getCity(city).slice(0, 2);
}
});
function fix(date) {
fixMonth(date);
fixDate(date);
}
function fixDate(date) {
if (date.day < 1) {
if (date.month == TISHREI) {
date.year -= 1;
}
date.day += daysInMonth(date.month, date.year);
date.month -= 1;
fix(date);
}
if (date.day > daysInMonth(date.month, date.year)) {
if (date.month == months.ELUL) {
date.year += 1;
}
date.day -= daysInMonth(date.month, date.year);
date.month += 1;
fix(date);
}
fixMonth(date);
}
function fixMonth(date) {
if (date.month == months.ADAR_II && !date.isLeapYear()) {
date.month -= 1; // to Adar I
fix(date);
}
if (date.month < 1) {
date.month += MONTH_CNT(date.year);
date.year -= 1;
fix(date);
}
if (date.month > MONTH_CNT(date.year)) {
date.month -= MONTH_CNT(date.year);
date.year += 1;
fix(date);
}
}
prototype[getFullYear] = function() {
return this.year;
};
prototype.isLeapYear = function() {
return c.LEAP(this.year);
};
prototype[getMonth] = function() {
return this.month;
};
prototype.getTishreiMonth = function() {
var nummonths = MONTH_CNT(this[getFullYear]());
return (this[getMonth]() + nummonths - 6) % nummonths || nummonths;
};
prototype.daysInMonth = function() {
return daysInMonth(this[getMonth](), this[getFullYear]());
};
prototype[getDate] = function() {
return this.day;
};
prototype.getDay = function() {
return this.greg().getDay();
};
prototype.setFullYear = function(year) {
this.year = year;
fix(this);
return this;
};
prototype.setMonth = function(month) {
this.month = c.monthNum(month);
fix(this);
return this;
};
prototype.setTishreiMonth = function(month) {
return this.setMonth((month + 6) % MONTH_CNT(this[getFullYear]()) || 13);
};
prototype.setDate = function(date) {
this.day = date;
fix(this);
return this;
};
/* convert hebrew date to absolute date */
/* Absolute date of Hebrew DATE.
The absolute date is the number of days elapsed since the (imaginary)
Gregorian date Sunday, December 31, 1 BC. */
function hebrew2abs(d) {
var m, tempabs = d[getDate](), year = d[getFullYear]();
if (d[getMonth]() < TISHREI) {
for (m = TISHREI; m <= MONTH_CNT(year); m++) {
tempabs += daysInMonth(m, year);
}
for (m = months.NISAN; m < d[getMonth](); m++) {
tempabs += daysInMonth(m, year);
}
} else {
for (m = TISHREI; m < d[getMonth](); m++) {
tempabs += daysInMonth(m, year);
}
}
return c.hebElapsedDays(year) - 1373429 + tempabs;
}
function abs2hebrew(d) {
var mmap = [
months.KISLEV, months.TEVET, months.SHVAT, months.ADAR_I, months.NISAN,
months.IYYAR, months.SIVAN, months.TAMUZ, TISHREI, TISHREI, TISHREI, months.CHESHVAN
], hebdate, gregdate, month, year;
if (d >= 10555144) {
throw new RangeError("parameter to abs2hebrew " + d + " out of range");
}
gregdate = greg.abs2greg(d);
hebdate = new HDate(1, TISHREI, (year = 3760 + gregdate[getFullYear]()));
while (d >= hebrew2abs(hebdate.setFullYear(year + 1))) {
year++;
}
if (year > 4634 && year < 10666) {
// optimize search
month = mmap[gregdate[getMonth]()];
} else {
// we're outside the usual range, so assume nothing about Hebrew/Gregorian calendar drift...
month = TISHREI;
}
while (d > hebrew2abs(hebdate = new HDate(daysInMonth(month, year), month, year))) {
month = (month % MONTH_CNT(year)) + 1;
}
return hebdate.setLocation.apply(hebdate.setDate(d - hebrew2abs(hebdate.setDate(1)) + 1), HDate.defaultLocation);
}
prototype.greg = function() {
return greg.abs2greg(hebrew2abs(this));
};
prototype.gregEve = function() {
return this.prev().sunset();
};
prototype[abs] = function() {
return hebrew2abs(this);
};
prototype.toString = function(o) {
return c.LANG([this[getDate](), null, gematriya(this[getDate]())], o) + ' ' +
this.getMonthName(o) + ' ' +
c.LANG([this[getFullYear](), null, gematriya(this[getFullYear]())], o);
};
prototype.getMonthName = function(o) {
return c.LANG(c.monthNames[+this.isLeapYear()][this[getMonth]()], o);
};
prototype.setCity = function(city) {
return this.setLocation(cities.getCity(city));
};
prototype.setLocation = function(lat, lon) {
if (typeof lat == 'object' && !Array.isArray(lat)) {
lon = lat.long;
lat = lat.lat;
}
if (Array.isArray(lat) && typeof lon == 'undefined') {
lon = lat[0];
lat = lat[1];
}
if (Array.isArray(lat)) {
lat = (lat[0] * 60 + lat[1]) / 60;
}
if (Array.isArray(lon)) {
lon = (lon[0] * 60 + lon[1]) / 60;
}
if (typeof lat != 'number') {
throw new TypeError('incorrect lat type passed to HDate.setLocation()');
}
if (typeof lon != 'number') {
throw new TypeError('incorrect long type passed to HDate.setLocation()');
}
this.lat = lat;
this.long = lon;
this.il = cities.getCity(cities.nearest(lat, lon))[2];
return this;
};
function suntime(hdate) {
// reset the date to midday before calling suncalc api
// https://github.com/mourner/suncalc/issues/11
var date = hdate.greg();
return suncalc.getTimes(new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0, 0, 0), hdate.lat, hdate.long);
}
prototype.sunrise = function() {
return suntime(this).sunrise;
};
prototype.sunset = function() {
return suntime(this).sunset;
};
prototype[hour] = function() {
return (this.sunset() - this.sunrise()) / 12; // ms in hour
};
prototype.hourMins = function() {
// hour in ms / (1000 ms in s * 60 s in m) = mins in halachic hour
return this[hour]() / (1000 * 60);
};
prototype.nightHour = function() {
return (this.sunrise() - this.gregEve()) / 12; // ms in hour
};
prototype.nightHourMins = function() {
// hour in ms / (1000 ms in s * 60 s in m) = mins in halachic hour
return this.nightHour() / (1000 * 60);
};
function hourOffset(hdate, hours) {
return new Date(hdate.sunrise()[getTime]() + (hdate[hour]() * hours));
}
var zemanim = {
chatzot: function(hdate) {
return hourOffset(hdate, 6);
},
chatzot_night: function(hdate) {
return new Date(hdate.sunrise()[getTime]() - (hdate.nightHour() * 6));
},
alot_hashachar: function(hdate) {
return suntime(hdate).alot_hashachar;
},
alot_hashacher: function(hdate) {
return suntime(hdate).alot_hashachar;
},
misheyakir: function(hdate) {
return suntime(hdate).misheyakir;
},
misheyakir_machmir: function(hdate) {
return suntime(hdate).misheyakir_machmir;
},
sof_zman_shma: function(hdate) { // Gra
return hourOffset(hdate, 3);
},
sof_zman_tfilla: function(hdate) { // Gra
return hourOffset(hdate, 4);
},
mincha_gedola: function(hdate) {
return hourOffset(hdate, 6.5);
},
mincha_ketana: function(hdate) {
return hourOffset(hdate, 9.5);
},
plag_hamincha: function(hdate) {
return hourOffset(hdate, 10.75);
},
tzeit: function(hdate) {
return suntime(hdate).tzeit;
},
tzeitstars: function(hdate) {
return suntime(hdate).tzeitstars;
},
neitz_hachama: function(hdate) {
return hdate.sunrise();
},
shkiah: function(hdate) {
return hdate.sunset();
}
};
prototype.getZemanim = function() {
return c.map(zemanim, function(z){
return z(this);
}, this);
};
HDate.addZeman = function(zeman, func) {
zemanim[zeman] = func;
};
prototype.next = function() {
return abs2hebrew(this.abs() + 1).setLocation(this.lat, this.long);
};
prototype.prev = function() {
return abs2hebrew(this.abs() - 1).setLocation(this.lat, this.long);
};
prototype.isSameDate = function(other) {
if (other instanceof HDate) {
if (other[getFullYear]() == -1) {
other = new HDate(other).setFullYear(this[getFullYear]());
}
return this[abs]() == other[abs]();
}
return false;
};
function onOrBefore(day, t, offset) {
return new HDate(dayOnOrBefore(day, t[abs]() + offset));
}
prototype.before = function(day) {
return onOrBefore(day, this, -1);
};
prototype.onOrBefore = function(day) {
return onOrBefore(day, this, 0);
};
prototype.nearest = function(day) {
return onOrBefore(day, this, 3);
};
prototype.onOrAfter = function(day) {
return onOrBefore(day, this, 6);
};
prototype.after = function(day) {
return onOrBefore(day, this, 7);
};
module.exports = HDate;