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

High-level API for common astronomical observations.

This module is the primary public interface for the Astro library. It
provides sunrise and sunset, moonrise and moonset, equinoxes and
solstices, lunar phases, crescent visibility, and the positions of the
sun and moon. Functions accept standard Elixir dates and date times.
Those that can fail, such as the rise and set times, return
`{:ok, value}` or `{:error, reason}`. The positions and phases, which
cannot fail, return their value directly.

For lower-level access see `Astro.Solar`, `Astro.Lunar`, `Astro.Time`,
`Astro.Earth` and `Astro.Ephemeris`.

How accurate the results are, inside and outside the JPL ephemeris and
far from the present, is described in the [accuracy guide](accuracy.md).

## Specifying a location

A location is a `{longitude, latitude}` tuple (note the order, which
matches `Geo.Point`), a `Geo.Point` struct, or a `Geo.PointZ` struct
that also carries an elevation in metres.

* Longitude is positive east and negative west, in degrees.

* Latitude is positive north and negative south, in degrees.

## Time zone resolution

The rise and set functions (`sunrise/3`, `sunset/3`, `moonrise/3` and
`moonset/3`) return a `DateTime` in the local time zone of the
location, resolved with `TzWorld` when it is a dependency. These
options override that:

* `:time_zone` is a time zone name, `:utc`, or `:default` to resolve
  the time zone from the location.

* `:time_zone_database` is the time zone database module, such as
  `Tz.TimeZoneDatabase`.

* `:time_zone_resolver` is a 1-arity function that receives a
  `Geo.Point` and returns `{:ok, time_zone_name}`.

## Function groups

### Solar

* `sunrise/3` and `sunset/3` return the local times of sunrise and
  sunset.

* `solar_noon/2` returns the solar noon for a location and date.

* `hours_of_daylight/2` and `duration_of_daylight/2` return the length
  of the day.

* `sun_position_at/1` returns the sun's right ascension, declination
  and distance.

* `sun_azimuth_elevation/2` returns the sun's azimuth and elevation at
  a date time.

* `sun_apparent_longitude/1` returns the sun's apparent ecliptic
  longitude.

### Lunar

* `moonrise/3` and `moonset/3` return the local times of moonrise and
  moonset.

* `moon_position_at/1` returns the moon's right ascension, declination
  and distance.

* `illuminated_fraction_of_moon_at/1` returns the fraction of the moon
  that is illuminated.

* `lunar_phase_at/1` returns the phase angle, from 0° to 360°.

* `lunar_phase_emoji/1` returns the emoji for a phase angle.

### New moon and phase searches

* `date_time_new_moon_before/1`, `date_time_new_moon_at_or_after/1` and
  `date_time_new_moon_nearest/1` find new moons.

* `date_time_lunar_phase_at_or_before/2` and
  `date_time_lunar_phase_at_or_after/2` find any phase.

### Crescent visibility

* `new_visible_crescent/4` predicts the visibility of the new crescent
  moon.

### Equinoxes and solstices

* `equinox/3` returns the March or September equinox.

* `solstice/3` returns the June or December solstice.

# `altitude`

```elixir
@type altitude() :: float()
```

# `angle`

```elixir
@type angle() :: number()
```

# `astronomical_units`

```elixir
@type astronomical_units() :: number()
```

# `date`

```elixir
@type date() :: Calendar.date() | Calendar.datetime()
```

# `degrees`

```elixir
@type degrees() :: float()
```

# `kilometers`

```elixir
@type kilometers() :: number()
```

# `latitude`

```elixir
@type latitude() :: float()
```

# `location`

```elixir
@type location() ::
  {longitude(), latitude()}
  | {longitude(), latitude(), altitude()}
  | Geo.Point.t()
  | Geo.PointZ.t()
```

# `longitude`

```elixir
@type longitude() :: float()
```

# `meters`

```elixir
@type meters() :: number()
```

# `method`

```elixir
@type method() :: :odeh | :yallop | :schaefer
```

# `options`

```elixir
@type options() :: keyword()
```

# `phase`

```elixir
@type phase() :: angle()
```

# `radians`

```elixir
@type radians() :: float()
```

# `date_time_lunar_phase_at_or_after`
*since 0.5.0* 

```elixir
@spec date_time_lunar_phase_at_or_after(date(), phase()) ::
  {:ok, DateTime.t()}
  | {:error, :invalid_date | :invalid_phase | :year_out_of_range}
```

Returns the date time of a given
lunar phase at or after a given
date time or date.

The lunar phase (see `lunar_phase_at/1`) is the Moon's apparent
ecliptic longitude less the Sun's: 0° at new moon, 90° at first
quarter, 180° at full moon and 270° at last quarter. The search
estimates when the phase is next reached at or after the date from
the mean synodic month of 29.530589 days, then bisects a four-day
bracket around the estimate until it is narrower than a
millisecond. The phase comes from the JPL DE440s ephemeris where
the loaded kernel covers the date, and from the analytical series
elsewhere.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`.

* `phase` is the required lunar phase expressed
  as a float number of degrees between `0.0` and
  `360.0`.

### Returns

* `{:ok, date_time}`, the UTC `t:DateTime.t/0` at which the phase
  occurs.

* `{:error, :invalid_date}` if `date_time` is not a valid date or
  date time in its calendar.

* `{:error, :year_out_of_range}` if `date_time` is more than 50,000
  years from J2000, the range of the new moon searches.

* `{:error, :invalid_phase}` if `phase` is not a number from 0 to
  360.

### Accuracy

The search ends within a millisecond of the crossing. For the new
moon of 18 February 1977, Meeus gives 3h37m40s TD as the correct
time (*Astronomical Algorithms*, example 49.a); Astro gives
3h37m39.9s TD. Outside the ephemeris the series place the phases
within about 6 s RMS of JPL over 1900–2100 (new moons 20 s and full
moons 30 s at most). Far from the present the result is limited by
ΔT; the [accuracy guide](accuracy.md) gives the details.

### Examples

    iex> Astro.date_time_lunar_phase_at_or_after(~D[2021-08-01], Astro.Lunar.full_moon_phase())
    {:ok, ~U[2021-08-22 12:01:58.152306Z]}

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 49 (phases of the Moon).

* [Lunar phase](https://en.wikipedia.org/wiki/Lunar_phase) and
  [Bisection method](https://en.wikipedia.org/wiki/Bisection_method),
  Wikipedia.

# `date_time_lunar_phase_at_or_before`
*since 0.5.0* 

```elixir
@spec date_time_lunar_phase_at_or_before(date(), phase()) ::
  {:ok, DateTime.t()}
  | {:error, :invalid_date | :invalid_phase | :year_out_of_range}
```

Returns the date time of a given
lunar phase at or before a given
date time or date.

The lunar phase (see `lunar_phase_at/1`) is the Moon's apparent
ecliptic longitude less the Sun's: 0° at new moon, 90° at first
quarter, 180° at full moon and 270° at last quarter. The search
estimates when the phase is next reached at or before the date from
the mean synodic month of 29.530589 days, then bisects a four-day
bracket around the estimate until it is narrower than a
millisecond. The phase comes from the JPL DE440s ephemeris where
the loaded kernel covers the date, and from the analytical series
elsewhere.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`.

* `phase` is the required lunar phase expressed
  as a float number of degrees between `0.0` and
  `3660.0`.

### Returns

* `{:ok, date_time}`, the UTC `t:DateTime.t/0` at which the phase
  occurs.

* `{:error, :invalid_date}` if `date_time` is not a valid date or
  date time in its calendar.

* `{:error, :year_out_of_range}` if `date_time` is more than 50,000
  years from J2000, the range of the new moon searches.

* `{:error, :invalid_phase}` if `phase` is not a number from 0 to
  360.

### Accuracy

The search ends within a millisecond of the crossing. For the new
moon of 18 February 1977, Meeus gives 3h37m40s TD as the correct
time (*Astronomical Algorithms*, example 49.a); Astro gives
3h37m39.9s TD. Outside the ephemeris the series place the phases
within about 6 s RMS of JPL over 1900–2100 (new moons 20 s and full
moons 30 s at most). Far from the present the result is limited by
ΔT; the [accuracy guide](accuracy.md) gives the details.

### Examples

    iex> Astro.date_time_lunar_phase_at_or_before(~D[2021-08-01], Astro.Lunar.new_moon_phase())
    {:ok, ~U[2021-07-10 01:16:37.291067Z]}

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 49 (phases of the Moon).

