astro

This module provides functions for the calculation of the position of the sun, sunrise, sunset, civil, nautical and astronomical dawn and dusk, as well as the position of the moon, moonrise and moonset for a given time (including timezone) an location through latitude and longitude.

For the sun calculations, the formulas are identical to those used in suncalc.js (https://github.com/mourner/suncalc), which are based on http://aa.quae.nl/en/reken/zonpositie.html. For the calculations regarding the moon, formulas given in Chapter 47 in the book "Astronomical Algorithms", 2nd edition by Jean Meeus are used. Moonrise and moonset are used by calculating a table of moon position for the requested day and by approximating the time when the moon reaches an altitude of 0°, therefore the exact time is only within about 5 min correct. For extreme cases, when the moon is only visible for around less than 10 min, the calculations might therefore be incorrect.

Examples

Calculating data for a location within the local timezone

The following code calculates data for sun and moon in Berlin on March 13th, 2025 at 14:30 local time. Since no timezone is specified, the code assumes that you are in the timezone of Berlin. All location results are in degree, all time results are Option(DateTime) since there is sometimes no sunrise etc. for certain locations.

import astro, times

let
# Berlin
    lat_BE = Latitude(52.49)
    lon_BE = Longitude(13.43)
    day = initDateTime(13, mMar, 2025, 14, 30, 0, 0, local())

# results assume that your local timezone is Europe/Berlin, i.e. UTC+2
echo sunPosition(day, lat_BE, lon_BE)
echo sunDateTimes(day, lat_BE, lon_BE)
echo moonPosition(day, lat_BE, lon_BE)
echo moonDateTimes(day, lat_BE, lon_BE)

output if you are in the timezone Europe/Berlin (UTC+2):

(azimuth: 218.54, altitude: 27.89)

(solarNoon: (val: 2025-03-13T12:17:09+01:00, has: true), nadir: (val: 2025-03-13T00:17:09+01:00, has: true),
sunSet: (val: 2025-03-13T18:07:31+01:00, has: true), sunRise: (val: 2025-03-13T06:26:48+01:00, has: true),
sunRiseEnd: (val: 2025-03-13T06:30:18+01:00, has: true), sunSetStart: (val: 2025-03-13T18:04:01+01:00, has: true),
civilDawn: (val: 2025-03-13T05:52:50+01:00, has: true), civilDusk: (val: 2025-03-13T18:41:29+01:00, has: true),
nauticalDawn: (val: 2025-03-13T05:13:05+01:00, has: true), nauticalDusk: (val: 2025-03-13T19:21:14+01:00, has: true),
astroDawn: (val: 2025-03-13T04:32:11+01:00, has: true), astroDusk: (val: 2025-03-13T20:02:08+01:00, has: true))

(azimuth: 43.43, altitude: -21.53, distance: 399761)

(riseTime: (val: 2025-03-13T17:21:42+01:00, has: true), setTime: (val: 2025-03-13T06:22:51+01:00, has: true))

Calculating data for a location outside the local timezone

import astro, times

proc newTimezone(tzName: string, offset: int): Timezone {.raises: [].} =
    proc zoneInfoFromAdjTime(adjTime: Time): ZonedTime {.locks: 0.} =
        result.isDst = false
        result.utcOffset = offset
        result.time = adjTime + initDuration(seconds = offset)
    
    proc zoneInfoFromTime(time: Time): ZonedTime {.locks: 0.} =
        result.isDst = false
        result.utcOffset = offset
        result.time = time
    
    result = newTimezone(tzName, zoneInfoFromTime, zoneInfoFromAdjTime)

let
# San Diego
    lat_SD = Latitude(52.49)
    lon_SD = Longitude(13.43)
    tzSD = newTimezone("SD", 7 * 3600)
    day = initDateTime(13, mMar, 2025, 14, 30, 0, 0, tzSD)

echo sunPosition(day, lat_SD, lon_SD)
echo sunDateTimes(day, lat_SD, lon_SD, tzSD)
echo moonPosition(day, lat_SD, lon_SD)
echo moonDateTimes(day, lat_SD, lon_SD, tzSD)

output (times given in San Diego local time):

(azimuth: 215.86, altitude: 48.35)

(solarNoon: (val: 2025-03-13T12:59:12-07:00, has: true), nadir: (val: 2025-03-13T00:59:12-07:00, has: true),
sunSet: (val: 2025-03-13T18:56:04-07:00, has: true), sunRise: (val: 2025-03-13T07:02:21-07:00, has: true),
sunRiseEnd: (val: 2025-03-13T07:04:53-07:00, has: true), sunSetStart: (val: 2025-03-13T18:53:31-07:00, has: true),
civilDawn: (val: 2025-03-13T06:37:45-07:00, has: true), civilDusk: (val: 2025-03-13T19:20:40-07:00, has: true),
nauticalDawn: (val: 2025-03-13T06:09:09-07:00, has: true), nauticalDusk: (val: 2025-03-13T19:49:15-07:00, has: true),
astroDawn: (val: 2025-03-13T05:40:24-07:00, has: true), astroDusk: (val: 2025-03-13T20:18:01-07:00, has: true))

(azimuth: 38.37, altitude: -44.71, distance: 400587)

(riseTime: (val: 2025-03-13T18:41:26-07:00, has: true), setTime: (val: 2025-03-13T06:50:02-07:00, has: true))

For some locations, the sun or moon might not rise or set. In such a case, the return time is of type none(DateTime), e.g.:

let
# Hammerfest
    lat_HF = Latitude(70.66)
    lon_HF = Longitude(23.68)
    tzHF = newTimezone("HF", -7200)
    day = initDateTime(21, mJun, 2019, 9, 0, 0, 0, tzHF)

echo sunPosition(day, lat_HF, lon_HF)
echo sunDateTimes(day, lat_HF, lon_HF, tzHF)
echo moonPosition(day, lat_HF, lon_HF)
echo moonDateTimes(day, lat_HF, lon_HF, tzHF)

output:

(azimuth: 119.18, altitude: 34.26)

(solarNoon: (val: 2019-06-21T12:28:14+02:00, has: true), nadir: (val: 2019-06-21T00:28:14+02:00, has: true),
sunSet: (val: 0001-00-00T00:00:00+00:00, has: false), sunRise: (val: 0001-00-00T00:00:00+00:00, has: false),
sunRiseEnd: (val: 0001-00-00T00:00:00+00:00, has: false), sunSetStart: (val: 0001-00-00T00:00:00+00:00, has: false),
civilDawn: (val: 0001-00-00T00:00:00+00:00, has: false), civilDusk: (val: 0001-00-00T00:00:00+00:00, has: false),
nauticalDawn: (val: 0001-00-00T00:00:00+00:00, has: false), nauticalDusk: (val: 0001-00-00T00:00:00+00:00, has: false),
astroDawn: (val: 0001-00-00T00:00:00+00:00, has: false), astroDusk: (val: 0001-00-00T00:00:00+00:00, has: false))

(azimuth: 253.98, altitude: -14.27, distance: 402248)

(riseTime: (val: 2019-06-21T03:23:04+02:00, has: true), setTime: (val: 2019-06-21T03:48:47+02:00, has: true))

Types

Latitude = range[-90.0 .. 90.0]
Represents the geographical latitude in degrees.
Longitude = range[-180.0 .. 180.0]
Represents the geographical longitude in degrees.
Azimuth = range[-360.0 .. 360.0]
Represents the angular measurement in the horizontal coordinate system. 0° corresponds to north.
Altitude = range[-90.0 .. 90.0]
Represents the elevation in the horizontal coordinate system.
HorizontalCoordinate = tuple[azimuth: Azimuth, altitude: Altitude]
Represents the coordinate in the horizontal coordinate system.
HorizontalCoordinateDistance = tuple[azimuth: Azimuth, altitude: Altitude,
                                   distance: int64]
Represents the coordinate in the horizontal coordinate system including the distance in km.
DateTimeSet = tuple[riseTime: Option[DateTime], setTime: Option[DateTime]]
Represents rise and set times as a set of Option[DateTime].
SunDateTimes = tuple[solarNoon: Option[DateTime], nadir: Option[DateTime],
                   sunSet: Option[DateTime], sunRise: Option[DateTime],
                   sunRiseEnd: Option[DateTime], sunSetStart: Option[DateTime],
                   civilDawn: Option[DateTime], civilDusk: Option[DateTime],
                   nauticalDawn: Option[DateTime], nauticalDusk: Option[DateTime],
                   astroDawn: Option[DateTime], astroDusk: Option[DateTime]]
Represents time form sun calculation as set of Option[DateTime].

Funcs

func sunPosition(date: DateTime; lat: Latitude; lon: Longitude): HorizontalCoordinate {...}{.
    raises: [], tags: [].}
Calculates the position of the sun in horizontal coordinates for a given datetime and location.
func sunDateTimes(date: DateTime; lat: Latitude; lon: Longitude; zone = local()): SunDateTimes {...}{.
    raises: [], tags: [].}
Calculates solar noon, nadir, sunrise, sunset, sunrise end, sunset start, civil, nautical and astronomical dawn and dusk for a given datetime, latitude and longitude. If no timezone is specified, the local timezone is used. If you want the correct time for a random location, specify that locations timezone. If no value can be calculated, for instance the is no sunrise, none(DateTime) will be the result for that value.
func moonPosition(date: DateTime; lat: Latitude; lon: Longitude): HorizontalCoordinateDistance {...}{.
    raises: [], tags: [].}
Calculates the position of the moon in horizontal coordinates including the distance in km for a given datetime and location.
func moonDateTimes(date: DateTime; lat: Latitude; lon: Longitude; zone = local()): DateTimeSet {...}{.
    raises: [UnpackError], tags: [].}
Calculates moonrise and moonset for a given datetime, latitude and longitude. If no timezone is specified, the local timezone is used. If you want the correct time for a random location, specify that locations timezone. If no value can be calculated, for instance the is no moonrise, none(DateTime) will be the result for that value.