1919import java .time .Duration ;
2020import java .time .Instant ;
2121import java .time .LocalDate ;
22+ import java .time .LocalDateTime ;
2223import java .time .LocalTime ;
2324import java .time .ZoneOffset ;
2425import java .time .ZonedDateTime ;
26+ import java .time .temporal .ChronoUnit ;
27+ import java .util .TimeZone ;
28+
2529import com .kosherjava .zmanim .util .AstronomicalCalculator ;
2630import com .kosherjava .zmanim .util .GeoLocation ;
2731import 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