* [Lunar phase](https://en.wikipedia.org/wiki/Lunar_phase) and
  [Bisection method](https://en.wikipedia.org/wiki/Bisection_method),
  Wikipedia.

# `date_time_new_moon_at_or_after`
*since 0.5.0* 

```elixir
@spec date_time_new_moon_at_or_after(date()) ::
  {:ok, DateTime.t()} | {:error, :invalid_date | :year_out_of_range}
```

Returns the date time of the new
moon at or after a given date or
date time.

A new moon is the moment the lunar phase (see `lunar_phase_at/1`)
passes 0°: the Moon's apparent ecliptic longitude equals the Sun's.
The Moon is then between the Earth and the Sun with its dark side
towards us. This is a conjunction in longitude; the Moon can pass
up to about 5° north or south of the Sun, which is why most new
moons are not eclipses.

The search numbers lunations as Meeus, *Astronomical Algorithms*,
chapter 49, does, and that chapter's series estimates each
lunation's new moon to within seconds near the present. The
estimates of two consecutive lunations bracket the date, and each
is refined to the moment the phase passes 0° by the secant method,
which converges in a few steps, to under a millisecond. The phase
comes from the JPL DE440s ephemeris where the loaded kernel covers
the date, and from the analytical series elsewhere.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`.

### Returns

* `{:ok, date_time}`, the UTC `t:DateTime.t/0` at which the new
  moon occurs.

* `{:error, :invalid_date}` if `date_time` is not a valid date or
  date time in its calendar.

* `{:error, :year_out_of_range}` if `date_time` is more than 50,000
  years from J2000, beyond which the Meeus chapter 49 estimates can no
  longer tell the lunations apart.

### Accuracy

For the new moon of 18 February 1977, Meeus gives 3h37m40s TD as
the correct time (*Astronomical Algorithms*, example 49.a); Astro
gives 3h37m39.9s TD. Outside the ephemeris the series place new
moons within 6.0 s RMS of JPL over 1900–2100, and 20 s at most. Far
from the present the result is limited by ΔT, uncertain by minutes
before about 1000 and after about 2500; the
[accuracy guide](accuracy.md) gives the details.

### Examples

    iex> Astro.date_time_new_moon_at_or_after(~D[2021-08-23])
    {:ok, ~U[2021-09-07 00:51:46.307784Z]}

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 49 (phases of the Moon).

* [New moon](https://en.wikipedia.org/wiki/New_moon) and
  [Secant method](https://en.wikipedia.org/wiki/Secant_method),
  Wikipedia.

# `date_time_new_moon_before`
*since 0.5.0* 

```elixir
@spec date_time_new_moon_before(date()) ::
  {:ok, DateTime.t()} | {:error, :invalid_date | :year_out_of_range}
```

Returns the date time of the new
moon before a given date or date time.

A new moon is the moment the lunar phase (see `lunar_phase_at/1`)
passes 0°: the Moon's apparent ecliptic longitude equals the Sun's.
The Moon is then between the Earth and the Sun with its dark side
towards us. This is a conjunction in longitude; the Moon can pass
up to about 5° north or south of the Sun, which is why most new
moons are not eclipses.

The search numbers lunations as Meeus, *Astronomical Algorithms*,
chapter 49, does, and that chapter's series estimates each
lunation's new moon to within seconds near the present. The
estimates of two consecutive lunations bracket the date, and each
is refined to the moment the phase passes 0° by the secant method,
which converges in a few steps, to under a millisecond. The phase
comes from the JPL DE440s ephemeris where the loaded kernel covers
the date, and from the analytical series elsewhere.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`.

### Returns

* `{:ok, date_time}`, the UTC `t:DateTime.t/0` at which the new
  moon occurs.

* `{:error, :invalid_date}` if `date_time` is not a valid date or
  date time in its calendar.

* `{:error, :year_out_of_range}` if `date_time` is more than 50,000
  years from J2000, beyond which the Meeus chapter 49 estimates can no
  longer tell the lunations apart.

### Accuracy

For the new moon of 18 February 1977, Meeus gives 3h37m40s TD as
the correct time (*Astronomical Algorithms*, example 49.a); Astro
gives 3h37m39.9s TD. Outside the ephemeris the series place new
moons within 6.0 s RMS of JPL over 1900–2100, and 20 s at most. Far
from the present the result is limited by ΔT, uncertain by minutes
before about 1000 and after about 2500; the
[accuracy guide](accuracy.md) gives the details.

### Examples

    iex> Astro.date_time_new_moon_before(~D[2021-08-23])
    {:ok, ~U[2021-08-08 13:50:08.051917Z]}

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 49 (phases of the Moon).

* [New moon](https://en.wikipedia.org/wiki/New_moon) and
  [Secant method](https://en.wikipedia.org/wiki/Secant_method),
  Wikipedia.

# `date_time_new_moon_nearest`
*since 2.0.0* 

```elixir
@spec date_time_new_moon_nearest(date()) ::
  {:ok, DateTime.t()} | {:error, :invalid_date | :year_out_of_range}
```

Returns the date time of the new
moon nearest to a given date or date time.

A new moon is the moment the lunar phase (see `lunar_phase_at/1`)
passes 0°: the Moon's apparent ecliptic longitude equals the Sun's.
The Moon is then between the Earth and the Sun with its dark side
towards us. This is a conjunction in longitude; the Moon can pass
up to about 5° north or south of the Sun, which is why most new
moons are not eclipses.

It is the nearer of the new moons found by the lunar phase searches
either side of the date (see `date_time_lunar_phase_at_or_before/2`
and `date_time_lunar_phase_at_or_after/2`), and agrees with
`date_time_new_moon_before/1` and `date_time_new_moon_at_or_after/1`
to a millisecond.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`.

### Returns

* `{:ok, date_time}`, the UTC `t:DateTime.t/0` at which the new
  moon occurs.

* `{:error, :invalid_date}` if `date_time` is not a valid date or
  date time in its calendar.

* `{:error, :year_out_of_range}` if `date_time` is more than 50,000
  years from J2000, beyond which the Meeus chapter 49 estimates can no
  longer tell the lunations apart.

### Accuracy

For the new moon of 18 February 1977, Meeus gives 3h37m40s TD as
the correct time (*Astronomical Algorithms*, example 49.a); Astro
gives 3h37m39.9s TD. Outside the ephemeris the series place new
moons within 6.0 s RMS of JPL over 1900–2100, and 20 s at most. Far
from the present the result is limited by ΔT, uncertain by minutes
before about 1000 and after about 2500; the
[accuracy guide](accuracy.md) gives the details.

### Examples

    iex> Astro.date_time_new_moon_nearest(~D[2021-08-23])
    {:ok, ~U[2021-08-08 13:50:08.051866Z]}

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 49 (phases of the Moon).

* [New moon](https://en.wikipedia.org/wiki/New_moon) and
  [Secant method](https://en.wikipedia.org/wiki/Secant_method),
  Wikipedia.

# `duration_of_daylight`
*since 2.3.0* 

```elixir
@spec duration_of_daylight(location(), Calendar.date()) ::
  {:ok, Duration.t()} | {:error, atom()}
```

Returns the duration of daylight for a given location on a
given date as a `t:Duration.t/0`.

This is the same calculation as `hours_of_daylight/2` but,
because a `t:Duration.t/0` is not bounded like a `t:Time.t/0`,
it can represent a full 24 hours of daylight (returned as
`%Duration{hour: 24}`) during the polar summer rather than
capping at `~T[23:59:59]`.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0` that
  also carries an elevation in metres. Longitude comes first.

* `date` is any `t:Date.t/0` in the Gregorian calendar
  (for example, `Calendar.ISO`).

### Returns

* `{:ok, duration}` where `duration` is a `t:Duration.t/0`
  between `%Duration{}` (no daylight) and `%Duration{hour: 24}`
  (24 hours of daylight).

* `{:error, reason}` for an invalid location or date, or if the
  time zone for the location cannot be resolved, as for
  `sunrise/3`.

### Accuracy

It is taken from `sunrise/3` and `sunset/3`, each within 1.6 s of
Skyfield for the same definition of the horizon, and is rounded
down to the second. Refraction in the real atmosphere moves both
events, by different amounts.

### Examples

    iex> Astro.duration_of_daylight({151.20666584, -33.8559799094}, ~D[2019-12-10])
    {:ok, %Duration{hour: 14, minute: 20, second: 50}}

    # 24 hours of daylight in the polar summer, uncapped
    iex> Astro.duration_of_daylight({-62.3481, 82.5018}, ~D[2019-06-07])
    {:ok, %Duration{hour: 24}}

    # No daylight in the polar winter
    iex> Astro.duration_of_daylight({-62.3481, 82.5018}, ~D[2019-12-07])
    {:ok, %Duration{}}

### Reference

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

* [Daytime](https://en.wikipedia.org/wiki/Daytime),
  [Midnight sun](https://en.wikipedia.org/wiki/Midnight_sun) and
  [Polar night](https://en.wikipedia.org/wiki/Polar_night),
  Wikipedia.

# `equinox`

```elixir
@spec equinox(Calendar.year(), :march | :september, options()) ::
  {:ok, DateTime.t()}
  | {:error,
     :invalid_year
     | :invalid_event
     | :year_out_of_range
     | :time_zone_not_found
     | :utc_only_time_zone_database
     | :invalid_time_zone_database}
```

Returns the datetime of either the March or September
equinox, in UTC or a requested time zone.

An equinox is the moment the Sun's apparent ecliptic longitude
reaches 0° (the March equinox) or 180° (the September equinox).
The Sun is then very nearly on the celestial equator, heading
north in March and south in September, and day and night are of
almost equal length everywhere.

The moment is found from the Sun's apparent ecliptic longitude
(see `sun_apparent_longitude/1`). Starting 20 days before the 1st
of the month, the Sun's mean motion of 360° a tropical year
estimates when it reaches the target longitude, and a ten-day
bracket around that estimate is bisected until it is narrower than
a millisecond. The longitude comes from the JPL DE440s ephemeris where the
loaded kernel covers the year, and from the analytical series for
any other year.

### Arguments

* `year` is the Gregorian year for which the equinox is
  to be calculated, within 50,000 years of 2000.

* `event` is either `:march` or `:september` indicating
  which of the two annual equinox datetimes is required.

* `options` is a keyword list of options.

### Options

* `:time_zone` is `:utc` (the default) or a time zone
  name such as `"Asia/Tokyo"`. The equinox is one instant
  everywhere; the time zone decides the local date and
  time it is given in, and so the civil day it falls on.

* `:time_zone_database` is the module implementing
  `Calendar.TimeZoneDatabase` in which a named time zone
  is looked up. The default is the configured database,
  `Calendar.get_time_zone_database/0`. UTC needs none.

### Returns

* `{:ok, datetime}`, the equinox in the requested time
  zone.

* `{:error, :time_zone_not_found}` if the time zone is
  not known to the time zone database.

* `{:error, :utc_only_time_zone_database}` if a time zone
  other than UTC is requested and no time zone database
  is configured.

* `{:error, :invalid_time_zone_database}` if
  `:time_zone_database` is not a time zone database.

* `{:error, :invalid_year}` if `year` is not an integer.

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

* `{:error, :invalid_event}` if `event` is not `:march` or
  `:september`.

### Accuracy

The search ends within a millisecond of the crossing. For the June
solstice of 1962, Meeus gives 21h24m42s TD from the full VSOP87
theory (*Astronomical Algorithms*, chapter 27); Astro gives
21:24:07.8 UTC, which is 21h24m42.0s TD. Outside the ephemeris the
series put the events 14 s early on average over 1900–2100, 18 s
RMS and 50 s at most. Far from the present the time is limited by
ΔT, which is uncertain by minutes before about 1000 and after
about 2500; the [accuracy guide](accuracy.md) gives the details.

### Examples

    iex> {:ok, dt} = Astro.equinox 2019, :march
    iex> DateTime.truncate(dt, :second)
    ~U[2019-03-20 21:58:24Z]
    iex> {:ok, dt} = Astro.equinox 2019, :september
    iex> DateTime.truncate(dt, :second)
    ~U[2019-09-23 07:50:08Z]
    iex> {:ok, dt} = Astro.equinox 900, :march
    iex> DateTime.to_date(dt)
    ~D[0900-03-20]

    # The March 2019 equinox is on the 20th in UTC but the 21st in Tokyo
    iex> {:ok, dt} = Astro.equinox(2019, :march, time_zone: "Asia/Tokyo")
    iex> DateTime.to_date(dt)
    ~D[2019-03-21]

    iex> Astro.equinox(2019, :march,
    ...>   time_zone: "Asia/Tokyo",
    ...>   time_zone_database: Calendar.UTCOnlyTimeZoneDatabase
    ...> )
    {:error, :utc_only_time_zone_database}

### Reference

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

* United States Naval Observatory, [Earth's
  Seasons](https://aa.usno.navy.mil/data/Earth_Seasons).

* [Equinox](https://en.wikipedia.org/wiki/Equinox) and
  [Tropical year](https://en.wikipedia.org/wiki/Tropical_year), Wikipedia.

# `first_visible_crescent_on_or_after`
*since 2.10.0* 

```elixir
@spec first_visible_crescent_on_or_after(location(), date(), method(), keyword()) ::
  {:ok, Date.t()} | {:error, atom()}
```

Returns the evening on which the next new crescent is first seen,
on or after a date.

See `first_visible_crescent_on_or_before/4` for the first sighting.
The month after the one `date` falls in begins the day after the
first sighting on or after the day after `date`.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0`.
  Longitude comes first.

* `date` is a `t:Date.t/0`, the evening to search forward from.

* `method` is `:odeh` (the default), `:yallop` or `:schaefer`.

* `options` is a keyword list of options, as for
  `first_visible_crescent_on_or_before/4`.

### Returns

* `{:ok, date}`, the evening of the first sighting, or the errors of
  `first_visible_crescent_on_or_before/4`.

### Accuracy

As for `first_visible_crescent_on_or_before/4`.

### Examples

    iex> Astro.first_visible_crescent_on_or_after({39.8262, 21.4225}, ~D[2024-12-20])
    {:ok, ~D[2025-01-01]}

### Reference

* Edward M. Reingold and Nachum Dershowitz, *Calendrical
  Calculations*, `phasis-on-or-after`.

# `first_visible_crescent_on_or_before`
*since 2.10.0* 

```elixir
@spec first_visible_crescent_on_or_before(location(), date(), method(), keyword()) ::
  {:ok, Date.t()} | {:error, atom()}
```

Returns the evening on which the last new crescent was first seen,
on or before a date.

A lunar month that begins at the first sighting of the new crescent,
as an observational Islamic calendar's does, begins the day after the
evening it is first seen. A lunation's crescent is first seen on the
first evening, from the evening before the day of its new moon, on
which `new_visible_crescent/4` classes it visible (`:A`, `:B` or `:C`
by default). This is the latest such evening on or before `date`:
the month `date` falls in began the day after it.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0`.
  Longitude comes first.

* `date` is a `t:Date.t/0`, the evening to search back from.

* `method` is `:odeh` (the default), `:yallop` or `:schaefer`; see
  `new_visible_crescent/3`.

* `options` is a keyword list of options.

### Options

* `:visible` is the list of categories that count as a sighting. The
  default is `[:A, :B, :C]`.

* `:utc_offset` and `:extinction` are as for `new_visible_crescent/4`.

### Returns

* `{:ok, date}`, the evening of the first sighting.

* `{:error, :no_visible_crescent}` if a lunation's crescent is not
  seen within 30 evenings of its new moon, as at high latitudes.

* `{:error, :invalid_location}`, `{:error, :invalid_date}`,
  `{:error, :invalid_method}`, `{:error, :invalid_options}`,
  `{:error, :invalid_visible}` or `{:error, :invalid_utc_offset}` for
  an invalid argument or option.

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

### Accuracy

As for `new_visible_crescent/3`: the evening is the one the criterion
predicts, which can differ from an actual sighting by a day when the
crescent is near the criterion's boundary.

### Examples

    iex> Astro.first_visible_crescent_on_or_before({39.8262, 21.4225}, ~D[2024-12-20])
    {:ok, ~D[2024-12-02]}

### Reference

* Mohammad Sh. Odeh, [New Criterion for Lunar Crescent
  Visibility](https://doi.org/10.1007/s10686-005-9002-5),
  *Experimental Astronomy* 18 (2004), 39–64.

* Edward M. Reingold and Nachum Dershowitz, *Calendrical
  Calculations*, `phasis-on-or-before`.

# `hours_of_daylight`

```elixir
@spec hours_of_daylight(location(), Calendar.date()) ::
  {:ok, Time.t()} | {:error, atom()}
```

Returns the number of hours of daylight for a given
location on a given date.

Daylight is the time the Sun is above the horizon during the local
calendar day, from the sunrise and sunset of `sunrise/3` and
`sunset/3` in the location's time zone. It is counted from the
events that fall within the day rather than as sunset less
sunrise, so a day near a polar circle on which the Sun sets just
after midnight and rises again a few hours later is handled. With
neither event, the day is all daylight or none, according to
whether the Sun is up.

`duration_of_daylight/2` is the same calculation returning a
`t:Duration.t/0`, which can represent a full 24 hours of daylight.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0` that
  also carries an elevation in metres. Longitude comes first.

* `date` is any `t:Date.t/0` in the Gregorian
  calendar (for example, `Calendar.ISO`).

### Returns

* `{:ok, time}` where `time` is a `Time.t()`. The maximum value is
  `~T[23:59:59]`, representing 24 hours of daylight (a `Time.t()`
  cannot hold `24:00:00`).

* `{:error, reason}` for an invalid location or date, or if the
  time zone for the location cannot be resolved, as for
  `sunrise/3`.

### Accuracy

It is taken from `sunrise/3` and `sunset/3`, each within 1.6 s of
Skyfield for the same definition of the horizon, and is rounded
down to the second. Refraction in the real atmosphere moves both
events, by different amounts.

### Examples

    iex> Astro.hours_of_daylight({151.20666584, -33.8559799094}, ~D[2019-12-10])
    {:ok, ~T[14:20:50]}

    # No sunset in summer
    iex> Astro.hours_of_daylight({-62.3481, 82.5018}, ~D[2019-06-07])
    {:ok, ~T[23:59:59]}

    # No sunrise in winter
    iex> Astro.hours_of_daylight({-62.3481, 82.5018}, ~D[2019-12-07])
    {:ok, ~T[00:00:00]}

### Reference

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

* [Daytime](https://en.wikipedia.org/wiki/Daytime),
  [Midnight sun](https://en.wikipedia.org/wiki/Midnight_sun) and
  [Polar night](https://en.wikipedia.org/wiki/Polar_night),
  Wikipedia.

# `illuminated_fraction_of_moon_at`
*since 0.6.0* 

```elixir
@spec illuminated_fraction_of_moon_at(date()) :: number()
```

Returns the fraction of the Moon's disc that is illuminated at a
date or date time.

The illuminated fraction k is the part of the Moon's apparent disc
lit by the Sun, as seen from the centre of the Earth: 0 at new moon
and 1 at full moon. It depends on the phase angle i, the angle at
the Moon between the directions to the Sun and to the Earth:

    k = (1 + cos i) / 2

The phase angle is found from the Sun's and the Moon's apparent
positions (see `sun_position_at/1` and `moon_position_at/1`), as in
Meeus, *Astronomical Algorithms*, chapter 48: their elongation ψ
from

    cos ψ = sin δ₀ sin δ + cos δ₀ cos δ cos(α₀ − α)

and then

    tan i = R sin ψ / (Δ − R cos ψ)

where α₀ and δ₀ are the Sun's right ascension and declination, α
and δ the Moon's, R the Sun's distance and Δ the Moon's.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`. A date is
  taken at 00:00 UTC.

### Returns

* a `float` value between `0.0` and `1.0`
  representing the fractional illumination of
  the moon.

### Accuracy

For Meeus's example 48.a (1992 April 12, 0h TD) the result is
0.67856, against Meeus's 0.6786. Outside the ephemeris the series
agree with JPL over 1900–2100 to 0.0000045 RMS and 0.000018 at most.
The [accuracy guide](accuracy.md) gives the details.

### Examples

    iex> fraction = Astro.illuminated_fraction_of_moon_at(~D[2017-03-16])
    iex> Float.round(fraction, 4)
    0.8884

    iex> fraction = Astro.illuminated_fraction_of_moon_at(~D[1992-04-12])
    iex> Float.round(fraction, 4)
    0.6786

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 48 (illuminated fraction of the Moon's disk).

* [Phase angle](https://en.wikipedia.org/wiki/Phase_angle_(astronomy))
  and [Lunar phase](https://en.wikipedia.org/wiki/Lunar_phase),
  Wikipedia.

# `is_lunar_phase`
*macro* 

Guards that a value is a lunar phase angle.

### Arguments

* `phase` is the value to test.

### Returns

* `true` if `phase` is a number from 0.0 to 360.0 inclusive, otherwise
  `false`.

### Examples

    iex> require Astro
    iex> Astro.is_lunar_phase(90.0)
    true
    iex> Astro.is_lunar_phase(400.0)
    false

# `lunar_eclipse_at`
*since 2.10.0* 

```elixir
@spec lunar_eclipse_at(location(), Astro.Lunar.Eclipse.t() | date(), options()) ::
  {:ok, Astro.Lunar.Eclipse.Local.t()}
  | {:error,
     :not_visible
     | :invalid_location
     | :invalid_date
     | :year_out_of_range
     | :invalid_shadow
     | :invalid_options
     | :time_zone_not_found
     | :time_zone_not_resolved
     | :tz_world_data_not_installed
     | :tz_world_backend_not_running
     | :invalid_time_zone_resolver
     | :invalid_time_zone_database
     | :utc_only_time_zone_database}
```

Returns the local circumstances of a lunar eclipse at a location:
the eclipse's contacts and greatest eclipse, and the Moon's altitude
at each.

A lunar eclipse is the Moon passing through the Earth's shadow, so
its contacts happen at the same instants for everyone who can see
the Moon; what depends on the place is whether the Moon is above the
horizon while it happens. The Moon's altitude is topocentric,
allowing for its parallax of up to about 1° from the observer's
position on the Earth, and geometric, without refraction.
`Astro.Lunar.Eclipse.Local` describes the calculation.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0` that
  also carries a height in metres above the ellipsoid. Longitude
  comes first.

* `eclipse` is an `t:Astro.Lunar.Eclipse.t/0`, as
  `lunar_eclipse_at_or_after/2` returns it, or a `t:Date.t/0` or
  `t:DateTime.t/0`, or any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`, for the first lunar
  eclipse whose greatest eclipse is at or after it.

* `options` is a keyword list of options.

### Options

* `:shadow` is the convention for enlarging the Earth's shadow for
  its atmosphere, `:danjon` (the default) or `:chauvenet`, as for
  `lunar_eclipse_at_or_after/2`. An eclipse given as `eclipse` is
  found again with this convention.

* `:time_zone` is the time zone of the returned date times:
  `:default`, the time zone of the location, which is the default;
  `:utc`; or a time zone name.

* `:time_zone_database` is the module implementing the
  `Calendar.TimeZoneDatabase` behaviour. The default is the
  configured Elixir time zone database.

* `:time_zone_resolver` is a 1-arity function that receives a
  `%Geo.Point{coordinates: {lng, lat}}` and returns
  `{:ok, time_zone_name}` or `{:error, reason}`. The default is
  `TzWorld.timezone_at/1` when `:tz_world` is a dependency.

### Returns

* `{:ok, local}`, an `t:Astro.Lunar.Eclipse.Local.t/0` with the
  fields of `t:Astro.Lunar.Eclipse.t/0`, its instants as date times
  in the requested time zone, and the Moon's altitude in degrees at
  greatest eclipse and at each contact the eclipse has. A contact the
  Moon is below the horizon for has a negative altitude.

* `{:error, :not_visible}` if the Moon is below the horizon from the
  first penumbral contact to the last. The Moon has risen when its
  upper limb is above the horizon, allowing for standard refraction,
  as for `moonrise/3`.

* `{:error, :invalid_location}` if `location` is not a valid
  location.

* `{:error, :invalid_date}` if `eclipse` is neither an eclipse nor a
  valid date or date time in its calendar.

* `{:error, :year_out_of_range}` if `eclipse` is a date more than
  50,000 years from J2000.

* `{:error, :invalid_shadow}` if `:shadow` is not `:danjon` or
  `:chauvenet`.

* `{:error, :invalid_options}` if `options` is not a keyword list.

* The time zone errors of `sunrise/3`.

### Accuracy

The instants are those of `lunar_eclipse_at_or_after/2`. The Moon's
altitude at the instants of `moonrise/3` and `moonset/3` is the
horizon they use to within 0.00001°, and it is as accurate as the
Moon's position otherwise. Refraction near the horizon departs from
the standard 34′ with the weather, so whether a Moon that rises or
sets during an eclipse is seen at a contact within a few minutes of
it is uncertain by that much.

### Examples

    iex> {:ok, local} = Astro.lunar_eclipse_at({-96.80, 32.78}, ~D[2025-03-01], time_zone: "America/Chicago")
    iex> {local.type, Float.round(local.moon_altitudes.greatest, 1)}
    {:total, 59.0}
    iex> DateTime.truncate(local.greatest, :second)
    #DateTime<2025-03-14 01:58:46-05:00 CDT America/Chicago>

    iex> Astro.lunar_eclipse_at({77.21, 28.61}, ~D[2025-03-01], time_zone: :utc)
    {:error, :not_visible}

### Reference

* Fred Espenak and Jean Meeus, *Five Millennium Canon of Lunar
  Eclipses: −1999 to +3000*, NASA/TP-2009-214172 (2009).

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 40 (correction for parallax).

# `lunar_eclipse_at_or_after`
*since 2.10.0* 

```elixir
@spec lunar_eclipse_at_or_after(date(), Keyword.t()) ::
  {:ok, Astro.Lunar.Eclipse.t()}
  | {:error,
     :invalid_date | :year_out_of_range | :invalid_shadow | :invalid_options}
```

Returns the first lunar eclipse whose greatest eclipse is at or after
a date or date time.

An eclipse is found at a full moon near a node of the Moon's orbit.
At each full moon the Moon's angular distance from the axis of the
Earth's shadow, the point opposite the Sun, is compared with the
radius of the penumbra; where it can reach it, greatest eclipse is
the moment of least distance, found by golden section search, and
each contact the moment the Moon's limb meets a shadow's edge, found
by bisection. `Astro.Lunar.Eclipse` describes the geometry. The
positions come from the JPL DE440s ephemeris where the loaded kernel
covers the date, and from the analytical series elsewhere.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`, within 50,000
  years of J2000.

* `options` is a keyword list of options.

### Options

* `:shadow` is the convention for enlarging the Earth's shadow for
  its atmosphere: `:danjon` (the default, as NASA's eclipse
  catalogues use it) or `:chauvenet` (the older almanac convention,
  which makes the umbral magnitude about 0.006 larger).

### Returns

* `{:ok, eclipse}`, an `t:Astro.Lunar.Eclipse.t/0` with the type
  (`:penumbral`, `:partial` or `:total`), gamma, the penumbral and
  umbral magnitudes, greatest eclipse and the contacts as UTC
  `t:DateTime.t/0`s, `nil` for the contacts the eclipse does not
  have, and its Saros series as NASA numbers it (`Astro.Saros`).

* `{:error, :invalid_date}` if `date_time` is not a valid date or
  date time in its calendar.

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

* `{:error, :invalid_shadow}` if `:shadow` is not `:danjon` or
  `:chauvenet`.

* `{:error, :invalid_options}` if `options` is not a keyword list.

### Accuracy

Against NASA's catalogue for 1901–2100, compared in Terrestrial Time
so that ΔT does not enter, greatest eclipse is within 3 s (0.5 s on
average), gamma within 0.00015, both magnitudes within 0.0004, the
partial and total phases' durations within 0.1 minute, and every
eclipse's type is the same. Outside the ephemeris the series place
greatest eclipse within 15 s of NASA's in the nineteenth century,
40 s in the thirtieth and two and a half minutes around 950 BCE,
with magnitudes within 0.002 and the same types. In Universal Time
every instant also carries ΔT's uncertainty; the
[accuracy guide](accuracy.md) gives it.

### Examples

    iex> {:ok, eclipse} = Astro.lunar_eclipse_at_or_after(~D[2025-03-01])
    iex> {eclipse.type, DateTime.truncate(eclipse.greatest, :second)}
    {:total, ~U[2025-03-14 06:58:46Z]}

    iex> {:ok, eclipse} = Astro.lunar_eclipse_at_or_after(~D[2025-03-15])
    iex> {eclipse.type, DateTime.to_date(eclipse.greatest)}
    {:total, ~D[2025-09-07]}

### Reference

* Fred Espenak and Jean Meeus, *Five Millennium Canon of Lunar
  Eclipses: −1999 to +3000*, NASA/TP-2009-214172 (2009), and the
  [Five Millennium Catalog of Lunar Eclipses](https://eclipse.gsfc.nasa.gov/LEcat5/LEcatalog.html).

* [Enlargement of Earth's Shadows](https://eclipse.gsfc.nasa.gov/LEcat5/shadow.html),
  NASA, on Danjon's and Chauvenet's conventions.

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 54 (eclipses).

# `lunar_eclipse_before`
*since 2.10.0* 

```elixir
@spec lunar_eclipse_before(date(), Keyword.t()) ::
  {:ok, Astro.Lunar.Eclipse.t()}
  | {:error,
     :invalid_date | :year_out_of_range | :invalid_shadow | :invalid_options}
```

Returns the last lunar eclipse whose greatest eclipse is before a
date or date time.

An eclipse is found at a full moon near a node of the Moon's orbit.
At each full moon the Moon's angular distance from the axis of the
Earth's shadow, the point opposite the Sun, is compared with the
radius of the penumbra; where it can reach it, greatest eclipse is
the moment of least distance, found by golden section search, and
each contact the moment the Moon's limb meets a shadow's edge, found
by bisection. `Astro.Lunar.Eclipse` describes the geometry. The
positions come from the JPL DE440s ephemeris where the loaded kernel
covers the date, and from the analytical series elsewhere.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`, within 50,000
  years of J2000.

* `options` is a keyword list of options.

### Options

* `:shadow` is the convention for enlarging the Earth's shadow for
  its atmosphere: `:danjon` (the default, as NASA's eclipse
  catalogues use it) or `:chauvenet` (the older almanac convention,
  which makes the umbral magnitude about 0.006 larger).

### Returns

* `{:ok, eclipse}`, an `t:Astro.Lunar.Eclipse.t/0` with the type
  (`:penumbral`, `:partial` or `:total`), gamma, the penumbral and
  umbral magnitudes, greatest eclipse and the contacts as UTC
  `t:DateTime.t/0`s, `nil` for the contacts the eclipse does not
  have, and its Saros series as NASA numbers it (`Astro.Saros`).

* `{:error, :invalid_date}` if `date_time` is not a valid date or
  date time in its calendar.

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

* `{:error, :invalid_shadow}` if `:shadow` is not `:danjon` or
  `:chauvenet`.

* `{:error, :invalid_options}` if `options` is not a keyword list.

### Accuracy

Against NASA's catalogue for 1901–2100, compared in Terrestrial Time
so that ΔT does not enter, greatest eclipse is within 3 s (0.5 s on
average), gamma within 0.00015, both magnitudes within 0.0004, the
partial and total phases' durations within 0.1 minute, and every
eclipse's type is the same. Outside the ephemeris the series place
greatest eclipse within 15 s of NASA's in the nineteenth century,
40 s in the thirtieth and two and a half minutes around 950 BCE,
with magnitudes within 0.002 and the same types. In Universal Time
every instant also carries ΔT's uncertainty; the
[accuracy guide](accuracy.md) gives it.

### Examples

    iex> {:ok, eclipse} = Astro.lunar_eclipse_before(~D[2025-03-14])
    iex> {eclipse.type, DateTime.to_date(eclipse.greatest)}
    {:partial, ~D[2024-09-18]}

### Reference

* Fred Espenak and Jean Meeus, *Five Millennium Canon of Lunar
  Eclipses: −1999 to +3000*, NASA/TP-2009-214172 (2009), and the
  [Five Millennium Catalog of Lunar Eclipses](https://eclipse.gsfc.nasa.gov/LEcat5/LEcatalog.html).

* [Enlargement of Earth's Shadows](https://eclipse.gsfc.nasa.gov/LEcat5/shadow.html),
  NASA, on Danjon's and Chauvenet's conventions.

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 54 (eclipses).

# `lunar_phase_at`
*since 0.5.0* 

```elixir
@spec lunar_phase_at(date()) :: phase()
```

Returns the lunar phase in degrees at a date or date time.

The lunar phase is the Moon's apparent ecliptic longitude less the
Sun's, from 0° to 360°: 0° at new moon, 90° at first quarter, 180°
at full moon and 270° at last quarter. It is how far the Moon has
moved ahead of the Sun around the sky since the last new moon, and
grows by about 12.2° a day. It differs from the phase angle that
sets the illuminated fraction (see `illuminated_fraction_of_moon_at/1`),
because the Moon's orbit is tilted about 5° to the ecliptic.

Both longitudes are apparent ecliptic longitudes of the date (see
`sun_apparent_longitude/1` and `moon_position_at/1`): from the JPL
DE440s ephemeris where the loaded kernel covers the moment, and
from the analytical series elsewhere.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`. A date is
  taken at 00:00 UTC.

### Returns

* the lunar phase in degrees, from 0 to 360.

### Accuracy

Outside the ephemeris the series agree with JPL over 1900–2100 to
2.8″ RMS and 11.5″ at most. The phase grows about 30″ a minute, so
2.8″ is about 6 s in the time a phase is reached. The
[accuracy guide](accuracy.md) gives the details.

### Examples

    iex> Astro.lunar_phase_at ~U[2021-08-22 12:02:02.816534Z]
    180.00068383330358

    iex> Astro.lunar_phase_at(~U[2021-07-10 01:16:37.026816Z])
    359.9999651540469

Outside the loaded ephemeris the phase comes from the analytical
series:

    iex> Astro.lunar_phase_at(~D[1000-01-01]) |> Float.round(2)
    202.55

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 47 (position of the Moon) and chapter 49 (phases of the
  Moon).

* [Lunar phase](https://en.wikipedia.org/wiki/Lunar_phase), Wikipedia.

# `lunar_phase_emoji`

```elixir
@spec lunar_phase_emoji(phase()) :: String.t()
```

Returns the moon phase as a UTF8 binary
representing an emoji of the moon phase.

Unicode has eight moon phase symbols, U+1F311 to U+1F318, from
🌑 new moon through 🌓 first quarter, 🌕 full moon and 🌗 last
quarter. Each covers 45° of lunar phase centred on its own, so 🌑 is
returned from 337.5° to 22.5° and 🌒 from 22.5° to 67.5°. The
symbols show the Moon as it is seen from the northern hemisphere,
lit on the right while it waxes.

### Arguments

* `phase` is a moon phase between `0.0` and `360.0`, as returned by
  `lunar_phase_at/1`.

### Returns

* A single grapheme string, one of the Unicode moon phase symbols.

### Examples

    iex> Astro.lunar_phase_emoji 0
    "🌑"
    iex> Astro.lunar_phase_emoji 45
    "🌒"
    iex> Astro.lunar_phase_emoji 90
    "🌓"
    iex> Astro.lunar_phase_emoji 135
    "🌔"
    iex> Astro.lunar_phase_emoji 180
    "🌕"
    iex> Astro.lunar_phase_emoji 245
    "🌖"
    iex> Astro.lunar_phase_emoji 270
    "🌗"
    iex> Astro.lunar_phase_emoji 320
    "🌘"
    iex> Astro.lunar_phase_emoji 360
    "🌑"

    iex> ~U[2021-08-22 12:02:02.816534Z]
    ...> |> Astro.lunar_phase_at()
    ...> |> Astro.lunar_phase_emoji()
    "🌕"

### Reference

* The Unicode Consortium, [Miscellaneous Symbols and
  Pictographs](https://www.unicode.org/charts/PDF/U1F300.pdf), the
  chart that holds the moon phase symbols.

# `moon_position_at`
*since 0.6.0* 

```elixir
@spec moon_position_at(date()) :: Geo.PointZ.t()
```

Returns the Moon's apparent right ascension, declination and distance
at a date or date time.

This is the Moon's apparent geocentric position: where it is seen
from the centre of the Earth, referred to the true equator and
equinox of the date. The Moon is close enough that an observer on
the Earth's surface sees it displaced by up to about 1° from this
position (its parallax); `moonrise/3`, `moonset/3` and the Odeh
crescent criterion correct for that.

Where the loaded JPL DE440s ephemeris covers the moment, the
Earth-to-Moon vector is read from it 1.282 s earlier, the light
time, and rotated from the ICRF to the true equator of date by IAU
1976 precession and IAU 1980 nutation. No annual aberration is
applied, because the Moon travels around the Sun with the Earth.
Elsewhere the Moon's ecliptic longitude, latitude and distance come
from the periodic terms of Meeus, *Astronomical Algorithms*, chapter
47, abridged from the ELP-2000/82 lunar theory, with nutation in
longitude added, and are converted to right ascension and
declination with the true obliquity of the ecliptic (chapter 13).

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`. A date is
  taken at 00:00 UTC.

### Returns

* a `t:Geo.PointZ.t/0` struct with coordinates
  `{right_ascension, declination, distance}` with properties
  `%{reference: :celestial, object: :moon}`. Right ascension is in
  degrees from −180° to 180°, declination in degrees and
  `distance` in metres.

### Accuracy

Inside the ephemeris, for Meeus's example 47.a (1992 April 12, 0h
TD) the result is within 2.1″ in right ascension, 0.3″ in
declination and 4 km of Meeus's own series, about the accuracy of
that series. Outside it the series agree with JPL over 1900–2100 to
2.7″ RMS in right ascension and 1.3″ in declination (10.1″ and 6.5″
at most), and to 2.9 km in distance. The Moon moves about 0.55″ a
second, so far from the present the uncertainty of ΔT dominates:
the 265 s uncertainty at the year 0 is about 2.4′ of position. The
[accuracy guide](accuracy.md) gives the details.

### Examples

    iex> Astro.moon_position_at(~D[1992-04-12]) |> Astro.Location.round(6)
    %Geo.PointZ{
      coordinates: {134.697251, 13.765115, 368404879.697206},
      properties: %{object: :moon, reference: :celestial},
      srid: nil
    }

### Reference

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 47 (position of the Moon).

* Ryan S. Park and others, [The JPL Planetary and Lunar Ephemerides
  DE440 and DE441](https://doi.org/10.3847/1538-3881/abd414),
  *The Astronomical Journal* 161 (2021), 105.

* [Ephemeride Lunaire Parisienne](https://en.wikipedia.org/wiki/Ephemeride_Lunaire_Parisienne)
  and [Orbit of the Moon](https://en.wikipedia.org/wiki/Orbit_of_the_Moon),
  Wikipedia.

# `moonrise`
*since 2.0.0* 

```elixir
@spec moonrise(location(), date(), options()) ::
  {:ok, DateTime.t()}
  | {:error,
     :no_time
     | :invalid_location
     | :invalid_date
     | :invalid_options
     | :invalid_utc_offset
     | :invalid_limb
     | :invalid_interpolation
     | :time_zone_not_found
     | :time_zone_not_resolved
     | :tz_world_data_not_installed
     | :tz_world_backend_not_running
     | :invalid_time_zone_resolver
     | :invalid_time_zone_database
     | :utc_only_time_zone_database}
```

Returns the moonrise for a given location and date.

Moonrise is the moment the upper limb of the Moon appears above the
horizon.

Refraction lifts the Moon by a standard 34′ at the horizon, and its
semi-diameter is 14.7′ to 16.8′ depending on its distance, so this
is the moment the Moon's centre, computed without refraction, is
34′ plus its semi-diameter below the horizon.

The Moon is close enough that where it appears depends on where it
is seen from: from the Earth's surface it is displaced by up to
about 1°, its horizontal parallax, from where it is seen from the
Earth's centre. Its altitude is therefore computed from its
topocentric position, corrected for parallax in both right
ascension and declination (Meeus, *Astronomical Algorithms*,
chapter 40), and the local apparent sidereal time. The altitude is
sampled every 24 minutes across the day to bracket the crossing,
which is then narrowed by bisection to 0.01 s. The position comes
from the JPL DE440s ephemeris where the loaded kernel covers the
date, and from the analytical series elsewhere.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0` that
  also carries an elevation in metres. Longitude comes first. An
  elevation enters the parallax but does not lower the horizon.

* `date` is a `t:Date.t/0`, `t:NaiveDateTime.t/0` or `t:DateTime.t/0`
  for the day of the moonrise.

* `options` is a keyword list of options.

### Options

* `:limb` is the part of the Moon's disk that defines the event.

  * `:upper`, the default, puts the upper limb on the apparent
    horizon, the USNO standard. The event threshold is
    `−(34′ refraction + semi-diameter)`.

  * `:center` puts the centre of the disk on the apparent horizon.
    The event threshold is `−34′` of refraction only.

* `:interpolation` is how the Moon's position is evaluated while the
  event is bisected.

  * `:direct`, the default, evaluates the JPL ephemeris at every step.

  * `:lagrange` interpolates the geocentric position quadratically
    from three points, as Meeus Ch. 15 does.

* `:time_zone` is the time zone of the returned date time: `:default`,
  the time zone of the location, which is the default; `:utc`; or a
  time zone name.

* `:utc_offset` is a whole number of seconds from UTC, from −50,400
  to 50,400. When it is given, the event is the one whose date at
  that offset is `date`, whatever time zone it is returned in: the
  location's own day, for a calendar that keeps local mean time.
  Otherwise the date is taken in the requested time zone.

* `:time_zone_database` is the module implementing the
  `Calendar.TimeZoneDatabase` behaviour. The default is the configured
  Elixir time zone database.

* `:time_zone_resolver` is a 1-arity function that receives a
  `%Geo.Point{coordinates: {lng, lat}}` and returns
  `{:ok, time_zone_name}` or `{:error, reason}`. The default is
  `TzWorld.timezone_at/1` when `:tz_world` is a dependency.

### Returns

* `{:ok, date_time}` where `date_time` is the moonrise in the requested
  time zone.

* `{:error, :invalid_location}` if `location` is not a valid
  location.

* `{:error, :invalid_date}` if `date` is not a valid date in its
  calendar.

* `{:error, :invalid_options}` if `options` is not a list.

* `{:error, :invalid_utc_offset}` if `:utc_offset` is not a whole
  number of seconds from −50,400 to 50,400.

* `{:error, :no_time}` if the Moon does not rise on that date at that
  location, as it can stay below the horizon for a whole day.

* `{:error, :invalid_limb}` if `:limb` is not `:upper` or `:center`.

* `{:error, :invalid_interpolation}` if `:interpolation` is not
  `:direct` or `:lagrange`.

* `{:error, :time_zone_not_found}` if the requested time zone is
  unknown.

* `{:error, :time_zone_not_resolved}` if no time zone can be resolved
  for the location, which happens when `:tz_world` is not a dependency
  and no `:time_zone_resolver` is given, or when the resolver returns
  neither `{:ok, time_zone}` nor `{:error, reason}`.

* `{:error, :tz_world_data_not_installed}` if `:tz_world` is a
  dependency but its data has not been installed. Run
  `mix tz_world.update` to install it.

* `{:error, :tz_world_backend_not_running}` if `:tz_world` is a
  dependency but none of its backends is running; see
  `Astro.Supervisor`.

* `{:error, :invalid_time_zone_resolver}` if `:time_zone_resolver` is
  not a 1-arity function.

* `{:error, :invalid_time_zone_database}` if `:time_zone_database` is
  not a time zone database.

* `{:error, :utc_only_time_zone_database}` if the moonrise is requested
  in a time zone other than UTC and no time zone database is
  configured.

### Accuracy

Against Skyfield, using the same ephemeris, results agree to 0.08 s
on average and 0.1 s at most. Against the USNO, which publishes to
the minute, they agree to 15.5 s on average and 29 s at most in 70
cases. Outside the ephemeris the series move the result by 0.22 s
RMS and 0.93 s at most. As for the Sun, the atmosphere matters more
than any of these: refraction at the horizon departs from the
standard 34′ with temperature and pressure, and a high observer sees
a lower horizon, and neither is modelled. The
[accuracy guide](accuracy.md) gives the details.

### Examples

    iex> {:ok, date_time} = Astro.moonrise({151.20666584, -33.8559799094}, ~D[2019-12-04])
    iex> date_time
    #DateTime<2019-12-04 12:20:54.388717+11:00 AEDT Australia/Sydney>

    iex> Astro.moonrise({-62.3481, 82.5018}, ~D[2024-01-01])
    {:error, :no_time}

### Reference

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

* United States Naval Observatory, [Rise, Set, and Twilight
  Definitions](https://aa.usno.navy.mil/faq/RST_defs).

* [Moonrise and moonset](https://en.wikipedia.org/wiki/Moonrise_and_moonset)
  and [Lunar parallax](https://en.wikipedia.org/wiki/Lunar_parallax),
  Wikipedia.

# `moonset`
*since 2.0.0* 

```elixir
@spec moonset(location(), date(), options()) ::
  {:ok, DateTime.t()}
  | {:error,
     :no_time
     | :invalid_location
     | :invalid_date
     | :invalid_options
     | :invalid_utc_offset
     | :invalid_limb
     | :invalid_interpolation
     | :time_zone_not_found
     | :time_zone_not_resolved
     | :tz_world_data_not_installed
     | :tz_world_backend_not_running
     | :invalid_time_zone_resolver
     | :invalid_time_zone_database
     | :utc_only_time_zone_database}
```

Returns the moonset for a given location and date.

Moonset is the moment the upper limb of the Moon disappears below
the horizon.

Refraction lifts the Moon by a standard 34′ at the horizon, and its
semi-diameter is 14.7′ to 16.8′ depending on its distance, so this
is the moment the Moon's centre, computed without refraction, is
34′ plus its semi-diameter below the horizon.

The Moon is close enough that where it appears depends on where it
is seen from: from the Earth's surface it is displaced by up to
about 1°, its horizontal parallax, from where it is seen from the
Earth's centre. Its altitude is therefore computed from its
topocentric position, corrected for parallax in both right
ascension and declination (Meeus, *Astronomical Algorithms*,
chapter 40), and the local apparent sidereal time. The altitude is
sampled every 24 minutes across the day to bracket the crossing,
which is then narrowed by bisection to 0.01 s. The position comes
from the JPL DE440s ephemeris where the loaded kernel covers the
date, and from the analytical series elsewhere.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0` that
  also carries an elevation in metres. Longitude comes first. An
  elevation enters the parallax but does not lower the horizon.

* `date` is a `t:Date.t/0`, `t:NaiveDateTime.t/0` or `t:DateTime.t/0`
  for the day of the moonset.

* `options` is a keyword list of options.

### Options

* `:limb` is the part of the Moon's disk that defines the event.

  * `:upper`, the default, puts the upper limb on the apparent
    horizon, the USNO standard. The event threshold is
    `−(34′ refraction + semi-diameter)`.

  * `:center` puts the centre of the disk on the apparent horizon.
    The event threshold is `−34′` of refraction only.

* `:interpolation` is how the Moon's position is evaluated while the
  event is bisected.

  * `:direct`, the default, evaluates the JPL ephemeris at every step.

  * `:lagrange` interpolates the geocentric position quadratically
    from three points, as Meeus Ch. 15 does.

* `:time_zone` is the time zone of the returned date time: `:default`,
  the time zone of the location, which is the default; `:utc`; or a
  time zone name.

* `:utc_offset` is a whole number of seconds from UTC, from −50,400
  to 50,400. When it is given, the event is the one whose date at
  that offset is `date`, whatever time zone it is returned in: the
  location's own day, for a calendar that keeps local mean time.
  Otherwise the date is taken in the requested time zone.

* `:time_zone_database` is the module implementing the
  `Calendar.TimeZoneDatabase` behaviour. The default is the configured
  Elixir time zone database.

* `:time_zone_resolver` is a 1-arity function that receives a
  `%Geo.Point{coordinates: {lng, lat}}` and returns
  `{:ok, time_zone_name}` or `{:error, reason}`. The default is
  `TzWorld.timezone_at/1` when `:tz_world` is a dependency.

### Returns

* `{:ok, date_time}` where `date_time` is the moonset in the requested
  time zone.

* `{:error, :invalid_location}` if `location` is not a valid
  location.

* `{:error, :invalid_date}` if `date` is not a valid date in its
  calendar.

* `{:error, :invalid_options}` if `options` is not a list.

* `{:error, :invalid_utc_offset}` if `:utc_offset` is not a whole
  number of seconds from −50,400 to 50,400.

* `{:error, :no_time}` if the Moon does not set on that date at that
  location, as it can stay above the horizon for a whole day.

* `{:error, :invalid_limb}` if `:limb` is not `:upper` or `:center`.

* `{:error, :invalid_interpolation}` if `:interpolation` is not
  `:direct` or `:lagrange`.

* `{:error, :time_zone_not_found}` if the requested time zone is
  unknown.

* `{:error, :time_zone_not_resolved}` if no time zone can be resolved
  for the location, which happens when `:tz_world` is not a dependency
  and no `:time_zone_resolver` is given, or when the resolver returns
  neither `{:ok, time_zone}` nor `{:error, reason}`.

* `{:error, :tz_world_data_not_installed}` if `:tz_world` is a
  dependency but its data has not been installed. Run
  `mix tz_world.update` to install it.

* `{:error, :tz_world_backend_not_running}` if `:tz_world` is a
  dependency but none of its backends is running; see
  `Astro.Supervisor`.

* `{:error, :invalid_time_zone_resolver}` if `:time_zone_resolver` is
  not a 1-arity function.

* `{:error, :invalid_time_zone_database}` if `:time_zone_database` is
  not a time zone database.

* `{:error, :utc_only_time_zone_database}` if the moonset is requested
  in a time zone other than UTC and no time zone database is
  configured.

### Accuracy

Against Skyfield, using the same ephemeris, results agree to 0.08 s
on average and 0.1 s at most. Against the USNO, which publishes to
the minute, they agree to 15.5 s on average and 29 s at most in 70
cases. Outside the ephemeris the series move the result by 0.22 s
RMS and 0.93 s at most. As for the Sun, the atmosphere matters more
than any of these: refraction at the horizon departs from the
standard 34′ with temperature and pressure, and a high observer sees
a lower horizon, and neither is modelled. The
[accuracy guide](accuracy.md) gives the details.

### Examples

    iex> {:ok, date_time} = Astro.moonset({151.20666584, -33.8559799094}, ~D[2019-12-04])
    iex> date_time
    #DateTime<2019-12-04 01:13:25.822599+11:00 AEDT Australia/Sydney>

    iex> Astro.moonset({-62.3481, 82.5018}, ~D[2024-01-01])
    {:error, :no_time}

### Reference

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

* United States Naval Observatory, [Rise, Set, and Twilight
  Definitions](https://aa.usno.navy.mil/faq/RST_defs).

* [Moonrise and moonset](https://en.wikipedia.org/wiki/Moonrise_and_moonset)
  and [Lunar parallax](https://en.wikipedia.org/wiki/Lunar_parallax),
  Wikipedia.

# `new_visible_crescent`
*since 2.1.0* 

```elixir
@spec new_visible_crescent(location(), date(), method()) ::
  {:ok, Astro.Lunar.CrescentVisibility.visibility()}
  | {:error, :no_sunset | :invalid_location | :invalid_date | :invalid_method}
```

Predicts the visibility of the new crescent moon at a given location
on a given date using one of three published criteria.

A new crescent is first seen in the west after sunset, a day or two
after new moon. Whether it can be seen depends on how far the Moon
is from the Sun, how wide its lit crescent is, and how dark the sky
has become. Each criterion predicts this from the Sun's and the
Moon's positions on the evening of `date`, and classifies the
crescent into one of five categories.

Yallop and Odeh both work at the best time to look, sunset plus
four-ninths of the interval from sunset to moonset. There they find
the arc of vision ARCV, the Moon's altitude less the Sun's, and the
width of the crescent W in arcminutes, and compare ARCV with a cubic
in W fitted to recorded sightings:

    q = (ARCV − (11.8371 − 6.3226 W + 0.7319 W² − 0.1018 W³)) / 10
    V = ARCV − (7.1651 − 6.3226 W + 0.7319 W² − 0.1018 W³)

Yallop's q uses the geocentric arc of vision and Odeh's V the
topocentric one; Odeh also treats a Moon less than 6.4° from the Sun
as invisible, the Danjon limit. The class follows from thresholds on
q or V. Schaefer instead models the eye: from sunset to moonset it
compares the brightness of the crescent against the twilight sky
with the smallest contrast the eye can detect.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0` that
  also carries an elevation in metres. Longitude comes first.

* `date` is a `t:Date.t/0` or `t:DateTime.t/0` indicating the evening
  on which crescent visibility is to be evaluated.

* `method` selects the prediction criterion. Default `:odeh`.

  * `:odeh` — Odeh (2004). Empirical criterion based on 737 observations.
    Uses topocentric ARCV with a Danjon limit of 6.4°. The most widely
    used modern criterion.

  * `:yallop` — Yallop (1997). Empirical criterion based on 295
    observations. Uses geocentric ARCV. The original single-parameter
    approach that Odeh later refined.

  * `:schaefer` — Schaefer (1988/2000). Physics-based model computing
    the contrast between crescent brightness and twilight sky brightness
    against the human contrast detection threshold. The best observation
    time is found by scanning from sunset to moonset.

### Options

When `method` is `:schaefer`, the following options are accepted as
an optional fourth argument (a keyword list):

* `:extinction` — V-band zenith extinction coefficient. Default `0.172`
  (clean sea-level site). Typical values: `0.12` (high mountain),
  `0.17` (sea level), `0.25` (hazy conditions).

### Returns

* `{:ok, visibility}` where `visibility` is one of:

  * `:A` — Visible to the naked eye.

  * `:B` — Visible with optical aid.

  * `:C` — May need optical aid.

  * `:D` — Not visible with optical aid.

  * `:E` — Not visible.

* `{:error, :no_sunset}` if no sunset occurs on the given date at
  the given location (e.g. polar day).

* `{:error, :invalid_location}` if `location` is not a valid
  location.

* `{:error, :invalid_date}` if `date` is not a valid date in its
  calendar.

* `{:error, :invalid_method}` if `method` is not `:yallop`, `:odeh`
  or `:schaefer`.

### Method comparison

| Aspect | Yallop (1997) | Odeh (2004) | Schaefer (1988/2000) |
|---|---|---|---|
| Basis | Empirical polynomial | Empirical polynomial | Physical model |
| Observations | 295 | 737 | N/A (theory) |
| ARCV type | Geocentric | Topocentric | N/A |
| Best time | Sunset + 4/9 lag | Sunset + 4/9 lag | Scanned (max Rs) |
| Atmosphere | Not modelled | Not modelled | Extinction coefficient |

### Accuracy

The categories depend on the Sun's and the Moon's positions far less
than on the criteria: outside the ephemeris the series give the same
category as JPL in 3,749 of 3,750 evenings. The criteria are fits to
sightings made under real skies, and a crescent near the boundary of
two categories is as likely to be decided by the evening's weather
as by the calculation; Astro's predictions have not themselves been
compared with a record of sightings. Far from the present, ΔT moves
the Moon against the Sun by about 0.5″ for each second it is wrong,
so the 265 s uncertainty at the year 0 changes ARCV by about 2′. The
[accuracy guide](accuracy.md) gives the details.

### Examples

    iex> location = {-0.1275, 51.5072}
    iex> Astro.new_visible_crescent(location, ~D[2025-03-31])
    {:ok, :A}

    iex> location = {-0.1275, 51.5072}
    iex> Astro.new_visible_crescent(location, ~D[2025-03-31], :yallop)
    {:ok, :A}

### Reference

* Bernard D. Yallop, *A Method for Predicting the First Sighting of
  the New Crescent Moon*, NAO Technical Note 69 (1997).

* Mohammad Sh. Odeh, [New Criterion for Lunar Crescent
  Visibility](https://doi.org/10.1007/s10686-005-9002-5),
  *Experimental Astronomy* 18 (2004), 39–64.

* Bradley E. Schaefer, [Astronomy and the Limits of
  Vision](https://doi.org/10.1016/0083-6656(93)90113-X),
  *Vistas in Astronomy* 36 (1993), 311–361.

# `new_visible_crescent`
*since 2.1.0* 

```elixir
@spec new_visible_crescent(location(), date(), method(), keyword()) ::
  {:ok, Astro.Lunar.CrescentVisibility.visibility()}
  | {:error,
     :no_sunset
     | :invalid_location
     | :invalid_date
     | :invalid_method
     | :invalid_options
     | :invalid_utc_offset}
```

Predicts the visibility of the new crescent moon, with options.

This is `new_visible_crescent/3` with options: the evening of a
location's own day, and for Schaefer's method the clarity of the
atmosphere. It takes the same location, date and method and returns
the same classes; see `new_visible_crescent/3` for how the methods
compare.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0` that
  also carries an elevation in metres. Longitude comes first.

* `date` is a `t:Date.t/0` or `t:DateTime.t/0` indicating the evening
  on which crescent visibility is to be evaluated.

* `method` is `:odeh`, `:yallop` or `:schaefer`.

* `options` is a keyword list of options.

### Options

* `:utc_offset` is a whole number of seconds from UTC, from −50,400
  to 50,400. When it is given, the evening is that of `date` at the
  offset, as for `sunset/3`; otherwise it is the evening of the UTC
  date.

* `:extinction` is the V-band zenith extinction coefficient, for
  `:schaefer`. The default is `0.172`, a clean sea-level site.
  Typical values are `0.12` on a high mountain, `0.17` at sea level
  and `0.25` in hazy conditions.

### Returns

* `{:ok, visibility}` where `visibility` is one of the classes `:A`
  to `:E` described in `new_visible_crescent/3`.

* `{:error, :no_sunset}` if no sunset occurs on the given date at the
  given location.

* `{:error, :invalid_location}` if `location` is not a valid
  location.

* `{:error, :invalid_date}` if `date` is not a valid date in its
  calendar.

* `{:error, :invalid_method}` if `method` is not `:yallop`, `:odeh`
  or `:schaefer`.

* `{:error, :invalid_options}` if `options` is not a list.

* `{:error, :invalid_utc_offset}` if `:utc_offset` is not a whole
  number of seconds from −50,400 to 50,400.

### Examples

    iex> location = {39.8579, 21.3891}
    iex> Astro.new_visible_crescent(location, ~D[2025-03-31], :schaefer, extinction: 0.25)
    {:ok, :A}

### Reference

* Bradley E. Schaefer, [Astronomy and the Limits of
  Vision](https://doi.org/10.1016/0083-6656(93)90113-X),
  *Vistas in Astronomy* 36 (1993), 311–361.

# `solar_eclipse_at`
*since 2.10.0* 

```elixir
@spec solar_eclipse_at(location(), Astro.Solar.Eclipse.t() | date(), options()) ::
  {:ok, Astro.Solar.Eclipse.Local.t()}
  | {:error,
     :not_visible
     | :invalid_location
     | :invalid_date
     | :year_out_of_range
     | :invalid_options
     | :time_zone_not_found
     | :time_zone_not_resolved
     | :tz_world_data_not_installed
     | :tz_world_backend_not_running
     | :invalid_time_zone_resolver
     | :invalid_time_zone_database
     | :utc_only_time_zone_database}
```

Returns the local circumstances of a solar eclipse at a location: when
it begins and ends there, its maximum, how much of the Sun is covered,
and the Sun's altitude at each contact.

The observer is placed on the fundamental plane of the eclipse's
Besselian elements, from the observer's geodetic latitude, longitude
and height. Maximum eclipse is the moment the observer is nearest the
shadow axis, found by golden section search; the contacts are the
moments the observer enters and leaves the penumbra and, for a total
or annular eclipse there, the umbra or antumbra, found by bisection.
`Astro.Solar.Eclipse.Local` describes the geometry.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0` that
  also carries a height in metres above the ellipsoid. Longitude
  comes first.

* `eclipse` is an `t:Astro.Solar.Eclipse.t/0`, as
  `solar_eclipse_at_or_after/1` returns it, or a `t:Date.t/0` or
  `t:DateTime.t/0`, or any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`, for the first solar
  eclipse whose greatest eclipse is at or after it.

* `options` is a keyword list of options.

### Options

* `:time_zone` is the time zone of the returned date times:
  `:default`, the time zone of the location, which is the default;
  `:utc`; or a time zone name.

* `:time_zone_database` is the module implementing the
  `Calendar.TimeZoneDatabase` behaviour. The default is the
  configured Elixir time zone database.

* `:time_zone_resolver` is a 1-arity function that receives a
  `%Geo.Point{coordinates: {lng, lat}}` and returns
  `{:ok, time_zone_name}` or `{:error, reason}`. The default is
  `TzWorld.timezone_at/1` when `:tz_world` is a dependency.

### Returns

* `{:ok, local}`, an `t:Astro.Solar.Eclipse.Local.t/0` with the type
  there (`:partial`, `:annular` or `:total`), the contacts
  `:partial_begins`, `:annular_begins` or `:total_begins`,
  `:maximum`, `:total_ends` or `:annular_ends` and `:partial_ends` as
  date times in the requested time zone (`nil` for those the eclipse
  does not have there), the magnitude, the obscuration, and the Sun's
  geometric altitude in degrees at each contact. A contact the Sun is
  below the horizon for has a negative altitude.

* `{:error, :not_visible}` if the eclipse is not seen from the
  location: the location is outside the Moon's penumbra throughout,
  or the Sun is below the horizon from first contact to last.

* `{:error, :invalid_location}` if `location` is not a valid
  location.

* `{:error, :invalid_date}` if `eclipse` is neither an eclipse nor a
  valid date or date time in its calendar.

* `{:error, :year_out_of_range}` if `eclipse` is a date more than
  50,000 years from J2000.

* `{:error, :invalid_options}` if `options` is not a keyword list.

* The time zone errors of `sunrise/3`.

### Accuracy

Against the USNO's eclipse computer for 17 eclipses and places from
1999 to 2026, with the USNO's ΔT, the contacts agree within 2 s,
except where the observer is near the edge of an annular path and
the contacts of annularity change quickly with position (4 s at
Singapore in 2019). The partial contacts agree within 1.1 s. Totality
is about 2 s shorter, and annularity about 2 s longer, than the
USNO's, which takes a larger radius for the Moon than NASA's
`k2 = 0.272281` used here. The magnitude agrees within 0.0005, the
obscuration within 0.0012, the Sun's altitudes within 0.05°, and the
type and visibility are the same. The USNO's maximum is 1 s earlier
than the midpoint of its own second and third contacts, where
Astro's is. In Universal Time every instant also carries ΔT's
uncertainty, which moves the shadow across the Earth: an error of a
second in ΔT moves every contact by about a second.

### Examples

    iex> dallas = {-96.80, 32.78}
    iex> {:ok, local} = Astro.solar_eclipse_at(dallas, ~D[2024-04-01], time_zone: :utc)
    iex> {local.type, DateTime.truncate(local.total_begins, :second)}
    {:total, ~U[2024-04-08 18:40:43Z]}
    iex> {Float.round(local.magnitude, 3), Float.round(local.sun_altitudes.maximum, 1)}
    {1.015, 64.6}

    iex> {:ok, eclipse} = Astro.solar_eclipse_at_or_after(~D[2024-04-01])
    iex> {:ok, local} = Astro.solar_eclipse_at({-74.01, 40.71}, eclipse, time_zone: "America/New_York")
    iex> {local.type, Float.round(local.obscuration, 3)}
    {:partial, 0.899}
    iex> DateTime.truncate(local.maximum, :second)
    #DateTime<2024-04-08 15:25:35-04:00 EDT America/New_York>

    iex> Astro.solar_eclipse_at({-0.13, 51.50}, ~D[2024-04-01], time_zone: :utc)
    {:error, :not_visible}

### Reference

* P. Kenneth Seidelmann (editor), *Explanatory Supplement to the
  Astronomical Almanac* (1992), chapter 8 (eclipses of the Sun and
  Moon).

* Jean Meeus, *Elements of Solar Eclipses 1951–2200* (1989), on
  local circumstances from Besselian elements.

* United States Naval Observatory,
  [Solar Eclipse Computer](https://aa.usno.navy.mil/data/SolarEclipses).

# `solar_eclipse_at_or_after`
*since 2.10.0* 

```elixir
@spec solar_eclipse_at_or_after(date()) ::
  {:ok, Astro.Solar.Eclipse.t()} | {:error, :invalid_date | :year_out_of_range}
```

Returns the first solar eclipse whose greatest eclipse is at or after
a date or date time.

An eclipse is found at a new moon near a node of the Moon's orbit. At
each new moon the Besselian elements of the Moon's shadow, computed
from the Sun's and the Moon's apparent positions, give the distance
of the shadow axis from the centre of the Earth; where the shadow can
reach the Earth, greatest eclipse is the moment of least distance,
found by golden section search. Where the axis meets the Earth the
eclipse is central, total or annular by the umbra at the surface, and
hybrid if a total eclipse's umbra falls short of the surface at
either end of the central line; otherwise the eclipse is partial, or
total or annular without a central line where the umbra still
reaches the limb. `Astro.Solar.Eclipse` describes the geometry. The
positions come from the JPL DE440s ephemeris where the loaded kernel
covers the date, and from the analytical series elsewhere.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`, within 50,000
  years of J2000.

### Returns

* `{:ok, eclipse}`, an `t:Astro.Solar.Eclipse.t/0` with the type
  (`:partial`, `:annular`, `:total` or `:hybrid`), greatest eclipse
  as a UTC `t:DateTime.t/0`, gamma and the magnitude: for a central
  eclipse the ratio of the Moon's apparent diameter to the Sun's at
  greatest eclipse, and otherwise the fraction of the Sun's diameter
  covered there; and its Saros series as NASA numbers it
  (`Astro.Saros`).

* `{:error, :invalid_date}` if `date_time` is not a valid date or
  date time in its calendar.

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

### Accuracy

Against NASA's catalogue for 1901–2100, compared in Terrestrial Time
so that ΔT does not enter, greatest eclipse is within 0.6 s, gamma
within 0.0001, the magnitude within 0.0001, and every eclipse's type
is the same, hybrids included. Outside the ephemeris the series
place greatest eclipse within 9 s of NASA's in the nineteenth
century, 35 s in the thirtieth and two and a quarter minutes around
950 BCE, with gamma within 0.001, magnitudes within 0.0015 and the
same types. In Universal Time every instant also carries ΔT's
uncertainty; the [accuracy guide](accuracy.md) gives it.

### Examples

    iex> {:ok, eclipse} = Astro.solar_eclipse_at_or_after(~D[2024-04-01])
    iex> {eclipse.type, DateTime.truncate(eclipse.greatest, :second)}
    {:total, ~U[2024-04-08 18:17:20Z]}
    iex> {Float.round(eclipse.gamma, 4), Float.round(eclipse.magnitude, 4)}
    {0.3431, 1.0565}

    iex> {:ok, eclipse} = Astro.solar_eclipse_at_or_after(~D[2024-04-09])
    iex> {eclipse.type, DateTime.to_date(eclipse.greatest)}
    {:annular, ~D[2024-10-02]}

### Reference

* Fred Espenak and Jean Meeus, *Five Millennium Canon of Solar
  Eclipses: −1999 to +3000*, NASA/TP-2006-214141 (2006), and the
  [Five Millennium Catalog of Solar Eclipses](https://eclipse.gsfc.nasa.gov/SEcat5/SEcatalog.html).

* P. Kenneth Seidelmann (editor), *Explanatory Supplement to the
  Astronomical Almanac* (1992), chapter 8 (eclipses of the Sun and
  Moon).

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 54 (eclipses).

# `solar_eclipse_before`
*since 2.10.0* 

```elixir
@spec solar_eclipse_before(date()) ::
  {:ok, Astro.Solar.Eclipse.t()} | {:error, :invalid_date | :year_out_of_range}
```

Returns the last solar eclipse whose greatest eclipse is before a
date or date time.

An eclipse is found at a new moon near a node of the Moon's orbit. At
each new moon the Besselian elements of the Moon's shadow, computed
from the Sun's and the Moon's apparent positions, give the distance
of the shadow axis from the centre of the Earth; where the shadow can
reach the Earth, greatest eclipse is the moment of least distance,
found by golden section search. `Astro.Solar.Eclipse` describes the
geometry and the types. The positions come from the JPL DE440s
ephemeris where the loaded kernel covers the date, and from the
analytical series elsewhere.

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`, within 50,000
  years of J2000.

### Returns

* `{:ok, eclipse}`, an `t:Astro.Solar.Eclipse.t/0` with the type
  (`:partial`, `:annular`, `:total` or `:hybrid`), greatest eclipse
  as a UTC `t:DateTime.t/0`, gamma and the magnitude: for a central
  eclipse the ratio of the Moon's apparent diameter to the Sun's at
  greatest eclipse, and otherwise the fraction of the Sun's diameter
  covered there; and its Saros series as NASA numbers it
  (`Astro.Saros`).

* `{:error, :invalid_date}` if `date_time` is not a valid date or
  date time in its calendar.

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

### Accuracy

As `solar_eclipse_at_or_after/1`: within 0.6 s of NASA's greatest
eclipse in Terrestrial Time over 1901–2100, gamma and the magnitude
within 0.0001, and every type the same.

### Examples

    iex> {:ok, eclipse} = Astro.solar_eclipse_before(~D[2024-04-01])
    iex> {eclipse.type, DateTime.to_date(eclipse.greatest)}
    {:annular, ~D[2023-10-14]}

### Reference

* Fred Espenak and Jean Meeus, *Five Millennium Canon of Solar
  Eclipses: −1999 to +3000*, NASA/TP-2006-214141 (2006), and the
  [Five Millennium Catalog of Solar Eclipses](https://eclipse.gsfc.nasa.gov/SEcat5/SEcatalog.html).

* Jean Meeus, *Astronomical Algorithms*, 2nd edition (1998),
  chapter 54 (eclipses).

# `solar_eclipse_path`
*since 2.10.0* 

```elixir
@spec solar_eclipse_path(Astro.Solar.Eclipse.t() | date(), options()) ::
  {:ok, Astro.Solar.Eclipse.Path.t()}
  | {:error,
     :invalid_date | :year_out_of_range | :invalid_options | :invalid_interval}
```

Returns the path of a solar eclipse: its central line and the
northern and southern limits of the band in which it is total or
annular, and the bounds of the region in which it is seen as a
partial eclipse.

The central line is where the shadow axis meets the Earth, and a
limit is where the edge of the umbra, or of the antumbra for an
annular eclipse, just grazes an observer. Each is computed from the
eclipse's Besselian elements at a sequence of moments while the
shadow is on the Earth. The partial eclipse is seen within the
penumbral limits, where the edge of the penumbra grazes an observer,
and the sunrise and sunset limits, where the eclipse ends just as the
Sun rises or begins just as it sets. `Astro.Solar.Eclipse.Path`
describes the geometry.

The lines are drawn on a map by joining their points in order, and
the band by joining the northern limit to the southern limit taken
backwards. Where a limit lies beyond the horizon, near the ends of the
path and on the open side of an eclipse whose axis misses the Earth,
its points follow the horizon, the places having their greatest
eclipse at sunrise or sunset, so the two limits begin together where
the shadow first touches the Earth and end together where it leaves.
Every location is a `t:Geo.Point.t/0` with coordinates
`{longitude, latitude}` in degrees, as Astro takes locations, the
longitude from −180° to 180°, so a line that crosses the antimeridian
jumps from one end of that range to the other.

### Arguments

* `eclipse` is an `t:Astro.Solar.Eclipse.t/0`, as
  `solar_eclipse_at_or_after/1` returns it, or a `t:Date.t/0` or
  `t:DateTime.t/0`, or any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`, for the first solar
  eclipse whose greatest eclipse is at or after it.

* `options` is a keyword list of options.

### Options

* `:interval` is the time between points in seconds, at least 1. The
  default is 60. Points are at whole multiples of the interval in
  Universal Time, and at greatest eclipse.

### Returns

* `{:ok, path}`, an `t:Astro.Solar.Eclipse.Path.t/0` with:

  * The eclipse's `:type`.

  * `:central_line`, `:northern_limit` and `:southern_limit`, lists
    of points in time order. The central line is empty for an eclipse
    whose shadow axis misses the Earth, and all three for a partial
    eclipse.

  * `:penumbral_northern_limit` and `:penumbral_southern_limit`,
    lists of points in time order, empty where the limit is never on
    the Earth with the Sun up.

  * `:sunrise_limit`, `:sunset_limit`, `:maximum_at_sunrise` and
    `:maximum_at_sunset`, each a list of lines in the order they
    begin, each line a list of points in time order: a curve on the
    horizon can have two branches at once.

  * `:first_contact` and `:last_contact`, the points where and when
    the penumbra first touches the Earth and last leaves it.

  Every point has its `:time`, a UTC `t:DateTime.t/0` to the
  millisecond, and its `:location`, a `t:Geo.Point.t/0`. A point of
  the central line also has the eclipse there (`:total` or
  `:annular`), the path's `:width` in kilometres, the `:duration` of
  totality or annularity in seconds, and the Sun's geometric altitude
  in degrees.

* `{:error, :invalid_date}` if `eclipse` is neither an eclipse nor a
  valid date or date time in its calendar.

* `{:error, :year_out_of_range}` if `eclipse` is a date more than
  50,000 years from J2000.

* `{:error, :invalid_options}` if `options` is not a keyword list.

* `{:error, :invalid_interval}` if `:interval` is not a number of
  seconds of at least 1.

### Accuracy

Against NASA's path tables for eight eclipses from 1999 to 2027
(total, annular, hybrid, one over Antarctica and one whose axis misses
the Earth), 674 instants with NASA's ΔT, every point NASA gives is
found. Across the path the central line and the limits lie within
0.7 km of NASA's where the Sun is 20° or more above the horizon, and
within 2.6 km over Antarctica with the Sun lower, where the ground is
tilted to the shadow. Along the path they are within 2 s of the
shadow's motion. The differences follow those of the Besselian
elements, which come from different ephemerides. The path width
agrees within 1 km and 0.6% (a path 400 km wide curves with the Earth
across its width, where the two measure it differently), the duration
within 0.7 s, and the Sun's altitude within NASA's rounding to a
degree. NASA names a hybrid eclipse's limits by the sign of the
umbra's radius, so in the total part of its path NASA's northern limit
is the southern one here. In Universal Time the path also carries
ΔT's uncertainty: a second of ΔT moves it 0.46 km east or west at the
equator.

First and last contact agree with NASA's, as EclipseWise publishes
them, within 0.3 s in Terrestrial Time for eight eclipses from 2014
to 2026, total, annular, hybrid and partial. Within each line of the
sunrise and sunset limits and of the curves of maximum eclipse on the
horizon, every point is where `solar_eclipse_at/3` puts that contact,
or maximum, within 0.01 s and with the Sun on the horizon within
0.001°; and a point 0.05° inside a penumbral limit sees a partial
eclipse of magnitude under 0.003 where a point 0.05° outside sees
none. Where lines meet, at first and last contact and where a limit
reaches the horizon, the penumbra touches a place for an instant and
there is no eclipse to see.

### Examples

    iex> {:ok, path} = Astro.solar_eclipse_path(~D[2024-04-01])
    iex> path.type
    :total
    iex> {length(path.central_line), length(path.northern_limit)}
    {197, 198}
    iex> point = Enum.find(path.central_line, &(&1.time == ~U[2024-04-08 18:17:00.000Z]))
    iex> {longitude, latitude} = point.location.coordinates
    iex> {Float.round(longitude, 2), Float.round(latitude, 2), Float.round(point.width, 0)}
    {-104.24, 25.19, 198.0}

A partial eclipse has no central line, but has a region in which it
is seen:

    iex> {:ok, path} = Astro.solar_eclipse_path(~D[2025-03-01])
    iex> {path.type, path.central_line}
    {:partial, []}
    iex> DateTime.truncate(path.first_contact.time, :second)
    ~U[2025-03-29 08:50:43Z]

### Reference

* P. Kenneth Seidelmann (editor), *Explanatory Supplement to the
  Astronomical Almanac* (1992), chapter 8 (eclipses of the Sun and
  Moon).

* Jean Meeus, *Elements of Solar Eclipses 1951–2200* (1989), on the
  central line and the limits from Besselian elements.

* Fred Espenak, path tables of
  [Central Solar Eclipses: 1991–2050](https://eclipse.gsfc.nasa.gov/SEpath/SEpath.html),
  NASA Goddard Space Flight Center.

# `solar_noon`

```elixir
@spec solar_noon(location(), Calendar.date()) ::
  {:ok, DateTime.t()}
  | {:error, :invalid_location | :invalid_date | :incompatible_calendars}
```

Returns solar noon at a location on a date, as a UTC date time.

Solar noon is the moment the Sun crosses the local meridian, when it
is due north or south and highest in the sky. It is rarely 12:00 on
the clock: a mean Sun moving uniformly would cross the meridian at
12:00 local mean time, but the true Sun runs ahead of or behind it
by the equation of time, up to about 16 minutes, because the Earth's
orbit is elliptical and its axis is tilted.

Solar noon is found as the moment the Sun's local hour angle (the
local apparent sidereal time less the Sun's apparent right
ascension) is zero, by Newton's method from 12:00 local mean time;
see `Astro.Solar.solar_noon/2`. The Sun's position comes from the
JPL DE440s ephemeris where the loaded kernel covers the date, and
from the analytical series elsewhere.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0` that
  also carries an elevation in metres. Longitude comes first.

* `date` is any `t:Calendar.date/0`. A date in a calendar other
  than `Calendar.ISO` is converted to its ISO date first.

### Returns

* `{:ok, datetime}`, the UTC date time of solar noon at the given
  location on the given UTC date.

* `{:error, :invalid_location}` if `location` is not a valid
  location.

* `{:error, :invalid_date}` if `date` is not a valid date in its
  calendar.

* `{:error, :incompatible_calendars}` if its calendar cannot be
  converted to `Calendar.ISO`.

### 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. Inside
the ephemeris the right ascension is accurate to well under 0.01 s
of time; outside it the series move solar noon by 0.06 s RMS and
0.15 s at most over 1900–2100. Sidereal time follows the Earth's
rotation, UT1, and Astro takes UTC for it; the two differ by up to
0.9 s. The [accuracy guide](accuracy.md) describes how ΔT limits
results far from the present.

### Examples

    iex> Astro.solar_noon {151.20666584, -33.8559799094}, ~D[2019-12-06]
    {:ok, ~U[2019-12-06 01:45:52.977018Z]}

### Reference

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

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

# `solstice`

```elixir
@spec solstice(Calendar.year(), :june | :december, options()) ::
  {:ok, DateTime.t()}
  | {:error,
     :invalid_year
     | :invalid_event
     | :year_out_of_range
     | :time_zone_not_found
     | :utc_only_time_zone_database
     | :invalid_time_zone_database}
```

Returns the datetime of either the June or December
solstice, in UTC or a requested time zone.

A solstice is the moment the Sun's apparent ecliptic longitude
reaches 90° (the June solstice) or 270° (the December solstice).
The Sun is then at its farthest north or south of the celestial
equator, and the day is the longest of the year in one hemisphere
and the shortest in the other.

The moment is found from the Sun's apparent ecliptic longitude
(see `sun_apparent_longitude/1`). Starting 20 days before the 1st
of the month, the Sun's mean motion of 360° a tropical year
estimates when it reaches the target longitude, and a ten-day
bracket around that estimate is bisected until it is narrower than
a millisecond. The longitude comes from the JPL DE440s ephemeris where the
loaded kernel covers the year, and from the analytical series for
any other year.

### Arguments

* `year` is the Gregorian year for which the solstice is
  to be calculated, within 50,000 years of 2000.

* `event` is either `:june` or `:december` indicating
  which of the two annual solstice datetimes is required.

* `options` is a keyword list of options.

### Options

* `:time_zone` is `:utc` (the default) or a time zone
  name such as `"Asia/Tokyo"`. The solstice is one instant
  everywhere; the time zone decides the local date and
  time it is given in, and so the civil day it falls on.

* `:time_zone_database` is the module implementing
  `Calendar.TimeZoneDatabase` in which a named time zone
  is looked up. The default is the configured database,
  `Calendar.get_time_zone_database/0`. UTC needs none.

### Returns

* `{:ok, datetime}`, the solstice in the requested time
  zone.

* `{:error, :time_zone_not_found}` if the time zone is
  not known to the time zone database.

* `{:error, :utc_only_time_zone_database}` if a time zone
  other than UTC is requested and no time zone database
  is configured.

* `{:error, :invalid_time_zone_database}` if
  `:time_zone_database` is not a time zone database.

* `{:error, :invalid_year}` if `year` is not an integer.

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

* `{:error, :invalid_event}` if `event` is not `:june` or
  `:december`.

### Accuracy

The search ends within a millisecond of the crossing. For the June
solstice of 1962, Meeus gives 21h24m42s TD from the full VSOP87
theory (*Astronomical Algorithms*, chapter 27); Astro gives
21:24:07.8 UTC, which is 21h24m42.0s TD. Outside the ephemeris the
series put the events 14 s early on average over 1900–2100, 18 s
RMS and 50 s at most. Far from the present the time is limited by
ΔT, which is uncertain by minutes before about 1000 and after
about 2500; the [accuracy guide](accuracy.md) gives the details.

### Examples

    iex> {:ok, dt} = Astro.solstice 2019, :december
    iex> DateTime.truncate(dt, :second)
    ~U[2019-12-22 04:19:24Z]
    iex> {:ok, dt} = Astro.solstice 2019, :june
    iex> DateTime.truncate(dt, :second)
    ~U[2019-06-21 15:54:13Z]
    iex> {:ok, dt} = Astro.solstice 3500, :june
    iex> DateTime.to_date(dt)
    ~D[3500-06-20]

    # The June 2021 solstice is on the 21st in UTC but the 20th in Santiago
    iex> {:ok, dt} = Astro.solstice(2021, :june, time_zone: "America/Santiago")
    iex> DateTime.to_date(dt)
    ~D[2021-06-20]

### Reference

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

* United States Naval Observatory, [Earth's
  Seasons](https://aa.usno.navy.mil/data/Earth_Seasons).

* [Solstice](https://en.wikipedia.org/wiki/Solstice) and
  [Tropical year](https://en.wikipedia.org/wiki/Tropical_year), Wikipedia.

# `sun_apparent_longitude`

```elixir
@spec sun_apparent_longitude(date()) :: degrees()
```

Returns the Sun's apparent ecliptic longitude at a date or date time.

The ecliptic longitude is the Sun's position along the ecliptic,
the plane of the Earth's orbit, measured eastward from the equinox
of the date. It is 0° at the March equinox, 90° at the June
solstice, 180° at the September equinox and 270° at the December
solstice, so it measures how far through the tropical year (the
year of the seasons) the Earth has moved, without the irregularity
of leap years. Solar terms, such as those of the Chinese calendar,
are the moments it reaches multiples of 15°.

Where the loaded JPL DE440s ephemeris covers the moment, the
apparent position (see `sun_position_at/1`) is rotated from the
true equator to the ecliptic of date by the true obliquity.
Elsewhere it is the 49-term series of Bretagnon and Simon, with
aberration and nutation in longitude added.

### Arguments

* `date` is a `t:Date.t/0`, `t:NaiveDateTime.t/0` or
  `t:DateTime.t/0`. A date is taken at 00:00 UTC.

### Returns

* the apparent ecliptic longitude in degrees, from 0 to 360.

### Accuracy

Outside the ephemeris the series agree with JPL over 1900–2100 to
0.75″ RMS and 2.0″ at most, with a mean bias of +0.56″. The Sun
moves about 2.5″ a minute, so that bias puts the moment the Sun
reaches a given longitude about 14 s early. The
[accuracy guide](accuracy.md) gives the details, and how the series
and ΔT drift far from the present.

### Examples

    iex> Astro.sun_apparent_longitude ~D[2019-03-21]
    0.0838600951350463
    iex> Astro.sun_apparent_longitude ~D[2019-06-22]
    90.32182153965914
    iex> Astro.sun_apparent_longitude ~D[2019-09-23]
    179.68053562968015
    iex> Astro.sun_apparent_longitude ~D[2019-12-23]
    270.8352772904792

### Reference

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

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

* [Ecliptic coordinate system](https://en.wikipedia.org/wiki/Ecliptic_coordinate_system),
  Wikipedia.

# `sun_azimuth_elevation`
*since 0.11.0* 

```elixir
@spec sun_azimuth_elevation(location(), Calendar.datetime()) ::
  {azimuth :: float(), altitude :: float()}
```

Returns the Sun's azimuth and elevation as seen from a location at a
date time.

Azimuth and elevation are the Sun's horizontal coordinates: its
direction around the horizon and its height above it. They are
calculated from the Sun's apparent right ascension α and
declination δ (see `sun_position_at/1`) and the local hour angle
H, the local apparent sidereal time less α, by the transformation
of Meeus, *Astronomical Algorithms*, chapter 13:

    tan A = sin H / (cos H sin φ − tan δ cos φ)
    sin h = sin φ sin δ + cos φ cos δ cos H

where φ is the observer's latitude, A is measured from the south
and is returned measured from the north, and h is the elevation.

The position is geocentric and airless: it omits the Sun's parallax
(at most 8.8″) and atmospheric refraction, which raises the Sun by
about 34′ at the horizon and by less than 1′ above an elevation of
45°.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0`.
  Longitude comes first. An elevation does not change the result.

* `date_time` is a `t:DateTime.t/0`, or any map that meets
  `t:Calendar.datetime/0`.

### Returns

* `{azimuth, elevation}` in degrees. Azimuth is measured clockwise
  from north, from 0° to 360°, and elevation above the horizon, from
  −90° to 90°.

### Accuracy

Inside the loaded JPL ephemeris the Sun's apparent position is
accurate to a small fraction of an arcsecond, so the result is
limited by what it leaves out: for the reference case of NREL's
solar position algorithm (Golden, Colorado, 2003-10-17 19:30:30
UTC) it agrees to 0.05″ in azimuth and is 0.87′ lower, the
refraction NREL includes.
Outside the ephemeris the analytical series agree with JPL to 0.8″
RMS in azimuth and 0.6″ in elevation. The [accuracy guide](accuracy.md)
gives the details.

### Examples

    iex> {azimuth, elevation} =
    ...>   Astro.sun_azimuth_elevation({151.20666584, -33.8559799094}, ~U[2019-12-04 02:00:00Z])
    iex> {Float.round(azimuth, 4), Float.round(elevation, 4)}
    {343.3379, 77.8643}

### Reference

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

* Ibrahim Reda and Afshin Andreas, [Solar position algorithm for
  solar radiation applications](https://doi.org/10.1016/j.solener.2003.12.003),
  *Solar Energy* 76 (2004), 577–589.

* [Horizontal coordinate system](https://en.wikipedia.org/wiki/Horizontal_coordinate_system),
  Wikipedia.

# `sun_position_at`
*since 0.6.0* 

```elixir
@spec sun_position_at(date()) :: Geo.PointZ.t()
```

Returns the Sun's apparent right ascension, declination and distance
at a date or date time.

This is the Sun's apparent geocentric position: where it is seen
from the centre of the Earth, referred to the true equator and
equinox of the date. Right ascension is measured eastward along the
celestial equator from the equinox and declination north of the
equator.

Where the loaded JPL DE440s ephemeris covers the moment, the
Earth-to-Sun vector is read from it at the moment less the light
time, corrected for aberration, and rotated from the ICRF to the
true equator of date by IAU 1976 precession and IAU 1980 nutation.
Elsewhere the Sun's longitude comes from the 49-term series of
Bretagnon and Simon, with aberration and nutation added, and is
converted to right ascension and declination with the true
obliquity of the ecliptic (Meeus, *Astronomical Algorithms*,
chapter 13).

### Arguments

* `date_time` is a `t:DateTime.t/0` or a `t:Date.t/0` or
  any struct that meets the requirements of
  `t:Calendar.date/0` or `t:Calendar.datetime/0`. A date is
  taken at 00:00 UTC.

### Returns

* a `t:Geo.PointZ.t/0` struct with coordinates
  `{right_ascension, declination, distance}` with properties
  `%{reference: :celestial, object: :sun}`. Right ascension is in
  degrees from −180° to 180°, declination in degrees and
  `distance` in metres.

### Accuracy

Inside the ephemeris the reduction reproduces Meeus's
full-precision example 25.b to 0.2″. Outside it the series agree
with JPL over 1900–2100 to 0.76″ RMS in right ascension and 0.50″ in
declination (2.2″ and 1.3″ at most), and to 4,300 km in distance.
Far from the present the series drift, and ΔT, which places the
moment in Terrestrial Time, becomes uncertain by minutes to hours;
the [accuracy guide](accuracy.md) gives the details.

### Examples

    iex> %Geo.PointZ{coordinates: {right_ascension, declination, distance}, properties: properties} =
    ...>   Astro.sun_position_at(~D[1992-10-13])
    iex> {Float.round(right_ascension, 6), Float.round(declination, 6), round(distance)}
    {-161.621245, -7.784069, 149240324715}
    iex> properties
    %{object: :sun, reference: :celestial}

### Reference

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

* Ryan S. Park and others, [The JPL Planetary and Lunar Ephemerides
  DE440 and DE441](https://doi.org/10.3847/1538-3881/abd414),
  *The Astronomical Journal* 161 (2021), 105.

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

* [Position of the Sun](https://en.wikipedia.org/wiki/Position_of_the_Sun),
  Wikipedia.

# `sunrise`

```elixir
@spec sunrise(location(), date(), options()) ::
  {:ok, DateTime.t()}
  | {:error,
     :no_time
     | :invalid_location
     | :invalid_date
     | :invalid_options
     | :invalid_utc_offset
     | :invalid_limb
     | :invalid_solar_elevation
     | :time_zone_not_found
     | :time_zone_not_resolved
     | :tz_world_data_not_installed
     | :tz_world_backend_not_running
     | :invalid_time_zone_resolver
     | :invalid_time_zone_database
     | :utc_only_time_zone_database}
```

Calculates the sunrise for a given location and date.

Sunrise is the moment when the upper limb of the sun appears on the
horizon in the morning.

Refraction lifts the Sun by a standard 34′ at the horizon and its
semi-diameter is about 16′, so this is the moment the Sun's centre,
computed without refraction, is 50′ below the horizon. The
twilights are the moments the centre is 6°, 12° or 18° below it.

The Sun's altitude is calculated from its apparent geocentric
position (see `sun_position_at/1`) and the local apparent sidereal
time; its parallax, at most 8.8″, is ignored. The altitude is
sampled every 24 minutes across the day to bracket the crossing,
which is then narrowed by bisection to 0.01 s. The position comes
from the JPL DE440s ephemeris where the loaded kernel covers the
date, and from the analytical series elsewhere.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0`.
  Longitude comes first. An elevation does not change the result.

* `date` is a `t:Date.t/0`, `t:NaiveDateTime.t/0` or `t:DateTime.t/0`
  for the day of the sunrise.

* `options` is a keyword list of options.

### Options

* `:solar_elevation` is the zenith angle of the sun, in degrees, that
  marks the sunrise, or one of the names below. The default is
  `:geometric`.

  * `:geometric` is 90°, corrected for refraction and the sun's
    apparent radius so that it matches the moment the upper limb
    appears to touch the horizon.

  * `:civil` is 96°. The sun is below the horizon but there is
    generally enough natural light for most outdoor activities.

  * `:nautical` is 102°. The horizon is barely visible, and the moon
    and stars can still be used for navigation.

  * `:astronomical` is 108°. Beyond this, astronomical observation
    becomes impractical.

* `:limb` is `:upper`, the default, for the moment the Sun's upper
  limb is on the horizon, or `:center` for its centre (34′ of
  refraction alone). It applies to `:geometric` only.

* `:time_zone` is the time zone of the returned date time: `:default`,
  the time zone of the location, which is the default; `:utc`; or a
  time zone name.

* `:utc_offset` is a whole number of seconds from UTC, from −50,400
  to 50,400. When it is given, the event is the one whose date at
  that offset is `date`, whatever time zone it is returned in: the
  location's own day, for a calendar that keeps local mean time.
  Otherwise the date is taken in the requested time zone.

* `:time_zone_database` is the module implementing the
  `Calendar.TimeZoneDatabase` behaviour. The default is the configured
  Elixir time zone database.

* `:time_zone_resolver` is a 1-arity function that receives a
  `%Geo.Point{coordinates: {lng, lat}}` and returns
  `{:ok, time_zone_name}` or `{:error, reason}`. The default is
  `TzWorld.timezone_at/1` when `:tz_world` is a dependency.

### Returns

* `{:ok, date_time}` where `date_time` is the sunrise in the requested
  time zone.

* `{:error, :invalid_location}` if `location` is not a valid
  location.

* `{:error, :invalid_date}` if `date` is not a valid date in its
  calendar.

* `{:error, :invalid_options}` if `options` is not a list.

* `{:error, :invalid_utc_offset}` if `:utc_offset` is not a whole
  number of seconds from −50,400 to 50,400.

* `{:error, :invalid_limb}` if `:limb` is not `:upper` or `:center`.

* `{:error, :no_time}` if there is no sunrise on that date at that
  location, as happens at very high latitudes in summer and winter.

* `{:error, :invalid_solar_elevation}` if `:solar_elevation` is neither
  a number nor one of the names above.

* `{:error, :time_zone_not_found}` if the requested time zone is
  unknown.

* `{:error, :time_zone_not_resolved}` if no time zone can be resolved
  for the location, which happens when `:tz_world` is not a dependency
  and no `:time_zone_resolver` is given, or when the resolver returns
  neither `{:ok, time_zone}` nor `{:error, reason}`.

* `{:error, :tz_world_data_not_installed}` if `:tz_world` is a
  dependency but its data has not been installed. Run
  `mix tz_world.update` to install it.

* `{:error, :tz_world_backend_not_running}` if `:tz_world` is a
  dependency but none of its backends is running; see
  `Astro.Supervisor`.

* `{:error, :invalid_time_zone_resolver}` if `:time_zone_resolver` is
  not a 1-arity function.

* `{:error, :invalid_time_zone_database}` if `:time_zone_database` is
  not a time zone database.

* `{:error, :utc_only_time_zone_database}` if the sunrise is requested
  in a time zone other than UTC and no time zone database is
  configured.

### Accuracy

Against Skyfield, using the same ephemeris, results agree to 0.95 s
on average and 1.6 s at most; Skyfield uses the Sun's apparent
semi-diameter for the day rather than a fixed 16′. Against
timeanddate.com, which publishes to the minute, they agree to
28.8 s on average and within a minute in 307 of 310 cases. Outside
the ephemeris the series move the result by 0.07 s RMS and 0.21 s
at most. The atmosphere matters more than any of these: refraction
at the horizon departs from the standard 34′ with temperature and
pressure, and a high observer sees a lower horizon, and neither is
modelled. The [accuracy guide](accuracy.md) gives the details.

### Examples

    iex> {:ok, date_time} = Astro.sunrise({151.20666584, -33.8559799094}, ~D[2019-12-04])
    iex> date_time
    #DateTime<2019-12-04 05:37:06.206432+11:00 AEDT Australia/Sydney>

    iex> Astro.sunrise({-62.3481, 82.5018}, ~D[2019-12-04])
    {:error, :no_time}

### Reference

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

* United States Naval Observatory, [Rise, Set, and Twilight
  Definitions](https://aa.usno.navy.mil/faq/RST_defs).

* [Sunrise](https://en.wikipedia.org/wiki/Sunrise) and
  [Twilight](https://en.wikipedia.org/wiki/Twilight), Wikipedia.

# `sunset`

```elixir
@spec sunset(location(), date(), options()) ::
  {:ok, DateTime.t()}
  | {:error,
     :no_time
     | :invalid_location
     | :invalid_date
     | :invalid_options
     | :invalid_utc_offset
     | :invalid_limb
     | :invalid_solar_elevation
     | :time_zone_not_found
     | :time_zone_not_resolved
     | :tz_world_data_not_installed
     | :tz_world_backend_not_running
     | :invalid_time_zone_resolver
     | :invalid_time_zone_database
     | :utc_only_time_zone_database}
```

Calculates the sunset for a given location and date.

Sunset is the moment when the upper limb of the sun disappears below
the horizon in the evening.

Refraction lifts the Sun by a standard 34′ at the horizon and its
semi-diameter is about 16′, so this is the moment the Sun's centre,
computed without refraction, is 50′ below the horizon. The
twilights are the moments the centre is 6°, 12° or 18° below it.

The Sun's altitude is calculated from its apparent geocentric
position (see `sun_position_at/1`) and the local apparent sidereal
time; its parallax, at most 8.8″, is ignored. The altitude is
sampled every 24 minutes across the day to bracket the crossing,
which is then narrowed by bisection to 0.01 s. The position comes
from the JPL DE440s ephemeris where the loaded kernel covers the
date, and from the analytical series elsewhere.

### Arguments

* `location` is the observer's position as a `{longitude, latitude}`
  tuple in degrees, a `t:Geo.Point.t/0`, or a `t:Geo.PointZ.t/0`.
  Longitude comes first. An elevation does not change the result.

* `date` is a `t:Date.t/0`, `t:NaiveDateTime.t/0` or `t:DateTime.t/0`
  for the day of the sunset.

* `options` is a keyword list of options.

### Options

* `:solar_elevation` is the zenith angle of the sun, in degrees, that
  marks the sunset, or one of the names below. The default is
  `:geometric`.

  * `:geometric` is 90°, corrected for refraction and the sun's
    apparent radius so that it matches the moment the upper limb
    appears to touch the horizon.

  * `:civil` is 96°. The sun is below the horizon but there is
    generally enough natural light for most outdoor activities.

  * `:nautical` is 102°. The horizon is barely visible, and the moon
    and stars can still be used for navigation.

  * `:astronomical` is 108°. Beyond this, astronomical observation
    becomes impractical.

* `:limb` is `:upper`, the default, for the moment the Sun's upper
  limb is on the horizon, or `:center` for its centre (34′ of
  refraction alone). It applies to `:geometric` only.

* `:time_zone` is the time zone of the returned date time: `:default`,
  the time zone of the location, which is the default; `:utc`; or a
  time zone name.

* `:utc_offset` is a whole number of seconds from UTC, from −50,400
  to 50,400. When it is given, the event is the one whose date at
  that offset is `date`, whatever time zone it is returned in: the
  location's own day, for a calendar that keeps local mean time.
  Otherwise the date is taken in the requested time zone.

* `:time_zone_database` is the module implementing the
  `Calendar.TimeZoneDatabase` behaviour. The default is the configured
  Elixir time zone database.

* `:time_zone_resolver` is a 1-arity function that receives a
  `%Geo.Point{coordinates: {lng, lat}}` and returns
  `{:ok, time_zone_name}` or `{:error, reason}`. The default is
  `TzWorld.timezone_at/1` when `:tz_world` is a dependency.

### Returns

* `{:ok, date_time}` where `date_time` is the sunset in the requested
  time zone.

* `{:error, :invalid_location}` if `location` is not a valid
  location.

* `{:error, :invalid_date}` if `date` is not a valid date in its
  calendar.

* `{:error, :invalid_options}` if `options` is not a list.

* `{:error, :invalid_utc_offset}` if `:utc_offset` is not a whole
  number of seconds from −50,400 to 50,400.

* `{:error, :invalid_limb}` if `:limb` is not `:upper` or `:center`.

* `{:error, :no_time}` if there is no sunset on that date at that
  location, as happens at very high latitudes in summer and winter.

* `{:error, :invalid_solar_elevation}` if `:solar_elevation` is neither
  a number nor one of the names above.

* `{:error, :time_zone_not_found}` if the requested time zone is
  unknown.

* `{:error, :time_zone_not_resolved}` if no time zone can be resolved
  for the location, which happens when `:tz_world` is not a dependency
  and no `:time_zone_resolver` is given, or when the resolver returns
  neither `{:ok, time_zone}` nor `{:error, reason}`.

* `{:error, :tz_world_data_not_installed}` if `:tz_world` is a
  dependency but its data has not been installed. Run
  `mix tz_world.update` to install it.

* `{:error, :tz_world_backend_not_running}` if `:tz_world` is a
  dependency but none of its backends is running; see
  `Astro.Supervisor`.

* `{:error, :invalid_time_zone_resolver}` if `:time_zone_resolver` is
  not a 1-arity function.

* `{:error, :invalid_time_zone_database}` if `:time_zone_database` is
  not a time zone database.

* `{:error, :utc_only_time_zone_database}` if the sunset is requested
  in a time zone other than UTC and no time zone database is
  configured.

### Accuracy

Against Skyfield, using the same ephemeris, results agree to 0.95 s
on average and 1.6 s at most; Skyfield uses the Sun's apparent
semi-diameter for the day rather than a fixed 16′. Against
timeanddate.com, which publishes to the minute, they agree to
28.8 s on average and within a minute in 307 of 310 cases. Outside
the ephemeris the series move the result by 0.07 s RMS and 0.21 s
at most. The atmosphere matters more than any of these: refraction
at the horizon departs from the standard 34′ with temperature and
pressure, and a high observer sees a lower horizon, and neither is
modelled. The [accuracy guide](accuracy.md) gives the details.

### Examples

    iex> {:ok, date_time} = Astro.sunset({151.20666584, -33.8559799094}, ~D[2019-12-04])
    iex> date_time
    #DateTime<2019-12-04 19:53:18.155710+11:00 AEDT Australia/Sydney>

    iex> Astro.sunset({-62.3481, 82.5018}, ~D[2019-12-04])
    {:error, :no_time}

### Reference

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

* United States Naval Observatory, [Rise, Set, and Twilight
  Definitions](https://aa.usno.navy.mil/faq/RST_defs).

* [Sunset](https://en.wikipedia.org/wiki/Sunset) and
  [Twilight](https://en.wikipedia.org/wiki/Twilight), Wikipedia.

---

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