Ephemeris doesn't recognize Jewish holidays

  • Platform information:
    • Hardware: Synology DS420+
    • OS: Linux based
    • Java Runtime Environment: 11
    • openHAB version: 3.0.2

Ephemeris doesn’t seem to recognize Jewish Holidays…

I’m running OH3 (newbie, but with some good experience in programming).
I configured Ephemeris in the MainUI to Israel and the weekend days as well (no region for us).
Due to what I’ve seen in another post (I think it was Tron who reported it), I created ephemeris.cfg in /conf/services to include:

country=il
dayset-workday=[SUNDAY,MONDAY,TUESDAY,WEDNESDAY,THURSDAY]
dayset-weekend=[FRIDAY,SATURDAY]

And indeed since then the weekend days don’t bounce back after restarting OH.

I configured Holiday_il.xml in the above directory as:

<?xml version="1.0" encoding="UTF-8"?>
<tns:Configuration hierarchy="il" description="Israel"
    xmlns:tns="http://www.example.org/Holiday" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.example.org/Holiday/Holiday.xsd">
    <tns:Holidays>
        <tns:Fixed month="MAY" day="22" descriptionPropertiesKey="Test Birthday" />
	<tns:HebrewHoliday type="ROSH_HASHANAH"/>
        <tns:HebrewHoliday type="YOM_KIPPUR"/>
        <tns:HebrewHoliday type="SUKKOT"/>
        <tns:HebrewHoliday type="SHEMINI_ATZERET"/>
        <tns:HebrewHoliday type="PESACH"/>
        <tns:HebrewHoliday type="SHAVOUT"/>
    </tns:Holidays>
</tns:Configuration>

And try to run the following test rule from the scratchPad:

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.TimeOfDay");
scriptExtension.importPreset("default");
this.Ephemeris = (this.Ephemeris === undefined) ? Java.type("org.openhab.core.model.script.actions.Ephemeris") : this.Ephemeris;
this.ZonedDateTime = (this.ZonedDateTime === undefined) ? Java.type("java.time.ZonedDateTime") : this.ZonedDateTime;

logger.info("Is today a holiday? " + Ephemeris.isBankHoliday(ZonedDateTime.now().minusDays(6), '/volume1/public/openHAB/conf/services/Holiday_il.xml'));
logger.info("Is today a weekend? " + Ephemeris.isWeekend(ZonedDateTime.now()));
logger.info("Next holiday? " + Ephemeris.getNextBankHoliday('/volume1/public/openHAB/conf/services/Holiday_il.xml'));
logger.info("Rosh Hashsana in? " + Ephemeris.getDaysUntil("ROSH_HASHANA", '/volume1/public/openHAB/conf/services/Holiday_il.xml'));

I expected to see a “true” answer for the today is holiday (6 days ago was SHAVUOT), and definitely expected to see some next holiday which is not May/22 (a year from now).
Same for the number of days until ROSH_HASHANA.

What I get are 3 warning lines, which I admit - I don’t understand, and answers that seem to indicate that the “fixed” holiday is recognized but the Jewish holidays are not.

Appreciate your thoughts :slight_smile:

2021-05-23 18:20:02.779 [WARN ] [de.jollyday.util.ClassLoadingUtil   ] - Could not load class with current threads context classloader. Using default. Reason: ClassNotFoundException: de.jollyday.impl.DefaultHolidayManager cannot be found by org.apache.aries.jax.rs.whiteboard_1.0.9
2021-05-23 18:20:02.784 [WARN ] [de.jollyday.util.ClassLoadingUtil   ] - Could not load class with current threads context classloader. Using default. Reason: ClassNotFoundException: de.jollyday.datasource.impl.XmlFileDataSource cannot be found by org.apache.aries.jax.rs.whiteboard_1.0.9
2021-05-23 18:20:02.793 [WARN ] [de.jollyday.util.XMLUtil            ] - Could not create JAXB context using the current threads context classloader. Falling back to ObjectFactory class classloader.
2021-05-23 18:20:02.861 [WARN ] [de.jollyday.util.ClassLoadingUtil   ] - Could not load class with current threads context classloader. Using default. Reason: ClassNotFoundException: de.jollyday.parser.impl.FixedParser cannot be found by org.apache.aries.jax.rs.whiteboard_1.0.9
2021-05-23 18:20:02.867 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Is today a holiday? false
2021-05-23 18:20:02.883 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Is today a weekend? false
2021-05-23 18:20:02.888 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Next holiday? Test Birthday
2021-05-23 18:20:02.892 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Rosh Hashsana in? -1

