โ˜€๏ธ sun_times

July 7, 2026 ยท View on GitHub

Crystal CI GitHub release Docs License

A simple Crystal library for calculating ๐ŸŒ… sunrise and ๐ŸŒ‡ sunset given latitude, longitude, and date based on the NOAA Solar Calculator and formulas from Jean Meeus' Astronomical Algorithms (2nd Edition, 1998).

๐Ÿ“Š Accuracy

The library's astronomical constants and algorithms follow authoritative sources (NOAA, JPL, Meeus) and were verified manually against the NOAA Solar Calculator (https://gml.noaa.gov/grad/solcalc/).

Verification

  • A validation script (samples/accuracy_check.cr) compares calculated sunrise, sunset and solar noon against NOAA's apparent times for several locations.
  • The comparison was performed manually by running the script and checking the NOAA site for reference values.

Results

  • Calculated times are within the margin of error and are effectively identical to NOAA's apparent times. Differences are typically under 1 minute.
  • Tested locations include New York, London, Tokyo and Sydney. Example: New York on 2025-11-05 matched NOAA apparent sunrise/sunset closely (06:31 / 16:47 local).

๐Ÿ“ฆ Installation

  1. Add the dependency to your shard.yml:

    dependencies:
      sun_times:
        github: geocrystal/sun_times
    
  2. Run shards install

๐Ÿ’ป Usage

require "sun_times"

# Example: Lviv, Ukraine
# SunTime.new(latitude : Float64, longitude : Float64)
sun = SunTimes::SunTime.new(49.8419, 24.0311)
location = Time::Location.load("Europe/Kyiv")
date = Time.local

astronomical_dawn_time = sun.astronomical_dawn(date, location)
nautical_dawn_time = sun.nautical_dawn(date, location)
civil_dawn_time = sun.civil_dawn(date, location)
sunrise_time = sun.sunrise(date, location)
solar_noon_time = sun.solar_noon(date, location)
sunset_time = sun.sunset(date, location)
civil_dusk_time = sun.civil_dusk(date, location)
nautical_dusk_time = sun.nautical_dusk(date, location)
astronomical_dusk_time = sun.astronomical_dusk(date, location)
daylight_length = sun.daylight_length(date, location)

puts "Timezone: #{location}"
puts "Now:      #{date}"
puts
puts "=== Twilight Periods ==="
puts "Astronomical dawn:  #{astronomical_dawn_time}"
puts "Nautical dawn:      #{nautical_dawn_time}"
puts "Civil dawn:         #{civil_dawn_time}"
puts "Sunrise:            #{sunrise_time}"
puts "Solar noon:         #{solar_noon_time}"
puts "Sunset:             #{sunset_time}"
puts "Civil dusk:         #{civil_dusk_time}"
puts "Nautical dusk:      #{nautical_dusk_time}"
puts "Astronomical dusk:  #{astronomical_dusk_time}"
puts ""
puts "=== Daylight ==="
puts "Daylight:      #{daylight_length}"

Latitude must be between -90.0 and 90.0 degrees. Longitude must be between -180.0 and 180.0 degrees. Invalid coordinates raise SunTimes::InvalidInputError.

In polar regions, sunrise and sunset may not occur on a given date. The raising methods (sunrise, sunset, and twilight methods) raise SunTimes::CalculationError when the event does not occur. The non-raising variants (sunrise?, sunset?, and twilight ? methods) return nil. daylight_length returns 24.hours for polar day and Time::Span.zero for polar night.

Output:

Timezone: Europe/Kyiv
Now:      2025-11-05 14:05:23 +02:00

=== Twilight Periods ===
Astronomical dawn:  2025-11-05 05:30:09 +02:00
Nautical dawn:      2025-11-05 06:07:40 +02:00
Civil dawn:         2025-11-05 06:46:02 +02:00
Sunrise:            2025-11-05 07:20:19 +02:00
Solar noon:         2025-11-05 12:07:24 +02:00
Sunset:             2025-11-05 16:54:29 +02:00
Civil dusk:         2025-11-05 17:28:46 +02:00
Nautical dusk:      2025-11-05 18:07:08 +02:00
Astronomical dusk:  2025-11-05 18:44:39 +02:00

=== Daylight ===
Daylight:      9h 34m 9s

readme

You can also get all solar events as a NamedTuple using the events method, which is useful for serialization (e.g., JSON): ๐Ÿ“‹

require "json"
require "sun_times"

sun = SunTimes::SunTime.new(49.8419, 24.0311)
location = Time::Location.load("Europe/Kyiv")
date = Time.local

sun.events(date, location).to_json

Output

{
  "astronomical_dawn":"2025-11-05T05:30:09+02:00",
  "nautical_dawn":"2025-11-05T06:07:40+02:00",
  "civil_dawn":"2025-11-05T06:46:02+02:00",
  "sunrise":"2025-11-05T07:20:19+02:00",
  "solar_noon":"2025-11-05T12:07:24+02:00",
  "sunset":"2025-11-05T16:54:29+02:00",
  "civil_dusk":"2025-11-05T17:28:46+02:00",
  "nautical_dusk":"2025-11-05T18:07:08+02:00",
  "astronomical_dusk":"2025-11-05T18:44:39+02:00"
}

๐ŸŒ† Twilight Periods

The library supports three types of twilight periods:

  • Civil twilight ๐ŸŒ… (sun 6ยฐ below horizon): Enough light for most outdoor activities
  • Nautical twilight โš“ (sun 12ยฐ below horizon): Horizon is still visible for navigation
  • Astronomical twilight ๐Ÿ”ญ (sun 18ยฐ below horizon): Sky is dark enough for astronomical observations

image

๐Ÿš€ Performance

The library is fast as the speed of light! โšก Individual calculations are sub-microsecond, and the events method that computes all solar events is still under 4 microseconds per call.

Benchmark results were obtained on Intel(R) Core(TM) i7-8550U (8) @ 4.00 GHz.

You can run the benchmark with:

crystal run --release samples/benchmark.cr

๐Ÿค Contributing

  1. Fork it (https://github.com/geocrystal/sun_times/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

๐Ÿ‘ฅ Contributors

๐Ÿ“„ License

This library is distributed under the MIT license. Please see the LICENSE file.