[frameworks/kholidays] /: Support Hebrew Calendar holidays

Allen Winter <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 26642d2e35331c2e007e4adb27d0e7c8adf4aa73 by Allen Winter.
Committed on 04/08/2026 at 10:33.
Pushed by winterz into branch 'master'.

Support Hebrew Calendar holidays

This will allow KOrganizer to show the Jewish holidays for Israel.

BUG: 383896

M  +8    -0    autotests/testholidayregion.cpp
M  +3    -0    src/CMakeLists.txt
A  +569  -0    src/hebrewconverter.cpp     [License: GPL(v2.0+)]
A  +155  -0    src/hebrewconverter.h     [License: GPL(v2.0+)]
M  +89   -3    src/parsers/qcalendarsystem.cpp
M  +0    -2    src/parsers/qcalendarsystem_p.h

https://invent.kde.org/frameworks/kholidays/-/commit/26642d2e35331c2e007e4adb27d0e7c8adf4aa73

diff --git a/autotests/testholidayregion.cpp b/autotests/testholidayregion.cpp
index e278152..2570f22 100644
--- a/autotests/testholidayregion.cpp
+++ b/autotests/testholidayregion.cpp
@@ -162,6 +162,14 @@ void HolidayRegionTest::testIsrael()
     parseRegionCalendarYear(region, 2023);
     parseRegionCalendarYear(region, 2024);
     parseRegionCalendarYear(region, 2025);
