Ofek Yerushalayim
Closed this issue · 27 comments
Shalom uvrachah
Hebcal's zmanim calculations diverge significantly from the printed luchot commonly sold in jerusalem
without descending into a lengthy discussion of the halachic merits of the various approaches, it would be helpful to many gabbaim (and gabbai-affiliated nerds) to provide computed times which accord more-or-less with "the luach i bought in the shop"
my personal ignorance knows no bounds, nonetheless my cursory surveys of the relevant sugyas as well as the code suggest to me that if the elevation in jerusalem, or better yet at a given lat/long were taken into account in @hebcal/solar-time
, then it would be possible for the gabbai-affiliated to soothe the hesitations of the gabbaim themselves where @hebcal/core
integrations are concerned
Thank you for your consideration
besoros tovos
Thanks for your feedback. Can you share a bit more detail about where the zmanim calculations diverge, and by how many minutes? Some concrete examples would be extremely useful. I don't live in Jerusalem, so perhaps you could post a photo of your favorite printed luach?
As you already know, all zmanim are approximated from a location (latitude, longitude) and day of year. The algorithm used by @hebcal/solar-calc
is based on the NOAA Solar Calculator (US National Oceanic and Atmospheric Administration), which in turn are based on equations from Astronomical Algorithms by Jean Meeus.
Differences of 1-2 minutes between Hebcal and other sources publishing candle lighting times or sunset times are expected. Remember that candle lighting times can only be approximated based on location.
Here are a few common reasons why you may see differences in candle lighting times:
https://www.hebcal.com/home/94/how-accurate-are-candle-lighting-times
The NOAA algorithm, as implemented, utilizes only latitude, longitude, and day of year. It does not utilize elevation. Even if the algorithm did use elevation, including Jerusalem's elevation into the formula would change the calculations by less than one minute.
Sure thing. Here's some brief halachik background to whet the appetite.
So, taking last week as an example, here are lighting times in jerusalem (40m)
according to hebcal, chabad.org, and times derived from the "Luah dinim
uminhagim lishnat tashpad" published by Heichal Shlomo and the itim lebinah luah
Parshah | Source | Time |
---|---|---|
Lech Lecha | @hebcal/core |
17:14 |
Lech Lecha | Chabad.org | 17:15 |
Lech Lecha | Luah | 17:19 |
Vayera | @hebcal/core |
4:08 |
Vayera | Chabad.org | 4:08 |
Vayera | Luah | 4:13 |
Looking here things seem more or less on target. I'll post later with some times which seemed much more extremely divergent.
Thanks :)
Thanks. The 5-minute delta is difficult to explain. Sunset in Tel Aviv is only a minute after sunset in Jerusalem, so this isn't a latitude/longitude issue or an elevation issue. Could your luach be using a different formula for candle lighting time, for example 35 minutes before sundown? Or solar elevation of some number of degrees (instead of minutes before sundown)?
I am not surprised by the 1-minute delta between Hebcal and Chabad.
Tzeit is even more problematic. Depending on the angle i might be off by 15 minutes on either side, and that's worrisome wrt havdalah
We give many options for Tzeit because there are many traditions. Some use fixed minutes past sundown (most common are 42 min for three medium-sized stars, 50 min for three small stars, 72 min for Rabbeinu Tam). Some use degrees below horizon (most commonly 8.5 degrees for three small stars, occasionally 7.0833 degrees for three medium-sized stars).
https://github.com/hebcal/hebcal-es6/blob/main/README.md#HebrewCalendar.calendar
options.havdalahMins
- minutes after sundown for Havdalah (typical values are 42, 50, or 72). Havdalah times are suppressed whenoptions.havdalahMins=0
.options.havdalahDeg
- degrees for solar depression for Havdalah. Default is 8.5 degrees for 3 small stars. Use 7.083 degrees for 3 medium-sized stars. Havdalah times are suppressed whenoptions.havdalahDeg=0
.
You can probably get the tzeit times to line up within 1-2 minutes if you learn what formula your favorite luach uses.
clearly i have some homework to do. thanks.
With your permission i'd like to keep this issue open until I can write up my findings
a question: why are there separate parameters for tzeit and for havdallah? When would I want to compute tzeit by degrees, but havdallah by minutes?
The API will throw an exception if you specify both havdalahDeg
and havdalahMins
. You should specify one or the other. The default is for havdalahMins
to be undefined
and for havdalahDeg
to be 8.5.
The HebrewCalendar.calendar function is very convenient, but if you want more fine-grained control, consider using the Zmanim API directly.
const {Zmanim} = require('@hebcal/core');
const latitude = 41.822232;
const longitude = -71.448292;
const friday = new Date(2023, 8, 8);
const zmanim = new Zmanim(friday, latitude, longitude);
const candleLighting = zmanim.sunsetOffset(-18, true);
const timeStr = Zmanim.formatISOWithTimeZone('America/New_York', candleLighting);
console.log(timeStr);
So here are candle and havdalah times for some parshiot from the 'לוח דינים ומנהגים לשנת תשפ״ד'. Most of the printed luchot in jerusalem accord with these times give-or-take a minute or two.
Parshah | Candles | Havdalah |
---|---|---|
Bereshit | 17:35 | 18:45 |
Noach | 17:27 | 18:37 |
Lech-Lecha | 17:19 | 18:31 |
Vayera | 16:13 | 17:25 |
Chayei Sarah | 16:08 | 17:20 |
And here are times derived from Hebcal with "Jerusalem" set as the city and an
exagerrated havdalah time based on 8.5 degrees.
Parshah | Candles | Havdalah |
---|---|---|
Bereshit | 17:29 | 18:45 |
Noach | 17:21 | 18:37 |
Lech-Lecha | 17:14 | 18:31 |
Vayera | 16:08 | 17:25 |
Chayei Sara | 16:03 | 17:20 |
Code for hebcal table
import { HebrewCalendar, Location, HDate } from '@hebcal/core';
const times = [
'2023-10-13',
'2023-10-20',
'2023-10-27',
'2023-11-03',
'2023-11-10',
].map(iso => {
const start = new Date(iso);
const end = new HDate(start).add(1);
const location = Location.lookup('Jerusalem');
const [
{memo: parsha, eventTimeStr: candles},
,
{eventTimeStr: havdalah}
] = HebrewCalendar.calendar({
location,
candlelighting: true,
candleLightingMins: 40,
havdalahDeg: 8.5,
sedrot: true,
start,
end,
});
return { parsha, candles, havdalah }
})
console.log(`
| Parshah | Candles | Havdalah |
| ------- | ------- | -------- |${times.map(({ parsha, candles, havdalah }) => `
| ${parsha.replace('Parashat ', '')} | ${candles} | ${havdalah} |`).join('')}
`)
As you can see, I'm able to align havdalah times with an artificially inflated
havdalah angle, however candle lighting times (as well as daily prayer times,
based on shkiya) will be off by a significant margin. I suspect that considering
elevation would help here.
Thanks for the comments and for sharing your code.
Why do you say 8.5 degrees is an artificially inflated Havdalah angle? This is the default angle, which is used on many calendars for 3 small stars. Because it's the default, note that if you comment-out havdalahDeg: 8.5
from your options, you should get the exact same result.
Second, for Friday night candle-lighting times, are you sure that your luach doesn't use 35 minutes before sunset? Have you checked any sources for Jerusalem sunset? According to this source https://www.timeanddate.com/sun/israel/jerusalem here are the sunset times for the dates you mention, and here are the times if you specify candle-lighting 35 minutes before sunset, or 40 minutes before sunset:
Date | Sunset | 35 mins | 40 mins |
---|---|---|---|
2023-10-13 | 18:09 | 17:34 | 17:29 |
2023-10-20 | 18:01 | 17:25 | 17:21 |
2023-10-27 | 17:54 | 17:19 | 17:14 |
2023-11-03 | 16:48 | 16:13 | 16:08 |
2023-11-10 | 16:43 | 16:08 | 16:03 |
You'll note that the Hebcal sunset and 40-minutes before sunset candle-lighting times align 100% with this source.
In other words, try changing your code as follows, and you will be pleased to discover that the times generated by @hebcal/core
match your luach nearly perfectly.
const events = HebrewCalendar.calendar({
location,
noHolidays: true, // don't include holidays
candlelighting: true,
candleLightingMins: 35, // not 40
// havdalahDeg: 8.5, // 8.5 is the default
sedrot: true,
start: start,
end: end,
});
As explained earlier, taking the elevation of Jerusalem into account changes the sunset calculation by less than one minute, and as such, it's not something that we will plan to include.
If I go this way, members of my synagogue who are arithmetically inclined will notice two things
- My candle lighting calculations are based on 35 minutes from my published shkiya times
- My published shkiya times do not accord with the printed shkiya times, calling into question the rest of the computed luah
I don't think "we can just offset candle times and no one will notice" is a satisfactory answer to the fact that hebcal's fundamental zmanim (read: shkiya) do not accord with the accepted times in Jerusalem.
זכור את רבני צפת שרצו להקים מחדש את הסמיכה בצדק ומתוך אהבת השם גמורה. היו להם כל הסיבות והסברות. היו נכונים וצודקים לגמרי, אבל לא התיעצו ולא המליכו בחכמי ירושלים, וכל עירם נחרב ונעשה שממה ברעש אדירה עד לחורבן שלמה.
I will endeavour to provide a comprehensive comparison between hebcal and the detailed printer luhot.
I understand the desire to provide a singular general codepath, but Yerushalayim is not a typical city.
ואם תסכים להודות ללוח המקובל אצלינו בירושלים גם אם זה מצריך שינויים מיוחדים וכללים שונים לגבי עיר קודשינו ומקום מקדשינו מהרה יבנה, אז השם יברך אותך בכל הברכות הכתובות בתורת משה רבינו ויצליח דרכיך בכל אשר תלך, וראה בנים לבניך ויהיו נכסיך קרובים לעיר, תשיש ותשמח כל הימים באושר וכבוד
Please consider the kavod of Yerushalayim, מקום שממנו תצא תורה ודבר השם, and endeavour to make the necessary exceptions in hebcal's codepaths such that hebcal's results for Jerusalem accord by default and without additional configuration with the minhag hamakom. I'm am willing and available to contribute in any way I can to making this so.
It may help to illustrate the problem, so I prepared this demo
the kavod of Yerushalayim
If, however, you can provide me with a significant community in Jerusalem which relies on your times as a matter of halakha (rather than engineering) then I will be happy to retract my position and seek solutions elsewhere
Let's start with location.
When your code does Location.lookup('Jerusalem')
, you are getting a location which is the "center" of Jerusalem (Hebcal uses 31.76904
, 35.21633
). You might discover that the printed luach you are consulting uses a different latitude/longitude within Jerusalem. You don't have to do if you want a different location. You could, for example, use any location like this:
const location = new Location(31.7759751, 35.2143054, true, 'Asia/Jerusalem', 'Jerusalem Great Synagogue', 'IL');
Specifying a different latitude and longitude will indeed change the calculation, although likely by only a small number of seconds.
Next, let's take a look at sunset. If your published shkiya times do not accord with the printed shkiya times, I would encourage you to start your investigations there. By shkiya, I am referring to the astronomical definition of sunset, which is defined as:
When the upper edge of the Sun disappears below the horizon (0.833° below horizon)
For a consistent latitude and longitude, you will find that any nearly any sunset algorithm, including Hebcal's, will generate consistent times within 1 minute according to that definition of sunset. The two most common algorithms are those published by NOAA (what Hebcal uses) and the USNO (generally considered to be less accurate). Either way, those calculations won't change by more than 1 minute, even when taking the elevation (estimated 786 meters above sea level) into account.
If you find, however, that the printed source uses a different definition of sunset (for example, do they define sunset, for some reason, as when the sun is at angle 0.0° instead of -0.833° ?) then you really should start there.
Next, you should determine how your printed source determines the minhag for candle-lighting time in Jerusalem. Do they really define it as 40 minutes before sunset, as is the usual custom in Jerusalem? Or, do they define it as solar depression by some number of degrees?
We are very transparent with how the Hebcal algorithm works. I hope that your printed sources can also be as transparent, as this will help you to get to the answer for why you are seeing different calculations.
Thank you for engaging. Again, I'm not saying your times are wrong or inaccurate, or that they can't be relied upon ח``ו. Nor am I claiming that you aren't transparent about your shittot. I applaud your correct and highly beneficial decision to release these libraries.
I'm saying that your times don't accord with the accepted minhag in Jerusalem.
Here's a table I prepared to illustrate the problem:
https://bennypowers.github.io/shul-tools/zmanim/yerushalayim.html
In this table, I've calculated shkiah and neitz times for 5 locations (lat/long) in jerusalem:
- the approximate northernmost municipal boundary
- the approximate easternmost municipal boundary
- the approximate southernmost municipal boundary
- the approximate westernmost municipal boundary
- the makom hamikdash
I then compared the results for shkiah from Hebcal at those 5 locations with the printed times from the לוח עיתים לבינה (by far, the most popular and comprehensive) and the לוח דינים ומנהגים published by Hechal Shlomo for the following dates from last year:
- Rosh Hashannah 1
- Hannukah 1
- Pesah 1
- Tisha B'Av
Itim Lebinah publishes a comprehensive kuntress outlining their approach.
Shkiya Times
Date | Hebcal | לוח דינים ומנהגים | לוח עיתים לבינה |
---|---|---|---|
Rosh Hashannah 9/26/2022 |
|
6:35:00 PM | 6:35:00 PM |
Hannukah 1 12/19/2022 |
|
4:43:15 PM | 4:45:00 PM |
Pesach 4/6/2023 |
|
7:06:00 PM | 7:07:00 PM |
Tisha B'Av 6/26/2023 |
|
7:45:00 PM | 7:44:15 PM |
I sincerely hope it can be demonstrated that I made a mistake in my calculations here, but it does seem to me that Hebcal's times consistently diverge by several minutes from the accepted minhagim in Jerusalem.
So what I'm asking for here isn't that you change your whole shittah or anything, what I'm asking is that for simple cases, you make an exception for the Hebcal
constructor's Jerusalem
location so that your times accord more-or-less with the accepted times; and that for advanced cases (Zmanim
constructor) you provide the necessary APIs to calculate the zmanim according to these minhagim, which probably require elevation.
Thanks for your perseverance on this.
Looking at the table you prepared, it would appear that the root cause of the problem is a different definition of skiyah. I'm glad that you were able to make this discovery. Since all calculations are derived from sunrise and sunset times, knowing that skiyah is the root cause of the discrepancy helps to get one step closer to the solution.
The sunset discrepancy seems unlikely to be caused by a different latitude/longitude definitions, as evidenced by your experiments with different locations within Jerusalem generating a difference less than one minute. It could be caused by a different definition of sunset. Regrettably, my Hebrew comprehension is very poor, so I am unable to understand the Itim Lebinah document you posted.
Or, as you suggested earlier, it could be caused by an elevation calculation difference. One excellent open source API, the ZmanimCalendar from KosherJava, has a long discussion about elevation.
In the meantime, you might consider KosherJava's ZmanimCalendar, or a paid API such as the MyZmanim API; their developers may be able to offer you better support.
If you are able to determine the root cause of the discrepancies between the published times, and have a suggested fix, we would be happy to receive a pull request.
We're so sorry we can't help further with this issue.
We wanted to let you know that we have done a bit of research on this. In fact, as you originally suggested, this may appear to be an elevation calculation difference. The ZmanimCalendar from KosherJava that we mentioned earlier supports elevation and calculates a 3-5 minute different for Jerusalem, depending on whether you utilize their sea-level sunset (e.g. 0 elevation) or sunset-with-elevation function.
No fix expected anytime soon. The NOAA solar position calculator we use in JavaScript doesn't support elevation. We'll either have to find a suitable JavaScript package or port the elevation-aware code from Java to JavaScript. There is a KosherZmanim port but it uses an outdated and gigantic Luxon date-time library so it can't be incorporated into @hebcal/core
might be worth porting over the relevant code, but replacing the luxon stuff with a Temporal polyfill
Thanks for the suggestion! We've taken a subset of the KosherZmanim code and have replaced luxon with Temporal polyfill.
Can you take a look at the 5.0.0-rc2
release of @hebcal/core
and tell us if the times line up better for you? Please note that the Zmanim
constructor has changed.
const {GeoLocation, Zmanim} = require('@hebcal/core');
const latitude = 41.822232;
const longitude = -71.448292;
const tzid = 'America/New_York';
const friday = new Date(2023, 8, 8);
const gloc = new GeoLocation(null, latitude, longitude, 0, tzid);
const zmanim = new Zmanim(gloc, friday);
const candleLighting = zmanim.sunsetOffset(-18, true);
const timeStr = Zmanim.formatISOWithTimeZone(tzid, candleLighting);
See also
new GeoLocation(name, latitude, longitude, elevation, timeZoneId)
GeoLocation constructor with parameters for all required fields.
Param | Type | Description |
---|---|---|
name | string |
The location name for display use such as "Lakewood, NJ" |
latitude | number |
the latitude in a double format such as 40.095965 for Lakewood, NJ. Note: For latitudes south of the equator, a negative value should be used. |
longitude | number |
double the longitude in a double format such as -74.222130 for Lakewood, NJ. Note: For longitudes west of the Prime Meridian (Greenwich), a negative value should be used. |
elevation | number |
the elevation above sea level in Meters. Elevation is not used in most algorithms used for calculating sunrise and set. |
timeZoneId | string |
the TimeZone for the location. |
This is so exciting! I'll be reviewing over the next few days and will get back to you. A groyse yashkoyech!
Please try 5.0.0-rc3
instead of rc2
, as this version actually uses the location's elevation in the candle-lighting times.
A few initial notes:
Engineering-wise
there are some typescript errors when loading the library, one having to do with export declare namespace
which should be export namespace
, and the other:
node_modules/@hebcal/noaa/dist/index.d.ts(1,26): error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("temporal-spec")' call instead.
Regarding that error, and even if it didn't throw, it would be better to not bundle the temporal-polyfill, but instead require the user to load a global-mutating polyfill on their own.
About the GeoLocation constructor, would it make sense to roll that stuff into the HebCalLocation class? currently there are two separate and overlapping entities for location.
Integration
I'm working a bit on the integration, and it appears that candle-lighting time offset isn't being reflected in the output. This is probably a careless mistake on my part, but it's worth checking that the candleLightingMins
option is respected. Also, being that this is a breaking change, it's an opportunity perhaps to combine the properties into one
// before
candlelighting: boolean;
candleLightingMins: number;
// after
candleLighting: boolean | number;
I suppose that's a matter of taste.
I'll have some more notes on the zmanim themselves once those are resolved.
Here's a demonstration of how the candle lighting times do not respect candleLightingMins'. I think the root cause here is the ambiguity between Location and GeoLocation, since the
calendar` constructor function doesn't take a geolocation. I could work around this by calculating my own candle times, but might as well pave this cowpath, no?
Thanks for the quick feedback. It will take a few days to make changes. One thing we are considering is to match the KosherJava behavior of requiring a boolean opt-in to use elevation to adjust sunrise/sunset times. I am not expert on the halacha and I see some significant advantage to offering a consistent API to one that was informed by much research.
We've been able to implement some of your suggestions in 5.0.0-rc4
. As we noted earlier, we have adjusted the API, similar to KosherJava, so elevation requires an explicit opt-in.
If you are using HebrewCalendar.calendar()
, you will need to specify useElevation: true
in the options.
If you are using Zmanim
, you now need to pass true
to the useElevation
argument in the constructor.
new Zmanim(gloc, date, useElevation)
Initialize a Zmanim instance.
Param | Type | Description |
---|---|---|
gloc | GeoLocation |
GeoLocation including latitude, longitude, and timezone |
date | Date | HDate |
Regular or Hebrew Date. If date is a regular Date , hours, minutes, seconds and milliseconds are ignored. |
useElevation | boolean |
use elevation for calculations (default false ). If true , use elevation to affect the calculation of all sunrise/sunset based zmanim. Note: there are some zmanim such as degree-based zmanim that are driven by the amount of light in the sky and are not impacted by elevation. These zmanim intentionally do not support elevation adjustment. |
Very nice! Looks like I'll be able to use this at shul 💪
There are some compilation errors with rc.4. it looks like the esm build of noaa is actually a cjs file. see hebcal/noaa#1 and #365
errors:
node_modules/@hebcal/core/hebcal.d.ts(822,12): error TS1038: A 'declare' modifier cannot be used in an already ambient context.
node_modules/@hebcal/noaa/dist/index.d.ts(137,49): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(186,19): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(200,27): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(208,30): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(216,33): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(225,37): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(245,18): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(258,26): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(266,28): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(275,31): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(284,35): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(297,32): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(297,80): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(313,54): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(328,53): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(463,25): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(467,24): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(484,34): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(484,76): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(503,32): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(503,74): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(503,106): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(513,66): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(662,36): error TS2503: Cannot find namespace 'Temporal'.
node_modules/@hebcal/noaa/dist/index.d.ts(676,34): error TS2503: Cannot find namespace 'Temporal'.
I did some cursory reading of the Stage 3 Temporal spec, and there are many references to the Hebrew calendar, although it seems that this is implementation dependent. So we can imagine a future where this code
date.withCalendar('hebrew').add({ months: 1 });
is supported by (hypothetical version numbers incoming) Chromium 130 and Node 25, but not by Firefox 130 or Node 24.
@hebcal/core
could significantly reduce it's code size by upstreaming the core calendar calculations into temporal-polyfill
such that they're usable with Temporal.Calendar.from
. That way, environments with partial Temporal support could still get a standards-based Hebrew calendar, and this library's bundle size would be significantly reduced for fully-capable platforms.
I hope I'm making a bit of sense here.
Version 5.0.0 released. Closing this for now.