astronomical.go
package astro
import (
"time"
"github.com/sixdouglas/suncalc"
)
// AstronomicalData contains calculated astronomical data for a recording
type AstronomicalData struct {
SolarNight bool // True if recording midpoint is between sunset and sunrise
CivilNight bool // True if recording midpoint is between dusk and dawn (6° below horizon)
MoonPhase float64 // 0.00=New Moon, 0.25=First Quarter, 0.50=Full Moon, 0.75=Last Quarter
}
// CalculateAstronomicalData calculates astronomical data for a recording.
// Uses the recording MIDPOINT time (not start time) for calculations.
//
// Parameters:
// - timestampUTC: Recording start time in UTC
// - durationSec: Recording duration in seconds
// - lat, lon: Location coordinates in decimal degrees
//
// Returns:
// - solarNight: true if recording midpoint is between sunset and sunrise
// - civilNight: true if recording midpoint is between dusk and dawn
// - moonPhase: 0.00-1.00 representing moon phase (0=New, 0.5=Full)
func CalculateAstronomicalData(
timestampUTC time.Time,
durationSec float64,
lat, lon float64,
) AstronomicalData {
// Calculate recording MIDPOINT (not start time)
midpoint := timestampUTC.Add(time.Duration(durationSec/2) * time.Second)
// Get solar times for midpoint date
times := suncalc.GetTimes(midpoint, lat, lon)
// Solar night: between sunset and sunrise
// Note: Handle day/night transitions properly
sunrise := times[suncalc.Sunrise].Value
sunset := times[suncalc.Sunset].Value
solarNight := isBetweenSunTimes(midpoint, sunset, sunrise)
// Civil night: between dusk and dawn (6° below horizon)
dawn := times[suncalc.Dawn].Value
dusk := times[suncalc.Dusk].Value
civilNight := isBetweenSunTimes(midpoint, dusk, dawn)
// Moon phase: 0.00=New Moon, 0.25=First Quarter, 0.50=Full Moon, 0.75=Last Quarter
moonIllum := suncalc.GetMoonIllumination(midpoint)
moonPhase := moonIllum.Phase
return AstronomicalData{
SolarNight: solarNight,
CivilNight: civilNight,
MoonPhase: moonPhase,
}
}
// isBetweenSunTimes determines if a time is between sunset/dusk and sunrise/dawn
// Handles the case where the night period crosses midnight
func isBetweenSunTimes(t, evening, morning time.Time) bool {
// If evening time is before morning time (normal case: both on same day)
// Then we're NOT in night period (daytime)
if evening.Before(morning) {
return false
}
// Otherwise, night period crosses midnight
// Night is: after evening OR before morning
return t.After(evening) || t.Before(morning)
}
// CalculateMidpointTime calculates the midpoint time of a recording
func CalculateMidpointTime(startTime time.Time, durationSec float64) time.Time {
return startTime.Add(time.Duration(durationSec/2) * time.Second)
}