From 4cf3bd7898347726f46f91b65f09c56a1f3c84c7 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Mon, 10 Nov 2025 22:07:24 -0500 Subject: [PATCH 01/14] improve naive timezone conversion --- .../zmanim/util/NOAACalculator.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java b/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java index ec9a0453..cc477fbe 100644 --- a/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java +++ b/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java @@ -16,6 +16,7 @@ package com.kosherjava.zmanim.util; import java.util.Calendar; +import java.util.TimeZone; /** * Implementation of sunrise and sunset methods to calculate astronomical times based on the Universal Coordinated Time (UTC) From 657b60c2528e4a48f4256baf9a4b81581b3f70c6 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Tue, 11 Nov 2025 17:31:39 -0500 Subject: [PATCH 02/14] use java.time for improved accuracy for distant dates --- .../zmanim/AstronomicalCalendar.java | 27 +++++++---- .../zmanim/hebrewcalendar/JewishCalendar.java | 4 +- .../kosherjava/zmanim/util/GeoLocation.java | 38 ++++++++++----- .../zmanim/util/NOAACalculator.java | 11 +++-- .../kosherjava/zmanim/util/TimeZoneUtils.java | 48 +++++++++++++++++++ 5 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java diff --git a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java index 04f829d6..08298977 100644 --- a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java @@ -16,12 +16,15 @@ package com.kosherjava.zmanim; import java.math.BigDecimal; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import com.kosherjava.zmanim.util.AstronomicalCalculator; import com.kosherjava.zmanim.util.GeoLocation; +import com.kosherjava.zmanim.util.TimeZoneUtils; import com.kosherjava.zmanim.util.ZmanimFormatter; /** @@ -626,21 +629,26 @@ protected Date getDateFromTime(double time, SolarEvent solarEvent) { return null; } double calculatedTime = time; - + Calendar adjustedCalendar = getAdjustedCalendar(); + + // Convert Calendar to java.time for accurate date extraction, especially for distant future dates + ZoneId zoneId = adjustedCalendar.getTimeZone().toZoneId(); + ZonedDateTime adjustedZdt = adjustedCalendar.toInstant().atZone(zoneId); + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cal.clear();// clear all fields - cal.set(Calendar.YEAR, adjustedCalendar.get(Calendar.YEAR)); - cal.set(Calendar.MONTH, adjustedCalendar.get(Calendar.MONTH)); - cal.set(Calendar.DAY_OF_MONTH, adjustedCalendar.get(Calendar.DAY_OF_MONTH)); + cal.set(Calendar.YEAR, adjustedZdt.getYear()); + cal.set(Calendar.MONTH, adjustedZdt.getMonthValue() - 1); // Calendar months are 0-based + cal.set(Calendar.DAY_OF_MONTH, adjustedZdt.getDayOfMonth()); int hours = (int) calculatedTime; // retain only the hours calculatedTime -= hours; int minutes = (int) (calculatedTime *= 60); // retain only the minutes - calculatedTime -= minutes; + calculatedTime -= minutes; int seconds = (int) (calculatedTime *= 60); // retain only the seconds calculatedTime -= seconds; // remaining milliseconds - + // Check if a date transition has occurred, or is about to occur - this indicates the date of the event is // actually not the target date, but the day prior or after int localTimeHours = (int)getGeoLocation().getLongitude() / 15; @@ -758,8 +766,9 @@ public Date getLocalMeanTime(double hours) { if (hours < 0 || hours >= 24) { throw new IllegalArgumentException("Hours must between 0 and 23.9999..."); } - return getTimeOffset(getDateFromTime(hours - getGeoLocation().getTimeZone().getRawOffset() - / (double) HOUR_MILLIS, SolarEvent.SUNRISE), -getGeoLocation().getLocalMeanTimeOffset()); + long timezoneOffsetMillis = TimeZoneUtils.getTimezoneOffsetAt(getCalendar()); + return getTimeOffset(getDateFromTime(hours - timezoneOffsetMillis + / (double) HOUR_MILLIS, SolarEvent.SUNRISE), -getGeoLocation().getLocalMeanTimeOffset(calendar)); } /** @@ -769,7 +778,7 @@ public Date getLocalMeanTime(double hours) { * @return the adjusted Calendar */ private Calendar getAdjustedCalendar(){ - int offset = getGeoLocation().getAntimeridianAdjustment(); + int offset = getGeoLocation().getAntimeridianAdjustment(getCalendar()); if (offset == 0) { return getCalendar(); } diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java index acc987f2..5cb1c678 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java @@ -1249,7 +1249,7 @@ public Date getMoladAsDate() { molad.getMoladHours(), molad.getMoladMinutes(), (int) moladSeconds); cal.set(Calendar.MILLISECOND, (int) (1000 * (moladSeconds - (int) moladSeconds))); // subtract local time difference of 20.94 minutes (20 minutes and 56.496 seconds) to get to Standard time - cal.add(Calendar.MILLISECOND, -1 * (int) geo.getLocalMeanTimeOffset()); + cal.add(Calendar.MILLISECOND, -1 * (int) geo.getLocalMeanTimeOffset(cal)); return cal.getTime(); } @@ -1290,7 +1290,7 @@ public Date getTchilasZmanKidushLevana7Days() { cal.add(Calendar.HOUR, 168); // 7 days after the molad return cal.getTime(); } - + /** * Returns the latest time of Kiddush Levana according to the Maharil's opinion that it is calculated as diff --git a/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java b/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java index b2888a33..b3c53b4c 100644 --- a/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java +++ b/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java @@ -15,6 +15,7 @@ */ package com.kosherjava.zmanim.util; +import java.util.Calendar; import java.util.Objects; import java.util.TimeZone; @@ -329,15 +330,17 @@ public void setTimeZone(TimeZone timeZone) { * so a user who is 1° west of this will have noon at 4 minutes after standard time noon, and conversely, a user * who is 1° east of the 15° longitude will have noon at 11:56 AM. Lakewood, N.J., whose longitude is * -74.222, is 0.778 away from the closest multiple of 15 at -75°. This is multiplied by 4 to yield 3 minutes - * and 10 seconds earlier than standard time. The offset returned does not account for the Daylight saving time offset since this class is - * unaware of dates. + * and 10 seconds earlier than standard time. The offset returned uses the actual timezone offset at the specific + * date/time from the Calendar, accounting for Daylight + * saving time. * - * @return the offset in milliseconds not accounting for Daylight saving time. A positive value will be returned - * East of the 15° timezone line, and a negative value West of it. + * @param calendar the Calendar containing the date/time to calculate the offset for + * @return the offset in milliseconds. A positive value will be returned East of the 15° timezone line, and a + * negative value West of it. */ - public long getLocalMeanTimeOffset() { - return (long) (getLongitude() * 4 * MINUTE_MILLIS - getTimeZone().getRawOffset()); + public long getLocalMeanTimeOffset(Calendar calendar) { + long timezoneOffsetMillis = TimeZoneUtils.getTimezoneOffsetAt(calendar); + return (long) (getLongitude() * 4 * MINUTE_MILLIS - timezoneOffsetMillis); } /** @@ -355,10 +358,11 @@ public long getLocalMeanTimeOffset() { * UTC time, the local DST offset of UTC+14:00 should be applied * to bring the date back to 2018-02-03. * + * @param calendar the Calendar containing the date/time to calculate the adjustment for * @return the number of days to adjust the date This will typically be 0 unless the date crosses the antimeridian */ - public int getAntimeridianAdjustment() { - double localHoursOffset = getLocalMeanTimeOffset() / (double)HOUR_MILLIS; + public int getAntimeridianAdjustment(Calendar calendar) { + double localHoursOffset = getLocalMeanTimeOffset(calendar) / (double)HOUR_MILLIS; if (localHoursOffset >= 20){// if the offset is 20 hours or more in the future (never expected anywhere other // than a location using a timezone across the antimeridian to the east such as Samoa) @@ -565,6 +569,10 @@ public double getRhumbLineDistance(GeoLocation location) { * @return The XML formatted String. */ public String toXML() { + Calendar cal = Calendar.getInstance(getTimeZone()); + long gmtOffsetMillis = TimeZoneUtils.getTimezoneOffsetAt(cal); + long dstOffsetMillis = getTimeZone().getDSTSavings(); + return "\n" + "\t" + getLocationName() + "\n" + "\t" + getLatitude() + "\n" + @@ -572,9 +580,9 @@ public String toXML() { "\t" + getElevation() + " Meters" + "\n" + "\t" + getTimeZone().getID() + "\n" + "\t" + getTimeZone().getDisplayName() + "\n" + - "\t" + getTimeZone().getRawOffset() / HOUR_MILLIS + + "\t" + gmtOffsetMillis / HOUR_MILLIS + "\n" + - "\t" + getTimeZone().getDSTSavings() / HOUR_MILLIS + + "\t" + dstOffsetMillis / HOUR_MILLIS + "\n" + ""; } @@ -620,6 +628,10 @@ public int hashCode() { * @see java.lang.Object#toString() */ public String toString() { + Calendar cal = Calendar.getInstance(getTimeZone()); + long gmtOffsetMillis = TimeZoneUtils.getTimezoneOffsetAt(cal); + long dstOffsetMillis = getTimeZone().getDSTSavings(); + return "\nLocation Name:\t\t\t" + getLocationName() + "\nLatitude:\t\t\t" + getLatitude() + "\u00B0" + "\nLongitude:\t\t\t" + getLongitude() + "\u00B0" + @@ -627,8 +639,8 @@ public String toString() { "\nTimezone ID:\t\t\t" + getTimeZone().getID() + "\nTimezone Display Name:\t\t" + getTimeZone().getDisplayName() + " (" + getTimeZone().getDisplayName(false, TimeZone.SHORT) + ")" + - "\nTimezone GMT Offset:\t\t" + getTimeZone().getRawOffset() / HOUR_MILLIS + - "\nTimezone DST Offset:\t\t" + getTimeZone().getDSTSavings() / HOUR_MILLIS; + "\nTimezone GMT Offset:\t\t" + gmtOffsetMillis / HOUR_MILLIS + + "\nTimezone DST Offset:\t\t" + dstOffsetMillis / HOUR_MILLIS; } /** diff --git a/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java b/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java index cc477fbe..f60d29cd 100644 --- a/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java +++ b/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java @@ -15,6 +15,8 @@ */ package com.kosherjava.zmanim.util; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Calendar; import java.util.TimeZone; @@ -101,9 +103,12 @@ public double getUTCSunset(Calendar calendar, GeoLocation geoLocation, double ze * day. Fractional days / time should be added later. */ private static double getJulianDay(Calendar calendar) { - int year = calendar.get(Calendar.YEAR); - int month = calendar.get(Calendar.MONTH) + 1; - int day = calendar.get(Calendar.DAY_OF_MONTH); + // Convert Calendar to java.time for accurate date extraction, especially for distant future dates + ZoneId zoneId = calendar.getTimeZone().toZoneId(); + ZonedDateTime zdt = calendar.toInstant().atZone(zoneId); + int year = zdt.getYear(); + int month = zdt.getMonthValue(); // Already 1-12, no need to add 1 + int day = zdt.getDayOfMonth(); if (month <= 2) { year -= 1; month += 12; diff --git a/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java b/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java new file mode 100644 index 00000000..31b6a194 --- /dev/null +++ b/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java @@ -0,0 +1,48 @@ +/* + * Zmanim Java API + * Copyright (C) 2004-2025 Eliyahu Hershfeld + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA, + * or connect to: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html + */ +package com.kosherjava.zmanim.util; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.TimeZone; + +/** + * Utility class for timezone-related calculations using java.time APIs. + * + * @author © Eliyahu Hershfeld 2004 - 2025 + */ +public class TimeZoneUtils { + + /** + * Gets the timezone offset in milliseconds at a specific instant using java.time APIs. + * This method correctly handles Daylight Saving Time (DST) changes by calculating the offset + * at the specific date/time represented by the Calendar. + * + * @param calendar the Calendar containing the date/time and timezone to calculate the offset for + * @return the timezone offset in milliseconds + */ + public static long getTimezoneOffsetAt(Calendar calendar) { + TimeZone timeZone = calendar.getTimeZone(); + long unixTimestampMillis = calendar.getTimeInMillis(); + ZoneId zoneId = timeZone.toZoneId(); + Instant instant = Instant.ofEpochMilli(unixTimestampMillis); + ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId); + return zonedDateTime.getOffset().getTotalSeconds() * 1000; + } +} + From 05a983c6eba2f830178ffd886c1d5abb75024b97 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Tue, 11 Nov 2025 23:14:23 -0500 Subject: [PATCH 03/14] additional java.time conversions --- .../zmanim/AstronomicalCalendar.java | 12 +++++++---- .../kosherjava/zmanim/util/TimeZoneUtils.java | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java index 08298977..26abcba7 100644 --- a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java @@ -18,6 +18,7 @@ import java.math.BigDecimal; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.Instant; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; @@ -353,6 +354,7 @@ public Date getSunriseOffsetByDegrees(double offsetZenith) { */ public Date getSunsetOffsetByDegrees(double offsetZenith) { double sunset = getUTCSunset(offsetZenith); + // System.out.println("Jsunset: " + sunset); if (Double.isNaN(sunset)) { return null; } else { @@ -633,8 +635,11 @@ protected Date getDateFromTime(double time, SolarEvent solarEvent) { Calendar adjustedCalendar = getAdjustedCalendar(); // Convert Calendar to java.time for accurate date extraction, especially for distant future dates - ZoneId zoneId = adjustedCalendar.getTimeZone().toZoneId(); - ZonedDateTime adjustedZdt = adjustedCalendar.toInstant().atZone(zoneId); + long milliseconds = adjustedCalendar.getTimeInMillis(); + Instant instant = Instant.ofEpochMilli(milliseconds); + TimeZone timeZone = adjustedCalendar.getTimeZone(); + ZoneId zoneId = timeZone.toZoneId(); + ZonedDateTime adjustedZdt = instant.atZone(zoneId); Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cal.clear();// clear all fields @@ -782,8 +787,7 @@ private Calendar getAdjustedCalendar(){ if (offset == 0) { return getCalendar(); } - Calendar adjustedCalendar = (Calendar) getCalendar().clone(); - adjustedCalendar.add(Calendar.DAY_OF_MONTH, offset); + Calendar adjustedCalendar = TimeZoneUtils.addDay(getCalendar()); return adjustedCalendar; } diff --git a/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java b/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java index 31b6a194..f206bae6 100644 --- a/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java +++ b/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java @@ -44,5 +44,26 @@ public static long getTimezoneOffsetAt(Calendar calendar) { ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId); return zonedDateTime.getOffset().getTotalSeconds() * 1000; } + + /** + * Adds one day to the given Calendar by converting it to a ZonedDateTime, + * adding a day, and converting it back to a Calendar. + * The returned Calendar will have the same TimeZone as the input Calendar. + * + * @param calendar the Calendar to add a day to + * @return a new Calendar with one day added, preserving the original TimeZone + */ + public static Calendar addDay(Calendar calendar) { + TimeZone timeZone = calendar.getTimeZone(); + long unixTimestampMillis = calendar.getTimeInMillis(); + ZoneId zoneId = timeZone.toZoneId(); + Instant instant = Instant.ofEpochMilli(unixTimestampMillis); + ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId); + ZonedDateTime nextDay = zonedDateTime.plusDays(1); + Instant nextDayInstant = nextDay.toInstant(); + Calendar result = Calendar.getInstance(timeZone); + result.setTimeInMillis(nextDayInstant.toEpochMilli()); + return result; + } } From 81e56cd62735593aa0148094e87efacfd8293e16 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Thu, 4 Dec 2025 18:42:26 -0500 Subject: [PATCH 04/14] validate length of hebrew months --- .../zmanim/hebrewcalendar/JewishDate.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java index eb47d309..dccf0bed 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java @@ -567,8 +567,8 @@ private static int getJewishMonthOfYear(int year, int month) { * @param month * the Jewish month to validate. It will reject a month < 1 or > 12 (or 13 on a leap year) . * @param dayOfMonth - * the day of the Jewish month to validate. It will reject any value < 1 or > 30 TODO: check calling - * methods to see if there is any reason that the class can validate that 30 is invalid for some months. + * the day of the Jewish month to validate. It will reject any value < 1 or > the number of days in the month + * for that year. * @param hours * the hours (for molad calculations). It will reject an hour < 0 or > 23 * @param minutes @@ -590,9 +590,12 @@ private static void validateJewishDate(int year, int month, int dayOfMonth, int throw new IllegalArgumentException("The Jewish month has to be between 1 and 12 (or 13 on a leap year). " + month + " is invalid for the year " + year + "."); } - if (dayOfMonth < 1 || dayOfMonth > 30) { - throw new IllegalArgumentException("The Jewish day of month can't be < 1 or > 30. " + dayOfMonth - + " is invalid."); + + int maxDaysInMonth = getDaysInJewishMonth(month, year); + + if (dayOfMonth < 1 || dayOfMonth > maxDaysInMonth) { + throw new IllegalArgumentException("The Jewish day of month can't be < 1 or > " + maxDaysInMonth + ". " + dayOfMonth + + " is invalid for the month " + month + " of the year " + year + "."); } // reject dates prior to 18 Teves, 3761 (1/1/1 AD). This restriction can be relaxed if the date coding is // changed/corrected From f15204e1206460af9eaea058fef85a22ef174fe5 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Thu, 4 Dec 2025 20:07:49 -0500 Subject: [PATCH 05/14] apply offset correctly --- src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java | 2 +- src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java index 26abcba7..358619ae 100644 --- a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java @@ -787,7 +787,7 @@ private Calendar getAdjustedCalendar(){ if (offset == 0) { return getCalendar(); } - Calendar adjustedCalendar = TimeZoneUtils.addDay(getCalendar()); + Calendar adjustedCalendar = TimeZoneUtils.addDay(getCalendar(), offset); return adjustedCalendar; } diff --git a/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java b/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java index f206bae6..82fa787a 100644 --- a/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java +++ b/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java @@ -53,13 +53,13 @@ public static long getTimezoneOffsetAt(Calendar calendar) { * @param calendar the Calendar to add a day to * @return a new Calendar with one day added, preserving the original TimeZone */ - public static Calendar addDay(Calendar calendar) { + public static Calendar addDay(Calendar calendar, int days) { TimeZone timeZone = calendar.getTimeZone(); long unixTimestampMillis = calendar.getTimeInMillis(); ZoneId zoneId = timeZone.toZoneId(); Instant instant = Instant.ofEpochMilli(unixTimestampMillis); ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId); - ZonedDateTime nextDay = zonedDateTime.plusDays(1); + ZonedDateTime nextDay = zonedDateTime.plusDays(days); Instant nextDayInstant = nextDay.toInstant(); Calendar result = Calendar.getInstance(timeZone); result.setTimeInMillis(nextDayInstant.toEpochMilli()); From 931f23929b9f7839501cea545f131bb1df8b01b2 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Sun, 7 Dec 2025 00:32:55 -0500 Subject: [PATCH 06/14] fix null error in getMoladBasedTime --- .../zmanim/ComplexZmanimCalendar.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/ComplexZmanimCalendar.java b/src/main/java/com/kosherjava/zmanim/ComplexZmanimCalendar.java index 4a0e2961..b7f17377 100644 --- a/src/main/java/com/kosherjava/zmanim/ComplexZmanimCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/ComplexZmanimCalendar.java @@ -3486,18 +3486,22 @@ public Date getSofZmanKidushLevanaBetweenMoldos(Date alos, Date tzais) { */ private Date getMoladBasedTime(Date moladBasedTime, Date alos, Date tzais, boolean techila) { Date lastMidnight = getMidnightLastNight(); - Date midnightTonight = getMidnightTonight(); - if (!(moladBasedTime.before(lastMidnight) || moladBasedTime.after(midnightTonight))){ - if (alos != null || tzais != null) { - if (techila && !(moladBasedTime.before(tzais) || moladBasedTime.after(alos))){ + Date midnightTonight = getMidnightTonight(); + if(moladBasedTime.before(lastMidnight) || moladBasedTime.after(midnightTonight)){ // Invalid time, bailout + return null; + } else if (alos == null || tzais == null){ // Not enough info to adjust + return moladBasedTime; + } else { // It's the daytime, get the next/prev night + if (moladBasedTime.after(alos) && moladBasedTime.before(tzais)) { + if (techila) { return tzais; } else { return alos; } - } - return moladBasedTime; - } - return null; + } else { // It's the night, the provided time is valid + return moladBasedTime; + } + } } /** From 9b74e47b61176b4adf8733c7bc7c6be0c9f68859 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Sun, 7 Dec 2025 09:21:59 -0500 Subject: [PATCH 07/14] use UTC in yerushalmi calc --- .../kosherjava/zmanim/hebrewcalendar/JewishDate.java | 1 + .../hebrewcalendar/YerushalmiYomiCalculator.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java index dccf0bed..db7361e2 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java @@ -1183,6 +1183,7 @@ public void setJewishDate(int year, int month, int dayOfMonth, int hours, int mi */ public Calendar getGregorianCalendar() { Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); calendar.set(getGregorianYear(), getGregorianMonth(), getGregorianDayOfMonth()); return calendar; } diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java index a5603e9b..41941c88 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java @@ -17,6 +17,7 @@ import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.TimeZone; /** @@ -31,7 +32,12 @@ public class YerushalmiYomiCalculator { /** * The start date of the first Daf Yomi Yerushalmi cycle of February 2, 1980 / 15 Shevat, 5740. */ - private final static Calendar DAF_YOMI_START_DAY = new GregorianCalendar(1980, Calendar.FEBRUARY, 2); + private final static Calendar DAF_YOMI_START_DAY; + static { + DAF_YOMI_START_DAY = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + DAF_YOMI_START_DAY.set(1980, Calendar.FEBRUARY, 2, 0, 0, 0); + DAF_YOMI_START_DAY.set(Calendar.MILLISECOND, 0); + } /** The number of milliseconds in a day. */ private final static int DAY_MILIS = 1000 * 60 * 60 * 24; /** The number of pages in the Talmud Yerushalmi.*/ @@ -64,8 +70,8 @@ public YerushalmiYomiCalculator() { */ public static Daf getDafYomiYerushalmi(JewishCalendar calendar) { - Calendar nextCycle = new GregorianCalendar(); - Calendar prevCycle = new GregorianCalendar(); + Calendar nextCycle = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + Calendar prevCycle = new GregorianCalendar(TimeZone.getTimeZone("UTC")); Calendar requested = calendar.getGregorianCalendar(); int masechta = 0; Daf dafYomi = null; From e4dc75550eb9bc524910c69e36d40e1bb8b2f8e9 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Sun, 7 Dec 2025 09:39:10 -0500 Subject: [PATCH 08/14] fix missing import --- .../java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java index db7361e2..7cd8a92e 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java @@ -20,6 +20,7 @@ import java.util.Date; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.TimeZone; /** * The JewishDate is the base calendar class, that supports maintenance of a {@link java.util.GregorianCalendar} From 0179cab599da53179bc8cbd13f0cbe36ead45f62 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Sun, 7 Dec 2025 14:56:55 -0500 Subject: [PATCH 09/14] some more yerushalmi changes --- .../kosherjava/zmanim/AstronomicalCalendar.java | 8 ++++---- .../zmanim/hebrewcalendar/JewishDate.java | 4 ++++ .../hebrewcalendar/YerushalmiYomiCalculator.java | 14 +++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java index 358619ae..f891f35f 100644 --- a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java @@ -658,13 +658,13 @@ protected Date getDateFromTime(double time, SolarEvent solarEvent) { // actually not the target date, but the day prior or after int localTimeHours = (int)getGeoLocation().getLongitude() / 15; if (solarEvent == SolarEvent.SUNRISE && localTimeHours + hours > 18) { - cal.add(Calendar.DAY_OF_MONTH, -1); + cal = TimeZoneUtils.addDay(cal, -1); } else if (solarEvent == SolarEvent.SUNSET && localTimeHours + hours < 6) { - cal.add(Calendar.DAY_OF_MONTH, 1); + cal = TimeZoneUtils.addDay(cal, 1); } else if (solarEvent == SolarEvent.MIDNIGHT && localTimeHours + hours < 12) { - cal.add(Calendar.DAY_OF_MONTH, 1); + cal = TimeZoneUtils.addDay(cal, 1); } else if (solarEvent == SolarEvent.NOON && localTimeHours + hours > 24) { - cal.add(Calendar.DAY_OF_MONTH, -1); + cal = TimeZoneUtils.addDay(cal, -1); } cal.set(Calendar.HOUR_OF_DAY, hours); diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java index 7cd8a92e..b617065c 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java @@ -1186,6 +1186,10 @@ public Calendar getGregorianCalendar() { Calendar calendar = Calendar.getInstance(); calendar.setTimeZone(TimeZone.getTimeZone("UTC")); calendar.set(getGregorianYear(), getGregorianMonth(), getGregorianDayOfMonth()); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); return calendar; } diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java index 41941c88..f85d01f1 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java @@ -19,6 +19,7 @@ import java.util.GregorianCalendar; import java.util.TimeZone; +import com.kosherjava.zmanim.util.TimeZoneUtils; /** * This class calculates the Talmud Yerusalmi Date: Sun, 7 Dec 2025 15:00:10 -0500 Subject: [PATCH 10/14] Fix formatting and spacing in JewishCalendar.java --- .../com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java index 5cb1c678..bc612758 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java @@ -1290,7 +1290,7 @@ public Date getTchilasZmanKidushLevana7Days() { cal.add(Calendar.HOUR, 168); // 7 days after the molad return cal.getTime(); } - + /** * Returns the latest time of Kiddush Levana according to the Maharil's opinion that it is calculated as From c944303966f10e7520fef986fa6f35d877b42de1 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Sun, 7 Dec 2025 19:44:08 -0500 Subject: [PATCH 11/14] fix yomi for transition date --- .../hebrewcalendar/YerushalmiYomiCalculator.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java index f85d01f1..d330476e 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java @@ -89,13 +89,19 @@ public static Daf getDafYomiYerushalmi(JewishCalendar calendar) { } // Start to calculate current cycle. init the start day + prevCycle.setTime(DAF_YOMI_START_DAY.getTime()); nextCycle.setTime(DAF_YOMI_START_DAY.getTime()); + // Move the nextCycle to the last day of the current cycle + nextCycle = TimeZoneUtils.addDay(nextCycle, WHOLE_SHAS_DAFS - 1); + nextCycle = TimeZoneUtils.addDay(nextCycle, getNumOfSpecialDays(prevCycle, nextCycle)); // Go cycle by cycle, until we get the next cycle while (requested.after(nextCycle)) { + // Move the prevCycle from the 1st day of the current cycle to the 1st day of the next cycle prevCycle.setTime(nextCycle.getTime()); - - // Adds the number of whole shas dafs. and the number of days that not have daf. + prevCycle = TimeZoneUtils.addDay(prevCycle, 1); + + // Move the nextCycle from the last day of the current cycle to the last day of the next cycle nextCycle = TimeZoneUtils.addDay(nextCycle, WHOLE_SHAS_DAFS); nextCycle = TimeZoneUtils.addDay(nextCycle, getNumOfSpecialDays(prevCycle, nextCycle)); } @@ -116,6 +122,7 @@ public static Daf getDafYomiYerushalmi(JewishCalendar calendar) { total -= i; masechta++; } + return dafYomi; } From 0f8f92bb18253cc428eb5520ac8d5cf8c847f144 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Tue, 9 Dec 2025 20:54:36 -0500 Subject: [PATCH 12/14] check for valid dates with forwarding dates --- .../zmanim/hebrewcalendar/JewishDate.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java index b617065c..71b68ec2 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java @@ -1301,28 +1301,36 @@ public void forward(int field, int amount) { } /** - * Forward the Jewish date by the number of months passed in. - * FIXME: Deal with forwarding a date such as 30 Nissan by a month. 30 Iyar does not exist. This should be dealt with similar to - * the way that the Java Calendar behaves (not that simple since there is a difference between add() or roll(). + * Advances the Jewish date forward by the specified number of months. + * If the day doesn't exist in the target month (e.g., 30 Iyar), it adjusts to the last day of that month (29 Iyar). * * @throws IllegalArgumentException if the amount is less than 1 - * @param amount the number of months to roll the month forward + * @param amount the number of months to advance (must be at least 1) */ private void forwardJewishMonth(int amount) { if (amount < 1) { throw new IllegalArgumentException("the amount of months to forward has to be greater than zero."); } + int currentMonth = getJewishMonth(); + int currentYear = getJewishYear(); + int currentDay = getJewishDayOfMonth(); for (int i = 0; i < amount; i++) { - if (getJewishMonth() == ELUL) { - setJewishMonth(TISHREI); - setJewishYear(getJewishYear() + 1); - } else if ((! isJewishLeapYear() && getJewishMonth() == ADAR) - || (isJewishLeapYear() && getJewishMonth() == ADAR_II)){ - setJewishMonth(NISSAN); + boolean isLeapYear = JewishDate.isJewishLeapYear(currentYear); + if (currentMonth == ELUL) { + currentMonth = TISHREI; + currentYear = currentYear + 1; + } else if ((!isLeapYear && currentMonth == ADAR) + || (isLeapYear && currentMonth == ADAR_II)){ + currentMonth = NISSAN; } else { - setJewishMonth(getJewishMonth() + 1); + currentMonth = currentMonth + 1; } } + int maxDaysInMonth = JewishDate.getDaysInJewishMonth(currentMonth, currentYear); + if (currentDay > maxDaysInMonth) { + currentDay = maxDaysInMonth; + } + setJewishDate(currentYear, currentMonth, currentDay); } /** From 85130ca4b27ce8baaadcabe8cfcec217c2652fc4 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Thu, 11 Dec 2025 17:12:59 -0500 Subject: [PATCH 13/14] fix issue with daf yomi calculation on start date --- .../zmanim/hebrewcalendar/YomiCalculator.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YomiCalculator.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YomiCalculator.java index 90ba0b8c..c17ed396 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YomiCalculator.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YomiCalculator.java @@ -17,6 +17,7 @@ import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.TimeZone; /** * This class calculates the Daf Yomi Bavli page (daf) for a given date. To calculate Daf Yomi Yerushalmi @@ -30,19 +31,27 @@ public class YomiCalculator { /** * The start date of the first Daf Yomi Bavli cycle of September 11, 1923 / Rosh Hashana 5684. */ - private static final Calendar dafYomiStartDay = new GregorianCalendar(1923, Calendar.SEPTEMBER, 11); + private static final Calendar DAF_YOMI_START_DAY; + static { + DAF_YOMI_START_DAY = new GregorianCalendar(1923, Calendar.SEPTEMBER, 11,0,0,0); + DAF_YOMI_START_DAY.setTimeZone(TimeZone.getTimeZone("UTC")); + } /** The start date of the first Daf Yomi Bavli cycle in the Julian calendar. Used internally for calculations.*/ - private static final int dafYomiJulianStartDay = getJulianDay(dafYomiStartDay); + private static final int DAF_YOMI_JULIAN_START_DAY = getJulianDay(DAF_YOMI_START_DAY); /** * The date that the pagination for the Daf Yomi Maseches Shekalim changed to use the commonly used Vilna * Shas pagination from the no longer commonly available Zhitomir / Slavuta Shas used by Rabbi Meir Shapiro. */ - private static final Calendar shekalimChangeDay = new GregorianCalendar(1975, Calendar.JUNE, 24); + private static final Calendar SHEKALIM_CHANGE_DAY; + static { + SHEKALIM_CHANGE_DAY = new GregorianCalendar(1975, Calendar.JUNE, 24,0,0,0); + SHEKALIM_CHANGE_DAY.setTimeZone(TimeZone.getTimeZone("UTC")); + } /** The Julian date that the cycle for Shekalim changed. * @see #getDafYomiBavli(JewishCalendar) for details. */ - private static final int shekalimJulianChangeDay = getJulianDay(shekalimChangeDay); + private static final int SHEKALIM_JULIAN_CHANGE_DAY = getJulianDay(SHEKALIM_CHANGE_DAY); /** * Default constructor. @@ -89,17 +98,17 @@ public static Daf getDafYomiBavli(JewishCalendar jewishCalendar) { int julianDay = getJulianDay(calendar); int cycleNo; int dafNo; - if (calendar.before(dafYomiStartDay)) { + if (calendar.before(DAF_YOMI_START_DAY)) { // TODO: should we return a null or throw an IllegalArgumentException? throw new IllegalArgumentException(calendar + " is prior to organized Daf Yomi Bavli cycles that started on " - + dafYomiStartDay); + + DAF_YOMI_START_DAY); } - if (calendar.equals(shekalimChangeDay) || calendar.after(shekalimChangeDay)) { - cycleNo = 8 + ((julianDay - shekalimJulianChangeDay) / 2711); - dafNo = ((julianDay - shekalimJulianChangeDay) % 2711); + if (calendar.equals(SHEKALIM_CHANGE_DAY) || calendar.after(SHEKALIM_CHANGE_DAY)) { + cycleNo = 8 + ((julianDay - SHEKALIM_JULIAN_CHANGE_DAY) / 2711); + dafNo = ((julianDay - SHEKALIM_JULIAN_CHANGE_DAY) % 2711); } else { - cycleNo = 1 + ((julianDay - dafYomiJulianStartDay) / 2702); - dafNo = ((julianDay - dafYomiJulianStartDay) % 2702); + cycleNo = 1 + ((julianDay - DAF_YOMI_JULIAN_START_DAY) / 2702); + dafNo = ((julianDay - DAF_YOMI_JULIAN_START_DAY) % 2702); } int total = 0; From 499f36d99460d2eda5b98f540fe27b6187b123ac Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Sun, 14 Dec 2025 13:23:37 -0500 Subject: [PATCH 14/14] Add ICU --- pom.xml | 23 +++++++ .../zmanim/AstronomicalCalendar.java | 40 ++++------- .../zmanim/ComplexZmanimCalendar.java | 4 +- .../com/kosherjava/zmanim/ZmanimCalendar.java | 2 +- .../zmanim/hebrewcalendar/JewishCalendar.java | 8 +-- .../zmanim/hebrewcalendar/JewishDate.java | 24 +++---- .../zmanim/hebrewcalendar/TefilaRules.java | 2 +- .../YerushalmiYomiCalculator.java | 17 +++-- .../zmanim/hebrewcalendar/YomiCalculator.java | 6 +- .../zmanim/hebrewcalendar/package-info.java | 2 +- .../com/kosherjava/zmanim/package-info.java | 2 +- .../zmanim/util/AstronomicalCalculator.java | 2 +- .../kosherjava/zmanim/util/GeoLocation.java | 16 ++--- .../zmanim/util/NOAACalculator.java | 15 ++-- .../zmanim/util/SunTimesCalculator.java | 2 +- .../java/com/kosherjava/zmanim/util/Time.java | 2 +- .../kosherjava/zmanim/util/TimeZoneUtils.java | 69 ------------------- .../java/com/kosherjava/zmanim/util/Zman.java | 2 +- .../zmanim/util/ZmanimFormatter.java | 10 +-- .../RegressionTestFileWriter.java | 8 ++- .../UT_DaysInGregorianMonth.java | 2 +- .../UT_GregorianDateNavigation.java | 2 +- .../UT_JewishDateNavigation.java | 2 +- .../hebrewcalendar/UT_YerushalmiTest.java | 2 +- 24 files changed, 104 insertions(+), 160 deletions(-) delete mode 100644 src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java diff --git a/pom.xml b/pom.xml index 5cbb98de..56dacce6 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,11 @@ 4.13.1 test + + com.ibm.icu + icu4j + 78.1 + @@ -111,6 +116,24 @@ 8 + + org.apache.maven.plugins + maven-dependency-plugin + 3.7.0 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/dependency + compile + + + + diff --git a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java index f891f35f..2ba67378 100644 --- a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java @@ -16,16 +16,12 @@ package com.kosherjava.zmanim; import java.math.BigDecimal; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.Instant; -import java.util.Calendar; +import com.ibm.icu.util.Calendar; import java.util.Date; -import java.util.TimeZone; +import com.ibm.icu.util.TimeZone; import com.kosherjava.zmanim.util.AstronomicalCalculator; import com.kosherjava.zmanim.util.GeoLocation; -import com.kosherjava.zmanim.util.TimeZoneUtils; import com.kosherjava.zmanim.util.ZmanimFormatter; /** @@ -53,7 +49,7 @@ * double longitude = -74.2094; // Lakewood, NJ * double elevation = 20; // optional elevation correction in Meters * // the String parameter in getTimeZone() has to be a valid timezone listed in - * // {@link java.util.TimeZone#getAvailableIDs()} + * // {@link com.ibm.icu.util.TimeZone#getAvailableIDs()} * TimeZone timeZone = TimeZone.getTimeZone("America/New_York"); * GeoLocation location = new GeoLocation(locationName, latitude, longitude, elevation, timeZone); * AstronomicalCalendar ac = new AstronomicalCalendar(location); @@ -634,18 +630,11 @@ protected Date getDateFromTime(double time, SolarEvent solarEvent) { Calendar adjustedCalendar = getAdjustedCalendar(); - // Convert Calendar to java.time for accurate date extraction, especially for distant future dates - long milliseconds = adjustedCalendar.getTimeInMillis(); - Instant instant = Instant.ofEpochMilli(milliseconds); - TimeZone timeZone = adjustedCalendar.getTimeZone(); - ZoneId zoneId = timeZone.toZoneId(); - ZonedDateTime adjustedZdt = instant.atZone(zoneId); - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cal.clear();// clear all fields - cal.set(Calendar.YEAR, adjustedZdt.getYear()); - cal.set(Calendar.MONTH, adjustedZdt.getMonthValue() - 1); // Calendar months are 0-based - cal.set(Calendar.DAY_OF_MONTH, adjustedZdt.getDayOfMonth()); + cal.set(Calendar.YEAR, adjustedCalendar.get(Calendar.YEAR)); + cal.set(Calendar.MONTH, adjustedCalendar.get(Calendar.MONTH)); + cal.set(Calendar.DAY_OF_MONTH, adjustedCalendar.get(Calendar.DAY_OF_MONTH)); int hours = (int) calculatedTime; // retain only the hours calculatedTime -= hours; @@ -658,13 +647,13 @@ protected Date getDateFromTime(double time, SolarEvent solarEvent) { // actually not the target date, but the day prior or after int localTimeHours = (int)getGeoLocation().getLongitude() / 15; if (solarEvent == SolarEvent.SUNRISE && localTimeHours + hours > 18) { - cal = TimeZoneUtils.addDay(cal, -1); + cal.add(Calendar.DAY_OF_MONTH, -1); } else if (solarEvent == SolarEvent.SUNSET && localTimeHours + hours < 6) { - cal = TimeZoneUtils.addDay(cal, 1); + cal.add(Calendar.DAY_OF_MONTH, 1); } else if (solarEvent == SolarEvent.MIDNIGHT && localTimeHours + hours < 12) { - cal = TimeZoneUtils.addDay(cal, 1); + cal.add(Calendar.DAY_OF_MONTH, 1); } else if (solarEvent == SolarEvent.NOON && localTimeHours + hours > 24) { - cal = TimeZoneUtils.addDay(cal, -1); + cal.add(Calendar.DAY_OF_MONTH, -1); } cal.set(Calendar.HOUR_OF_DAY, hours); @@ -771,7 +760,7 @@ public Date getLocalMeanTime(double hours) { if (hours < 0 || hours >= 24) { throw new IllegalArgumentException("Hours must between 0 and 23.9999..."); } - long timezoneOffsetMillis = TimeZoneUtils.getTimezoneOffsetAt(getCalendar()); + long timezoneOffsetMillis = getCalendar().getTimeZone().getOffset(getCalendar().getTimeInMillis()); return getTimeOffset(getDateFromTime(hours - timezoneOffsetMillis / (double) HOUR_MILLIS, SolarEvent.SUNRISE), -getGeoLocation().getLocalMeanTimeOffset(calendar)); } @@ -787,7 +776,8 @@ private Calendar getAdjustedCalendar(){ if (offset == 0) { return getCalendar(); } - Calendar adjustedCalendar = TimeZoneUtils.addDay(getCalendar(), offset); + Calendar adjustedCalendar = getCalendar().clone(); + adjustedCalendar.add(Calendar.DAY_OF_MONTH, offset); return adjustedCalendar; } @@ -913,10 +903,10 @@ public void setCalendar(Calendar calendar) { /** * A method that creates a deep copy of the object. - * Note: If the {@link java.util.TimeZone} in the cloned {@link com.kosherjava.zmanim.util.GeoLocation} will + * Note: If the {@link com.ibm.icu.util.TimeZone} in the cloned {@link com.kosherjava.zmanim.util.GeoLocation} will * be changed from the original, it is critical that * {@link com.kosherjava.zmanim.AstronomicalCalendar#getCalendar()}. - * {@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} be called in order for the + * {@link com.ibm.icu.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} be called in order for the * AstronomicalCalendar to output times in the expected offset after being cloned. * * @see java.lang.Object#clone() diff --git a/src/main/java/com/kosherjava/zmanim/ComplexZmanimCalendar.java b/src/main/java/com/kosherjava/zmanim/ComplexZmanimCalendar.java index b7f17377..a42f7362 100644 --- a/src/main/java/com/kosherjava/zmanim/ComplexZmanimCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/ComplexZmanimCalendar.java @@ -15,7 +15,7 @@ */ package com.kosherjava.zmanim; -import java.util.Calendar; +import com.ibm.icu.util.Calendar; import java.util.Date; import com.kosherjava.zmanim.util.AstronomicalCalculator; import com.kosherjava.zmanim.util.GeoLocation; @@ -40,7 +40,7 @@ * double longitude = -74.222; // Lakewood, NJ * double elevation = 20; // optional elevation correction in Meters * // the String parameter in getTimeZone() has to be a valid time zone listed in - * // {@link java.util.TimeZone#getAvailableIDs()} + * // {@link com.ibm.icu.util.TimeZone#getAvailableIDs()} * TimeZone timeZone = TimeZone.getTimeZone("America/New_York"); * GeoLocation location = new GeoLocation(locationName, latitude, longitude, elevation, timeZone); * ComplexZmanimCalendar czc = new ComplexZmanimCalendar(location); diff --git a/src/main/java/com/kosherjava/zmanim/ZmanimCalendar.java b/src/main/java/com/kosherjava/zmanim/ZmanimCalendar.java index 74b79495..209d2dbd 100644 --- a/src/main/java/com/kosherjava/zmanim/ZmanimCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/ZmanimCalendar.java @@ -15,7 +15,7 @@ */ package com.kosherjava.zmanim; -import java.util.Calendar; +import com.ibm.icu.util.Calendar; import java.util.Date; import com.kosherjava.zmanim.hebrewcalendar.JewishCalendar; diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java index bc612758..044c1ac6 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java @@ -20,9 +20,9 @@ import com.kosherjava.zmanim.util.GeoLocation; import java.time.LocalDate; -import java.util.Calendar; +import com.ibm.icu.util.Calendar; import java.util.Date; -import java.util.TimeZone; +import com.ibm.icu.util.TimeZone; /** * The JewishCalendar extends the JewishDate class and adds calendar methods. @@ -39,7 +39,7 @@ * * * @see java.util.Date - * @see java.util.Calendar + * @see com.ibm.icu.util.Calendar * @author © Y. Paritcher 2019 - 2022 * @author © Avrom Finkelstien 2002 * @author © Eliyahu Hershfeld 2011 - 2024 @@ -264,7 +264,7 @@ public JewishCalendar(Date date) { } /** - * A constructor that initializes the date to the {@link java.util.Calendar Calendar} parameter. + * A constructor that initializes the date to the {@link com.ibm.icu.util.Calendar Calendar} parameter. * * @param calendar * the Calendar to set the calendar to diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java index 71b68ec2..80e1f7d5 100644 --- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java +++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java @@ -18,12 +18,12 @@ import java.time.LocalDate; import java.util.Date; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.TimeZone; +import com.ibm.icu.util.Calendar; +import com.ibm.icu.util.GregorianCalendar; +import com.ibm.icu.util.TimeZone; /** - * The JewishDate is the base calendar class, that supports maintenance of a {@link java.util.GregorianCalendar} + * The JewishDate is the base calendar class, that supports maintenance of a {@link com.ibm.icu.util.GregorianCalendar} * instance along with the corresponding Jewish date. This class can use the standard Java Date and Calendar * classes for setting and maintaining the dates, but it does not subclass these classes or use them internally * in any calculations. This class also does not have a concept of a time (which the Date class does). Please @@ -51,7 +51,7 @@ * @see JewishCalendar * @see HebrewDateFormatter * @see java.util.Date - * @see java.util.Calendar + * @see com.ibm.icu.util.Calendar * @author © Avrom Finkelstien 2002 * @author © Eliyahu Hershfeld 2011 - 2024 */ @@ -990,7 +990,7 @@ public JewishDate(Date date) { } /** - * A constructor that initializes the date to the {@link java.util.Calendar Calendar} parameter. + * A constructor that initializes the date to the {@link com.ibm.icu.util.Calendar Calendar} parameter. * * @param calendar * the Calendar to set the calendar to @@ -1014,7 +1014,7 @@ public JewishDate(LocalDate localDate) { } /** - * Sets the date based on a {@link java.util.Calendar Calendar} object. Modifies the Jewish date as well. + * Sets the date based on a {@link com.ibm.icu.util.Calendar Calendar} object. Modifies the Jewish date as well. * * @param calendar * the Calendar to set the calendar to @@ -1178,9 +1178,9 @@ public void setJewishDate(int year, int month, int dayOfMonth, int hours, int mi } /** - * Returns this object's date as a {@link java.util.Calendar} object. + * Returns this object's date as a {@link com.ibm.icu.util.Calendar} object. * - * @return The {@link java.util.Calendar} + * @return The {@link com.ibm.icu.util.Calendar} */ public Calendar getGregorianCalendar() { Calendar calendar = Calendar.getInstance(); @@ -1228,7 +1228,7 @@ public String toString() { * *
 	 * 
-	 * 	Calendar cal = jewishDate.getTime(); // get a java.util.Calendar representation of the JewishDate
+	 * 	Calendar cal = jewishDate.getTime(); // get a com.ibm.icu.util.Calendar representation of the JewishDate
 	 * 	cal.add(Calendar.MONTH, 3); // add 3 Gregorian months
 	 * 	jewishDate.setDate(cal); // set the updated calendar back to this class
 	 * 
@@ -1341,7 +1341,7 @@ private void forwardJewishMonth(int amount) {
 	 * 
 	 * 
 	 * 
-	 * 	Calendar cal = jewishDate.getTime(); // get a java.util.Calendar representation of the JewishDate
+	 * 	Calendar cal = jewishDate.getTime(); // get a com.ibm.icu.util.Calendar representation of the JewishDate
 	 * 	cal.add(Calendar.MONTH, -3); // subtract 3 Gregorian months
 	 * 	jewishDate.setDate(cal); // set the updated calendar back to this class
 	 * 
@@ -1415,7 +1415,7 @@ public int compareTo(JewishDate jewishDate) {
 	/**
 	 * Returns the Gregorian month (between 0-11).
 	 * 
-	 * @return the Gregorian month (between 0-11). Like the java.util.Calendar, months are 0 based.
+	 * @return the Gregorian month (between 0-11). Like the com.ibm.icu.util.Calendar, months are 0 based.
 	 */
 	public int getGregorianMonth() {
 		return gregorianMonth - 1;
diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/TefilaRules.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/TefilaRules.java
index 7e1c3b2b..8a1a7fd7 100644
--- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/TefilaRules.java
+++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/TefilaRules.java
@@ -16,7 +16,7 @@
  */
 package com.kosherjava.zmanim.hebrewcalendar;
 
-import java.util.Calendar;
+import com.ibm.icu.util.Calendar;
 
 /**
  * Tefila Rules is a utility class that covers the various halachos and minhagim regarding
diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java
index d330476e..00682520 100644
--- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java
+++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java
@@ -15,11 +15,10 @@
  */
 package com.kosherjava.zmanim.hebrewcalendar;
 
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.TimeZone;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.GregorianCalendar;
+import com.ibm.icu.util.TimeZone;
 
-import com.kosherjava.zmanim.util.TimeZoneUtils;
 
 /**
  * This class calculates the Talmud Yerusalmi Jewish Date/Calendar,
- * and allows conversion between {@link com.kosherjava.zmanim.hebrewcalendar.JewishDate Jewish} and {@link java.util.GregorianCalendar Gregorian dates}. The main calendar
+ * and allows conversion between {@link com.kosherjava.zmanim.hebrewcalendar.JewishDate Jewish} and {@link com.ibm.icu.util.GregorianCalendar Gregorian dates}. The main calendar
  * classes {@link com.kosherjava.zmanim.hebrewcalendar.JewishCalendar} and {@link com.kosherjava.zmanim.hebrewcalendar.JewishDate}
  * are based on Avrom Finkelstien's code,
  * refactored to fit the Zmanim API. The parsha and season-based tefila change code was ported by Y. Paritcher from his
diff --git a/src/main/java/com/kosherjava/zmanim/package-info.java b/src/main/java/com/kosherjava/zmanim/package-info.java
index a559e64f..82294242 100644
--- a/src/main/java/com/kosherjava/zmanim/package-info.java
+++ b/src/main/java/com/kosherjava/zmanim/package-info.java
@@ -1,7 +1,7 @@
 /**
  * The KosherJava Zmanim library is an API for a specialized calendar that can calculate different
  * astronomical times including sunrise and sunset and Jewish zmanim or religious
- * times for prayers and other Jewish religious duties. These classes extend the {@link java.util.GregorianCalendar} and can therefore use the
+ * times for prayers and other Jewish religious duties. These classes extend the {@link com.ibm.icu.util.GregorianCalendar} and can therefore use the
  * standard Calendar functionality to change dates etc. For non-religious astronomical / solar calculations such as sunrise, sunset and twilight, use the {@link com.kosherjava.zmanim.AstronomicalCalendar}. The {@link com.kosherjava.zmanim.ZmanimCalendar} contains the most
diff --git a/src/main/java/com/kosherjava/zmanim/util/AstronomicalCalculator.java b/src/main/java/com/kosherjava/zmanim/util/AstronomicalCalculator.java
index b4d5fdf4..5603f1c8 100644
--- a/src/main/java/com/kosherjava/zmanim/util/AstronomicalCalculator.java
+++ b/src/main/java/com/kosherjava/zmanim/util/AstronomicalCalculator.java
@@ -15,7 +15,7 @@
  */
 package com.kosherjava.zmanim.util;
 
-import java.util.Calendar;
+import com.ibm.icu.util.Calendar;
 
 /**
  * An abstract class that all sun time calculating classes extend. This allows the algorithm used to be changed at
diff --git a/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java b/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java
index b3c53b4c..aae07586 100644
--- a/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java
+++ b/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java
@@ -15,9 +15,9 @@
  */
 package com.kosherjava.zmanim.util;
 
-import java.util.Calendar;
+import com.ibm.icu.util.Calendar;
 import java.util.Objects;
-import java.util.TimeZone;
+import com.ibm.icu.util.TimeZone;
 
 /**
  * A class that contains location information such as latitude and longitude required for astronomical calculations. The
@@ -310,7 +310,7 @@ public TimeZone getTimeZone() {
 	 * Method to set the TimeZone. If this is ever set after the GeoLocation is set in the
 	 * {@link com.kosherjava.zmanim.AstronomicalCalendar}, it is critical that
 	 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getCalendar()}.
-	 * {@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} be called in order for the
+	 * {@link com.ibm.icu.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} be called in order for the
 	 * AstronomicalCalendar to output times in the expected offset. This situation will arise if the
 	 * AstronomicalCalendar is ever {@link com.kosherjava.zmanim.AstronomicalCalendar#clone() cloned}.
 	 * 
@@ -339,7 +339,7 @@ public void setTimeZone(TimeZone timeZone) {
 	 *         negative value West of it.
 	 */
 	public long getLocalMeanTimeOffset(Calendar calendar) {
-		long timezoneOffsetMillis = TimeZoneUtils.getTimezoneOffsetAt(calendar);
+		long timezoneOffsetMillis = calendar.getTimeZone().getOffset(calendar.getTimeInMillis());
 		return (long) (getLongitude() * 4 * MINUTE_MILLIS - timezoneOffsetMillis);
 	}
 	
@@ -570,7 +570,7 @@ public double getRhumbLineDistance(GeoLocation location) {
 	 */
 	public String toXML() {
 		Calendar cal = Calendar.getInstance(getTimeZone());
-		long gmtOffsetMillis = TimeZoneUtils.getTimezoneOffsetAt(cal);
+		long gmtOffsetMillis = cal.getTimeZone().getOffset(cal.getTimeInMillis());
 		long dstOffsetMillis = getTimeZone().getDSTSavings();
 		
 		return "\n" +
@@ -629,7 +629,7 @@ public int hashCode() {
 	 */
 	public String toString() {
 		Calendar cal = Calendar.getInstance(getTimeZone());
-		long gmtOffsetMillis = TimeZoneUtils.getTimezoneOffsetAt(cal);
+		long gmtOffsetMillis = cal.getTimeZone().getOffset(cal.getTimeInMillis());
 		long dstOffsetMillis = getTimeZone().getDSTSavings();
 		
 		return "\nLocation Name:\t\t\t" + getLocationName() +
@@ -646,9 +646,9 @@ public String toString() {
 	/**
 	 * An implementation of the {@link java.lang.Object#clone()} method that creates a deep copy of the object.
-	 * Note: If the {@link java.util.TimeZone} in the clone will be changed from the original, it is critical
+	 * Note: If the {@link com.ibm.icu.util.TimeZone} in the clone will be changed from the original, it is critical
 	 * that {@link com.kosherjava.zmanim.AstronomicalCalendar#getCalendar()}.
-	 * {@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} is called after cloning in order for the
+	 * {@link com.ibm.icu.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} is called after cloning in order for the
 	 * AstronomicalCalendar to output times in the expected offset.
 	 * 
 	 * @see java.lang.Object#clone()
diff --git a/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java b/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java
index f60d29cd..964d749f 100644
--- a/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java
+++ b/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java
@@ -15,10 +15,8 @@
  */
 package com.kosherjava.zmanim.util;
 
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.Calendar;
-import java.util.TimeZone;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.TimeZone;
 
 /**
  * Implementation of sunrise and sunset methods to calculate astronomical times based on the