+    region.setCategories({QLatin1String("religious")});
+    auto holidays = region.rawHolidays(QDate(2026, 1, 1), QDate(2026, 12, 31));
+    QCOMPARE(holidays.size(), 12);
+    QCOMPARE(holidays.first().observedStartDate(), QDate(2026, 2, 2));
+    QCOMPARE(holidays.first().name(), QLatin1String("Tu Bishvat (Fifteenth of Shvat)"));
+    QCOMPARE(holidays.last().observedStartDate(), QDate(2026, 12, 20));
+    QCOMPARE(holidays.last().name(), QLatin1String("Tsom Asarah b-Tevet (Tenth of Tevet Fast)"));
+    QVERIFY(holidayListContains(holidays, QLatin1String("Hanukkah (Feast of Rededication)")));
 }
 
 void HolidayRegionTest::testRegions()
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d60a4c6..a8ecc76 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -17,6 +17,8 @@ target_sources(
     PRIVATE
         astroseasons.cpp
         astroseasons.h
+        hebrewconverter.cpp
+        hebrewconverter.h
         holiday.cpp
         holiday.h
         holiday_p.h
@@ -94,6 +96,7 @@ target_link_libraries(KF6Holidays PUBLIC Qt6::Core)
 ecm_generate_headers(KHolidays_CamelCase_HEADERS
   HEADER_NAMES
   AstroSeasons
+  HebrewConverter
   Holiday
   HolidayCategories
   HolidayRegion
diff --git a/src/hebrewconverter.cpp b/src/hebrewconverter.cpp
new file mode 100644
index 0000000..4ad4130
--- /dev/null
+++ b/src/hebrewconverter.cpp
@@ -0,0 +1,569 @@
+/*
+  SPDX-FileCopyrightText: 2003 Jonathan Singer <[email protected]>
+  SPDX-FileCopyrightText: 2007 Loïc Corbasson <[email protected]>
+  Calendar routines from Hebrew Calendar by Frank Yellin.
+
+  Copyright (c) 2002-2003 Hans Petter Bieker <[email protected]>
+  SPDX-FileCopyrightText: 2007, 2009, 2010 John Layt <[email protected]>
+  Calendar conversion routines based on Hdate v6, by Amos Shapir 1978 (rev. 1985, 1992)
+
+  SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "hebrewconverter.h"
+
+using namespace KHolidays;
+
+// NOLINTBEGIN(readability-isolate-declaration)
+HebrewDate::HebrewDate(const HebrewDateResult &d)
+    : mYear(d.year)
+    , mMonth(d.month)
+    , mDay(d.day)
+    , mDayOfWeek(d.day_of_week)
+    , mHebrewMonthLength(d.hebrew_month_length)
+    , mSecularMonthLength(d.secular_month_length)
+    , mOnHebrewLeapYear(d.hebrew_leap_year_p)
+    , mOnSecularLeapYear(d.secular_leap_year_p)
+    , mKvia(d.kvia)
+    , mHebrewDayNumber(d.hebrew_day_number)
+{
+}
+
+HebrewDate::~HebrewDate()
+{
+}
+
+HebrewDate HebrewDate::fromSecular(int year, int month, int day)
+{
+    HebrewDateResult result;
+    HebrewConverter::secularToHebrewConversion(year, month, day, &result);
+    return HebrewDate(result);
+}
+
+HebrewDate HebrewDate::fromHebrew(int year, int month, int day)
+{
+    HebrewDateResult result;
+    HebrewConverter::hebrewToSecularConversion(year, month, day, &result);
+    return HebrewDate(result);
+}
+
+int HebrewDate::year() const
+{
+    return mYear;
+}
+
+int HebrewDate::month() const
+{
+    return mMonth;
+}
+
+int HebrewDate::day() const
+{
+    return mDay;
+}
+
+int HebrewDate::dayOfWeek() const
+{
+    return mDayOfWeek;
+}
+
+int HebrewDate::hebrewMonthLength() const
+{
+    return mHebrewMonthLength;
+}
+
+int HebrewDate::secularMonthLength() const
+{
+    return mSecularMonthLength;
+}
+
+bool HebrewDate::isOnHebrewLeapYear() const
+{
+    return mOnHebrewLeapYear;
+}
+
+bool HebrewDate::isOnSecularLeapYear() const
+{
+    return mOnSecularLeapYear;
+}
+
+int HebrewDate::kvia() const
+{
+    return mKvia;
+}
+
+int HebrewDate::hebrewDayNumber() const
+{
+    return mHebrewDayNumber;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+long HebrewConverter::absolute_from_gregorian(int year, int month, int day)
+{
+    int xyear, day_number;
+
+    xyear = year - 1;
+    day_number = day + 31 * (month - 1);
+    if (month > 2) {
+        day_number -= (23 + (4 * month)) / 10;
+        if (gregorian_leap_year_p(year)) {
+            day_number++;
+        }
+    }
+    return day_number /* the day number within the current year */
+        + 365L * xyear /* days in prior years */
+        + (xyear / 4) /* Julian leap years */
+        + (-(xyear / 100)) /* deduct century years */
+        + (xyear / 400); /* add Gregorian leap years */
+}
+
+/* Given a Hebrew date, calculate the number of days since January 0, 0001,
+   Gregorian */
+long HebrewConverter::absolute_from_hebrew(int year, int month, int day)
+{
+    long sum = day + hebrew_elapsed_days(year) - 1373429L;
+    int i;
+
+    if (month < 7) {
+        const int months = hebrew_months_in_year(year);
+        for (i = 7; i <= months; ++i) {
+            sum += hebrew_month_length(year, i);
+        }
+        for (i = 1; i < month; ++i) {
+            sum += hebrew_month_length(year, i);
+        }
+    } else {
+        for (i = 7; i < month; ++i) {
+            sum += hebrew_month_length(year, i);
+        }
+    }
+    return sum;
+}
+
+/* Given an absolute date, calculate the gregorian date  */
+void HebrewConverter::gregorian_from_absolute(long date, int *yearp, int *monthp, int *dayp)
+{
+    int year, month, day;
+
+    for (year = date / 366; date >= absolute_from_gregorian(year + 1, 1, 1); ++year) { }
+
+    for (month = 1; (month <= 11) && (date >= absolute_from_gregorian(year, 1 + month, 1)); ++month) { }
+
+    day = 1 + date - absolute_from_gregorian(year, month, 1);
+    *yearp = year;
+    *monthp = month;
+    *dayp = day;
+}
+
+/* Given an absolute date, calculate the Hebrew date */
+void HebrewConverter::hebrew_from_absolute(long date, int *yearp, int *monthp, int *dayp)
+{
+    int year, month, day, gyear, gmonth, gday, months;
+
+    gregorian_from_absolute(date, &gyear, &gmonth, &gday);
+    year = gyear + 3760;
+    while (date >= absolute_from_hebrew(1 + year, 7, 1)) {
+        year++;
+    }
+    months = hebrew_months_in_year(year);
+    for (month = 7; date > absolute_from_hebrew(year, month, hebrew_month_length(year, month)); month = 1 + (month % months)) { }
+    day = 1 + date - absolute_from_hebrew(year, month, 1);
+    *yearp = year;
+    *monthp = month;
+    *dayp = day;
+}
+
+/* Number of months in a Hebrew year */
+int HebrewConverter::hebrew_months_in_year(int year)
+{
+    if (hebrew_leap_year_p(year)) {
+        return 13;
+    } else {
+        return 12;
+    }
+}
+
+/* Number of days in a Hebrew month */
+int HebrewConverter::hebrew_month_length(int year, int month)
+{
+    switch (month) {
+    case Tishrei:
+    case Shvat:
+    case Nissan:
+    case Sivan:
+    case Ab:
+        return 30;
+
+    case Tevet:
+    case Iyar:
+    case Tamuz:
+    case Elul:
+    case AdarII:
+        return 29;
+
+    case Cheshvan:
+        // 29 days, unless it's a long year.
+        if ((hebrew_year_length(year) % 10) == 5) {
+            return 30;
+        } else {
+            return 29;
+        }
+
+    case Kislev:
+        // 30 days, unless it's a short year.
+        if ((hebrew_year_length(year) % 10) == 3) {
+            return 29;
+        } else {
+            return 30;
+        }
+
+    case Adar:
+        // Adar (non-leap year) has 29 days. Adar I has 30 days.
+        if (hebrew_leap_year_p(year)) {
+            return 30;
+        } else {
+            return 29;
+        }
+
+    default:
+        return 0;
+    }
+}
+
+/* Number of days in a Julian or gregorian month */
+int HebrewConverter::secular_month_length(int year, int month /*, bool julianp */)
+{
+    switch (month) {
+    case January:
+    case March:
+    case May:
+    case July:
+    case August:
+    case October:
+    case December:
+        return 31;
+
+    case April:
+    case June:
+    case September:
+    case November:
+        return 30;
+
+    case February:
+        if (gregorian_leap_year_p(year)) {
+            return 29;
+        } else {
+            return 28;
+        }
+
+    default:
+        return 0;
+    }
+}
+
+/* Is it a leap year in the gregorian calendar */
+bool HebrewConverter::gregorian_leap_year_p(int year)
+{
+    if ((year % 4) != 0) {
+        return 0;
+    }
+    if ((year % 400) == 0) {
+        return 1;
+    }
+    if ((year % 100) == 0) {
+        return 0;
+    }
+    return 1;
+}
+
+/* Is it a leap year in the Jewish Calendar */
+bool HebrewConverter::hebrew_leap_year_p(int year)
+{
+    switch (year % 19) {
+    case 0:
+    case 3:
+    case 6:
+    case 8:
+    case 11:
+    case 14:
+    case 17:
+        return 1;
+    default:
+        return 0;
+    }
+}
+
+/* Return the number of days from 1 Tishrei 0001 to the beginning of the given
+   year. Since this routine gets called frequently with the same year arguments,
+   we cache the most recent values. */
+#define MEMORY 5
+long HebrewConverter::hebrew_elapsed_days(int year)
+{
+    static int saved_year[MEMORY] = {-1, -1, -1, -1, -1};
+    static long saved_value[MEMORY];
+    int i;
+
+    for (i = 0; i < MEMORY; ++i) {
+        if (year == saved_year[i]) {
+            return saved_value[i];
+        }
+    }
+    for (i = 0; i < MEMORY - 1; ++i) {
+        saved_year[i] = saved_year[1 + i];
+        saved_value[i] = saved_value[1 + i];
+    }
+    saved_year[MEMORY - 1] = year;
+    saved_value[MEMORY - 1] = hebrew_elapsed_days2(year);
+    return saved_value[MEMORY - 1];
+}
+
+/* Called by hebrew_elapsed_days to make the calculations if the result is not
+   in the cache */
+long HebrewConverter::hebrew_elapsed_days2(int year)
+{
+    const long prev_year = year - 1;
+    const long months_elapsed = 235L * (prev_year / 19) + /* months in complete cycles so far */
+        12L * (prev_year % 19) + /* regular months in this cycle */
+        (((prev_year % 19) * 7 + 1) / 19);
+    /* leap months in this cycle */
+    const long parts_elapsed = 5604 + 13753 * months_elapsed;
+    const long day = 1 + 29 * months_elapsed + parts_elapsed / 25920;
+    const long parts = parts_elapsed % 25920;
+    const int weekday = day % 7;
+    const long alt_day = ((parts >= 19440) || (weekday == 2 && (parts >= 9924) && !hebrew_leap_year_p(year))
+                          || (weekday == 1 && (parts >= 16789) && hebrew_leap_year_p(prev_year)))
+        ? day + 1
+        : day;
+
+    switch (alt_day % 7) {
+    case 0:
+    case 3:
+    case 5:
+        return 1 + alt_day;
+    default:
+        return alt_day;
+    }
+}
+
+/* Number of days in the given Hebrew year */
+int HebrewConverter::hebrew_year_length(int year)
+{
+    return hebrew_elapsed_days(1 + year) - hebrew_elapsed_days(year);
+}
+
+/* Fill in the HebrewDateResult structure based on the given secular date */
+void HebrewConverter::secularToHebrewConversion(int syear, int smonth, int sday, struct HebrewDateResult *result)
+{
+    int hyear, hmonth, hday;
+    long absolute;
+
+    absolute = absolute_from_gregorian(syear, smonth, sday);
+
+    hebrew_from_absolute(absolute, &hyear, &hmonth, &hday);
+
+    result->year = hyear;
+    result->month = hmonth;
+    result->day = hday;
+    finish_up(absolute, hyear, hmonth, syear, smonth, result);
+}
+
+/* Fill in the HebrewDateResult structure based on the given Hebrew date */
+void HebrewConverter::hebrewToSecularConversion(int hyear, int hmonth, int hday, struct HebrewDateResult *result)
+{
+    int syear, smonth, sday;
+    long absolute;
+
+    absolute = absolute_from_hebrew(hyear, hmonth, hday);
+    gregorian_from_absolute(absolute, &syear, &smonth, &sday);
+    result->year = hyear;
+    result->month = hmonth;
+    result->day = hday;
+    finish_up(absolute, hyear, hmonth, syear, smonth, result);
+}
+
+/* This is common code for filling up the HebrewDateResult structure */
+void HebrewConverter::finish_up(long absolute, int hyear, int hmonth, int syear, int smonth, struct HebrewDateResult *result)
+{
+    result->hebrew_month_length = hebrew_month_length(hyear, hmonth);
+    result->secular_month_length = secular_month_length(syear, smonth);
+    result->hebrew_leap_year_p = hebrew_leap_year_p(hyear);
+    result->secular_leap_year_p = gregorian_leap_year_p(syear);
+    result->kvia = (hebrew_year_length(hyear) % 10) - 3;
+    // absolute is -1 on 1/1/0001 Julian
+    result->day_of_week = (7 + absolute) % 7;
+    result->hebrew_day_number = absolute - absolute_from_hebrew(hyear, 7, 1) + 1;
+}
+
+/* constants, in 1/18th of minute */
+static const int HOUR = 1080;
+static const int DAY = 24 * HOUR;
+static const int WEEK = 7 * DAY;
+#define M(h, p) ((h) * HOUR + (p))
+#define MONTH (DAY + M(12, 793))
+
+static int hebrewDaysElapsed(int y)
+{
+    int m, nm, dw, s, l;
+
+    l = y * 7 + 1; // no. of leap months
+    m = y * 12 + l / 19; // total no. of months
+    l %= 19;
+    nm = m * MONTH + M(1 + 6, 779); // molad new year 3744 (16BC) + 6 hours
+    s = m * 28 + nm / DAY - 2;
+
+    nm %= WEEK;
+    dw = nm / DAY;
+    nm %= DAY;
+
+    // special cases of Molad Zaken
+    if ((l < 12 && dw == 3 && nm >= M(9 + 6, 204)) || (l < 7 && dw == 2 && nm >= M(15 + 6, 589))) {
+        s++, dw++;
+    }
+
+    /* ADU */
+    if (dw == 1 || dw == 4 || dw == 6) {
+        s++;
+    }
+    return s;
+}
+
+class h_date *HebrewConverter::hebrewToGregorian(int y, int m, int d)
+{
+    static class h_date h;
+    int s;
+
+    y -= 3744;
+    s = hebrewDaysElapsed(y);
+    d += s;
+    s = hebrewDaysElapsed(y + 1) - s; /* length of year */
+
+    if (s > 365 && m > 6) {
+        --m;
+        d += 30;
+    }
+    d += (59 * (m - 1) + 1) / 2; /* regular months */
+    /* special cases */
+    if (s % 10 > 4 && m > 2) { /* long Heshvan */
+        d++;
+    }
+    if (s % 10 < 4 && m > 3) { /* short Kislev */
+        d--;
+    }
+    // ### HPB: Broken in leap years
+    // if (s > 365 && m > 6)  /* leap year */
+    //  d += 30;
+    d -= 6002;
+
+    y = (d + 36525) * 4 / 146097 - 1;
+    d -= y / 4 * 146097 + (y % 4) * 36524;
+    y *= 100;
+
+    /* compute year */
+    s = (d + 366) * 4 / 1461 - 1;
+    d -= s / 4 * 1461 + (s % 4) * 365;
+    y += s;
+    /* compute month */
+    m = (d + 245) * 12 / 367 - 7;
+    d -= m * 367 / 12 - 30;
+    if (++m >= 12) {
+        m -= 12;
+        y++;
+    }
+    h.hd_day = d;
+    h.hd_mon = m;
+    h.hd_year = y;
+    return (&h);
+}
+
+int HebrewConverter::long_cheshvan(int year)
+{
+    QDate first, last;
+    const class h_date *gd;
+
+    gd = hebrewToGregorian(year, 1, 1);
+    first.setDate(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);
+
+    gd = hebrewToGregorian(year + 1, 1, 1);
+    last.setDate(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);
+
+    return (first.daysTo(last) % 10 == 5);
+}
+
+int HebrewConverter::short_kislev(int year)
+{
+    QDate first, last;
+    const class h_date *gd;
+
+    gd = hebrewToGregorian(year, 1, 1);
+    first.setDate(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);
+
+    gd = hebrewToGregorian(year + 1, 1, 1);
+    last.setDate(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);
+
+    return (first.daysTo(last) % 10 == 3);
+}
+
+class h_date *HebrewConverter::gregorianToHebrew(int y, int m, int d)
+{
+    static class h_date h;
+    int s;
+
+    if ((m -= 2) <= 0) {
+        m += 12;
+        y--;
+    }
+    /* no. of days, Julian calendar */
+    d += 365 * y + y / 4 + 367 * m / 12 + 5968;
+    /* Gregorian calendar */
+    d -= y / 100 - y / 400 - 2;
+    h.hd_dw = (d + 1) % 7;
+
+    /* compute the year */
+    y += 16;
+    s = hebrewDaysElapsed(y);
+    m = hebrewDaysElapsed(y + 1);
+    while (d >= m) { /* computed year was underestimated */
+        s = m;
+        y++;
+        m = hebrewDaysElapsed(y + 1);
+    }
+    d -= s;
+    s = m - s; /* size of current year */
+    y += 3744;
+
+    h.hd_flg = s % 10 - 4;
+
+    /* compute day and month */
+    if (d >= s - 236) { /* last 8 months are regular */
+        d -= s - 236;
+        m = d * 2 / 59;
+        d -= (m * 59 + 1) / 2;
+        m += 4;
+        if (s > 365 && m <= 5) { /* Adar of Meuberet */
+            m += 8;
+        }
+    } else {
+        /* first 4 months have 117-119 days */
+        s = 114 + s % 10;
+        m = d * 4 / s;
+        d -= (m * s + 3) / 4;
+    }
+
+    h.hd_day = d;
+    h.hd_mon = m;
+    h.hd_year = y;
+    return (&h);
+}
+
+class h_date *HebrewConverter::qdateToHebrew(const QDate &date)
+{
+    class h_date *sd;
+
+    sd = gregorianToHebrew(date.year(), date.month(), date.day());
+    ++sd->hd_mon;
+    ++sd->hd_day;
+
+    return sd;
+}
+// NOLINTEND(readability-isolate-declaration)
diff --git a/src/hebrewconverter.h b/src/hebrewconverter.h
new file mode 100644
index 0000000..b4d70f5
--- /dev/null
+++ b/src/hebrewconverter.h
@@ -0,0 +1,155 @@
+/*
+  SPDX-FileCopyrightText: 2003 Jonathan Singer <[email protected]>
+  SPDX-FileCopyrightText: 2007 Loïc Corbasson <[email protected]>
+  Calendar routines from Hebrew Calendar by Frank Yellin.
+
+  Copyright (c) 2002-2003 Carlos Moro <[email protected]>
+  Copyright (c) 2002-2003 Hans Petter Bieker <[email protected]>
+  SPDX-FileCopyrightText: 2007, 2009, 2010 John Layt <[email protected]>
+  Calendar conversion routines based on Hdate v6, by Amos Shapir 1978 (rev. 1985, 1992)
+
+  SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#pragma once
+
+#include "kholidays_export.h"
+
+#include <QDate>
+
+namespace KHolidays
+{
+class h_date
+{
+public:
+    int hd_day;
+    int hd_mon;
+    int hd_year;
+    int hd_dw;
+    int hd_flg;
+};
+
+struct KHOLIDAYS_EXPORT HebrewDateResult {
+    int year;
+    int month;
+    int day;
+    int day_of_week;
+
+    int hebrew_month_length, secular_month_length;
+    bool hebrew_leap_year_p, secular_leap_year_p;
+    int kvia;
+    int hebrew_day_number;
+};
+
+/**
+  This class converts dates between the Hebrew and Gregorian (secular)
+  calendars.
+
+  @author Loïc Corbasson
+ */
+class KHOLIDAYS_EXPORT HebrewDate
+{
+public:
+    explicit HebrewDate(const HebrewDateResult &);
+    ~HebrewDate();
+
+    static HebrewDate fromSecular(int year, int month, int day);
+    static HebrewDate fromHebrew(int year, int month, int day);
+
+    [[nodiscard]] int year() const;
+    [[nodiscard]] int month() const;
+    [[nodiscard]] int day() const;
+    [[nodiscard]] int dayOfWeek() const;
+
+    [[nodiscard]] int hebrewMonthLength() const;
+    [[nodiscard]] int secularMonthLength() const;
+    [[nodiscard]] bool isOnHebrewLeapYear() const;
+    [[nodiscard]] bool isOnSecularLeapYear() const;
+    [[nodiscard]] int kvia() const;
+    [[nodiscard]] int hebrewDayNumber() const;
+
+private:
+    int mYear, mMonth, mDay, mDayOfWeek;
+    int mHebrewMonthLength, mSecularMonthLength;
+    bool mOnHebrewLeapYear, mOnSecularLeapYear;
+    int mKvia, mHebrewDayNumber;
+};
+
+/**
+  This class is used internally to convert dates between the Hebrew and
+  Gregorian (secular) calendars.
+
+  Calendar routines from Hebrew Calendar by Frank Yellin.
+
+  For more information, see “The Comprehensive Hebrew Calendar” by Arthur Spier
+  and “Calendrical Calculations” by E. M. Reingold and Nachum Dershowitz,
+  or the documentation of Remind by Roaring Penguin Software Inc.
+
+  @author Jonathan Singer
+*/
+class KHOLIDAYS_EXPORT HebrewConverter
+{
+    friend class HebrewDate;
+
+public:
+    enum HebrewMonths { // NOLINT(readability-enum-initial-value)
+        Nissan = 1,
+        Iyar,
+        Sivan,
+        Tamuz,
+        Ab,
+        Elul,
+        Tishrei,
+        Cheshvan,
+        Kislev,
+        Tevet,
+        Shvat,
+        Adar,
+        AdarII,
+        AdarI = 12,
+    };
+
+    enum SecularMonths {
+        January = 1,
+        February,
+        March,
+        April,
+        May,
+        June,
+        July,
+        August,
+        September,
+        October,
+        November,
+        December,
+    };
+
+    static int short_kislev(int year);
+    static int long_cheshvan(int year);
+    static class h_date *qdateToHebrew(const QDate &date);
+    static class h_date *gregorianToHebrew(int y, int m, int d);
+    static class h_date *hebrewToGregorian(int y, int m, int d);
+
+private:
+    static bool hebrew_leap_year_p(int year);
+    static bool gregorian_leap_year_p(int year);
+
+    static long absolute_from_gregorian(int year, int month, int day);
+    static long absolute_from_hebrew(int year, int month, int day);
+
+    static void gregorian_from_absolute(long date, int *yearp, int *monthp, int *dayp);
+    static void hebrew_from_absolute(long date, int *yearp, int *monthp, int *dayp);
+
+    static int hebrew_months_in_year(int year);
+    static int hebrew_month_length(int year, int month);
+    static int secular_month_length(int year, int month);
+
+    static long hebrew_elapsed_days(int year);
+    static long hebrew_elapsed_days2(int year);
+    static int hebrew_year_length(int year);
+
+    static void finish_up(long absolute, int hyear, int hmonth, int syear, int smonth, struct HebrewDateResult *result);
+
+    static void secularToHebrewConversion(int year, int month, int day, struct HebrewDateResult *result);
+    static void hebrewToSecularConversion(int year, int month, int day, struct HebrewDateResult *result);
+};
+}
diff --git a/src/parsers/qcalendarsystem.cpp b/src/parsers/qcalendarsystem.cpp
index d60af63..f8c554c 100644
--- a/src/parsers/qcalendarsystem.cpp
+++ b/src/parsers/qcalendarsystem.cpp
@@ -1,13 +1,17 @@
 /*
-    This file is part of the kholidays library.
+    SPDX-FileCopyrightText: 2007, 2009, 2010, 2014 John Layt <[email protected]>
 
-    SPDX-FileCopyrightText: 2014 John Layt <[email protected]>
+    Hebrew Calendar code:
+    Copyright (c) 2003 Hans Petter Bieker <[email protected]>
+    Calendar conversion routines based on Hdate v6, by Amos Shapir 1978 (rev. 1985, 1992)
 
     SPDX-License-Identifier: LGPL-2.0-or-later
 */
 
