# Getting started

Astro computes the position of the sun and moon, and the events that follow from them: sunrise and sunset, moonrise and moonset, twilight, daylight hours, lunar phases, equinoxes and solstices.

Positions come from JPL's DE440s ephemeris rather than from a closed-form approximation, so results agree with published almanacs to within a second or two over the supported date range.

## Installation

Add `astro` to your dependencies:

```elixir
def deps do
  [
    {:astro, "~> 2.11"},
    {:tz, "~> 0.28"},
    {:tz_world, "~> 2.4"}
  ]
end
```

Only `astro` is required. The other two are optional and cover two separate concerns, described below.

## The ephemeris

A compact ephemeris covering 1900 to 2100 ships inside the package, so Astro works immediately after `mix deps.get` with no download step.

For dates outside the ephemeris, Astro computes the Sun and the Moon from analytical series, within a few seconds of arc of JPL near the present, so every function answers for any date. Far from the present the uncertainty of ΔT dominates the time of any event.

For JPL accuracy over a wider span, download the full DE440s kernel, which covers 1849 to 2150:

```bash
mix astro.download_ephemeris
```

The downloaded file takes precedence over the bundled one when present. To keep it somewhere specific, configure the path:

```elixir
config :astro,
  ephemeris: "/path/to/de440s.bsp"
```

Setting an explicit path is worth doing in production. The default cache location falls back to the system temporary directory when the user has no writable home directory — common for service accounts in containers — and that is ephemeral.

## Time zones

Astro answers two different questions about time zones, and they need different things from you.

**Converting an instant to a named zone** requires a `Calendar.TimeZoneDatabase`. Add either `:tz` or `:tzdata`. Astro prefers Tz.TimeZoneDatabase when both are loaded, and the `:elixir` `:time_zone_database` configuration always wins if you set it.

**Working out which zone a coordinate falls in** requires `:tz_world`, plus its backend in your supervision tree:

```elixir
defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      TzWorld.Backend.SpatialIndex
    ]

    Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
  end
end
```

Then install its data, once:

```bash
mix tz_world.update --force
```

`--force` is needed on a first install because the package ships no data directory for the download to land in.

Without `:tz_world` you can still use every function — pass `time_zone: :utc`, or name a zone explicitly, or supply your own `:time_zone_resolver`.

## Locations

A location is a longitude/latitude pair, in that order. Longitude first is the GeoJSON convention and catches people out, so it is worth stating plainly: east is positive, north is positive.

Three forms are accepted, and they are equivalent:

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

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

`Geo.PointZ` also works and carries an altitude, which affects rise and set times slightly.

## Options

`sunrise/3`, `sunset/3`, `moonrise/3` and `moonset/3` share a keyword list:

* `:time_zone` — `:default` (the zone at the location, the default), `:utc`, or any zone name your time zone database knows.

* `:time_zone_database` — a `Calendar.TimeZoneDatabase` module. Defaults to whichever of `:tz` or `:tzdata` is loaded.

* `:time_zone_resolver` — a 1-arity function taking a `Geo.Point` and returning `{:ok, zone_name}` or `{:error, reason}`. Defaults to `TzWorld.timezone_at/1`.

* `:solar_elevation` — how far below the horizon counts as the event. Sun events only; see the [Solar guide](solar.html).

* `:limb` and `:interpolation` — which part of the Moon's disk marks the event, and how its position is evaluated. Moon events only; see `Astro.moonrise/3`.

The defaults in force are:

```elixir
iex> Astro.default_options()
[solar_elevation: 90.0, time_zone: :default, time_zone_database: Tz.TimeZoneDatabase]
```

Asking for UTC avoids the coordinate-to-zone lookup entirely, so it works with no `:tz_world` installed:

```elixir
iex> Astro.sunrise({151.20666584, -33.8559799094}, ~D[2019-12-04], time_zone: :utc)
{:ok, ~U[2019-12-04 18:37:03.223306Z]}
```

Note this is the sunrise falling inside the UTC day, which near a date boundary is a different event from the one inside the local day.

## Errors

Astro returns tagged tuples for the cases that are genuine astronomy, or a setting it cannot use, rather than bad input:

* `{:error, :no_time}` — the event does not occur on that date at that place. A polar summer has no sunset:

  ```elixir
  iex> Astro.sunset({-62.3481, 82.5018}, ~D[2019-07-01])
  {:error, :no_time}
  ```

* `{:error, :time_zone_not_found}` — the coordinate is in no time zone, typically open ocean, or the requested zone is unknown to the time zone database.

* `{:error, :time_zone_not_resolved}` — `:tz_world` is not a dependency and no `:time_zone_resolver` was given.

* `{:error, :tz_world_data_not_installed}` — `:tz_world` is installed but its data has never been downloaded. Run `mix tz_world.update --force`.

* `{:error, :utc_only_time_zone_database}` — a result was asked for in a local time zone, but no time zone database is configured.

* `{:error, :invalid_solar_elevation}`, `{:error, :invalid_limb}` and `{:error, :invalid_interpolation}` — an option has a value the function does not recognise.

## Where next

* [Solar guide](solar.html) — sunrise, sunset, twilight, daylight, equinoxes and solstices.

* [Lunar guide](lunar.html) — moonrise, moonset, phases, illumination and crescent visibility.
