Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java
Original file line number Diff line number Diff line change
Expand Up @@ -553,10 +553,31 @@ protected Instant getInstantFromTime(double time, SolarEvent solarEvent) {
LocalDate date = getAdjustedLocalDate();
double localTimeHours = (getGeoLocation().getLongitude() / 15) + time;

// The calculator gives only the UTC time of day, a number from 0 to 24. It doesn't say which UTC day the event
// is on, so start from the calculation date and correct it.
//
// localTimeHours is that time in local mean time, which is UTC plus the longitude's hours. It can come out below 0 or
// above 24, which means the event is really on the day before or the day after the date. Each event has a
// window of local mean time that belongs to the date, and these checks move the date by a day when the event
// falls outside it.
//
// - Sunrise belongs to the date from 18:00 the evening before through 18:00 of the date. After 18:00 it is
// really the evening before (an Arctic sunrise just after midnight, say), so go back a day. At -6:00 or
// earlier the UTC time wrapped around midnight, so go forward a day. For example, just west of the date line
// local mean time is about 12 hours behind UTC. A sunrise at noon local mean time is then 24:00 UTC, which
// arrives as 0:00 and reads as -12:00 local mean time.
// - Sunset belongs from 06:00 of the date through 06:00 the next morning. Before 06:00 go forward a day. At
// 30:00, which is 06:00 the next morning, or later the UTC time wrapped the other way, so go back a day.
// - Solar midnight is the one after the date's noon, so one before 12:00 moves forward a day.
// - Solar noon belongs between 0:00 and 24:00. Before 0:00 go forward a day, and after 24:00 go back a day.
if (solarEvent == SolarEvent.SUNRISE && localTimeHours > 18) {
date = date.minusDays(1);
} else if (solarEvent == SolarEvent.SUNRISE && localTimeHours <= -6) {
date = date.plusDays(1);
} else if (solarEvent == SolarEvent.SUNSET && localTimeHours < 6) {
date = date.plusDays(1);
} else if (solarEvent == SolarEvent.SUNSET && localTimeHours >= 30) {
date = date.minusDays(1);
} else if (solarEvent == SolarEvent.MIDNIGHT && localTimeHours < 12) {
date = date.plusDays(1);
} else if (solarEvent == SolarEvent.NOON) {
Expand Down
90 changes: 90 additions & 0 deletions src/test/java/com/kosherjava/zmanim/AstronomicalCalendarTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,23 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.function.Function;

import org.junit.Test;

import com.kosherjava.zmanim.util.AstronomicalCalculator;
import com.kosherjava.zmanim.util.GeoLocation;
import com.kosherjava.zmanim.util.MeeusCalculator;
import com.kosherjava.zmanim.util.NOAACalculator;
import com.kosherjava.zmanim.util.SPACalculator;
import com.kosherjava.zmanim.util.SunTimesCalculator;

/**
Expand Down Expand Up @@ -118,4 +128,84 @@ public void swappingCalculatorChangesResult() {

assertNotEquals(noaaSunset, sunTimesSunset);
}

/**
* Near the date line a sunrise close to local noon has a UTC time just past 24:00. It must still fall on its own
* date: Cape Schmidt, Chukotka, on 10 January 2021, where the sun is up for about ten minutes around noon.
*/
@Test
public void polarSunriseNearTheDateLineIsOnItsOwnDate() {
ZoneId anadyr = ZoneId.of("Asia/Anadyr");
LocalDate date = LocalDate.of(2021, 1, 10);
AstronomicalCalculator[] calculators = { new NOAACalculator(), new MeeusCalculator(), new SPACalculator() };
for (AstronomicalCalculator calculator : calculators) {
AstronomicalCalendar calendar = new AstronomicalCalendar(new GeoLocation("Cape Schmidt", 68.88, -179.37, anadyr));
calendar.setLocalDate(date);
calendar.setAstronomicalCalculator(calculator);
ZonedDateTime sunrise = calendar.getSeaLevelSunrise().atZone(anadyr);
ZonedDateTime sunset = calendar.getSeaLevelSunset().atZone(anadyr);
assertEquals(calculator.getCalculatorName(), date, sunrise.toLocalDate());
assertTrue(calculator.getCalculatorName(), sunrise.isBefore(sunset));
}
}

/**
* Asserts that {@code event} at the given place, in a zone a whole number of {@code zoneHours} from UTC, comes out
* within a second of {@code expectedMillis}. A result on the wrong day is 24 hours off.
*/
private void assertEventNear(String label, double latitude, double longitude, int zoneHours, LocalDate date,
Function<AstronomicalCalendar, Instant> event, long expectedMillis) {
AstronomicalCalendar calendar = new AstronomicalCalendar(
new GeoLocation(label, latitude, longitude, ZoneOffset.ofHours(zoneHours)));
calendar.setLocalDate(date);
Instant actual = event.apply(calendar);
Instant expected = Instant.ofEpochMilli(expectedMillis);
assertTrue(label + ": " + actual + " is not near " + expected,
Duration.between(expected, actual).abs().compareTo(Duration.ofSeconds(1)) < 0);
}

// One test for each date adjustment in getInstantFromTime. polarSunriseNearTheDateLineIsOnItsOwnDate covers a
// sunrise at -6 hours local mean time or earlier.

/** A sunrise just after local midnight, after 18:00 local mean time of the previous day, moves back a day. */
@Test
public void sunriseAfterSixPmLocalMeanTimeMovesBackADay() {
assertEventNear("sunrise after 18:00", -70, 35.235, 2, LocalDate.of(2021, 1, 25),
AstronomicalCalendar::getSeaLevelSunrise, 1611527037358L); // 2021-01-25T00:23:57+02:00
}

/** A sunset just before local midnight, before 06:00 local mean time of the date, moves forward a day. */
@Test
public void sunsetBeforeSixAmLocalMeanTimeMovesForwardADay() {
assertEventNear("sunset before 06:00", -70, -179.37, -12, LocalDate.of(2021, 1, 23),
AstronomicalCalendar::getSeaLevelSunset, 1611488915349L); // 2021-01-23T23:48:35-12:00
}

/** A sunset near local noon just east of the date line, at +30 hours local mean time or later, moves back a day. */
@Test
public void sunsetAfterThirtyHoursLocalMeanTimeMovesBackADay() {
assertEventNear("sunset after 30:00", 68.88, 179, 12, LocalDate.of(2021, 12, 2),
AstronomicalCalendar::getSeaLevelSunset, 1638403159563L); // 2021-12-02T11:59:19+12:00
}

/** Solar midnight before noon local mean time moves forward a day. */
@Test
public void solarMidnightBeforeNoonLocalMeanTimeMovesForwardADay() {
assertEventNear("midnight before 12:00", -70, -179.37, -12, LocalDate.of(2021, 1, 1),
AstronomicalCalendar::getSolarMidnight, 1609588897446L); // 2021-01-02T00:01:37-12:00
}

/** Solar noon before 0 hours local mean time moves forward a day. */
@Test
public void solarNoonBeforeMidnightLocalMeanTimeMovesForwardADay() {
assertEventNear("noon before 00:00", -70, -179.37, -12, LocalDate.of(2021, 1, 1),
AstronomicalCalendar::getSunTransit, 1609545683526L); // 2021-01-01T12:01:23-12:00
}

/** Solar noon after 24 hours local mean time moves back a day. */
@Test
public void solarNoonAfterTwentyFourHoursLocalMeanTimeMovesBackADay() {
assertEventNear("noon after 24:00", -70, 179, 12, LocalDate.of(2021, 9, 14),
AstronomicalCalendar::getSunTransit, 1631577581205L); // 2021-09-14T11:59:41+12:00
}
}
Loading