Class AstronomicalCalculator

java.lang.Object
com.kosherjava.zmanim.util.AstronomicalCalculator
All Implemented Interfaces:
Cloneable
Direct Known Subclasses:
MeeusCalculator, NOAACalculator, SPACalculator, SunTimesCalculator

public abstract class AstronomicalCalculator extends Object implements Cloneable
An abstract class that all sun time calculating classes extend. This allows the algorithm used to be changed at runtime, easily allowing comparison the results of using different algorithms.
Author:
© Eliyahu Hershfeld 2004 - 2026
TODO:
Consider methods that would allow atmospheric modeling. This can currently be adjusted by setting the refraction.
  • Field Details

    • refraction

      private double refraction
      The commonly used average solar refraction in degrees that defaults to 34′, or 0.5666°. Calendrical Calculations lists a more accurate global average of 34.478885263888294′ or 0.574648087731472°,
      See Also:
    • solarRadius

      private double solarRadius
      The commonly used average solar radius that is about 16′ or 0.2666°.
      See Also:
    • useApparentSolarRadius

      private boolean useApparentSolarRadius
      Whether sunrise and sunset should use the date-based apparent solar radius (getApparentSolarRadius(LocalDate)), which varies slightly through the year as the Earth-Sun distance changes, rather than the fixed solar radius. Defaults to true. When false, the fixed getSolarRadius() value (default 16′) is used instead, which is appropriate for matching other implementations that assume a constant solar radius. Note that calling setSolarRadius(double) automatically sets this to false.
      See Also:
    • earthRadius

      private double earthRadius
      The commonly used average earth radius of 6371.0088 KM (the IUGG mean radius R1 = (2a + b) / 3 of the WGS-84 ellipsoid). At this time, this only affects the elevation adjustment, not the underlying solar position calculations.
      See Also:
    • GEOMETRIC_ZENITH

      private static final double GEOMETRIC_ZENITH
      The zenith of astronomical sunrise and sunset. The sun is 90° from the vertical 0°
      See Also:
    • SOLAR_RADIUS_BY_DAY_OF_YEAR

      private static final double[] SOLAR_RADIUS_BY_DAY_OF_YEAR
      The Sun's apparent angular semi-diameter (the "solar radius") in degrees for each day of the year, precomputed from the VSOP87 Earth-Sun distance for reference year 2050. The Sun's apparent size varies through the year as the Earth-Sun distance changes between the perihelion (~Jan 3, about 16.27′ or 0.2710°) and the aphelion (~Jul 5, about 15.73′ or 0.2622°), with a mean of the conventional 16′. The table has 365 entries indexed by day-of-year (1-365); 2050 is a common year, so February 29 reuses the February 28 value (see getApparentSolarRadius(LocalDate)).

      Valid epoch. Reference year 2050 was chosen as the midpoint of the years 2000-2100 so that the small drift in the value for a fixed calendar date stays symmetric and minimal across that span. The date of the perihelion advances through the calendar by roughly one day every 57 years, so the apparent radius for a given calendar date is not perfectly constant from year to year. Across 2000-2100 this table is within about 0.4″ of the exact value (at most roughly 50 ms of sunrise / sunset time at latitude 60°, and less elsewhere) - negligible for any practical purpose. Outside 2000-2100 the table is still returned but its accuracy degrades gradually as the perihelion drifts further; for use centuries away from 2050 the table should be regenerated against a closer reference year.

      A fixed table is used in preference to evaluating the VSOP87 series at run time because, given the negligible drift above, the lookup is both simpler and faster while remaining far more accurate than any sunrise / sunset calculation requires.

      See Also:
  • Constructor Details

  • Method Details

    • isUseApparentSolarRadius

      public boolean isUseApparentSolarRadius()
      Returns if useApparentSolarRadius is true (the default) or false.
      Returns:
      if useApparentSolarRadius is true or false.
      See Also:
    • setUseApparentSolarRadius

      public void setUseApparentSolarRadius(boolean useApparentSolarRadius)
      Sets if useApparentSolarRadius should be true (the default) or false.
      Parameters:
      useApparentSolarRadius - should apparent solar radius be used (default is true).
      See Also:
    • getEarthRadius

      public double getEarthRadius()
      A method that returns the earth radius in KM. The value currently defaults to the commonly used average earth radius of 6371.0088 KM (the IUGG mean radius R1 = (2a + b) / 3 of the WGS-84 ellipsoid) if not set. At this time, this only affects the elevation adjustment, not the underlying solar position calculations.
      Returns:
      the earth radius in KM.
    • setEarthRadius

      public void setEarthRadius(double earthRadius)
      A method that allows setting the earth's radius.
      Parameters:
      earthRadius - the earthRadius to set in KM
    • getDefault

      Returns the default class for calculating sunrise and sunset. This is currently the more accurate NOAACalculator, but this may change in the future.
      Returns:
      AstronomicalCalculator the default class for calculating sunrise and sunset. In the current implementation the default calculator returned is the more accurate NOAACalculator.
    • getCalculatorName

      public abstract String getCalculatorName()
      Returns the name of the algorithm.
      Returns:
      the descriptive name of the algorithm.
    • getUTCSunrise

      public abstract double getUTCSunrise(LocalDate localDate, GeoLocation geoLocation, double zenith, boolean adjustForElevation)
      A method that calculates UTC sunrise as well as any time based on an angle above or below sunrise. This abstract method is implemented by the classes that extend this class.
      Parameters:
      localDate - The LocalDate representing the date to calculate sunrise for.
      geoLocation - The location information used for astronomical calculation of solar times.
      zenith - the azimuth below the vertical zenith of 90°. For sunrise typically the zenith used for the calculation uses geometric zenith of 90° and adjusts this slightly to account for solar refraction and the sun's radius. Another example would be AstronomicalCalendar.getBeginNauticalTwilight() that passes AstronomicalCalendar.NAUTICAL_ZENITH to this method.
      adjustForElevation - Should the time be adjusted for elevation
      Returns:
      The UTC time of sunrise in 24-hour format. 5:45:00 AM will return 5.75. If an error was encountered in the calculation (expected behavior for some locations such as near the poles, Double.NaN will be returned.
      See Also:
    • getUTCSunset

      public abstract double getUTCSunset(LocalDate localDate, GeoLocation geoLocation, double zenith, boolean adjustForElevation)
      A method that calculates UTC sunset as well as any time based on an angle above or below sunset. This abstract method is implemented by the classes that extend this class.
      Parameters:
      localDate - The LocalDate> representing the date to calculate sunset for.
      geoLocation - The location information used for astronomical calculation of solar times.
      zenith - the azimuth below the vertical zenith of 90°. For sunset typically the zenith used for the calculation uses geometric zenith of 90° and adjusts this slightly to account for solar refraction and the sun's radius. Another example would be AstronomicalCalendar.getEndNauticalTwilight() that passes AstronomicalCalendar.NAUTICAL_ZENITH to this method.
      adjustForElevation - Should the time be adjusted for elevation
      Returns:
      The UTC time of sunset in 24-hour format. 5:45:00 AM will return 5.75. If an error was encountered in the calculation (expected behavior for some locations such as near the poles, Double.NaN will be returned.
      See Also:
    • getUTCNoon

      public abstract double getUTCNoon(LocalDate localDate, GeoLocation geoLocation)
      Return the Universal Coordinated Time (UTC) of solar noon (UTC) for the given day at the given location. The NOAACalculator, the default calculator implementation calculates true solar noon, something that can be calculated even in the Arctic / Antarctic where there may be no sunrise or sunset, while the SunTimesCalculator approximates it, calculating the time as halfway between sunrise and sunset, something that can't be calculated in Polar regions where there is no sunrise or sunset for part of the year. See The Definition of Chatzos for details on solar noon / midnight calculations.
      Parameters:
      localDate - The LocalDate representing the date to calculate noon for.
      geoLocation - The location information used for astronomical calculation of solar times.
      Returns:
      The UTC time of solar noon in 24-hour format. 1:45:00 PM will return 13.75. If an error was encountered in the the calculation (expected behavior for some locations such as near the poles, Double.NaN will be returned.
      See Also:
    • getUTCMidnight

      public abstract double getUTCMidnight(LocalDate localDate, GeoLocation geoLocation)
      Return the Universal Coordinated Time (UTC) of solar midnight (UTC) for the given day at the given location. The NOAACalculator, the default calculator implementation calculates true solar midnight, something that can be calculated even in the Arctic / Antarctic where there may be no sunrise or sunset, while the SunTimesCalculator approximates it, calculating the time as 12 hours after halfway between sunrise and sunset, something that can't be calculated in Polar regions where there is no sunrise or sunset for part of the year. See The Definition of Chatzos for details on solar noon / midnight calculations.
      Parameters:
      localDate - The LocalDate representing the date to calculate midnight for. The calculation will be for midnight at the end of the day passed in.
      geoLocation - The location information used for astronomical calculation of solar times.
      Returns:
      The UTC time of solar midnight in a 24-hour double format. 1:45:00 AM will return 1.75. If an error was encountered in the calculation (expected behavior for the SunTimesCalculator at times of the year in Polar regions), Double.NaN will be returned.
      See Also:
    • getTimeAtAzimuth

      public abstract double getTimeAtAzimuth(LocalDate localDate, GeoLocation geoLocation, double azimuth)
      Returns the time that the azimuth will occur for the date and location passed to this method. As an example, to know when the sun will be directly west, pass in an azimuth of 270°, and for directly east, pass in an azimuth of 90°.
      Parameters:
      localDate - The LocalDate representing the date to calculate the time of the azimuth for.
      geoLocation - The location information used for astronomical calculation of solar times.
      azimuth - the azimuth to run the calculation for
      Returns:
      The UTC time that the azimuth will be reached in a 24-hour double format. 5:45:00 PM will return 17.75. If an error was encountered in the calculation (expected behavior for some dates at latitudes below 23.44°, a Double.NaN will be returned.
    • getSolarElevation

      public abstract double getSolarElevation(Instant instant, GeoLocation geoLocation)
      Return the Solar Elevation for the horizontal coordinate system at the given location at the given time. Can be negative if the sun is below the horizon. Not corrected for altitude.
      Parameters:
      instant - The instant to calculate the elevation for.
      geoLocation - The location information used for astronomical calculation of solar times.
      Returns:
      solar elevation in degrees. The horizon (calculated in a vacuum using the solar radius as the point)is 90°, civil twilight is 96° etc. This means that sunrise and sunset that do use refraction and are calculated from the upper limb of the sun will return about 0.8333°.
    • getSolarAzimuth

      public abstract double getSolarAzimuth(Instant instant, GeoLocation geoLocation)
      Return the Solar Azimuth for the horizontal coordinate system at the given location at the given time. Not corrected for altitude. True south is 180°.
      Parameters:
      instant - The instant to calculate the azimuth for.
      geoLocation - The location information used for astronomical calculation of solar times.
      Returns:
      the solar azimuth in degrees. Astronomical midday would be 180° in the northern hemisphere and 0° in the southern hemisphere. Depending on the location and time of year, sunrise will have an azimuth of about 90° and sunset about 270°.
    • getElevationAdjustment

      double getElevationAdjustment(double elevation)
      Method to return the adjustment to the zenith required to account for the elevation. Since a person at a higher elevation can see farther below the horizon, the calculation for sunrise / sunset is calculated below the horizon used at sea level. This is only used for sunrise and sunset and not times before or after it such as nautical twilight since those calculations are based on the level of available light at the given dip below the horizon, something that is not affected by elevation, the adjustment should only be made if the zenith == 90° adjusted for refraction and solar radius. The algorithm used is
      elevationAdjustment = Math.toDegrees(Math.acos(earthRadiusInMeters / (earthRadiusInMeters + elevationMeters)));
      
      The source of this algorithm is Calendrical Calculations by Edward M. Reingold and Nachum Dershowitz. An alternate algorithm that produces similar (but not completely accurate) result found in Ma'aglay Tzedek by Moishe Kosower and other sources is:
      elevationAdjustment = 0.0347 * Math.sqrt(elevationMeters);
      
      Parameters:
      elevation - elevation in Meters.
      Returns:
      the adjusted zenith
    • adjustZenith

      double adjustZenith(double zenith, double elevation, LocalDate localDate)
      Adjusts the zenith of astronomical sunrise and sunset to account for solar refraction, solar radius and elevation. The value for Sun's zenith and true rise/set Zenith (used in this class and subclasses) is the angle that the center of the Sun makes to a line perpendicular to the Earth's surface. If the Sun were a point and the Earth were without an atmosphere, true sunset and sunrise would correspond to a 90° zenith. Because the Sun is not a point, and because the atmosphere refracts light, this 90° zenith does not, in fact, correspond to true sunset or sunrise, instead the center of the Sun's disk must lie just below the horizon for the upper edge to be obscured. This means that a zenith of just above 90° must be used. The Sun subtends an angle of 16 minutes of arc (this can be changed via the setSolarRadius(double) method , and atmospheric refraction accounts for 34 minutes or so (this can be changed via the setRefraction(double) method), giving a total of 50 arcminutes. The total value for ZENITH is 90+(5/6) or 90.8333333° for true sunrise/sunset. Since a person at an elevation can see below the horizon of a person at sea level, this will also adjust the zenith to account for elevation if available. Note that this will only adjust the value if the zenith is exactly 90°. For values below and above this no correction is done. As an example, astronomical twilight is when the sun is 18° below the horizon or 108° below the zenith. This is traditionally calculated with none of the above mentioned adjustments. The same goes for various tzais and alos times such as the 16.1° dip used in ZmanimCalendar.getAlos16Point1Degrees().
      Parameters:
      zenith - the azimuth below the vertical zenith of 90°. For sunset typically the zenith used for the calculation uses geometric zenith of 90° and adjusts this slightly to account for solar refraction and the sun's radius. Another example would be AstronomicalCalendar.getEndNauticalTwilight() that passes AstronomicalCalendar.NAUTICAL_ZENITH to this method.
      elevation - elevation in Meters.
      localDate - the date to use for the solar radius. See getApparentSolarRadius(LocalDate).
      Returns:
      The zenith adjusted to include the sun's radius, refraction and elevation adjustment. This will only be adjusted for sunrise and sunset (if the zenith == 90°)
      See Also:
    • getRefraction

      public double getRefraction()
      Method to get the refraction value to be used when calculating sunrise and sunset. The default value is 34 arcminutes (returned in degrees). The Errata and Notes for Calendrical Calculations: The Millennium Edition by Edward M. Reingold and Nachum Dershowitz lists the actual average refraction value as 34.478885263888294 or approximately 34' 29". The refraction value as well as the solar radius and elevation adjustment are added to the zenith used to calculate sunrise and sunset.
      Returns:
      The refraction in degrees.
    • setRefraction

      public void setRefraction(double refraction)
      A method to allow overriding the default refraction of the calculator.
      Parameters:
      refraction - The refraction in degrees.
      See Also:
      TODO:
      At some point in the future, an AtmosphericModel or Refraction object that models the atmosphere of different locations might be used for increased accuracy.
    • getSolarRadius

      public double getSolarRadius()
      Method to get the fixed sun's radius. The default value is 16 arcminutes. The sun's radius as it appears from earth is almost universally given as 16 arcminutes but in fact it differs by the time of the year. At the perihelion it has an apparent radius of 16.293′ (0.2710°), while at the aphelion it has an apparent radius of 15.755′ (0.2622°). There is little effect for most locations, but at high and low latitudes the difference becomes more apparent. Calculations for the difference at the location of the Royal Observatory, Greenwich show only a 4.494-second difference between the perihelion and aphelion radii, but moving into the arctic circle the difference becomes more noticeable. Tests for Tromso, Norway (latitude 69.67°, longitude 19.05°) show that on May 17, the rise of the midnight sun, a 2 minute and 23 second difference is observed between the perihelion and aphelion radii using the USNO algorithm, but only 1 minute and 6 seconds difference using the NOAA algorithm. Areas farther north show an even greater difference. Note that these are not real-world tests. It simply compared the min and max solar radius at different locations. Real world examples of comparing the actual apparent solar radius would yield less significant differences. Regardless, this is exactly the error that the date-based apparent solar radius eliminates: by default (isUseApparentSolarRadius() is true) sunrise and sunset use getApparentSolarRadius(LocalDate) instead, and the fixed value returned here only applies when that setting is false (typically to match other implementations that assume a constant 16′ radius; see setSolarRadius(double), which switches that setting off automatically).
      Returns:
      The fixed sun's radius in degrees, used only when isUseApparentSolarRadius() is false.
      See Also:
    • setSolarRadius

      public void setSolarRadius(double solarRadius)
      Sets the Sun's radius as a fixed, date-independent value in degrees, for example 16.0 / 60.0 for the conventional 16′. This is typically used to match another implementation or reference that assumes a constant solar radius.

      A fixed radius has no effect while isUseApparentSolarRadius() is true (the default), since in that mode the date-based apparent solar radius is used instead. So that a value set here actually takes effect, this method also disables that mode (equivalent to calling setUseApparentSolarRadius(boolean)). To return to the date-based radius afterward, pass true to setUseApparentSolarRadius(boolean).

      Parameters:
      solarRadius - the Sun's radius in degrees. Must be a non-negative number.
      Throws:
      IllegalArgumentException - if solarRadius is < 0 or is Double.NaN.
      See Also:
    • equals

      public boolean equals(Object object)

      Two AstronomicalCalculator instances are considered equal if their Earth radius, refraction, and solar radius values are identical.

      Overrides:
      equals in class Object
      Parameters:
      object - the reference object with which to compare
      Returns:
    • hashCode

      public int hashCode()

      This implementation hashes the earth radius, refraction, and solar radius properties to maintain the contract with equals(Object).

      Overrides:
      hashCode in class Object
      Returns:
    • clone

      Overrides:
      clone in class Object
      Returns:
    • tanDegrees

      protected static double tanDegrees(double angle)
      A helper method to retun the tan / tangent in degrees.
      Parameters:
      angle - the angle
      Returns:
      the tangent in degrees
    • sinDegrees

      protected static double sinDegrees(double angle)
      A helper method to retun the sine in degrees.
      Parameters:
      angle - the angle
      Returns:
      the sine in degrees.
    • cosDegrees

      protected static double cosDegrees(double angle)
      A helper method to retun the cosine in degrees.
      Parameters:
      angle - the angle
      Returns:
      the cosine in degrees.
    • acosDegrees

      protected static double acosDegrees(double angle)
      A helper method to retun the arc cosine in degrees.
      Parameters:
      angle - the angle
      Returns:
      the arc cosine in degrees.
    • asinDegrees

      protected static double asinDegrees(double angle)
      A helper method to retun the arc sine in degrees.
      Parameters:
      angle - the angle
      Returns:
      the arc sine in degrees.
    • getApparentSolarRadius

      public double getApparentSolarRadius(LocalDate localDate)
      Returns the Sun's apparent angular semi-diameter (the "solar radius") for the given date, in degrees. The Sun's apparent size changes over the year as the Earth-Sun distance varies between the perihelion (~Jan 3, when the Sun is largest) and the aphelion (~Jul 5, when it is smallest). The value is read from a precomputed table (SOLAR_RADIUS_BY_DAY_OF_YEAR) keyed by calendar day; the date's month and day are mapped onto the (common-year) reference year 2050 via withYear(2050), so a February 29 input is automatically resolved to February 28 and assigned that day's value.

      This is the value the calculator applies automatically for sunrise and sunset while isUseApparentSolarRadius() is true (the default). It is recomputed from the date on every call and is never stored, so it is always correct for the date being calculated. The method is exposed only for inspection and comparison.

      Do not pass the result to setSolarRadius(double). setSolarRadius(double) is meant for a fixed, date-independent radius (for example to match another implementation that uses a constant 16′). Storing a single day's apparent radius there would freeze that one day's value onto this instance and apply it incorrectly to every other date subsequently calculated (and would also switch off apparent-radius mode). To use the date-based radius, simply leave isUseApparentSolarRadius() enabled and let the calculator call this method itself.

      Parameters:
      localDate - the date for which to return the Sun's apparent semi-diameter. If a null is passed, the default solar radius of 16/60 will be returned.
      Returns:
      the Sun's apparent semi-diameter in degrees: about 0.2711° near the perihelion, about 0.2622° near the aphelion, and about 0.2666° - the conventional 16′ - near the equinoxes.
      See Also: