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.