-#include "kholidays_debug.h"
+// clang-format off
 #include "qcalendarsystem_p.h"
+#include "hebrewconverter.h"
+// clang-format on
 
 #include <QDate>
 #include <QSharedData>
@@ -76,6 +80,8 @@ qint64 QCalendarSystemPrivate::epoch() const
         return -284655; //  0001-01-01 == -5492-08-29 Gregorian
     case QCalendarSystem::IndianNationalCalendar:
         return 1749994; //  0000-01-01 == 0078-03-22 Gregorian
+    case QCalendarSystem::HebrewCalendar:
+        return 347998; // 0001-01-01 (Gregorian -3760-09-07, Julian -3761-10-07)
     case QCalendarSystem::IslamicCivilCalendar:
         return 1948440; //  0001-01-01 == 0622-07-19 Gregorian
     case QCalendarSystem::ISO8601Calendar:
@@ -106,6 +112,11 @@ qint64 QCalendarSystemPrivate::earliestValidDate() const
         return -284655; //  0001-01-01 == -5492-08-29 Gregorian
     case QCalendarSystem::IndianNationalCalendar:
         return 1749994; //  0000-01-01 == 0078-03-22 Gregorian
+    case QCalendarSystem::HebrewCalendar:
+        // Current formulas using direct Gregorian <-> Hebrew conversion using Qt
+        // will return invalid results prior to the Gregorian switchover in 1582
+        // Next valid Hebrew year starts 5344-01-01 (Gregorian 1583-09-17)
+        return 2299498;
     case QCalendarSystem::IslamicCivilCalendar:
         return 1948440; //  0001-01-01 == 0622-07-19 Gregorian
     case QCalendarSystem::ISO8601Calendar:
@@ -131,6 +142,8 @@ int QCalendarSystemPrivate::earliestValidYear() const
     case QCalendarSystem::JulianCalendar:
         return -4800;
     case QCalendarSystem::IndianNationalCalendar:
+    case QCalendarSystem::HebrewCalendar:
+        return 5344;
     case QCalendarSystem::ISO8601Calendar:
     case QCalendarSystem::ThaiCalendar:
         return 0;
@@ -152,6 +165,9 @@ qint64 QCalendarSystemPrivate::latestValidDate() const
         return 3367114; //  9999-13-05 ==  4506-09-29 Gregorian
     case QCalendarSystem::IndianNationalCalendar:
         return 5402054; //  9999-12-30 == 10078-03-21 Gregorian
+    case QCalendarSystem::HebrewCalendar:
+        // Testing shows current formulas only work up to 8119-13-29 (Gregorian 4359-10-07)
+        return 3313431;
     case QCalendarSystem::IslamicCivilCalendar:
         return 5491751; //  9999-12-29 == 10323-10-21 Gregorian
     case QCalendarSystem::ISO8601Calendar:
@@ -172,6 +188,8 @@ qint64 QCalendarSystemPrivate::latestValidDate() const
 int QCalendarSystemPrivate::latestValidYear() const
 {
     switch (calendarSystem()) {
+    case QCalendarSystem::HebrewCalendar:
+        return 8119;
     default:
         return 9999;
     }
@@ -195,6 +213,7 @@ int QCalendarSystemPrivate::maxMonthsInYear() const
     case QCalendarSystem::CopticCalendar:
     case QCalendarSystem::EthiopicCalendar:
     case QCalendarSystem::EthiopicAmeteAlemCalendar:
+    case QCalendarSystem::HebrewCalendar:
         return 13;
     default:
         return 12;
@@ -231,6 +250,22 @@ int QCalendarSystemPrivate::daysInYear(int year) const
     switch (calendarSystem()) {
     case QCalendarSystem::IslamicCivilCalendar:
         return isLeapYear(year) ? 355 : 354;
+    case QCalendarSystem::HebrewCalendar: {
+        int days;
+        // Get Regular year length
+        if (isLeapYear(year)) { // Has 13 months
+            days = 384;
+        } else { // Has 12 months
+            days = 354;
+        }
+        // Check if is Deficient or Abundant year
+        if (KHolidays::HebrewConverter::short_kislev(year)) { // Deficient
+            days = days - 1;
+        } else if (KHolidays::HebrewConverter::long_cheshvan(year)) { // Abundant
+            days = days + 1;
+        }
+        return days;
+    }
     default:
         return isLeapYear(year) ? 366 : 365;
     }
@@ -288,6 +323,29 @@ int QCalendarSystemPrivate::daysInMonth(int year, int month) const
             return 30;
         }
     }
