Skip to content

Commit 3e54af7

Browse files
authored
Migrate to LocalDate, Fix some bugs (#253)
* Migrate AstronomicalCalendar to use LocalDate - Fix Timezone Bug - Use correct SUNRISE enum in getInstantFromTime * Fix Javadocs
1 parent 8b9dba0 commit 3e54af7

10 files changed

Lines changed: 184 additions & 202 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ target/
22
.idea/
33
zmanim.iml
44
.gradle
5-
build
5+
build
6+
local.properties

src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java

Lines changed: 60 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@
1919
import java.time.Duration;
2020
import java.time.Instant;
2121
import java.time.LocalDate;
22+
import java.time.LocalDateTime;
2223
import java.time.LocalTime;
2324
import java.time.ZoneOffset;
2425
import java.time.ZonedDateTime;
26+
import java.time.temporal.ChronoUnit;
27+
import java.util.TimeZone;
28+
2529
import com.kosherjava.zmanim.util.AstronomicalCalculator;
2630
import com.kosherjava.zmanim.util.GeoLocation;
2731
import com.kosherjava.zmanim.util.ZmanimFormatter;
2832

2933
/**
3034
* A Java calendar that calculates astronomical times such as {@link #getSunrise() sunrise}, {@link #getSunset()
31-
* sunset} and twilight times. This class contains a {@link #getZonedDateTime() zonedDateTime} and can therefore use the standard
35+
* sunset} and twilight times. This class contains a {@link #getLocalDate() zonedDateTime} and can therefore use the standard
3236
* Calendar functionality to change dates etc. The calculation engine used to calculate the astronomical times can be
3337
* changed to a different implementation by implementing the abstract {@link AstronomicalCalculator} and setting it with
3438
* the {@link #setAstronomicalCalculator(AstronomicalCalculator)}. A number of different calculation engine
@@ -95,7 +99,7 @@ public class AstronomicalCalendar implements Cloneable {
9599
/**
96100
* The <code>ZonedDateTime</code> encapsulated by this class to track the current date used by the class
97101
*/
98-
private ZonedDateTime zonedDateTime;
102+
private LocalDate localDate;
99103

100104
/**
101105
* the {@link GeoLocation} used for calculations.
@@ -329,7 +333,7 @@ public static Instant getTimeOffset(Instant time, long offsetMillis) {
329333
public Instant getSunriseOffsetByDegrees(double offsetZenith) {
330334
double dawn = getUTCSunrise(offsetZenith);
331335
return Double.isNaN(dawn) ? null
332-
: getInstantFromTime(dawn, SolarEvent.SUNSET);
336+
: getInstantFromTime(dawn, SolarEvent.SUNRISE);
333337
}
334338

335339
/**
@@ -371,7 +375,7 @@ public AstronomicalCalendar() {
371375
* @see #setAstronomicalCalculator(AstronomicalCalculator) for changing the calculator class.
372376
*/
373377
public AstronomicalCalendar(GeoLocation geoLocation) {
374-
setZonedDateTime(ZonedDateTime.now(geoLocation.getZoneId()));
378+
setLocalDate(LocalDate.now(geoLocation.getZoneId()));
375379
setGeoLocation(geoLocation);// duplicate call
376380
setAstronomicalCalculator(AstronomicalCalculator.getDefault());
377381
}
@@ -387,7 +391,7 @@ public AstronomicalCalendar(GeoLocation geoLocation) {
387391
* not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
388392
*/
389393
public double getUTCSunrise(double zenith) {
390-
return getAstronomicalCalculator().getUTCSunrise(getAdjustedCalendar(), getGeoLocation(), zenith, true);
394+
return getAstronomicalCalculator().getUTCSunrise(getAdjustedLocalDate(), getGeoLocation(), zenith, true);
391395
}
392396

393397
/**
@@ -405,7 +409,7 @@ public double getUTCSunrise(double zenith) {
405409
* @see AstronomicalCalendar#getUTCSeaLevelSunset
406410
*/
407411
public double getUTCSeaLevelSunrise(double zenith) {
408-
return getAstronomicalCalculator().getUTCSunrise(getAdjustedCalendar(), getGeoLocation(), zenith, false);
412+
return getAstronomicalCalculator().getUTCSunrise(getAdjustedLocalDate(), getGeoLocation(), zenith, false);
409413
}
410414

411415
/**
@@ -420,7 +424,7 @@ public double getUTCSeaLevelSunrise(double zenith) {
420424
* @see AstronomicalCalendar#getUTCSeaLevelSunset
421425
*/
422426
public double getUTCSunset(double zenith) {
423-
return getAstronomicalCalculator().getUTCSunset(getAdjustedCalendar(), getGeoLocation(), zenith, true);
427+
return getAstronomicalCalculator().getUTCSunset(getAdjustedLocalDate(), getGeoLocation(), zenith, true);
424428
}
425429

426430
/**
@@ -439,7 +443,7 @@ public double getUTCSunset(double zenith) {
439443
* @see AstronomicalCalendar#getUTCSeaLevelSunrise
440444
*/
441445
public double getUTCSeaLevelSunset(double zenith) {
442-
return getAstronomicalCalculator().getUTCSunset(getAdjustedCalendar(), getGeoLocation(), zenith, false);
446+
return getAstronomicalCalculator().getUTCSunset(getAdjustedLocalDate(), getGeoLocation(), zenith, false);
443447
}
444448

445449
/**
@@ -506,7 +510,7 @@ public long getTemporalHour(Instant startOfDay, Instant endOfDay) { //FIXME new
506510
* @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation)
507511
*/
508512
public Instant getSunTransit() {
509-
double noon = getAstronomicalCalculator().getUTCNoon(getAdjustedCalendar(), getGeoLocation());
513+
double noon = getAstronomicalCalculator().getUTCNoon(getAdjustedLocalDate(), getGeoLocation());
510514
return getInstantFromTime(noon, SolarEvent.NOON); //FIXME NEW CODE
511515
}
512516

@@ -536,7 +540,7 @@ public Instant getSunTransit() {
536540
* @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation)
537541
*/
538542
public Instant getSolarMidnight() {
539-
double noon = getAstronomicalCalculator().getUTCMidnight(getAdjustedCalendar(), getGeoLocation());
543+
double noon = getAstronomicalCalculator().getUTCMidnight(getAdjustedLocalDate(), getGeoLocation());
540544
return getInstantFromTime(noon, SolarEvent.MIDNIGHT);
541545
}
542546

@@ -589,35 +593,27 @@ protected Instant getInstantFromTime(double time, SolarEvent solarEvent) {
589593
return null;
590594
}
591595

592-
ZonedDateTime adjustedZonedDateTime = getAdjustedZonedDateTime();
593-
594-
LocalDate date = adjustedZonedDateTime
595-
.withZoneSameInstant(ZoneOffset.UTC)
596-
.toLocalDate();
596+
LocalDate date = getAdjustedLocalDate();
597597

598-
// Convert fractional hour to total seconds
599-
int totalSeconds = (int) Math.floor(time * 3600);
600-
int hours = totalSeconds / 3600;
601-
int minutes = (totalSeconds % 3600) / 60;
602-
int seconds = totalSeconds % 60;
603-
int localTimeHours = (int) getGeoLocation().getLongitude() / 15;
598+
double localTimeHours = (getGeoLocation().getLongitude() / 15) + time;
604599

605-
if (solarEvent == SolarEvent.SUNRISE && localTimeHours + hours > 18) {
600+
if (solarEvent == SolarEvent.SUNRISE && localTimeHours > 18) {
606601
date = date.minusDays(1);
607-
} else if (solarEvent == SolarEvent.SUNSET && localTimeHours + hours < 6) {
602+
} else if (solarEvent == SolarEvent.SUNSET && localTimeHours < 6) {
608603
date = date.plusDays(1);
609-
} else if (solarEvent == SolarEvent.MIDNIGHT && localTimeHours + hours < 12) {
604+
} else if (solarEvent == SolarEvent.MIDNIGHT && localTimeHours < 12) {
610605
date = date.plusDays(1);
611606
} else if (solarEvent == SolarEvent.NOON) {
612-
if (localTimeHours + hours < 0) {
607+
if (localTimeHours < 0) {
613608
date = date.plusDays(1);
614-
} else if (localTimeHours + hours > 24) {
609+
} else if (localTimeHours > 24) {
615610
date = date.minusDays(1);
616611
}
617612
}
613+
LocalDateTime dateTime = date.atStartOfDay().plusSeconds((long) (time*3600));
618614

619-
LocalTime localTime = LocalTime.of(hours, minutes, seconds);
620-
return ZonedDateTime.of(date, localTime, ZoneOffset.UTC).toInstant();
615+
// The computed time is in UTC fractional hours; anchor in UTC before converting.
616+
return ZonedDateTime.of(dateTime, ZoneOffset.UTC).toInstant();
621617
}
622618

623619
/**
@@ -669,10 +665,10 @@ public double getSunriseSolarDipFromOffset(double minutes) {
669665
* @return the degrees below the horizon after sunset that match the offset in minutes passed it as a parameter. If
670666
* the calculation can't be computed (no sunset occurs on this day) a {@link Double#NaN} will be returned.
671667
* @deprecated This method is slow and inefficient and should NEVER be used in a loop. This method should be replaced
672-
* by calls to {@link AstronomicalCalculator#getSolarElevation(Calendar, GeoLocation)}. That method will
668+
* by calls to {@link AstronomicalCalculator#getSolarElevation(ZonedDateTime, GeoLocation)}. That method will
673669
* efficiently return the the solar elevation (the sun's position in degrees below (or above) the horizon)
674670
* at the given time even in the arctic when there is no sunrise.
675-
* @see AstronomicalCalculator#getSolarElevation(Calendar, GeoLocation)
671+
* @see AstronomicalCalculator#getSolarElevation(ZonedDateTime, GeoLocation)
676672
* @see #getSunriseSolarDipFromOffset(double)
677673
*/
678674
@Deprecated(forRemoval=false)
@@ -718,39 +714,42 @@ public Instant getLocalMeanTime(double hours) {
718714
throw new IllegalArgumentException("Hours must be between 0 and 23.9999...");
719715
}
720716

721-
double rawOffset = getGeoLocation().getZoneId().getRules().getStandardOffset(getZonedDateTime().toInstant()).getTotalSeconds() * 1000;
717+
double rawOffset = getGeoLocation().getZoneId().getRules().getOffset(getMidnightLastNight().toInstant()).getTotalSeconds() * 1000;
722718
double utcTime = hours - rawOffset / (double) HOUR_MILLIS;
723719
Instant instant = getInstantFromTime(utcTime, SolarEvent.SUNRISE);
724720

725-
return getTimeOffset(instant, -getGeoLocation().getLocalMeanTimeOffset(getZonedDateTime().toInstant()));
721+
return getTimeOffset(instant, -getGeoLocation().getLocalMeanTimeOffset(getMidnightLastNight().toInstant()));
726722
}
727-
728-
/**
729-
* Adjusts the <code>ZonedDateTime</code> to deal with edge cases where the location crosses the antimeridian.
730-
*
731-
* @see GeoLocation#getAntimeridianAdjustment(Instant)
732-
* @return the adjusted Calendar
733-
*/
734-
private ZonedDateTime getAdjustedCalendar(){
735-
int offset = getGeoLocation().getAntimeridianAdjustment(getZonedDateTime().toInstant());
736-
if (offset == 0) {
737-
return getZonedDateTime();
738-
}
739-
ZonedDateTime adjustedZonedDateTime = getZonedDateTime();
740-
return adjustedZonedDateTime.plusDays(1);
741-
}
742-
723+
743724
/**
744725
* Adjusts the <code>ZonedDateTime</code> to deal with edge cases where the location crosses the antimeridian.
745726
*
746727
* @see GeoLocation#getAntimeridianAdjustment(Instant)
747728
* @return the adjusted Calendar
748729
*/
749-
private ZonedDateTime getAdjustedZonedDateTime(){
750-
ZonedDateTime adjustedZonedDateTime = getZonedDateTime();
751-
int offset = getGeoLocation().getAntimeridianAdjustment(getZonedDateTime().toInstant());
752-
return offset == 0 ? adjustedZonedDateTime : adjustedZonedDateTime.plusDays(offset);
753-
}
730+
private LocalDate getAdjustedLocalDate(){
731+
int offset = getGeoLocation().getAntimeridianAdjustment(getMidnightLastNight().toInstant());
732+
return offset == 0 ? getLocalDate() : getLocalDate().plusDays(offset);
733+
}
734+
735+
/**
736+
* Used by Molad based <em>zmanim</em> to determine if <em>zmanim</em> occur during the current day.
737+
* This is also used as the anchor for current timezone-offset calculations.
738+
* @see #getMoladBasedTime(Instant, Instant, Instant, boolean)
739+
* @return midnight at the start of the current local date in the configured timezone
740+
*/
741+
protected ZonedDateTime getMidnightLastNight() {
742+
return ZonedDateTime.of(getLocalDate(),LocalTime.MIDNIGHT,getGeoLocation().getZoneId());
743+
}
744+
745+
/**
746+
* Used by Molad based <em>zmanim</em> to determine if <em>zmanim</em> occur during the current day.
747+
* @see #getMoladBasedTime(Instant, Instant, Instant, boolean)
748+
* @return following midnight
749+
*/
750+
protected ZonedDateTime getMidnightTonight() {
751+
return ZonedDateTime.of(getLocalDate().plusDays(1),LocalTime.MIDNIGHT,getGeoLocation().getZoneId());
752+
}
754753

755754
/**
756755
* Returns an XML formatted representation of the class using the default output of the
@@ -787,7 +786,7 @@ public boolean equals(Object object) {
787786
return false;
788787
}
789788
AstronomicalCalendar aCal = (AstronomicalCalendar) object;
790-
return getZonedDateTime().equals(aCal.getZonedDateTime()) && getGeoLocation().equals(aCal.getGeoLocation())
789+
return getLocalDate().equals(aCal.getLocalDate()) && getGeoLocation().equals(aCal.getGeoLocation())
791790
&& getAstronomicalCalculator().equals(aCal.getAstronomicalCalculator());
792791
}
793792

@@ -797,7 +796,7 @@ public boolean equals(Object object) {
797796
public int hashCode() {
798797
int result = 17;
799798
result = 37 * result + getClass().hashCode(); // needed or this and subclasses will return identical hash
800-
result += 37 * result + getZonedDateTime().hashCode();
799+
result += 37 * result + getLocalDate().hashCode();
801800
result += 37 * result + getGeoLocation().hashCode();
802801
result += 37 * result + getAstronomicalCalculator().hashCode();
803802
return result;
@@ -823,7 +822,6 @@ public GeoLocation getGeoLocation() {
823822
*/
824823
public void setGeoLocation(GeoLocation geoLocation) {
825824
this.geoLocation = geoLocation;
826-
getZonedDateTime().withZoneSameInstant(getGeoLocation().getZoneId());
827825
}
828826

829827
/**
@@ -856,29 +854,24 @@ public void setAstronomicalCalculator(AstronomicalCalculator astronomicalCalcula
856854
*
857855
* @return Returns the ZonedDateTime.
858856
*/
859-
public ZonedDateTime getZonedDateTime() {
860-
return this.zonedDateTime;
857+
public LocalDate getLocalDate() {
858+
return this.localDate;
861859
}
862860

863861
/**
864862
* Sets the <code>ZonedDateTime</code> object for us in this class.
865-
* @param zonedDateTime
863+
* @param localDate
866864
* The <code>ZonedDateTime</code> to set.
867865
*/
868-
public void setZonedDateTime(ZonedDateTime zonedDateTime) {
869-
this.zonedDateTime = zonedDateTime;
870-
if (getGeoLocation() != null) {// if available set the Calendar's timezone to the GeoLocation TimeZone
871-
getZonedDateTime().withZoneSameInstant(getGeoLocation().getZoneId());
872-
}
866+
public void setLocalDate(LocalDate localDate) {
867+
this.localDate = localDate;
873868
}
874869

875870
/**
876871
* A method that creates a <a href="https://en.wikipedia.org/wiki/Object_copy#Deep_copy">deep copy</a> of the object.
877872
* <b>Note:</b> If the {@link java.util.TimeZone} in the cloned {@link com.kosherjava.zmanim.util.GeoLocation} will
878873
* be changed from the original, it is critical that
879-
* {@link com.kosherjava.zmanim.AstronomicalCalendar#getZonedDateTime()}.
880-
* {@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} be called in order for the
881-
* AstronomicalCalendar to output times in the expected offset after being cloned.
874+
* {@link com.kosherjava.zmanim.AstronomicalCalendar#getLocalDate()}.
882875
*
883876
* @see java.lang.Object#clone()
884877
*/

0 commit comments

Comments
 (0)