Current section
Files
Jump to
Current section
Files
lib/astro.ex
defmodule Astro do
@moduledoc """
Functions for basic astronomical observations such
as sunrise, sunset, solstice, equinox, moonrise,
moonset and moon phase.
"""
alias Astro.Solar
@type longitude :: float()
@type latitude :: float()
@type location :: {longitude, latitude} | Geo.Point.t() | Geo.PointZ.t()
@type date :: Calendar.date() | Calendar.naive_datetime() | Calendar.datetime()
@type options :: keyword()
@doc """
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.
## Arguments
* `location` is the latitude, longitude and
optionally elevation for the desired sunrise
time. It can be expressed as:
* `{lng, lat}` - a tuple with longitude and latitude
as floating point numbers. **Note** the order of the
arguments.
* a `Geo.Point.t` struct to represent a location without elevation
* a `Geo.PointZ.t` struct to represent a location and elevation
* `date` is a `Date.t`, `NaiveDateTime.t` or `DateTime.t`
to indicate the date of the year in which
the sunrise time is required.
* `options` is a keyword list of options.
## Options
* `solar_elevation` represents the type of sunrise
required. The default is `:geometric` which equates to
a solar elevation of 90°. In this case the calulation
also accounts for refraction and elevation to return a
result which accords with the eyes perception. Other
solar elevations are:
* `:civil` representing a solar elevation of 96.0°. At this
point the sun is just below the horizon so there is
generally enough natural light to carry out most
outdoor activities.
* `:nautical` representing a solar elevation of 102.0°
This is the point at which the horizon is just barely visible
and the moon and stars can still be used for navigation.
* `:astronomical`representing a solar elevation of 108.0°.
This is the point beyond which astronomical observation
becomes impractical.
* Any floating point number representing the desired
solar elevation.
* `:time_zone` is the time zone to in which the sunrise
is requested. The default is `:default` in which
the sunrise time is reported in the time zone of
the requested location. Any other time zone name
supported by the option `:time_zone_database` is
acceptabe.
* `:time_zone_database` represents the module that
implements the `Calendar.TimeZoneDatabase` behaviour.
The default is `Tzdata.TimeZoneDatabase`.
## Returns
* a `DateTime.t` representing the time of sunrise in the
requested timzone at the requested location or
* `{:error, :time_zone_not_found}` if the requested
time zone is unknown
* `{:error, :no_time}` if for the requested date
and location there is no sunrise. This can occur at
very high latitudes during summer and winter.
## Examples
```
# Sunrise in Sydney, Australia
Astro.sunrise({151.20666584, -33.8559799094}, ~D[2019-12-04])
{:ok, #DateTime<2019-12-04 05:37:00.000000+11:00 AEDT Australia/Sydney>}
# Sunrise in Alert, Nanavut, Canada
Astro.sunrise({-62.3481, 82.5018}, ~D[2019-12-04])
{:error, :no_time}
```
"""
@spec sunrise(location, date, options) ::
{:ok, DateTime.t()} | {:error, :time_zone_not_found | :no_time}
def sunrise(location, date, options \\ default_options()) when is_list(options) do
options = Keyword.put(options, :rise_or_set, :rise)
Solar.sun_rise_or_set(location, date, options)
end
@doc """
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.
## Arguments
* `location` is the latitude, longitude and
optionally elevation for the desired sunrise
time. It can be expressed as:
* `{lng, lat}` - a tuple with longitude and latitude
as floating point numbers. **Note** the order of the
arguments.
* a `Geo.Point.t` struct to represent a location without elevation
* a `Geo.PointZ.t` struct to represent a location and elevation
* `date` is a `Date.t`, `NaiveDateTime.t` or `DateTime.t`
to indicate the date of the year in which
the sunset time is required.
* `options` is a keyword list of options.
## Options
* `solar_elevation` represents the type of sunset
required. The default is `:geometric` which equates to
a solar elevation of 90°. In this case the calulation
also accounts for refraction and elevation to return a
result which accords with the eyes perception. Other
solar elevations are:
* `:civil` representing a solar elevation of 96.0°. At this
point the sun is just below the horizon so there is
generally enough natural light to carry out most
outdoor activities.
* `:nautical` representing a solar elevation of 102.0°
This is the point at which the horizon is just barely visible
and the moon and stars can still be used for navigation.
* `:astronomical`representing a solar elevation of 108.0°.
This is the point beyond which astronomical observation
becomes impractical.
* Any floating point number representing the desired
solar elevation.
* `:time_zone` is the time zone to in which the sunset
is requested. The default is `:default` in which
the sunset time is reported in the time zone of
the requested location. Any other time zone name
supported by the option `:time_zone_database` is
acceptabe.
* `:time_zone_database` represents the module that
implements the `Calendar.TimeZoneDatabase` behaviour.
The default is `Tzdata.TimeZoneDatabase`.
## Returns
* a `DateTime.t` representing the time of sunset in the
requested time zone at the requested location or
* `{:error, :time_zone_not_found}` if the requested
time zone is unknown
* `{:error, :no_time}` if for the requested date
and location there is no sunset. This can occur at
very high latitudes during summer and winter.
## Examples
```
# Sunset in Sydney, Australia
Astro.sunset({151.20666584, -33.8559799094}, ~D[2019-12-04])
{:ok, #DateTime<2019-12-04 19:53:00.000000+11:00 AEDT Australia/Sydney>}
# Sunset in Alert, Nanavut, Canada
Astro.sunset({-62.3481, 82.5018}, ~D[2019-12-04])
{:error, :no_time}
```
"""
@spec sunset(location, date, options) ::
{:ok, DateTime.t()} | {:error, :time_zone_not_found | :no_time}
def sunset(location, date, options \\ default_options()) when is_list(options) do
options = Keyword.put(options, :rise_or_set, :set)
Solar.sun_rise_or_set(location, date, options)
end
@doc false
def default_options do
[
solar_elevation: Solar.solar_elevation(:geometric),
time_zone: :default,
time_zone_database: Tzdata.TimeZoneDatabase
]
end
end