Does it work if you use the offset instead of the now.minusDays?

Ephemeris.isBankHoliday(-6, 'path to config.xml')

What if you use getBankHolidayName(<offset>)?

Try bouncing around the actual day. Maybe there is a timezone or off by one issue going on.

Those definitely seem to indicate that there is a bug of some sort going on here and that an issue should be filed. This could be the root cause of the problems you are seeing with the results or they might not actually be a problem at all in which case these log statements shouldn’t be created as WARN level statements.

Ephemeris is managed in the openhab-core repo. See How to file an Issue.

Thanks Rikoshak.
Since I’m new to openHAB and this forum, I must say I’ve learned a lot from your comments throughout the forum. Your TimeOfDay DP is very helpful. So before I continue - I think a proper thank you is in order :slight_smile:

I did try playing with the amount of days as well as just put an offset - didn’t work. It also shouldn’t affect the “getDaysUntil” function result.

I also changed the holiday to EASTER_MONDAY and everything started to work, so my syntax is OK.

I then went to download the entire jollyday master branch source code from GitHub and did a grep on things like “ROSH_HASHANA”, and just “Jewish” (with -i flag on the grep), with no result. I compared it to the grep results on EASTER_MONDAY (with grep -v xml) and I could see that the jollyday parser has a Christian parser, Hindu parser, Islamic parser etc’ but no Jewish parser.
This led me to the conclusion that the Jewish calendar is not implemented in Jollyday even though the xsd contains a list of Jewish holidays.

I then found a Java implementation of the Jewish Calendar with the functions I need (isJewishHoliday, and things like that), which is a free source code, here: http://www.david-greve.de/luach-code/jewish-java.html and I’m going to try and make a js lib file from it that I will load to the rule (something like your TimeUtils.js).

As for the warnings I get - I wouldn’t know how to file a bug report on it. Only thing I can say is that it seems to origin from this line:

this.Ephemeris = (this.Ephemeris === undefined) ? Java.type("org.openhab.core.model.script.actions.Ephemeris") : this.Ephemeris;

As subsequent runs of the rule don’t generate the warnings (this.Ephemeris is already defined).

That surprises me. I would have expected it to not import any holidays if your configured region doesn’t exist.

You should have at least gotten a hit on the Holiday.xsd file: jollyday/src/main/xsd/Holiday.xsd at master · svendiedrichsen/jollyday · GitHub

<complexType name="HebrewHoliday">
	<complexContent>
		<extension base="tns:Holiday">
			<attribute name="type" type="tns:HebrewHolidayType"></attribute>
		</extension>
	</complexContent>
</complexType>

<simpleType name="HebrewHolidayType">
	<restriction base="string">
		<enumeration value="ROSH_HASHANAH"></enumeration>
		<enumeration value="ASERET_YEMEI_TESHUVA"></enumeration>
		<enumeration value="YOM_KIPPUR"></enumeration>
		<enumeration value="SUKKOT"></enumeration>
		<enumeration value="SHEMINI_ATZERET"></enumeration>
		<enumeration value="HANUKKAH"></enumeration>
		<enumeration value="ASARAH_BETEVET"></enumeration>
		<enumeration value="TU_BISHVAT"></enumeration>
		<enumeration value="PURIM"></enumeration>
		<enumeration value="1_NISAN"></enumeration>
		<enumeration value="PESACH"></enumeration>
		<enumeration value="SEFIRAH"></enumeration>
		<enumeration value="LAG_BAOMER"></enumeration>
		<enumeration value="SHAVOUT"></enumeration>
		<enumeration value="17_TAMMUZ"></enumeration>
		<enumeration value="TISHA_BAV"></enumeration>
		<enumeration value="1_ELUL"></enumeration>
		<enumeration value="ROSH_CODESH"></enumeration>
		<enumeration value="SHABBAT"></enumeration>
		<enumeration value="YOM_HASHOAH"></enumeration>
		<enumeration value="YOM_HAZIKARON"></enumeration>
		<enumeration value="YOM_HAATZAMAUT"></enumeration>
		<enumeration value="YOM_YERUSHALAIM"></enumeration>
	</restriction>
