From 7608be4c0fe5dfc1b0cbaf1fa8fd92abca7d40d1 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Fri, 23 Oct 2026 09:19:57 -0400 Subject: [PATCH] Keep every Yerushalmi daf and give the molad of Teves 3761 A Yom Kippur or Tisha B'Av on the first day of a cycle extension was not counted, so the Yerushalmi cycle ending in 2172 lost Niddah 13. isBetween now includes the start date. The molad of Teves 3761 is on 12 December 0000, which setAbsDate rejected. setAbsDate now accepts it, and setGregorianDate and minusDays keep the check. gregorianDateToAbsDate, absDateToDate and the day of the week use floor division so year 0 gets its leap day and the right weekday. --- .../zmanim/hebrewcalendar/JewishDate.java | 28 ++++++++++------- .../YerushalmiYomiCalculator.java | 5 ++-- .../UT_JewishDateNavigation.java | 13 ++++++++ .../hebrewcalendar/UT_YerushalmiTest.java | 30 +++++++++++++++++++ 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java index dd38b1b50..dece8c48a 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java @@ -309,7 +309,7 @@ private static int getLastDayOfGregorianMonth(int year, int month) { * @return the {@code LocalDate}. */ private static LocalDate absDateToDate(int absDate) { - int year = absDate / 366; // Search forward year by year from approximate year + int year = Math.floorDiv(absDate, 366); // Search forward year by year from approximate year while (absDate >= gregorianDateToAbsDate(year + 1, 1, 1)) { year++; } @@ -347,11 +347,12 @@ private static int gregorianDateToAbsDate(int year, int month, int dayOfMonth) { } int y = year - 1; + // Floor division, so that year 0, a leap year, gets its leap day return (absDate // days this year + 365 * y // days in previous years ignoring leap days - + y / 4 // Julian leap days before this year - - y / 100 // minus prior century years - + y / 400); // plus prior years divisible by 400 + + Math.floorDiv(y, 4) // Julian leap days before this year + - Math.floorDiv(y, 100) // minus prior century years + + Math.floorDiv(y, 400)); // plus prior years divisible by 400 } /** @@ -670,13 +671,11 @@ public int getDaysInJewishMonth() { } /** - * Computes and sets the Jewish date fields based on the provided absolute (Gregorian) date. + * Computes and sets the Jewish date fields based on the provided absolute (Gregorian) date. The date may be before + * January 1, 0001, as the molad of Teves 3761 is, on December 12, 0000. * @param gregorianAbsDate the Gregorian absolute date. */ private synchronized void setAbsDate(int gregorianAbsDate) { - if (gregorianAbsDate <= 0) { - throw new IllegalArgumentException("Dates in the BC era are not supported"); - } this.gregorianAbsDate = gregorianAbsDate; // Approximation from below jewishYear = (gregorianAbsDate - JEWISH_EPOCH) / 366; @@ -696,8 +695,8 @@ private synchronized void setAbsDate(int gregorianAbsDate) { // Calculate the day by subtraction jewishDay = gregorianAbsDate - jewishDateToAbsDate(jewishYear, jewishMonth, 1) + 1; - // day of week (same calculation as original) - dayOfWeek = Math.abs(gregorianAbsDate % 7) + 1; + // day of week, with floorMod so that a date before January 1, 0001 gets the right day + dayOfWeek = Math.floorMod(gregorianAbsDate, 7) + 1; } /** @@ -855,6 +854,9 @@ public void setGregorianDate(ZonedDateTime zonedDateTime) { */ public void setGregorianDate(LocalDate localDate) { int absDate = gregorianDateToAbsDate(localDate.getYear(), localDate.getMonth().getValue(), localDate.getDayOfMonth()); + if (absDate <= 0) { + throw new IllegalArgumentException("Dates in the BC era are not supported"); + } setAbsDate(absDate); // convert to Jewish date } @@ -975,7 +977,11 @@ public void minusDays(int days){ if (days < 1) { throw new IllegalArgumentException("The number of days to subtract must be greater than zero."); } - setAbsDate(getAbsDate() - days); + int absDate = getAbsDate() - days; + if (absDate <= 0) { + throw new IllegalArgumentException("Dates in the BC era are not supported"); + } + setAbsDate(absDate); } diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java index f816ef2ef..ac3c06a26 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java @@ -149,14 +149,15 @@ private static int getNumOfSpecialDays(LocalDate start, LocalDate end) { /** - * Checks if a date is after start and on or before end. + * Checks if a date is on or after start and on or before end. A cycle can start on, or be extended to start + * on, Yom Kippur or Tisha B'Av, so the start day is counted. * @param start the start ZonedDateTime * @param date the ZonedDateTime to check * @param end the end ZonedDateTime * @return if the date is between the two dates */ private static boolean isBetween(LocalDate start, LocalDate date, LocalDate end) { - return start.isBefore(date) && !end.isBefore(date); + return !date.isBefore(start) && !end.isBefore(date); } /** diff --git a/src/test/java/com/kosherjava/zmanim/hebrewcalendar/UT_JewishDateNavigation.java b/src/test/java/com/kosherjava/zmanim/hebrewcalendar/UT_JewishDateNavigation.java index aad456023..7141ec80d 100644 --- a/src/test/java/com/kosherjava/zmanim/hebrewcalendar/UT_JewishDateNavigation.java +++ b/src/test/java/com/kosherjava/zmanim/hebrewcalendar/UT_JewishDateNavigation.java @@ -60,5 +60,18 @@ public void addYearsUsesConfiguredAdarMonthWhenMovingToLeapYear() { Assert.assertEquals(10, adarBeisDate.getJewishDayOfMonth()); } + /** + * The molad of Teves 3761 is Tuesday 12 December of Gregorian year 0, which is a leap year. + */ + @Test + public void moladInGregorianYear0() { + JewishDate molad = new JewishDate(3761, JewishDate.TEVES, 20).getMolad(); + Assert.assertEquals(LocalDate.of(0, 12, 12), molad.getLocalDate()); + Assert.assertEquals(Calendar.TUESDAY, molad.getDayOfWeek()); + Assert.assertEquals(20, molad.getMoladHours()); + Assert.assertEquals(7, molad.getMoladMinutes()); + Assert.assertEquals(2, molad.getMoladChalakim()); + } + } // End of UT_JewishDateNavigation class diff --git a/src/test/java/com/kosherjava/zmanim/hebrewcalendar/UT_YerushalmiTest.java b/src/test/java/com/kosherjava/zmanim/hebrewcalendar/UT_YerushalmiTest.java index 60b9d8b7e..d605f7c49 100644 --- a/src/test/java/com/kosherjava/zmanim/hebrewcalendar/UT_YerushalmiTest.java +++ b/src/test/java/com/kosherjava/zmanim/hebrewcalendar/UT_YerushalmiTest.java @@ -1,5 +1,6 @@ package com.kosherjava.zmanim.hebrewcalendar; +import java.time.LocalDate; import java.util.Calendar; import org.junit.*; @@ -50,4 +51,33 @@ public void testCorrectSpecialDate() { System.out.println(hdf.formatDafYomiYerushalmi(jewishCalendar.getDafYomiYerushalmi())); } + /** + * The cycle ending in 2172 is extended for Tisha B'Av 5932. It still has every daf, ending with Niddah 13 on + * 7 August 2172. + */ + @Test + public void testCycleExtendedForTishaBav() { + assertDaf(LocalDate.of(2172, 8, 6), 38, 12); + assertDaf(LocalDate.of(2172, 8, 7), 38, 13); + assertDaf(LocalDate.of(2172, 8, 8), 0, 1); + } + + /** + * Tisha B'Av 6240 falls the day before the last daf of a cycle. The cycle still has every daf, ending with + * Niddah 13 on 16 August 2480, and the next one starts with Berachos 1. + */ + @Test + public void testCycleWithTishaBavBeforeItsLastDaf() { + assertDaf(LocalDate.of(2480, 8, 14), 38, 12); + Assert.assertNull(new JewishCalendar(LocalDate.of(2480, 8, 15)).getDafYomiYerushalmi()); + assertDaf(LocalDate.of(2480, 8, 16), 38, 13); + assertDaf(LocalDate.of(2480, 8, 17), 0, 1); + } + + private static void assertDaf(LocalDate date, int masechtaNumber, int daf) { + Daf dafYomi = new JewishCalendar(date).getDafYomiYerushalmi(); + Assert.assertEquals(date.toString(), masechtaNumber, dafYomi.getMasechtaNumber()); + Assert.assertEquals(date.toString(), daf, dafYomi.getDaf()); + } + }