+    case QCalendarSystem::HebrewCalendar: {
+        int mi = month;
+        if (isLeapYear(year)) {
+            if (month == 6) {
+                mi = 13; // Adar I
+            } else if (month == 7) {
+                mi = 14; // Adar II
+            } else if (month > 7) {
+                mi = month - 1; // Because of Adar II
+            }
+        }
+        if (mi == 2 && KHolidays::HebrewConverter::long_cheshvan(year)) {
+            return 30;
+        }
+        if (mi == 3 && KHolidays::HebrewConverter::short_kislev(year)) {
+            return 29;
+        }
+        if (mi % 2 == 0) { // Even number months have 29 days
+            return 29;
+        } else { // Odd number months have 30 days
+            return 30;
+        }
+    }
     case QCalendarSystem::IslamicCivilCalendar: {
         if (month == 12 && isLeapYear(year)) {
             return 30;
@@ -317,6 +375,8 @@ bool QCalendarSystemPrivate::hasYearZero() const
 bool QCalendarSystemPrivate::hasLeapMonths() const
 {
     switch (calendarSystem()) {
+    case QCalendarSystem::HebrewCalendar:
+        return true;
     default:
         return false;
     }
@@ -367,6 +427,8 @@ bool QCalendarSystemPrivate::isLeapYear(int year) const
         return (year % 4 == 0);
     case QCalendarSystem::IslamicCivilCalendar:
         return ((((11 * year) + 14) % 30) < 11);
+    case QCalendarSystem::HebrewCalendar:
+        return ((((7 * year) + 1) % 19) < 7);
     default:
         return false;
     }
@@ -457,6 +519,23 @@ void QCalendarSystemPrivate::julianDayToDate(qint64 jd, int *year, int *month, i
         break;
     }
 
+    case QCalendarSystem::HebrewCalendar: {
+        const KHolidays::h_date *sd = KHolidays::HebrewConverter::qdateToHebrew(QDate::fromJulianDay(jd));
+        yy = sd->hd_year;
+        mm = sd->hd_mon;
+        dd = sd->hd_day;
+        if (isLeapYear(sd->hd_year)) {
+            if (mm == 13 /*AdarI*/) {
+                mm = 6;
+            } else if (mm == 14 /*AdarII*/) {
+                mm = 7;
+            } else if (mm > 6 && mm < 13) {
+                ++mm;
+            }
+        }
+        break;
+    }
+
     default:
         break;
     }
@@ -562,6 +641,13 @@ qint64 QCalendarSystemPrivate::julianDayFromDate(int year, int month, int day) c
         break;
     }
 
+    case QCalendarSystem::HebrewCalendar: {
+        KHolidays::h_date *gd = KHolidays::HebrewConverter::hebrewToGregorian(year, month, day);
+        QDate tempDate(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);
+        jd = tempDate.toJulianDay();
+        break;
+    }
+
     default:
         break;
     }
diff --git a/src/parsers/qcalendarsystem_p.h b/src/parsers/qcalendarsystem_p.h
index e0358a8..84b7ef9 100644
--- a/src/parsers/qcalendarsystem_p.h
+++ b/src/parsers/qcalendarsystem_p.h
@@ -1,6 +1,4 @@
 /*
-    This file is part of the kholidays library.
-
     SPDX-FileCopyrightText: 2014 John Layt <[email protected]>
 
     SPDX-License-Identifier: LGPL-2.0-or-later
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.