</simpleType>

[quote="lebovitz, post:3, topic:122704"]
a Christian parser, Hindu parser, Islamic parser etc’ but no Jewish parser.
[/quote]
That may or may not be meaningful. It might be that the calculations for those are embedded in one of the other parsers. Or it could mean that they are not supported at all. But at a minimum I would suggest you file an issue on that repo. If these are not supported, they probably shouldn't be in the XSD at all (until such time that they are supported). If they are, perhaps they can tell us what we are doing wrong here.

[quote="lebovitz, post:3, topic:122704"]
I wouldn’t know how to file a bug report on it
[/quote]
I did post a link in my reply with a pretty extensive tutorial on how to do that. All you'd need to include is what you are trying to do and the logs. They will ask for any additional information.

I can't do it for you as I'm not experiencing the same problem so I can't answer their questions or provide the info they need to help them solve the problem.

Israel as a country exists in Ephemeris, with no regions defined for it, so I don’t see any problem here.

As for finding it in the XSD file - I did see it there, but didn’t mention it as that was obvious (at least to me).
I doubt the Jewish calendar is implemented using any of the other calendar parsers. It’s unique in a sense that it is based both on solar and lunar years (Christian calendar is 100% solar, Islamic is 100% lunar, but Jewish is hybrid. you have 7 leap years every 19 years to gain the 11 days you miss every lunar year compared to solar year).

I’ll open a bug for Jollyday.

I managed to convert the Java classes in the above link to JavaScript and use them in openHAB.

For the benefit of others (until Jewish Calendar is supported in Ephemeris…) I’m pasting my jewishCal.js file, which can be placed in one of the directories and loaded the same way TimeUtils.js is loaded.


