From f291ca3633e82dd8342776b14f711a23ee65199f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Mon, 27 Nov 2017 22:36:20 +0100 Subject: [PATCH] Noon and nadir is always computed for the next 24 hours only --- .../shredzone/commons/suncalc/MoonTimes.java | 38 +++++--- .../shredzone/commons/suncalc/SunTimes.java | 90 +++++++++++++------ src/site/markdown/migration.md | 5 ++ .../commons/suncalc/MoonTimesTest.java | 4 +- .../commons/suncalc/SunTimesTest.java | 25 +++--- 5 files changed, 111 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/shredzone/commons/suncalc/MoonTimes.java b/src/main/java/org/shredzone/commons/suncalc/MoonTimes.java index 7f27cbf..10358fb 100644 --- a/src/main/java/org/shredzone/commons/suncalc/MoonTimes.java +++ b/src/main/java/org/shredzone/commons/suncalc/MoonTimes.java @@ -33,12 +33,14 @@ public final class MoonTimes { private final Date rise; private final Date set; - private final double ye; + private final boolean alwaysUp; + private final boolean alwaysDown; - private MoonTimes(Date rise, Date set, double ye) { + private MoonTimes(Date rise, Date set, boolean alwaysUp, boolean alwaysDown) { this.rise = rise; this.set = set; - this.ye = ye; + this.alwaysUp = alwaysUp; + this.alwaysDown = alwaysDown; } /** @@ -104,6 +106,8 @@ public MoonTimes execute() { Double rise = null; Double set = null; double ye = 0.0; + boolean alwaysUp = false; + boolean alwaysDown = false; double y_minus = correctedMoonHeight(jd); @@ -134,6 +138,11 @@ public MoonTimes execute() { } } + if (hour == 23 && rise == null && set == null) { + alwaysUp = ye >= 0.0; + alwaysDown = ye < 0.0; + } + if (rise != null && set != null) { break; } @@ -144,7 +153,8 @@ public MoonTimes execute() { return new MoonTimes( rise != null ? jd.atHour(rise).getDate() : null, set != null ? jd.atHour(set).getDate() : null, - ye); + alwaysUp, + alwaysDown); } /** @@ -177,19 +187,23 @@ public Date getSet() { } /** - * {@code true} if the moon never rises/sets, but is always above the horizon that - * day. Always returns {@code false} if {@link Parameters#fullCycle()} is used. + * {@code true} if the moon never rises/sets, but is always above the horizon within + * the next 24 hours. + *

+ * Note that {@link Parameters#fullCycle()} does not affect this result. */ public boolean isAlwaysUp() { - return rise == null && set == null && ye > 0.0; + return alwaysUp; } /** - * {@code true} if the moon never rises/sets, but is always below the horizon that - * day. Always returns {@code false} if {@link Parameters#fullCycle()} is used. + * {@code true} if the moon never rises/sets, but is always below the horizon within + * the next 24 hours. + *

+ * Note that {@link Parameters#fullCycle()} does not affect this result. */ public boolean isAlwaysDown() { - return rise == null && set == null && ye <= 0.0; + return alwaysDown; } @Override @@ -197,8 +211,8 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("MoonTimes[rise=").append(rise); sb.append(", set=").append(set); - sb.append(", alwaysUp=").append(isAlwaysUp()); - sb.append(", alwaysDown=").append(isAlwaysDown()); + sb.append(", alwaysUp=").append(alwaysUp); + sb.append(", alwaysDown=").append(alwaysDown); sb.append(']'); return sb.toString(); } diff --git a/src/main/java/org/shredzone/commons/suncalc/SunTimes.java b/src/main/java/org/shredzone/commons/suncalc/SunTimes.java index 5cba533..0ccad2d 100644 --- a/src/main/java/org/shredzone/commons/suncalc/SunTimes.java +++ b/src/main/java/org/shredzone/commons/suncalc/SunTimes.java @@ -36,12 +36,17 @@ public class SunTimes { private final Date set; private final Date noon; private final Date nadir; + private final boolean alwaysUp; + private final boolean alwaysDown; - private SunTimes(Date rise, Date set, Date noon, Date nadir) { + private SunTimes(Date rise, Date set, Date noon, Date nadir, boolean alwaysUp, + boolean alwaysDown) { this.rise = rise; this.set = set; this.noon = noon; this.nadir = nadir; + this.alwaysUp = alwaysUp; + this.alwaysDown = alwaysDown; } /** @@ -83,9 +88,8 @@ public static interface Parameters extends Parameters twilight(double angle); /** - * Checks only the next 24 hours. Rise, set, noon or nadir times can be - * {@code null} if the sun never reaches the point during one day (e.g. at - * solstice). + * Checks only the next 24 hours. Rise or set time can be {@code null} if the sun + * never reaches the point during one day (e.g. at solstice). *

* This is the default. * @@ -94,8 +98,8 @@ public static interface Parameters extends Parameters oneDay(); /** - * Computes until rise, set, noon, and nadir times are found, even if the sun - * needs more than a day for it. This can considerably increase computation time. + * Computes until rise and set times are found, even if the sun needs more than a + * day for it. This can considerably increase computation time. * * @return itself */ @@ -259,6 +263,11 @@ public SunTimes execute() { Double noon = null; Double nadir = null; double ye; + double lastXeAbs = Double.MAX_VALUE; + double noonXeAbs = Double.MAX_VALUE; + double nadirXeAbs = Double.MAX_VALUE; + double noonYe = 0.0; + double nadirYe = 0.0; double y_minus = correctedSunHeight(jd); @@ -289,16 +298,24 @@ public SunTimes execute() { } } - double xe = qi.getXe(); - if (xe > -1.01 && xe < 1.01) { - if (ye < 0.0) { - nadir = xe + hour; - } else { - noon = xe + hour; + if (hour < 24) { + double xeAbs = Math.abs(qi.getXe()); + if (xeAbs < lastXeAbs) { + double xeHour = qi.getXe() + hour; + if (qi.isMaximum() && xeAbs < noonXeAbs) { + noon = xeHour; + noonXeAbs = xeAbs; + noonYe = ye; + } else if (!qi.isMaximum() && xeAbs < nadirXeAbs) { + nadir = xeHour; + nadirXeAbs = xeAbs; + nadirYe = ye; + } } + lastXeAbs = xeAbs; } - if (rise != null && set != null && noon != null && nadir != null) { + if (hour >= 24 && rise != null && set != null) { break; } @@ -308,8 +325,11 @@ public SunTimes execute() { return new SunTimes( rise != null ? jd.atHour(rise).getDate() : null, set != null ? jd.atHour(set).getDate() : null, - noon != null ? jd.atHour(noon).getDate() : null, - nadir != null ? jd.atHour(nadir).getDate() : null); + jd.atHour(noon).getDate(), + jd.atHour(nadir).getDate(), + nadir == null || nadirYe > 0.0, + noon == null || noonYe < 0.0 + ); } /** @@ -334,6 +354,8 @@ private double correctedSunHeight(JulianDate jd) { /** * Sunrise time. {@code null} if the sun does not rise that day. + *

+ * Always returns a sunrise time if {@link Parameters#fullCycle()} was set. */ public Date getRise() { return rise != null ? new Date(rise.getTime()) : null; @@ -341,41 +363,55 @@ public Date getRise() { /** * Sunset time. {@code null} if the sun does not set that day. + *

+ * Always returns a sunset time if {@link Parameters#fullCycle()} was set. */ public Date getSet() { return set != null ? new Date(set.getTime()) : null; } /** - * The time when the sun reaches its highest point. {@code null} if the sun never - * rises on that day. + * The time when the sun reaches its highest point within the next 24 hours. + *

+ * Use {@link #isAlwaysDown()} to find out if the highest point is still below the + * twilight angle. + *

+ * Note that {@link Parameters#fullCycle()} does not affect this result. */ public Date getNoon() { - return noon != null ? new Date(noon.getTime()) : null; + return new Date(noon.getTime()); } /** - * The time when the sun reaches its lowest point. {@code null} if the sun never sets - * on that day. + * The time when the sun reaches its lowest point within the next 24 hours. + *

+ * Use {@link #isAlwaysUp()} to find out if the lowest point is still above the + * twilight angle. + *

+ * Note that {@link Parameters#fullCycle()} does not affect this result. */ public Date getNadir() { - return nadir != null ? new Date(nadir.getTime()) : null; + return new Date(nadir.getTime()); } /** * {@code true} if the sun never rises/sets, but is always above the twilight angle - * that day. Always returns {@code false} if {@link Parameters#fullCycle()} is used. + * within the next 24 hours. + *

+ * Note that {@link Parameters#fullCycle()} does not affect this result. */ public boolean isAlwaysUp() { - return rise == null && set == null && noon != null; + return alwaysUp; } /** * {@code true} if the sun never rises/sets, but is always below the twilight angle - * that day. Always returns {@code false} if {@link Parameters#fullCycle()} is used. + * within the next 24 hours. + *

+ * Note that {@link Parameters#fullCycle()} does not affect this result. */ public boolean isAlwaysDown() { - return rise == null && set == null && nadir != null; + return alwaysDown; } @Override @@ -385,8 +421,8 @@ public String toString() { sb.append(", set=").append(set); sb.append(", noon=").append(noon); sb.append(", nadir=").append(nadir); - sb.append(", alwaysUp=").append(isAlwaysUp()); - sb.append(", alwaysDown=").append(isAlwaysDown()); + sb.append(", alwaysUp=").append(alwaysUp); + sb.append(", alwaysDown=").append(alwaysDown); sb.append(']'); return sb.toString(); } diff --git a/src/site/markdown/migration.md b/src/site/markdown/migration.md index 37ec599..a68e295 100644 --- a/src/site/markdown/migration.md +++ b/src/site/markdown/migration.md @@ -2,6 +2,11 @@ This document will help you migrate your code to the latest _suncalc_ version. +## Version 2.1 + +* `SunTimes`'s `getNoon()` and `getNadir()` now always give a result, even if the sun stays below or above the twilight angle, respectively. To emulate the old behavior, use `isAlwaysUp()` and `isAlwaysDown()` (e.g. `Date noon = !sun.isAlwaysDown() ? sun.getNoon() : null`). +* At `SunTimes` and `MoonTimes`, the methods `isAlwaysUp()`, `isAlwaysDown()`, `getNoon()` and `getNadir()` ignore the `fullCycle` option now, and always consider the next 24 hours only. + ## Version 2.0 > **NOTE:** Version 2.0 is a major rewrite. It uses different and (hopefully) more accurate formulae than the previous version. If you rely on reproducable results (e.g. in unit tests), be careful when upgrading. The results may differ up to several minutes between both versions. diff --git a/src/test/java/org/shredzone/commons/suncalc/MoonTimesTest.java b/src/test/java/org/shredzone/commons/suncalc/MoonTimesTest.java index bc6c15f..cf08257 100644 --- a/src/test/java/org/shredzone/commons/suncalc/MoonTimesTest.java +++ b/src/test/java/org/shredzone/commons/suncalc/MoonTimesTest.java @@ -47,7 +47,7 @@ public void testAlert() { assertThat("rise", mt2.getRise(), DateMatcher.is("2017-07-14T05:45:33Z")); assertThat("set", mt2.getSet(), DateMatcher.is("2017-07-14T11:26:12Z")); assertThat("alwaysup", mt2.isAlwaysUp(), is(false)); - assertThat("alwaysdown", mt2.isAlwaysDown(), is(false)); + assertThat("alwaysdown", mt2.isAlwaysDown(), is(true)); MoonTimes mt3 = MoonTimes.compute().on(2017, 7, 14).utc().at(ALERT).execute(); assertThat("rise", mt3.getRise(), DateMatcher.is("2017-07-14T05:45:33Z")); @@ -62,7 +62,7 @@ public void testAlert() { MoonTimes mt5 = MoonTimes.compute().on(2017, 7, 18).utc().at(ALERT).fullCycle().execute(); assertThat("rise", mt5.getRise(), DateMatcher.is("2017-07-27T11:59:07Z")); assertThat("set", mt5.getSet(), DateMatcher.is("2017-07-27T04:07:14Z")); - assertThat("alwaysup", mt5.isAlwaysUp(), is(false)); + assertThat("alwaysup", mt5.isAlwaysUp(), is(true)); assertThat("alwaysdown", mt5.isAlwaysDown(), is(false)); } diff --git a/src/test/java/org/shredzone/commons/suncalc/SunTimesTest.java b/src/test/java/org/shredzone/commons/suncalc/SunTimesTest.java index 54ff720..d17ca92 100644 --- a/src/test/java/org/shredzone/commons/suncalc/SunTimesTest.java +++ b/src/test/java/org/shredzone/commons/suncalc/SunTimesTest.java @@ -78,19 +78,19 @@ public void testCologne() { @Test public void testAlert() { SunTimes t1 = SunTimes.compute().at(ALERT).on(2017, 8, 10).utc().execute(); - assertTimes(t1, null, null, "2017-08-10T16:12:47Z"); + assertTimes(t1, null, null, "2017-08-10T16:12:47Z", true); SunTimes t2 = SunTimes.compute().at(ALERT).on(2017, 9, 24).utc().execute(); assertTimes(t2, "2017-09-24T09:54:29Z", "2017-09-24T22:01:58Z", "2017-09-24T16:00:23Z"); SunTimes t3 = SunTimes.compute().at(ALERT).on(2017, 2, 10).utc().execute(); - assertTimes(t3, null, null, null); + assertTimes(t3, null, null, "2017-02-10T16:25:05Z", false); SunTimes t4 = SunTimes.compute().at(ALERT).on(2017, 8, 10).utc().fullCycle().execute(); - assertTimes(t4, "2017-09-06T05:13:15Z", "2017-09-06T03:06:02Z", "2017-09-05T16:05:21Z"); + assertTimes(t4, "2017-09-06T05:13:15Z", "2017-09-06T03:06:02Z", "2017-08-10T16:12:47Z", true); SunTimes t5 = SunTimes.compute().at(ALERT).on(2017, 2, 10).utc().fullCycle().execute(); - assertTimes(t5, "2017-02-27T15:24:18Z", "2017-02-27T17:23:46Z", "2017-02-27T16:23:41Z"); + assertTimes(t5, "2017-02-27T15:24:18Z", "2017-02-27T17:23:46Z", "2017-02-10T16:25:05Z", false); SunTimes t6 = SunTimes.compute().at(ALERT).on(2017, 9, 6).utc().oneDay().execute(); assertTimes(t6, "2017-09-06T05:13:15Z", "2017-09-06T03:06:02Z", "2017-09-06T16:04:59Z"); @@ -158,6 +158,10 @@ private Date createDate(int year, int month, int day, int hour, int minute) { } private void assertTimes(SunTimes t, String rise, String set, String noon) { + assertTimes(t, rise, set, noon, null); + } + + private void assertTimes(SunTimes t, String rise, String set, String noon, Boolean alwaysUp) { if (rise != null) { assertThat("sunrise", t.getRise(), DateMatcher.is(rise)); } else { @@ -170,14 +174,15 @@ private void assertTimes(SunTimes t, String rise, String set, String noon) { assertThat("sunset", t.getSet(), is(nullValue())); } - if (noon != null) { - assertThat("noon", t.getNoon(), DateMatcher.is(noon)); + assertThat("noon", t.getNoon(), DateMatcher.is(noon)); + + if (alwaysUp != null) { + assertThat("always-down", t.isAlwaysDown(), is(!alwaysUp)); + assertThat("always-up", t.isAlwaysUp(), is(alwaysUp)); } else { - assertThat("noon", t.getNoon(), is(nullValue())); + assertThat("always-down", t.isAlwaysDown(), is(false)); + assertThat("always-up", t.isAlwaysUp(), is(false)); } - - assertThat("always-down", t.isAlwaysDown(), is(rise == null && set == null && noon == null)); - assertThat("always-up", t.isAlwaysUp(), is(rise == null && set == null && noon != null)); } }