diff --git a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java
index 04f829d6..f891f35f 100644
--- a/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java
+++ b/src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java
@@ -16,12 +16,16 @@
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 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;
/**
@@ -350,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 {
@@ -626,32 +631,40 @@ 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
+ 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, 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;
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);
@@ -758,8 +771,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,12 +783,11 @@ 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();
}
- Calendar adjustedCalendar = (Calendar) getCalendar().clone();
- adjustedCalendar.add(Calendar.DAY_OF_MONTH, offset);
+ Calendar adjustedCalendar = TimeZoneUtils.addDay(getCalendar(), offset);
return adjustedCalendar;
}
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;
+ }
+ }
}
/**
diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishCalendar.java
index acc987f2..bc612758 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();
}
diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/JewishDate.java
index eb47d309..71b68ec2 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}
@@ -567,8 +568,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 +591,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
@@ -1180,7 +1184,12 @@ 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());
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
return calendar;
}
@@ -1292,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);
}
/**
diff --git a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java
index a5603e9b..d330476e 100644
--- a/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java
+++ b/src/main/java/com/kosherjava/zmanim/hebrewcalendar/YerushalmiYomiCalculator.java
@@ -17,7 +17,9 @@
import java.util.Calendar;
import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import com.kosherjava.zmanim.util.TimeZoneUtils;
/**
* This class calculates the Talmud Yerusalmi 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;
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 ec9a0453..f60d29cd 100644
--- a/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java
+++ b/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java
@@ -15,7 +15,10 @@
*/
package com.kosherjava.zmanim.util;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
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)
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..82fa787a
--- /dev/null
+++ b/src/main/java/com/kosherjava/zmanim/util/TimeZoneUtils.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+ }
+
+ /**
+ * 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, 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(days);
+ Instant nextDayInstant = nextDay.toInstant();
+ Calendar result = Calendar.getInstance(timeZone);
+ result.setTimeInMillis(nextDayInstant.toEpochMilli());
+ return result;
+ }
+}
+