The sunrise equation or sunset equation can be used to derive the time of sunrise or sunset for any solar declination and latitude in terms of local solar time when sunrise and sunset actually occur.
It is formulated as:
\cos\omega\circ=-\tan\phi x \tan\delta
where:
\omega\circ
\phi
\delta
The Earth rotates at an angular velocity of 15°/hour. Therefore, the expression
\omega\circ/15\circ
\omega\circ
The sign convention is typically that the observer latitude
\phi
\delta
The expression above is always applicable for latitudes between the Arctic Circle and Antarctic Circle. North of the Arctic Circle or south of the Antarctic Circle, there is at least one day of the year with no sunrise or sunset. Formally, there is a sunrise or sunset when
-90\circ+\delta<\phi<90\circ-\delta
-90\circ-\delta<\phi<90\circ+\delta
In the equation given at the beginning, the cosine function on the left side gives results in the range [-1, 1], but the value of the expression on the right side is in the range
[-infty,infty]
\omega\circ
omegao = acos(max(min(-tan(delta*rpd)*tan(phi*rpd), 1.0), -1.0))*dpr
where omegao is
\omega\circ
\delta
\phi
\pi | |
180 |
180 | |
\pi |
The above expression gives results in degree in the range
[0\circ,180\circ]
\circ | |
\omega | |
\circ=0 |
\circ | |
\omega | |
\circ=180 |
Suppose
\phiN
\omega\circ
\phiS
\phiS=-\phiN
\omega\circ
\cos\omega\circ=-\cos\omega\circ=\cos(-180\circ-\omega\circ)
which means
\omega\circ+\omega\circ=-180\circ
The above relation implies that on the same day, the lengths of daytime from sunrise to sunset at
\phiN
\phiS
\phiS=-\phiN
The equation above neglects the influence of atmospheric refraction (which lifts the solar disc — i.e. makes the solar disc appear higher in the sky — by approximately 0.6° when it is on the horizon) and the non-zero angle subtended by the solar disc — i.e. the apparent diameter of the sun — (about 0.5°). The times of the rising and the setting of the upper solar limb as given in astronomical almanacs correct for this by using the more general equation
\cos\omega\circ=\dfrac{\sina-\sin\phi x \sin\delta}{\cos\phi x \cos\delta}
with the altitude angle (a) of the center of the solar disc set to about −0.83° (or −50 arcminutes).
The above general equation can be also used for any other solar altitude. The NOAA provides additional approximate expressions for refraction corrections at these other altitudes.[1] There are also alternative formulations, such as a non-piecewise expression by G.G. Bennett used in the U.S. Naval Observatory's "Vector Astronomy Software".[2]
The generalized equation relies on a number of other variables which need to be calculated before it can itself be calculated. These equations have the solar-earth constants substituted with angular constants expressed in degrees.
n=\lceilJdate-2451545.0+0.0008\rceil
n
Jdate
2451545.0 is the equivalent Julian year of Julian days for Jan-01-2000, 12:00:00.
0.0008 is the fractional Julian Day for leap seconds and terrestrial time (TT).
TT was set to 32.184 sec lagging TAI on 1 January 1958. By 1972, when the leap second was introduced, 10 sec were added. By 1 January 2017, 27 more seconds were added coming to a total of 69.184 sec. 0.0008=69.184 / 86400 without DUT1.
The
\lceil ⋅ \rceil
J\star=n-
\circ} | |
\dfrac{l | |
w}{360 |
where:
J\star
n
l\omega
M=(357.5291+0.98560028 x J\star)\bmod360
where:
M is the solar mean anomaly used in the next three equations.
C=1.9148\sin(M)+0.0200\sin(2M)+0.0003\sin(3M)
where:
C is the Equation of the center value needed to calculate lambda (see next equation).
1.9148 is the coefficient of the Equation of the Center for the planet the observer is on (in this case, Earth)
λ=(M+C+180+102.9372)\bmod360
where:
λ is the ecliptic longitude.
102.9372 is a value for the argument of perihelion.
Jtransit=2451545.0+J\star+0.0053\sinM-0.0069\sin\left(2λ\right)
where:
Jtransit is the Julian date for the local true solar transit (or solar noon).
2451545.0 is noon of the equivalent Julian year reference.
0.0053\sinM-0.0069\sin\left(2λ\right)
\sin\delta=\sinλ x \sin23.4397\circ
where:
\delta
23.4397° is Earth's maximum axial tilt toward the sun [3]
Alternatively, the Sun's declination could be approximated [4] as:
\delta=23.45*\sin((360 x d/365.25)\circ)\circ
where:
d is the number of days after the spring equinox (usually March 21st).
This is the equation from above with corrections for atmospherical refraction and solar disc diameter.
\cos\omega\circ=\dfrac{\sin(-0.833\circ)-\sin\phi x \sin\delta}{\cos\phi x \cos\delta}
where:
ωo is the hour angle from the observer's meridian;
\phi
For observations on a sea horizon needing an elevation-of-observer correction, add
-1.15\circ\sqrt{elevationinfeet
-2.076\circ\sqrt{elevationinmetres
Jrise=Jtransit-
\circ} | |
\dfrac{\omega | |
\circ}{360 |
Jset=Jtransit+
\circ} | |
\dfrac{\omega | |
\circ}{360 |
where:
Jrise is the actual Julian date of sunrise;
Jset is the actual Julian date of sunset.
import loggingfrom datetime import datetime, timedelta, timezone, tzinfofrom math import acos, asin, ceil, cos, degrees, fmod, radians, sin, sqrtfrom time import time
log = logging.getLogger
def _ts2human(ts: int | float, debugtz: tzinfo | None) -> str: return str(datetime.fromtimestamp(ts, debugtz))
def j2ts(j: float | int) -> float: return (j - 2440587.5) * 86400
def ts2j(ts: float | int) -> float: return ts / 86400.0 + 2440587.5
def _j2human(j: float | int, debugtz: tzinfo | None) -> str: ts = j2ts(j) return f' = '
def _deg2human(deg: float | int) -> str: x = int(deg * 3600.0) num = f'∠°' rad = f'∠rad' human = f'∠°′″' return f' = = '
def calc(current_timestamp: float, f: float, l_w: float, elevation: float = 0.0, *, debugtz: tzinfo | None = None,) -> tuple[float, float, None] | tuple[None, None, bool]: log.debug(f'Latitude f = ') log.debug(f'Longitude l_w = ') log.debug(f'Now ts = ')
J_date = ts2j(current_timestamp) log.debug(f'Julian date j_date = days')
# Julian day # TODO: ceil ? n = ceil(J_date - (2451545.0 + 0.0009) + 69.184 / 86400.0) log.debug(f'Julian day n = days')
# Mean solar time J_ = n + 0.0009 - l_w / 360.0 log.debug(f'Mean solar time J_ = days')
# Solar mean anomaly # M_degrees = 357.5291 + 0.98560028 * J_ # Same, but looks ugly M_degrees = fmod(357.5291 + 0.98560028 * J_, 360) M_radians = radians(M_degrees) log.debug(f'Solar mean anomaly M = ')
# Equation of the center C_degrees = 1.9148 * sin(M_radians) + 0.02 * sin(2 * M_radians) + 0.0003 * sin(3 * M_radians) # The difference for final program result is few milliseconds # https://www.astrouw.edu.pl/~jskowron/pracownia/praca/sunspot_answerbook_expl/expl-4.html # e = 0.01671 # C_degrees = \ # degrees(2 * e - (1 / 4) * e ** 3 + (5 / 96) * e ** 5) * sin(M_radians) \ # + degrees(5 / 4 * e ** 2 - (11 / 24) * e ** 4 + (17 / 192) * e ** 6) * sin(2 * M_radians) \ # + degrees(13 / 12 * e ** 3 - (43 / 64) * e ** 5) * sin(3 * M_radians) \ # + degrees((103 / 96) * e ** 4 - (451 / 480) * e ** 6) * sin(4 * M_radians) \ # + degrees((1097 / 960) * e ** 5) * sin(5 * M_radians) \ # + degrees((1223 / 960) * e ** 6) * sin(6 * M_radians)
log.debug(f'Equation of the center C = ')
# Ecliptic longitude # L_degrees = M_degrees + C_degrees + 180.0 + 102.9372 # Same, but looks ugly L_degrees = fmod(M_degrees + C_degrees + 180.0 + 102.9372, 360) log.debug(f'Ecliptic longitude L = ')
Lambda_radians = radians(L_degrees)
# Solar transit (julian date) J_transit = 2451545.0 + J_ + 0.0053 * sin(M_radians) - 0.0069 * sin(2 * Lambda_radians) log.debug(f'Solar transit time J_trans = ')
# Declination of the Sun sin_d = sin(Lambda_radians) * sin(radians(23.4397)) # cos_d = sqrt(1-sin_d**2) # exactly the same precision, but 1.5 times slower cos_d = cos(asin(sin_d))
# Hour angle some_cos = (sin(radians(-0.833 - 2.076 * sqrt(elevation) / 60.0)) - sin(radians(f)) * sin_d) / (cos(radians(f)) * cos_d) try: w0_radians = acos(some_cos) except ValueError: return None, None, some_cos > 0.0 w0_degrees = degrees(w0_radians) # 0...180
log.debug(f'Hour angle w0 = ')
j_rise = J_transit - w0_degrees / 360 j_set = J_transit + w0_degrees / 360
log.debug(f'Sunrise j_rise = ') log.debug(f'Sunset j_set = ') log.debug(f'Day length hours')
return j2ts(j_rise), j2ts(j_set), None
def main: logging.basicConfig(level=logging.DEBUG) latitude = 33.00801 longitude = 35.08794 elevation = 0 print(calc(time, latitude, longitude, elevation, debugtz=timezone(timedelta(hours=3), 'fake-zone')))
if __name__