[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