# `Astro.Solar`
[🔗](https://github.com/kipcole9/astro/blob/v2.11.0/lib/astro/solar.ex#L1)

Solar position, orbital mechanics, and equinox/solstice calculations.

## Two sources of the Sun's position

The Sun's apparent position comes from one of two sources:

* **The JPL DE440s ephemeris**, read by `Astro.Ephemeris`. It is a
  numerical integration of the solar system, accurate to well under
  a milliarcsecond, but it covers a limited span of years (1900–2100
  for the file bundled with Astro).

* **Analytical series**, computed in this module. They describe the
  Earth's orbit with a mean motion, the equation of the centre and a
  sum of periodic perturbations. They are less accurate (about 1″
  near the present) but give a position for any date.

The [accuracy guide](accuracy.md) compares the two.

The analytical series work from the Earth's orbital elements:
`sun_geometric_mean_longitude/1`, `sun_geometric_mean_anomaly/1`,
`earth_orbit_eccentricity/1` and `sun_equation_of_center/1`.

For sunrise and sunset calculations see `Astro.Solar.SunRiseSet`.

## Function groups

### Position

* `solar_position/1` — right ascension, declination and distance
* `solar_ecliptic_longitude/1` — apparent ecliptic longitude at a moment
* `solar_ecliptic_longitude_after/2`, `estimate_prior_solar_ecliptic_longitude/2`

### Analytical series

* `analytical_solar_ecliptic_position/1` — apparent ecliptic longitude, latitude and distance
* `analytical_solar_position/1` — apparent right ascension, declination and distance

### Orbital elements

* `sun_geometric_mean_longitude/1`, `sun_geometric_mean_anomaly/1`
* `earth_orbit_eccentricity/1`, `sun_equation_of_center/1`

### Time

* `equation_of_time/1` — difference between apparent and mean solar time
* `solar_noon/2` — the moment the Sun crosses a meridian
* `solar_noon_utc/2` — the same, as minutes since midnight UTC

### Equinoxes and solstices

* `equinox_and_solstice/2` — the moment the Sun's longitude reaches 0°, 90°, 180° or 270°

### Solar elevation

* `solar_elevation/1` — zenith angle for geometric, civil, nautical,
  astronomical twilight or a custom elevation

## Time conventions

Functions in this module accept either a **moment** (fractional days
since epoch) or **Julian centuries** from J2000.0, as noted in each
function's documentation. Use `Astro.Time.julian_centuries_from_moment/1`
to convert between the two.

# `analytical_solar_ecliptic_position`
*since 2.10.0* 

```elixir
@spec analytical_solar_ecliptic_position(Astro.Time.moment()) ::
  {float(), float(), float()}
```

Returns the Sun's apparent geocentric ecliptic position from analytical
series.

The longitude is the Sun's true geometric longitude from the 49-term
series of Bretagnon and Simon (as given in *Calendrical Calculations*),
corrected for aberration and for the nutation in longitude of
`Astro.Earth.nutation/1`. Over 1900–2100 it agrees with the JPL DE440s
ephemeris to about 1″ RMS and 3.3″ at most. The latitude is taken as
zero, since the Sun's never exceeds about 1.2″, and the distance is the
radius vector of the Keplerian ellipse.

### Arguments

* `t` is a moment (fractional days since the epoch).

### Returns

* `{longitude, latitude, distance}` where `longitude` is in degrees in
  the range 0 to 360, `latitude` is `0.0` and `distance` is in
  astronomical units.

### Accuracy

Against JPL over 1900–2100: longitude 0.75″ RMS and 2.3″ at most,
with a mean bias of +0.56″, and distance 4,300 km RMS. Between −2000
and 3000 the series agrees with Meeus's chapter 25 series within
45″; at ±10,000 years they differ by more than a day of the Sun's
motion. The [accuracy guide](accuracy.md) gives the details.

### Examples

    iex> {longitude, latitude, distance} =
    ...>   Astro.Solar.analytical_solar_ecliptic_position(Astro.Time.j2000())
    iex> {Float.round(longitude, 4), latitude, Float.round(distance, 6)}
    {280.369, 0.0, 0.983308}

### Reference

* Edward M. Reingold and Nachum Dershowitz, *Calendrical
  Calculations*, for the series of Bretagnon and Simon.

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 25 (solar coordinates).

# `analytical_solar_position`
*since 2.10.0* 

```elixir
@spec analytical_solar_position(Astro.Time.moment()) :: {float(), float(), float()}
```

Returns the Sun's apparent geocentric right ascension, declination and
distance from analytical series.

The ecliptic position of `analytical_solar_ecliptic_position/1` is
rotated to the equator with the true obliquity of the ecliptic by
`Astro.Coordinates.ecliptic_to_equatorial/3`.

### Arguments

* `t` is a moment (fractional days since the epoch).

### Returns

* `{right_ascension, declination, distance}` where `right_ascension`
  is in degrees in the range 0 to 360, `declination` is in degrees and
  `distance` is in astronomical units.

### Accuracy

Against JPL over 1900–2100: right ascension 0.76″ RMS and
declination 0.50″ RMS (2.2″ and 1.3″ at most). The
[accuracy guide](accuracy.md) gives the details.

### Examples

    iex> {right_ascension, declination, distance} =
    ...>   Astro.Solar.analytical_solar_position(Astro.Time.j2000())
    iex> {Float.round(right_ascension, 4), Float.round(declination, 4), Float.round(distance, 6)}
    {281.2785, -23.0327, 0.983308}

### Reference

* Edward M. Reingold and Nachum Dershowitz, *Calendrical
  Calculations*, for the series of Bretagnon and Simon.

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 13 (transformation of coordinates).

# `earth_orbit_eccentricity`

```elixir
@spec earth_orbit_eccentricity(Astro.Time.julian_centuries()) :: float()
```

Returns the eccentricity of the Earth's orbit.

### Arguments

* `julian_centuries` is any moment in time expressed
  as Julian centuries from J2000.0.

### Returns

* the eccentricity as a unitless `float`. Zero is a circle and values
  between 0 and 1 are ellipses; the Earth's is about 0.0167 and slowly
  decreasing (Meeus, *Astronomical Algorithms*, equation 25.4).

### Accuracy

This is a mean element, not a position. Combined as in Meeus,
*Astronomical Algorithms*, chapter 25 (the Sun's true longitude is
the mean longitude plus the equation of the centre, then corrected
for aberration and nutation), the elements give the Sun's apparent
longitude to 0.003° RMS and 0.009° at most against JPL over
1900–2100, the 0.01° Meeus states for the method.

### Examples

    iex> Astro.Solar.earth_orbit_eccentricity(0.0)
    0.016708634

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 25 (solar coordinates).

* [Orbital eccentricity](https://en.wikipedia.org/wiki/Orbital_eccentricity),
  Wikipedia.

# `equation_of_time`

```elixir
@spec equation_of_time(float()) :: float()
```

Returns the equation of time in minutes.

### Arguments

* `julian_centuries` is any moment in time expressed
  as Julian centuries from J2000.0.

### Returns

* the discrepancy between apparent solar time and
  mean solar time in minutes as a `float`. A positive
  value means the sundial is ahead of the clock.

### Notes

The equation of time describes the discrepancy between
apparent solar time (which directly tracks the Sun's
diurnal motion) and mean solar time (which tracks a
theoretical mean Sun with uniform motion).

During a year the equation of time ranges from about
+16 min 33 s (around 3 November) to −14 min 6 s
(around 11 February), with zeros near 15 April,
13 June, 1 September, and 25 December.

The two principal causes are the obliquity of the
ecliptic (~23.44°) and the eccentricity of the
Earth's orbit (~0.0167).

It is calculated as in Meeus, *Astronomical Algorithms*,
chapter 28: the Sun's mean longitude, less the constant of
aberration (0.0057183°), less the Sun's apparent right
ascension, plus the nutation in right ascension (Δψ cos ε),
converted to minutes at four minutes of time a degree.

### Accuracy

Solar noon taken as 12:00 local mean time less this equation of
time agrees with the meridian transit to 0.2 s over 1850–2149. Far
from the present the mean Sun, in dynamical time, and the Earth's
rotation drift apart by ΔT, and the agreement falls to about 3 s at
the year 1000, which is why `solar_noon/2` solves for the transit
instead.

### Examples

    iex> Astro.Solar.equation_of_time(0.0) |> Float.round(4)
    -3.2815

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 28 (equation of time).

* [Equation of time](https://en.wikipedia.org/wiki/Equation_of_time),
  Wikipedia.

# `equinox_and_solstice`

```elixir
@spec equinox_and_solstice(integer(), :march | :june | :september | :december) ::
  {:ok, DateTime.t()} | {:error, :year_out_of_range}
```

Returns the UTC datetime of an equinox or solstice.

An equinox or solstice is the moment the Sun's apparent ecliptic
longitude reaches a multiple of 90°: 0° at the March equinox, 90° at
the June solstice, 180° at the September equinox and 270° at the
December solstice. It is found by searching for that longitude with
`solar_ecliptic_longitude_after/2`, starting 20 days before the 1st of
the month the event falls in, so it uses the same positions as the rest
of this module: the JPL ephemeris where it covers the year, and the
analytical series for any other year.

The month is a date in dynamical time, in which the Sun's motion is
uniform, so the search starts ΔT earlier in Universal Time. ΔT is about
a minute today but grows to days thousands of years from the present,
and far enough back the event falls in the previous year in UTC.

### Arguments

* `year` is an integer Gregorian year within 50,000 years of 2000.

* `event` is one of `:march`, `:june`, `:september`,
  or `:december` identifying the equinox or solstice.

### Returns

* `{:ok, datetime}` where `datetime` is a `DateTime.t()`
  in UTC.

* `{:error, :year_out_of_range}` if `year` is more than 50,000
  years from 2000.

### Accuracy

As for `Astro.equinox/3`: within a second of Meeus's full VSOP87
time for the June 1962 solstice inside the ephemeris, and 18 s RMS
from JPL with the series outside it.

### Examples

    iex> {:ok, dt} = Astro.Solar.equinox_and_solstice(2024, :march)
    iex> {dt.year, dt.month, dt.day}
    {2024, 3, 20}

    iex> {:ok, dt} = Astro.Solar.equinox_and_solstice(-1000, :june)
    iex> {dt.year, dt.month}
    {-1000, 6}

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 27 (equinoxes and solstices).

# `estimate_prior_solar_ecliptic_longitude`

Returns an approximate moment at or before `t`
when the solar ecliptic longitude just exceeded `lambda` degrees.

### Arguments

* `lambda` is the target solar ecliptic longitude in degrees.

* `t` is any moment in time expressed as a moment
  (fractional days since the epoch).

### Returns

* the approximate moment (fractional days since the epoch)
  at or before `t` when the solar ecliptic longitude
  last exceeded `lambda` degrees.

### Examples

    # Estimate when the Sun last passed 90° before the June solstice
    iex> moment = Astro.Time.date_time_to_moment(~D[2024-06-21])
    iex> result = Astro.Solar.estimate_prior_solar_ecliptic_longitude(90, moment)
    iex> {:ok, dt} = Astro.Time.date_time_from_moment(result)
    iex> dt.year
    2024
    iex> dt.month
    6

### Reference

* Edward M. Reingold and Nachum Dershowitz, *Calendrical
  Calculations*, `estimate-prior-solar-longitude`.

# `solar_ecliptic_longitude`

Returns the solar ecliptic longitude in degrees.

### Arguments

* `t` is any moment in time expressed as a moment
  (fractional days since the epoch).

### Returns

* the solar ecliptic longitude in degrees as a `float`,
  in the range 0..360.

### Notes

The solar ecliptic longitude is the position of the Sun on the
celestial sphere along the ecliptic. It is also an effective
measure of the position of the Earth in its orbit around the Sun,
taken as 0° at the moment of the vernal equinox.

Since it is based on how far the Earth has moved in its orbit
since the equinox, it is a measure of what time of the tropical
year (the year of seasons) has elapsed, without the inaccuracies
of a calendar date which is perturbed by leap years and calendar
imperfections.

The longitude comes from the JPL ephemeris where the loaded kernel
covers `t`, and from `analytical_solar_ecliptic_position/1`, accurate
to about 1″ near the present, everywhere else. Calendars that count
months by the Sun's longitude, such as the Chinese and Japanese
lunisolar calendars, find their solar terms for dates of any era this
way.

### Accuracy

From JPL inside the loaded ephemeris; elsewhere from
`analytical_solar_ecliptic_position/1`, within 0.75″ RMS and about
2″ of JPL over 1900–2100. The [accuracy guide](accuracy.md) gives
the details.

### Examples

    iex> moment = Astro.Time.date_time_to_moment(~D[2024-03-20])
    iex> Astro.Solar.solar_ecliptic_longitude(moment)
    359.87140902027863

    iex> moment = Astro.Time.date_time_to_moment(~D[1000-06-01])
    iex> Astro.Solar.solar_ecliptic_longitude(moment) |> Float.round(4)
    69.5898

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 25 (solar coordinates).

# `solar_ecliptic_longitude_after`

```elixir
@spec solar_ecliptic_longitude_after(number(), Astro.Time.time()) :: Astro.Time.time()
```

Returns the moment (UT) of the first time at or after moment `t`
when the solar ecliptic longitude will be `lambda` degrees.

### Arguments

* `lambda` is the target solar ecliptic longitude in degrees.

* `t` is any moment in time expressed as a moment
  (fractional days since the epoch).

### Returns

* the moment (fractional days since the epoch) when
  the solar ecliptic longitude reaches `lambda` degrees.

### Accuracy

The search ends within a millisecond of the crossing, so the moment
is as accurate as the longitude: the Sun moves about 2.5″ a minute,
so 1″ of longitude is about 24 s.

### Examples

    # Find the June solstice (longitude 90°) in 2024
    iex> moment = Astro.Time.date_time_to_moment(~D[2024-05-22])
    iex> result = Astro.Solar.solar_ecliptic_longitude_after(90, moment)
    iex> {:ok, dt} = Astro.Time.date_time_from_moment(result)
    iex> dt.year
    2024
    iex> dt.month
    6

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 27 (equinoxes and solstices).

# `solar_longitude_day_on_or_after`
*since 2.10.0* 

```elixir
@spec solar_longitude_day_on_or_after(
  number(),
  integer(),
  number() | (Astro.Time.moment() -&gt; number())
) ::
  integer()
```

Returns the local day on which the Sun's apparent longitude first
reaches `lambda` at or after the start of a local day.

Calendars that follow the Sun, such as the Chinese solar terms, need
only the day on which its longitude reaches a value, in the time they
keep. This is that day, at the offset in force when the longitude is
reached: the same day as `solar_ecliptic_longitude_after/2` from
local midnight, read at the local date, found faster.

The Sun's longitude always advances, at between 0.95° and 1.03° a
day, so one longitude evaluated near the crossing places it within a
narrow window. The search narrows the window until it falls on one
local day, and only a crossing near local midnight is found to its
moment.

### Arguments

* `lambda` is the apparent ecliptic longitude in degrees.

* `day` is a local day as an integer number of days since
  `0000-01-01`.

* `offset` is the local time's offset from UTC in days, as a number,
  or a 1-arity function that returns the offset for a moment.

### Returns

* the local day as an integer number of days since `0000-01-01`.

### Accuracy

The same day as the moment search; see
`solar_ecliptic_longitude_after/2`.

### Examples

    iex> day = Date.to_gregorian_days(~D[2024-01-01])
    iex> Astro.Solar.solar_longitude_day_on_or_after(315, day, 8 / 24) |> Date.from_gregorian_days()
    ~D[2024-02-04]

### Reference

* Edward M. Reingold and Nachum Dershowitz, *Calendrical
  Calculations*, `solar-longitude-after`.

# `solar_longitude_day_on_or_before`
*since 2.10.0* 

```elixir
@spec solar_longitude_day_on_or_before(
  number(),
  integer(),
  number() | (Astro.Time.moment() -&gt; number())
) :: integer()
```

Returns the local day on which the Sun's apparent longitude last
reached `lambda`, on or before a local day.

This is the day, at the offset in force when the longitude is
reached, of the last time it reached `lambda` before the end of
`day`. With `lambda` 270 it is the day of the December solstice on
or before `day`, which the Chinese calendar counts its years from.
See `solar_longitude_day_on_or_after/3` for how it is found.

### Arguments

* `lambda` is the apparent ecliptic longitude in degrees.

* `day` is a local day as an integer number of days since
  `0000-01-01`.

* `offset` is the local time's offset from UTC in days, as a number,
  or a 1-arity function that returns the offset for a moment.

### Returns

* the local day as an integer number of days since `0000-01-01`.

### Accuracy

The day of the crossing found to within a millisecond, as
`solar_ecliptic_longitude_after/2` finds it.

### Examples

    iex> day = Date.to_gregorian_days(~D[2025-03-01])
    iex> Astro.Solar.solar_longitude_day_on_or_before(270, day, 8 / 24) |> Date.from_gregorian_days()
    ~D[2024-12-21]

### Reference

* Edward M. Reingold and Nachum Dershowitz, *Calendrical
  Calculations*, `estimate-prior-solar-longitude` and the winter
  solstice of the Chinese calendar.

# `solar_noon`
*since 2.10.0* 

```elixir
@spec solar_noon(Astro.Time.moment(), Astro.longitude()) :: Astro.Time.moment()
```

Returns the moment of solar noon on a day at a longitude.

Solar noon is the moment the Sun crosses a location's meridian,
when its local hour angle (the local apparent sidereal time less
the Sun's apparent right ascension) is zero. The search starts at
12:00 local mean time, `longitude / 360` of a day before 12:00 UT,
which is within about 16 minutes of the answer (the equation of
time, `equation_of_time/1`). Each step then moves the moment by the
hour angle divided by the 360° a day at which it grows, until the
step is under a millisecond; three steps are usually enough.

### Arguments

* `t` is the moment of 0h UT on the day of solar noon.

* `longitude` is the longitude in degrees of the location from
  which solar noon is to be measured. West is negative.

### Returns

* the moment of solar noon.

### Accuracy

The hour angle is solved to a millisecond, so solar noon is as
accurate as the Sun's right ascension and the sidereal time; see
`Astro.solar_noon/2`.

### Examples

    iex> midnight = Astro.Time.date_time_to_moment(~D[2019-12-06])
    iex> noon = Astro.Solar.solar_noon(midnight, 151.20666584)
    iex> {:ok, date_time} = Astro.Time.date_time_from_moment(noon)
    iex> DateTime.truncate(date_time, :second)
    ~U[2019-12-06 01:45:52Z]

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 15 (rising, transit and setting).

# `solar_noon_utc`

```elixir
@spec solar_noon_utc(float(), Astro.longitude()) :: float()
```

Returns solar noon as minutes since midnight UTC.

This is `solar_noon/2` measured in minutes from the start of the
day.

### Arguments

* `julian_centuries` is 0h UT on the day of solar noon, as
  Julian centuries from J2000.0 (for example, from
  `Astro.Time.julian_day_from_date/1` and
  `Astro.Time.julian_centuries_from_julian_day/1`).

* `longitude` is the longitude in degrees of the
  location from which solar noon is to be measured.
  West is negative.

### Returns

* solar noon as a `float` number of minutes since
  midnight UTC.

### Accuracy

As for `solar_noon/2`.

### Examples

    iex> julian_day = Astro.Time.julian_day_from_date(~D[2000-01-01])
    iex> julian_centuries = Astro.Time.julian_centuries_from_julian_day(julian_day)
    iex> Astro.Solar.solar_noon_utc(julian_centuries, 151.2093) |> Float.round(2)
    118.25

# `sun_equation_of_center`

```elixir
@spec sun_equation_of_center(Astro.Time.julian_centuries()) :: float()
```

Returns the Sun's equation of the centre in degrees.

### Arguments

* `julian_centuries` is any moment in time expressed
  as Julian centuries from J2000.0.

### Returns

* the equation of the centre in degrees as a `float`.

### Notes

The equation of the centre is the difference between the true
anomaly, where the Earth actually is on its ellipse, and the mean
anomaly, where it would be at a constant speed. Adding it to the mean
longitude gives the true longitude. This is the series of Meeus,
*Astronomical Algorithms*, chapter 25, in the mean anomaly.

### Accuracy

This is a mean element, not a position. Combined as in Meeus,
*Astronomical Algorithms*, chapter 25 (the Sun's true longitude is
the mean longitude plus the equation of the centre, then corrected
for aberration and nutation), the elements give the Sun's apparent
longitude to 0.003° RMS and 0.009° at most against JPL over
1900–2100, the 0.01° Meeus states for the method.

### Examples

    iex> Astro.Solar.sun_equation_of_center(0.0)
    -0.08430148943719645

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 25 (solar coordinates).

* [Equation of the center](https://en.wikipedia.org/wiki/Equation_of_the_center),
  Wikipedia.

# `sun_geometric_mean_anomaly`

```elixir
@spec sun_geometric_mean_anomaly(Astro.Time.julian_centuries()) :: float()
```

Returns the Sun's geometric mean anomaly in degrees.

### Arguments

* `julian_centuries` is any moment in time expressed
  as Julian centuries from J2000.0.

### Returns

* the mean anomaly in degrees as a `float`,
  in the range 0..360.

### Notes

The mean anomaly is the angle the Earth would have moved through
since perihelion if it travelled its orbit at a constant speed
(Meeus, *Astronomical Algorithms*, equation 25.3). It grows
uniformly with time, which makes it the natural argument for the
equation of the centre.

### Accuracy

This is a mean element, not a position. Combined as in Meeus,
*Astronomical Algorithms*, chapter 25 (the Sun's true longitude is
the mean longitude plus the equation of the centre, then corrected
for aberration and nutation), the elements give the Sun's apparent
longitude to 0.003° RMS and 0.009° at most against JPL over
1900–2100, the 0.01° Meeus states for the method.

### Examples

    iex> Astro.Solar.sun_geometric_mean_anomaly(0.0)
    357.52911

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 25 (solar coordinates).

* [Mean anomaly](https://en.wikipedia.org/wiki/Mean_anomaly),
  Wikipedia.

# `sun_geometric_mean_longitude`

```elixir
@spec sun_geometric_mean_longitude(float()) :: float()
```

Returns the Sun's geometric mean longitude in degrees.

### Arguments

* `julian_centuries` is any moment in time expressed
  as Julian centuries from J2000.0.

### Returns

* the mean solar longitude in degrees as a `float`,
  in the range 0..360.

### Notes

Mean longitude is a convenient uniform measure of how
far around its orbit a body has progressed since passing
the reference direction. While mean longitude assumes
constant speed, true longitude accounts for the body's
actual speed which varies around its elliptical orbit.
The difference between the two is the equation of the
centre.

### Accuracy

This is a mean element, not a position. Combined as in Meeus,
*Astronomical Algorithms*, chapter 25 (the Sun's true longitude is
the mean longitude plus the equation of the centre, then corrected
for aberration and nutation), the elements give the Sun's apparent
longitude to 0.003° RMS and 0.009° at most against JPL over
1900–2100, the 0.01° Meeus states for the method.

### Examples

    iex> Astro.Solar.sun_geometric_mean_longitude(0.0)
    280.46646

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 25 (solar coordinates).

* [Mean longitude](https://en.wikipedia.org/wiki/Mean_longitude),
  Wikipedia.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
