hebcal/hebcal-es6

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 when options.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 when options.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

  1. My candle lighting calculations are based on 35 minutes from my published shkiya times
  2. 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

https://bennypowers.github.io/shul-tools/

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
North
6:30:52 PM
East
6:30:37 PM
South
6:30:51 PM
West
6:31:12 PM
Mikdash
6:30:48 PM
6:35:00 PM 6:35:00 PM
Hannukah 1 12/19/2022
North
4:38:11 PM
East
4:38:13 PM
South
4:38:33 PM
West
4:38:49 PM
Mikdash
4:38:21 PM
4:43:15 PM 4:45:00 PM
Pesach 4/6/2023
North
7:01:49 PM
East
7:01:29 PM
South
7:01:41 PM
West
7:02:03 PM
Mikdash
7:01:40 PM
7:06:00 PM 7:07:00 PM
Tisha B'Av 6/26/2023
North
7:48:45 PM
East
7:48:12 PM
South
7:48:19 PM
West
7:48:45 PM
Mikdash
7:48:25 PM
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?

image

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 💪

image


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.