(function(context) {

    'use strict';
    var log = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.time_utils");
    var LocalDateTime = (context.LocalDateTime === undefined) ? Java.type("java.time.LocalDateTime") : context.LocalDateTime;


      //------------------------------------------------
      context.getWeekday = function(absDate) { return (absDate % 7); }

      var month_list = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];



      context.getLastDayOfGregorianMonth = function(month, year) {
        if ((month == 2) &&
            ((year % 4) == 0) &&
            ((year % 400) != 100) &&
            ((year % 400) != 200) &&
            ((year % 400) != 300))
          return 29;
        else
          return month_list[month-1];
      }

      context.absoluteFromGregorianDate = function(locDate) {
        var value, m;

        if(!(locDate instanceof LocalDateTime)) {
            log.error("argument to absoluteFromGregorianDate not in LocalDateTime format");
            return null;
        }
        /* Days so far this month */
        value = locDate.getDayOfMonth();

        /* Days in prior months this year */
        for (m = 1; m < locDate.getMonthValue(); m++) {
          value += getLastDayOfGregorianMonth(m, locDate.getYear());
        }
        /* Days in prior years */
        value += (365 * (locDate.getYear()-1));

       /* Julian leap days in prior years ... */
        value += parseInt((locDate.getYear()-1) / 4);

        /* ... minus prior century years ... */
        value -= parseInt((locDate.getYear()-1) / 100);

        /* ... plus prior years divisible by 400 */
        value += parseInt((locDate.getYear()-1) / 400);
        return (value);
      }

      context.gregorianDateFromAbsolute = function(absDate) {
        var approx, y, m, day, month, year, temp;

        /* Approximation */
        approx = parseInt(absDate/366);

        /* Search forward from the approximation */
        y = approx;
        for (;;) {
          temp = absoluteFromGregorianDate(LocalDateTime.of(y+1, 1, 1, 0, 0));
          if (absDate < temp) break;
          y++;
        }
        year = y;

        /* Search forward from January */
        m = 1;
        for (;;) {
          temp = absoluteFromGregorianDate(LocalDateTime.of(year, m, getLastDayOfGregorianMonth(m, year), 0, 0));
          if (absDate <= temp) break;
          m++;
        }
        month = m;

        /* Calculate the day by subtraction */
        temp = absoluteFromGregorianDate(LocalDateTime.of(year, month, 1, 0, 0));
        day = absDate-temp+1;

        return LocalDateTime.of(year, month, day, 0, 0);
      }

      context.hebrewLeapYear = function(year) {
        if ((((year*7)+1) % 19) < 7)
          return true;
        else
          return false;
      }

      context.getLastMonthOfJewishYear = function(year) {
        if (hebrewLeapYear(year))
          return 13;
        else
          return 12;
      }

      context.getLastDayOfJewishMonth = function(month, year) {
        if ((month == 2) ||
            (month == 4) ||
            (month == 6) ||
            (month == 10) ||
            (month == 13))
          return 29;
        if ((month == 12) && (!hebrewLeapYear(year)))
          return 29;
        if ((month == 8) && (!longHeshvan(year)))
          return 29;
        if ((month == 9) && (shortKislev(year)))
          return 29;
        return 30;
      }

      context.hebrewCalendarElapsedDays = function(year) {
        var value, monthsElapsed, partsElapsed, hoursElapsed;
        var day, parts, alternativeDay;

        /* Months in complete cycles so far */
        value = 235 * parseInt((year-1) / 19);
        monthsElapsed = value;

        /* Regular months in this cycle */
        value = 12 * ((year-1) % 19);
        monthsElapsed += value;

        /* Leap months this cycle */
        value = parseInt(((((year-1) % 19) * 7) + 1) / 19);
        monthsElapsed += value;

        partsElapsed = (((monthsElapsed % 1080) * 793) + 204);
        hoursElapsed = (5 +
                         (monthsElapsed * 12) +
                         (parseInt(monthsElapsed / 1080) * 793) +
                         parseInt(partsElapsed / 1080));

        /* Conjunction day */
        day = 1 + (29 * monthsElapsed) + parseInt(hoursElapsed/24);

        /* Conjunction parts */
        parts = ((hoursElapsed % 24) * 1080) +
                 (partsElapsed % 1080);

        /* If new moon is at or after midday, */
        if ((parts >= 19440) ||

        /* ...or is on a Tuesday... */
            (((day % 7) == 2) &&
        /* at 9 hours, 204 parts or later */
             (parts >= 9924)  &&
        /* of a common year */
             (!hebrewLeapYear(year))) ||

        /* ...or is on a Monday at... */
            (((day % 7) == 1) &&
        /* 15 hours, 589 parts or later... */
             (parts >= 16789) &&
        /* at the end of a leap year */
             (hebrewLeapYear(year-1))))
        /* Then postpone Rosh HaShanah one day */
          alternativeDay = day+1;
        else
          alternativeDay = day;                                 

        /* If Rosh HaShanah would occur on Sunday, Wednesday, */
        /* or Friday */
        if (((alternativeDay % 7) == 0) ||
            ((alternativeDay % 7) == 3) ||
            ((alternativeDay % 7) == 5))
        /* Then postpone it one (more) day and return */
          alternativeDay++;

        return (alternativeDay);                
      }

      context.daysInHebrewYear = function(year) {
        return (hebrewCalendarElapsedDays(year+1) -
                hebrewCalendarElapsedDays(year));
      }

      context.longHeshvan = function(year) {
        if ((daysInHebrewYear(year) % 10) == 5)
          return true;
        else
          return false;
      }

      context.shortKislev = function(year) {
        if ((daysInHebrewYear(year) % 10) == 3)
          return true;
        else
          return false;
      }

      context.absoluteFromJewishDate = function(year, month, day) {
        var value, returnValue, m;

        /* Days so far this month */
        value = day;
        returnValue = value;

        /* If before Tishri */
        if (month < 7) {
          /* Then add days in prior months this year before and */
          /* after Nisan. */
          for (m = 7; m <= getLastMonthOfJewishYear(year); m++) {
            value = getLastDayOfJewishMonth(m, year);
            returnValue += value;
          }
          for (m = 1; m < month; m++) {
            value = getLastDayOfJewishMonth(m, year);
            returnValue += value;
          }
        } else {
          for (m = 7; m < month; m++) {
            value = getLastDayOfJewishMonth(m, year);
            returnValue += value;
          }
        }

        /* Days in prior years */
        value = hebrewCalendarElapsedDays(year);
        returnValue += value;

        /* Days elapsed before absolute date 1 */
        value = 1373429;
        returnValue -= value;

        return (returnValue);
      }

      context.jewishDateFromAbsolute = function(absDate) {
        var approx, y, m, year, month, day, temp, start;
        log.info("In jewishDateFromAbsolute absDate = " + absDate);

        /* Approximation */
        approx = parseInt((absDate+1373429) / 366);

        /* Search forward from the approximation */
        y = approx;
        for (;;) {
          temp = absoluteFromJewishDate(y+1, 7, 1);
          if (absDate < temp) break;
          y++;
        }
        year = y;

        /* Starting month for search for month */
        temp = absoluteFromJewishDate(year, 1, 1);
        if (absDate < temp)
          start = 7;
        else
          start = 1;

        /* Search forward from either Tishri or Nisan */
        m = start;
        for (;;) {
          temp = absoluteFromJewishDate(year, m, getLastDayOfJewishMonth(m, year));
          if (absDate <= temp)
            break;
          m++;
        }
        month = m;

        /* Calculate the day by subtraction */
        temp = absoluteFromJewishDate(year, month, 1);
        day = absDate-temp+1;

        return [year, month, day];
      }
      //------------------------------------------------

      context.getJewishHolidayForDate = function(gdate, diaspora) {
        var absDate  = absoluteFromGregorianDate(gdate);
        var jdate    = jewishDateFromAbsolute(absDate);
        var hebYear  = jdate[0];
        var hebMonth = jdate[1];
        var hebDay   = jdate[2];

        // Holidays in Nisan
        if (hebDay == 15 && hebMonth == 1)
          return "PESACH";
        if (hebDay == 16 && hebMonth == 1 && diaspora) 
          return "PESACH";
        if (hebDay == 21 && hebMonth == 1) 
          return "PESACH_VII";
        if (hebDay == 22 && hebMonth == 1 && diaspora) 
          return "PESACH_VII";

        // Holidays in Sivan
        if (hebDay == 6 && hebMonth == 3) 
          return "SHAVUOT";
        if (hebDay == 7 && hebMonth == 3 && diaspora) 
          return "SHAVUOT";

        // Holidays in Tishri
        if (hebDay == 1 && hebMonth == 7)
          return "ROSH_HASHANA";
        if (hebDay == 2 && hebMonth == 7)
          return "ROSH_HASHANA";
        if (hebDay == 10 && hebMonth == 7)
          return "YOM_KIPPUR";
        if (hebDay == 15 && hebMonth == 7) 
            return "SUKKOT";
        if (hebDay == 16 && hebMonth == 7 && diaspora) 
            return "SUKKOT";
        if (hebDay == 22 && hebMonth == 7) 
            return "SIMCHAT_TORAH";
        if (hebDay == 23 && hebMonth == 7 && diaspora) 
            return "SIMCHAT_TORAH";

        return 0;
      }

      context.isJewishHoliday = function(gdate, diaspora) {
          return (getJewishHolidayForDate(gdate, diaspora) != 0);
      }
    })(this);

