Documentation
¶
Overview ¶
Package solar provides functions for calculating sunrise, sunset, and solar position for any location on Earth. It uses astronomical algorithms to compute solar elevation angles and times when the sun crosses specific elevation thresholds.
Index ¶
- Constants
- Variables
- func Abs[T Signed](x T) T
- func AlmostEqual[T Float](a, b, tolerance T) bool
- func Azimuth(loc Location, when time.Time) float64
- func Dawn(loc Location, t Time, twilightType ...TwilightType) time.Time
- func DawnDusk(loc Location, t Time, twilightType ...TwilightType) (dawn, dusk time.Time)
- func Dusk(loc Location, t Time, twilightType ...TwilightType) time.Time
- func Elevation(loc Location, when time.Time) float64
- func JulianDayToTime(d float64) time.Time
- func MeanSolarNoon(loc Location, t Time) time.Time
- func Round(f float64, places int) float64
- func Sunrise(loc Location, t Time) (time.Time, error)
- func SunriseSunset(loc Location, t Time) (time.Time, time.Time, error)
- func Sunset(loc Location, t Time) (time.Time, error)
- func TimeOfElevation(loc Location, elevation float64, t Time) (morning, evening time.Time)
- func TimeToJulianDay(t time.Time) float64
- type Float
- type Location
- type Signed
- type Time
- type TwilightType
Examples ¶
- Abs
- AlmostEqual
- Azimuth
- Dawn
- Dawn (Nautical)
- DawnDusk
- DawnDusk (Astronomical)
- Dusk
- Elevation
- NewLocationFromNMEA
- NewLocationFromNMEA
- NewLocationFromNMEA (Gga)
- NewLocationFromNMEA (Gga)
- NewLocationFromNMEA (MultipleCalculations)
- NewLocationFromNMEA (WithSolarCalculation)
- NewTimeFromNMEA
- Sunrise
- SunriseSunset
- SunriseSunset
- SunriseSunset (PolarNight)
- Sunset
- Sunset
- TimeOfElevation
Constants ¶
const ( // Degree provides a precise fraction for converting between degrees and // radians. Degree = math.Pi / 180 // J2000 is the Julian date for January 1, 2000, 12:00:00 TT. // This is the standard epoch used in modern astronomical calculations. J2000 = 2451545 // SolarMeanAnomalyBase is the mean anomaly of the sun at J2000 epoch (in degrees). // This represents the angular distance from perihelion at the reference epoch. SolarMeanAnomalyBase = 357.5291 // SolarMeanAnomalyRate is the daily change in mean anomaly (degrees per day). // This accounts for Earth's orbital motion around the sun. SolarMeanAnomalyRate = 0.98560028 // FullCircleDegrees represents a complete rotation in degrees. FullCircleDegrees = 360.0 // HalfCircleDegrees represents a semicircle in degrees. HalfCircleDegrees = 180.0 // EquationOfCenterC1 is the first coefficient in the equation of center calculation. // This is the dominant term in the Earth's orbital eccentricity correction. EquationOfCenterC1 = 1.9148 // EquationOfCenterC2 is the second coefficient in the equation of center calculation. // This accounts for higher-order orbital perturbations. EquationOfCenterC2 = 0.0200 // EquationOfCenterC3 is the third coefficient in the equation of center calculation. // This is a small correction term for orbital accuracy. EquationOfCenterC3 = 0.0003 // SinDeclinationCoefficient is sin(ε) where ε is Earth's axial tilt (obliquity). // Earth's axial tilt is approximately 23.44°, and sin(23.44°) ≈ 0.39779 SinDeclinationCoefficient = 0.39779 // EquationOfTimeC1 is the first coefficient in the equation of time calculation. // This accounts for the eccentricity of Earth's orbit. EquationOfTimeC1 = 0.0053 // EquationOfTimeC2 is the second coefficient in the equation of time calculation. // This accounts for the obliquity of the ecliptic. EquationOfTimeC2 = 0.0069 // SunriseCorrectionAngle is the correction angle for sunrise/sunset calculations. // This is precisely -0.833° (-50 arc minutes), accounting for: // - Atmospheric refraction (34 arc minutes) // - Sun's angular diameter (16 arc minutes) // Calculated as: -0.833 * (π/180) = -0.0145385927 radians SunriseCorrectionAngle = -0.0145385927 // PerihelionBase is the argument of perihelion at J2000 epoch (in degrees). // This is the angle from the vernal equinox to perihelion. PerihelionBase = 102.93005 // PerihelionRate is the rate of change of perihelion per Julian century. // This accounts for the precession of Earth's orbit. PerihelionRate = 0.3179526 // JulianCenturyDays is the number of days in a Julian century. JulianCenturyDays = 36525.0 // LongitudeDivisor is used in mean solar noon calculation. // Dividing longitude by 360 converts it to a fractional day offset. LongitudeDivisor = 360.0 // CivilTwilightAngle is the solar elevation angle for civil twilight (-6°). // Civil twilight occurs when the sun is between the horizon and 6° below it. // During civil twilight, there is enough natural light for most outdoor activities // without artificial lighting. This is the most commonly used definition for // dawn and dusk in everyday contexts. CivilTwilightAngle = -6.0 // NauticalTwilightAngle is the solar elevation angle for nautical twilight (-12°). // Nautical twilight occurs when the sun is between 6° and 12° below the horizon. // During nautical twilight, the horizon is still visible at sea, allowing sailors // to take reliable readings using a marine sextant. The general outline of ground // objects may be distinguishable, but detailed outdoor work is difficult. NauticalTwilightAngle = -12.0 // AstronomicalTwilightAngle is the solar elevation angle for astronomical twilight (-18°). // Astronomical twilight occurs when the sun is between 12° and 18° below the horizon. // During astronomical twilight, the sky is dark enough for most astronomical // observations of point sources of light such as stars, though the Sun's light // may still interfere with observations of extremely faint objects. AstronomicalTwilightAngle = -18.0 )
const DefaultPlaces = 5
DefaultPlaces specifies the default precision for rounding.
Variables ¶
var ( // ErrInvalidNMEA is returned when the NMEA sentence is malformed or unsupported. ErrInvalidNMEA = errors.New("invalid NMEA sentence") // ErrInvalidChecksum is returned when the NMEA checksum validation fails. ErrInvalidChecksum = errors.New("invalid NMEA checksum") // ErrUnsupportedSentence is returned when the NMEA sentence type is not supported. ErrUnsupportedSentence = errors.New("unsupported NMEA sentence type") // ErrInvalidPosition is returned when latitude or longitude cannot be parsed. ErrInvalidPosition = errors.New("invalid position data") // ErrInvalidDate is returned when date/time cannot be parsed from NMEA sentence. ErrInvalidDate = errors.New("invalid date/time data") )
var ( // ErrSunNeverRises is returned when the sun never rises at the given location and date (polar night). ErrSunNeverRises = errors.New("sun never rises at this location on this date") // ErrSunNeverSets is returned when the sun never sets at the given location and date (midnight sun). ErrSunNeverSets = errors.New("sun never sets at this location on this date") )
Functions ¶
func Abs ¶
func Abs[T Signed](x T) T
Abs returns the absolute value of x for any numeric type. This is a generic replacement for type-specific abs functions.
Example:
Abs(-5) // returns 5 (int) Abs(-5.5) // returns 5.5 (float64) Abs(int64(-10)) // returns 10 (int64)
Example ¶
ExampleAbs demonstrates the generic Abs function with different types.
package main
import (
"fmt"
"github.com/mstephenholl/go-solar"
)
func main() {
// Works with int
fmt.Println(solar.Abs(-5))
// Works with int64
fmt.Println(solar.Abs(int64(-100)))
// Works with float64
fmt.Println(solar.Abs(-3.14))
}
Output: 5 100 3.14
func AlmostEqual ¶
AlmostEqual checks if two floating-point numbers are within a given tolerance. This is useful for comparing floating-point results where exact equality is not possible due to rounding errors.
Example:
AlmostEqual(1.0, 1.00001, 0.001) // true AlmostEqual(1.0, 1.1, 0.001) // false
Example ¶
ExampleAlmostEqual demonstrates floating-point comparison with tolerance.
package main
import (
"fmt"
"github.com/mstephenholl/go-solar"
)
func main() {
a := 1.0
b := 1.00001
// These are almost equal within tolerance
fmt.Println(solar.AlmostEqual(a, b, 0.001))
// But not with stricter tolerance
fmt.Println(solar.AlmostEqual(a, b, 0.00001))
}
Output: true false
func Azimuth ¶
Azimuth calculates the solar azimuth angle at a specific time and location. The azimuth is the sun's compass direction, measured clockwise from true north.
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- when: The datetime at which to calculate the azimuth (use time.Date, time.Now(), or Time.DateTime())
Returns:
- The solar azimuth angle in degrees (0° = North, 90° = East, 180° = South, 270° = West)
The calculation uses the solar hour angle and declination to determine the sun's position in the sky. The azimuth is measured clockwise from north, ranging from 0° to 360°.
Note: All calculations assume UTC time. Ensure the input time is in UTC timezone.
Example:
loc := solar.NewLocation(43.65, -79.38) when := time.Date(2000, time.January, 1, 17, 0, 0, 0, time.UTC) azimuth := solar.Azimuth(loc, when) // azimuth is in degrees: 0°=North, 90°=East, 180°=South, 270°=West
Example ¶
ExampleAzimuth demonstrates calculating the solar azimuth angle. The azimuth is the sun's compass direction measured clockwise from north.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Create location for Toronto
loc := solar.NewLocation(43.65, -79.38)
// Calculate azimuth for January 1, 2000 at 5:00 PM UTC (noon local time)
when := time.Date(2000, time.January, 1, 17, 0, 0, 0, time.UTC)
azimuth := solar.Azimuth(loc, when)
fmt.Printf("Azimuth: %.1f degrees (South)\n", azimuth)
}
Output: Azimuth: 174.8 degrees (South)
func Dawn ¶
func Dawn(loc Location, t Time, twilightType ...TwilightType) time.Time
Dawn calculates the dawn time for a given location and date. Dawn is the beginning of morning twilight, when the sun reaches the specified angle below the horizon and natural light begins to appear.
By default, civil twilight (-6°) is used, which is the most common definition of dawn in everyday contexts. You can optionally specify Nautical or Astronomical twilight types for specialized applications.
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- t: Time created via NewTime(), NewTimeFromDateTime(), or NewTimeFromNMEA()
- twilightType: Optional twilight type (Civil, Nautical, or Astronomical). Defaults to Civil.
Returns:
- Dawn time in UTC (time.Time{} if the sun never reaches the twilight angle on this day)
Example:
loc := solar.NewLocation(40.7128, -74.0060) t := solar.NewTime(2024, time.June, 21) // Calculate civil dawn (default) dawn := solar.Dawn(loc, t) // Calculate nautical dawn dawn := solar.Dawn(loc, t, solar.Nautical) // Calculate astronomical dawn dawn := solar.Dawn(loc, t, solar.Astronomical)
Example ¶
ExampleDawn demonstrates calculating civil dawn (beginning of morning twilight).
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Toronto coordinates
loc := solar.NewLocation(43.65, -79.38)
t := solar.NewTime(2000, time.January, 1)
// Calculate civil dawn (default)
dawn := solar.Dawn(loc, t)
fmt.Printf("Civil dawn: %s\n", dawn.Format("15:04 MST"))
}
Output: Civil dawn: 12:18 UTC
Example (Nautical) ¶
ExampleDawn_nautical demonstrates calculating nautical dawn.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Toronto coordinates
loc := solar.NewLocation(43.65, -79.38)
t := solar.NewTime(2000, time.January, 1)
// Calculate nautical dawn (sun at -12° below horizon)
nauticalDawn := solar.Dawn(loc, t, solar.Nautical)
fmt.Printf("Nautical dawn: %s\n", nauticalDawn.Format("15:04 MST"))
}
Output: Nautical dawn: 11:42 UTC
func DawnDusk ¶
func DawnDusk(loc Location, t Time, twilightType ...TwilightType) (dawn, dusk time.Time)
DawnDusk calculates both dawn and dusk times for a given location and date. This is more efficient than calling Dawn() and Dusk() separately.
By default, civil twilight (-6°) is used. You can optionally specify Nautical or Astronomical twilight types for specialized applications.
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- t: Time created via NewTime(), NewTimeFromDateTime(), or NewTimeFromNMEA()
- twilightType: Optional twilight type (Civil, Nautical, or Astronomical). Defaults to Civil.
Returns:
- dawn: Dawn time in UTC (time.Time{} if never occurs)
- dusk: Dusk time in UTC (time.Time{} if never occurs)
Example:
loc := solar.NewLocation(40.7128, -74.0060) t := solar.NewTime(2024, time.June, 21) // Calculate civil dawn and dusk (default) dawn, dusk := solar.DawnDusk(loc, t) // Calculate nautical dawn and dusk dawn, dusk := solar.DawnDusk(loc, t, solar.Nautical)
Example ¶
ExampleDawnDusk demonstrates calculating both dawn and dusk times.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Toronto coordinates
loc := solar.NewLocation(43.65, -79.38)
t := solar.NewTime(2000, time.January, 1)
// Calculate civil dawn and dusk
dawn, dusk := solar.DawnDusk(loc, t)
fmt.Printf("Dawn: %s\n", dawn.Format("15:04 MST"))
fmt.Printf("Dusk: %s\n", dusk.Format("15:04 MST"))
}
Output: Dawn: 12:18 UTC Dusk: 22:23 UTC
Example (Astronomical) ¶
ExampleDawnDusk_astronomical demonstrates calculating astronomical twilight.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Toronto coordinates
loc := solar.NewLocation(43.65, -79.38)
t := solar.NewTime(2000, time.January, 1)
// Calculate astronomical dawn and dusk (sun at -18° below horizon)
dawn, dusk := solar.DawnDusk(loc, t, solar.Astronomical)
fmt.Printf("Astronomical dawn: %s\n", dawn.Format("15:04 MST"))
fmt.Printf("Astronomical dusk: %s\n", dusk.Format("15:04 MST"))
}
Output: Astronomical dawn: 11:07 UTC Astronomical dusk: 23:34 UTC
func Dusk ¶
func Dusk(loc Location, t Time, twilightType ...TwilightType) time.Time
Dusk calculates the dusk time for a given location and date. Dusk is the end of evening twilight, when the sun reaches the specified angle below the horizon and natural light fades to darkness.
By default, civil twilight (-6°) is used, which is the most common definition of dusk in everyday contexts. You can optionally specify Nautical or Astronomical twilight types for specialized applications.
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- t: Time created via NewTime(), NewTimeFromDateTime(), or NewTimeFromNMEA()
- twilightType: Optional twilight type (Civil, Nautical, or Astronomical). Defaults to Civil.
Returns:
- Dusk time in UTC (time.Time{} if the sun never reaches the twilight angle on this day)
Example:
loc := solar.NewLocation(40.7128, -74.0060) t := solar.NewTime(2024, time.June, 21) // Calculate civil dusk (default) dusk := solar.Dusk(loc, t) // Calculate nautical dusk dusk := solar.Dusk(loc, t, solar.Nautical) // Calculate astronomical dusk dusk := solar.Dusk(loc, t, solar.Astronomical)
Example ¶
ExampleDusk demonstrates calculating civil dusk (end of evening twilight).
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Toronto coordinates
loc := solar.NewLocation(43.65, -79.38)
t := solar.NewTime(2000, time.January, 1)
// Calculate civil dusk (default)
dusk := solar.Dusk(loc, t)
fmt.Printf("Civil dusk: %s\n", dusk.Format("15:04 MST"))
}
Output: Civil dusk: 22:23 UTC
func Elevation ¶
Elevation calculates the angle of the sun above the horizon at a given moment at the specified location.
The time parameter should be in UTC. If you have a local time, convert it to UTC first using time.UTC() or time.In(time.UTC).
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- when: The moment in time to calculate elevation (in UTC)
Returns:
- Solar elevation angle in degrees (positive above horizon, negative below)
Example:
loc := solar.NewLocation(40.7128, -74.0060) elevation := solar.Elevation(loc, time.Now().UTC()) // elevation is in degrees, positive above horizon, negative below
Example ¶
ExampleElevation demonstrates calculating the sun's elevation angle at a specific time and location.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Create location for New York City
loc := solar.NewLocation(40.7128, -74.0060)
// Check sun elevation at noon UTC on summer solstice
when := time.Date(2022, time.June, 21, 12, 0, 0, 0, time.UTC)
elevation := solar.Elevation(loc, when)
fmt.Printf("Sun elevation: %.1f degrees\n", elevation)
}
Output: Sun elevation: 26.5 degrees
func JulianDayToTime ¶
JulianDayToTime converts a Julian day number into a time.Time.
The returned time is always in UTC timezone. This is the standard for astronomical calculations.
Parameters:
- d: Julian day number as float64
Returns:
- Time in UTC corresponding to the Julian day
Example:
t := solar.JulianDayToTime(2451545.0) // J2000.0 epoch // Returns 2000-01-01 12:00:00 +0000 UTC
func MeanSolarNoon ¶
MeanSolarNoon calculates the time at which the sun is at its highest altitude (solar noon) for the given location and date.
All times are calculated and returned in UTC. To convert to local time, use time.In() with the appropriate timezone.
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- t: Time created via NewTime(), NewTimeFromDateTime(), or NewTimeFromNMEA()
Returns:
- Solar noon time in UTC
Example:
loc := solar.NewLocation(43.65, -79.38) t := solar.NewTime(2024, time.June, 21) noon := solar.MeanSolarNoon(loc, t) // noon is in UTC - convert to local time if needed
func Round ¶
Round takes the provided float and rounds it to the specified number of decimal places. This function is adapted from user korya on GitHub (https://gist.github.com/DavidVaini/10308388#gistcomment-1391788).
func Sunrise ¶
Sunrise calculates when the sun will rise on the given day at the specified location.
All times are calculated and returned in UTC. To convert to local time, use time.In() with the appropriate timezone.
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- t: Time created via NewTime(), NewTimeFromDateTime(), or NewTimeFromNMEA()
Returns:
- Sunrise time in UTC
- error if the sun does not rise on this day (e.g., polar night)
Example:
loc := solar.NewLocation(40.7128, -74.0060)
t := solar.NewTime(2024, time.June, 21)
sunrise, err := solar.Sunrise(loc, t)
if err != nil {
// Handle polar night or other errors
}
// sunrise is in UTC - convert to local time if needed
localTime := sunrise.In(time.Local)
Example ¶
ExampleSunrise demonstrates calculating just the sunrise time for Toronto, Canada on January 1, 2000.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Create location for Toronto
loc := solar.NewLocation(43.65, -79.38)
// Create time for January 1, 2000
t := solar.NewTime(2000, time.January, 1)
// Calculate sunrise
rise, err := solar.Sunrise(loc, t)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Sunrise: %s\n", rise.Format("15:04:05 MST"))
}
Output: Sunrise: 12:50:59 UTC
func SunriseSunset ¶
SunriseSunset calculates when the sun will rise and when it will set on the given day at the specified location.
All times are calculated and returned in UTC. To convert to local time, use time.In() with the appropriate timezone.
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- t: Time created via NewTime(), NewTimeFromDateTime(), or NewTimeFromNMEA()
Returns:
- sunrise: Sunrise time in UTC
- sunset: Sunset time in UTC
- error if the sun does not rise or set (e.g., polar night or midnight sun)
Example:
loc := solar.NewLocation(40.7128, -74.0060)
t := solar.NewTime(2024, time.June, 21)
sunrise, sunset, err := solar.SunriseSunset(loc, t)
if err != nil {
// Handle polar night or midnight sun
}
// Both times are in UTC - convert to local time if needed
Example ¶
ExampleSunriseSunset demonstrates basic sunrise and sunset calculation for Toronto, Canada on January 1, 2000.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Create location for Toronto
loc := solar.NewLocation(43.65, -79.38)
// Create time for January 1, 2000
t := solar.NewTime(2000, time.January, 1)
// Calculate sunrise and sunset
rise, set, err := solar.SunriseSunset(loc, t)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Sunrise: %s\n", rise.Format("15:04:05 MST"))
fmt.Printf("Sunset: %s\n", set.Format("15:04:05 MST"))
}
Output: Sunrise: 12:50:59 UTC Sunset: 21:50:37 UTC
Example ¶
// Using an RMC sentence to get both sunrise and sunset
nmea := "$GPRMC,123519,A,4339.192,N,07922.992,W,022.4,084.4,230394,003.1,W*71"
loc, err := NewLocationFromNMEA(nmea, 0, 0, 0)
if err != nil {
panic(err)
}
tm, err := NewTimeFromNMEA(nmea, 0, 0, 0)
if err != nil {
panic(err)
}
sunrise, sunset, err := SunriseSunset(loc, tm)
if err != nil {
panic(err)
}
_, _ = sunrise, sunset // Use the times
Example (PolarNight) ¶
ExampleSunriseSunset_polarNight demonstrates the case where the sun never rises (polar night).
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Create location for Igloolik, Nunavut
loc := solar.NewLocation(69.3321443, -81.6781126)
// Create time for June 25, 2020 (midnight sun period)
t := solar.NewTime(2020, time.June, 25)
// Calculate sunrise and sunset
_, _, err := solar.SunriseSunset(loc, t)
// Check for error (sun never rises or sets)
if err != nil {
fmt.Println("The sun does not rise or set on this day")
}
}
Output: The sun does not rise or set on this day
func Sunset ¶
Sunset calculates when the sun will set on the given day at the specified location.
All times are calculated and returned in UTC. To convert to local time, use time.In() with the appropriate timezone.
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- t: Time created via NewTime(), NewTimeFromDateTime(), or NewTimeFromNMEA()
Returns:
- Sunset time in UTC
- error if the sun does not set on this day (e.g., midnight sun)
Example:
loc := solar.NewLocation(40.7128, -74.0060)
t := solar.NewTime(2024, time.June, 21)
sunset, err := solar.Sunset(loc, t)
if err != nil {
// Handle midnight sun or other errors
}
// sunset is in UTC - convert to local time if needed
localTime := sunset.In(time.Local)
Example ¶
ExampleSunset demonstrates calculating just the sunset time for Toronto, Canada on January 1, 2000.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Create location for Toronto
loc := solar.NewLocation(43.65, -79.38)
// Create time for January 1, 2000
t := solar.NewTime(2000, time.January, 1)
// Calculate sunset
set, err := solar.Sunset(loc, t)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Sunset: %s\n", set.Format("15:04:05 MST"))
}
Output: Sunset: 21:50:37 UTC
Example ¶
// Using an RMC sentence
nmea := "$GPRMC,123519,A,4339.192,N,07922.992,W,022.4,084.4,230394,003.1,W*71"
loc, err := NewLocationFromNMEA(nmea, 0, 0, 0)
if err != nil {
panic(err)
}
tm, err := NewTimeFromNMEA(nmea, 0, 0, 0)
if err != nil {
panic(err)
}
sunset, err := Sunset(loc, tm)
if err != nil {
panic(err)
}
_ = sunset // Use the sunset time
func TimeOfElevation ¶
TimeOfElevation calculates the times of day when the sun is at a given elevation above the horizon on a given day at the specified location.
All times are calculated and returned in UTC. Useful for calculating twilight times, golden hour, etc.
Common elevation angles:
- -0.833°: Official sunrise/sunset (accounts for atmospheric refraction)
- -6°: Civil twilight
- -12°: Nautical twilight
- -18°: Astronomical twilight
- 6°: Golden hour
Parameters:
- loc: Location created via NewLocation() or NewLocationFromNMEA()
- elevation: Solar elevation angle in degrees (negative for below horizon)
- t: Time created via NewTime(), NewTimeFromDateTime(), or NewTimeFromNMEA()
Returns:
- morning: Time in UTC when sun reaches elevation in the morning (time.Time{} if never reached)
- evening: Time in UTC when sun reaches elevation in the evening (time.Time{} if never reached)
Example:
loc := solar.NewLocation(40.7128, -74.0060) t := solar.NewTime(2024, time.June, 21) // Calculate civil twilight times morning, evening := solar.TimeOfElevation(loc, -6.0, t)
Example ¶
ExampleTimeOfElevation demonstrates finding when the sun reaches a specific elevation angle.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Create location for London
loc := solar.NewLocation(51.5072, -0.1276)
t := solar.NewTime(2022, time.June, 21)
// Find when sun is at 10 degrees above horizon
morning, evening := solar.TimeOfElevation(loc, 10.0, t)
fmt.Printf("Morning: %s\n", morning.Format("15:04 MST"))
fmt.Printf("Evening: %s\n", evening.Format("15:04 MST"))
}
Output: Morning: 05:06 UTC Evening: 18:58 UTC
func TimeToJulianDay ¶
TimeToJulianDay converts a time.Time into a Julian day number.
The input time should be in UTC for accurate astronomical calculations. The timezone of the input time is preserved in the calculation via Unix timestamp.
Parameters:
- t: Time to convert (should be in UTC for astronomical calculations)
Returns:
- Julian day number as float64
Example:
jd := solar.TimeToJulianDay(time.Date(2000, time.January, 1, 12, 0, 0, 0, time.UTC)) // Returns 2451545.0 (J2000.0 epoch)
Types ¶
type Location ¶
type Location struct {
// contains filtered or unexported fields
}
Location represents a geographical location. It can be created from direct latitude/longitude coordinates or parsed from an NMEA GPS sentence.
func NewLocation ¶
NewLocation creates a Location from latitude and longitude coordinates.
Parameters:
- latitude: Decimal degrees, positive north, negative south (-90 to +90)
- longitude: Decimal degrees, positive east, negative west (-180 to +180)
Example:
loc := solar.NewLocation(43.65, -79.38) // Toronto, Canada
func NewLocationFromNMEA ¶
NewLocationFromNMEA creates a Location from an NMEA GPS sentence.
Supported NMEA sentence types:
- GGA (Global Positioning System Fix Data)
- RMC (Recommended Minimum Specific GPS/Transit Data)
The year, month, and day parameters are required for GGA sentences (which don't include date information). For RMC sentences, these parameters are ignored as the date is parsed from the sentence.
Parameters:
- nmea: NMEA sentence string (e.g., "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47")
- year: Year (e.g., 2025) - ignored for RMC sentences
- month: Month (e.g., time.January) - ignored for RMC sentences
- day: Day of month (e.g., 15) - ignored for RMC sentences
Returns:
- Location: The parsed location
- error: Any error encountered during parsing
Example:
// From GGA sentence (requires date) nmea := "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47" loc, err := solar.NewLocationFromNMEA(nmea, 2025, time.January, 15) // From RMC sentence (includes date, parameters ignored) nmea := "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A" loc, err := solar.NewLocationFromNMEA(nmea, 0, 0, 0)
Example ¶
Example tests for documentation
// Using an RMC sentence (includes date: March 23, 1994)
nmea := "$GPRMC,123519,A,4339.192,N,07922.992,W,022.4,084.4,230394,003.1,W*71"
loc, err := NewLocationFromNMEA(nmea, 0, 0, 0)
if err != nil {
panic(err)
}
tm, err := NewTimeFromNMEA(nmea, 0, 0, 0)
if err != nil {
panic(err)
}
sunrise, err := Sunrise(loc, tm)
if err != nil {
panic(err)
}
// Sunrise is returned in UTC
_ = sunrise // Use the sunrise time
Example ¶
ExampleNewLocationFromNMEA demonstrates parsing location from an NMEA GPS sentence.
package main
import (
"fmt"
"github.com/mstephenholl/go-solar"
)
func main() {
// Parse an NMEA RMC sentence (includes date)
nmea := "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"
loc, err := solar.NewLocationFromNMEA(nmea, 0, 0, 0) // Date ignored for RMC
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Latitude: %.4f\n", loc.Latitude())
fmt.Printf("Longitude: %.4f\n", loc.Longitude())
}
Output: Latitude: 48.1173 Longitude: 11.5167
Example (Gga) ¶
ExampleNewLocationFromNMEA_gga demonstrates parsing a GGA sentence which requires an external date.
package main
import (
"fmt"
"time"
"github.com/mstephenholl/go-solar"
)
func main() {
// Parse an NMEA GGA sentence (requires external date)
nmea := "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47"
loc, err := solar.NewLocationFromNMEA(nmea, 2024, time.June, 21) // Must provide date
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Latitude: %.4f\n", loc.Latitude())
fmt.Printf("Longitude: %.4f\n", loc.Longitude())
}
Output: Latitude: 48.1173 Longitude: 11.5167
Example (Gga) ¶
// Using a GGA sentence (requires external date)
nmea := "$GPGGA,123519,4339.192,N,07922.992,W,1,08,0.9,545.4,M,46.9,M,,*5C"
loc, err := NewLocationFromNMEA(nmea, 2024, time.June, 21)
if err != nil {
panic(err)
}
tm, err := NewTimeFromNMEA(nmea, 2024, time.June, 21)
if err != nil {
panic(err)
}
sunrise, err := Sunrise(loc, tm)
if err != nil {
panic(err)
}
_ = sunrise // Use the sunrise time
Example (MultipleCalculations) ¶
ExampleNewLocationFromNMEA_multipleCalculations demonstrates the efficiency of parsing NMEA once and reusing the parsed data for multiple calculations.
package main
import (
"fmt"
"github.com/mstephenholl/go-solar"
)
func main() {
nmea := "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"
// Parse NMEA once
loc, err := solar.NewLocationFromNMEA(nmea, 0, 0, 0)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
t, err := solar.NewTimeFromNMEA(nmea, 0, 0, 0)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// Use with multiple calculations
sunrise, _ := solar.Sunrise(loc, t)
sunset, _ := solar.Sunset(loc, t)
noon := solar.MeanSolarNoon(loc, t)
dawn, dusk := solar.DawnDusk(loc, t)
fmt.Printf("Dawn: %s\n", dawn.Format("15:04"))
fmt.Printf("Sunrise: %s\n", sunrise.Format("15:04"))
fmt.Printf("Solar Noon: %s\n", noon.Format("15:04"))
fmt.Printf("Sunset: %s\n", sunset.Format("15:04"))
fmt.Printf("Dusk: %s\n", dusk.Format("15:04"))
}
Output: Dawn: 04:39 Sunrise: 05:10 Solar Noon: 11:13 Sunset: 17:30 Dusk: 18:01
Example (WithSolarCalculation) ¶
ExampleNewLocationFromNMEA_withSolarCalculation demonstrates the complete pattern of parsing NMEA data and using it with solar calculations.
package main
import (
"fmt"
"github.com/mstephenholl/go-solar"
)
func main() {
// NMEA sentence from GPS
nmea := "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"
// Parse location from NMEA
loc, err := solar.NewLocationFromNMEA(nmea, 0, 0, 0)
if err != nil {
fmt.Printf("Error parsing location: %v\n", err)
return
}
// Parse time from NMEA
t, err := solar.NewTimeFromNMEA(nmea, 0, 0, 0)
if err != nil {
fmt.Printf("Error parsing time: %v\n", err)
return
}
// Now use with any solar calculation
sunrise, err := solar.Sunrise(loc, t)
if err != nil {
fmt.Printf("Error calculating sunrise: %v\n", err)
return
}
sunset, err := solar.Sunset(loc, t)
if err != nil {
fmt.Printf("Error calculating sunset: %v\n", err)
return
}
fmt.Printf("Sunrise: %s\n", sunrise.Format("15:04 MST"))
fmt.Printf("Sunset: %s\n", sunset.Format("15:04 MST"))
}
Output: Sunrise: 05:10 UTC Sunset: 17:30 UTC
func (Location) Latitude ¶
Latitude returns the latitude in decimal degrees. Positive values are north, negative values are south.
type Time ¶
type Time struct {
// contains filtered or unexported fields
}
Time represents a specific date for solar calculations. It can be created from individual date components or from a time.Time object. All times are treated as UTC.
func NewTime ¶
NewTime creates a Time from individual date components. The time is set to midnight (00:00:00) UTC.
Parameters:
- year: Year (e.g., 2025)
- month: Month (e.g., time.January)
- day: Day of month (e.g., 15)
Example:
t := solar.NewTime(2025, time.January, 15)
func NewTimeFromDateTime ¶
NewTimeFromDateTime creates a Time from a time.Time object. The time is converted to UTC if it isn't already.
Parameters:
- when: A time.Time object
Example:
now := time.Now() t := solar.NewTimeFromDateTime(now)
func NewTimeFromNMEA ¶
NewTimeFromNMEA creates a Time from an NMEA GPS sentence. The time is extracted from the NMEA sentence and combined with the provided date.
For RMC sentences, the date is parsed from the sentence and the provided year, month, day parameters are ignored. For GGA sentences, the provided date parameters are used.
Parameters:
- nmea: NMEA sentence string
- year: Year (e.g., 2025) - ignored for RMC sentences
- month: Month (e.g., time.January) - ignored for RMC sentences
- day: Day of month (e.g., 15) - ignored for RMC sentences
Returns:
- Time: The parsed time
- error: Any error encountered during parsing
Example:
nmea := "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A" t, err := solar.NewTimeFromNMEA(nmea, 0, 0, 0)
Example ¶
ExampleNewTimeFromNMEA demonstrates parsing time from an NMEA GPS sentence.
package main
import (
"fmt"
"github.com/mstephenholl/go-solar"
)
func main() {
// Parse time from an NMEA RMC sentence
nmea := "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"
t, err := solar.NewTimeFromNMEA(nmea, 0, 0, 0)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Year: %d\n", t.Year())
fmt.Printf("Month: %s\n", t.Month())
fmt.Printf("Day: %d\n", t.Day())
}
Output: Year: 1994 Month: March Day: 23
type TwilightType ¶
type TwilightType int
TwilightType represents the type of twilight for dawn/dusk calculations. Twilight is the period between daylight and darkness (or vice versa) when the sun is below the horizon but its light is still visible.
const ( // Civil twilight occurs when the sun is between 0° and 6° below the horizon. // This is the most commonly used definition for dawn and dusk in everyday contexts. // During civil twilight, there is enough natural light for most outdoor activities // without artificial lighting. The brightest stars and planets are visible. Civil TwilightType = iota // Nautical twilight occurs when the sun is between 6° and 12° below the horizon. // During nautical twilight, the horizon is still visible at sea, allowing sailors // to take reliable star sights for navigation. General ground outlines are visible, // but detailed outdoor work is difficult. Nautical // Astronomical twilight occurs when the sun is between 12° and 18° below the horizon. // During astronomical twilight, the sky is dark enough for most astronomical // observations, though the Sun's light may still interfere with observing // extremely faint objects. Beyond astronomical twilight is true night. Astronomical )