And the rule example that provides the relevant information:

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.TimeOfDay");
scriptExtension.importPreset("default");
this.Ephemeris = (this.Ephemeris === undefined) ? Java.type("org.openhab.core.model.script.actions.Ephemeris") : this.Ephemeris;
this.ZonedDateTime = (this.ZonedDateTime === undefined) ? Java.type("java.time.ZonedDateTime") : this.ZonedDateTime;
this.LocalDateTime = (this.LocalDateTime === undefined) ? Java.type("java.time.LocalDateTime") : this.LocalDateTime;



this.OPENHAB_CONF = (this.OPENHAB_CONF === undefined) ? java.lang.System.getenv("OPENHAB_CONF") : this.OPENHAB_CONF;
load(OPENHAB_CONF+'/automation/lib/javascript/community/jewishCal.js');


logger.info("Is today a holiday? " + isJewishHoliday(LocalDateTime.now().minusDays(7), false));
logger.info("Today is? " + getJewishHolidayForDate(LocalDateTime.of(2021,9,7,0,0), false));
logger.info("Is 5782 a leap year? " + hebrewLeapYear(5782));

This is the first time I’ve written anything in JS, so comments are welcome if improvements are needed.

ToDo:
Open a bug to Ephemeris
Finally update the TimeOfDay DP to support Jewish holidays

1 Like