package suncalc import ( "errors" "math" "time" ) const ( dayMs = 1000 * 60 * 60 * 24 j1970 = 2440588 j2000 = 2451545 rad = math.Pi / 180 e = rad * 23.4397 ) var ErrNoCivilTwilight = errors.New("civil twilight is not available for the selected date and location") type CivilTwilight struct { Dawn time.Time Dusk time.Time } func GetCivilTwilight(date time.Time, lat, lng float64) (CivilTwilight, error) { if math.IsNaN(lat) || math.IsNaN(lng) || lat < -90 || lat > 90 || lng < -180 || lng > 180 { return CivilTwilight{}, errors.New("invalid coordinates") } d := toDays(date) lw := -lng * rad phi := lat * rad n := julianCycle(d, lw) ds := approxTransit(0, lw, n) M := solarMeanAnomaly(ds) L := eclipticLongitude(M) dec := declination(L, 0) noonJ := solarTransitJ(ds, M, L) duskJ, ok := getSetJ(-6*rad, lw, phi, dec, n, M, L) if !ok { return CivilTwilight{}, ErrNoCivilTwilight } dawnJ := 2*noonJ - duskJ return CivilTwilight{ Dawn: fromJulian(dawnJ), Dusk: fromJulian(duskJ), }, nil } func toJulian(date time.Time) float64 { return float64(date.UTC().Unix())/86400 - 0.5 + j1970 } func fromJulian(j float64) time.Time { return time.Unix(int64((j+0.5-float64(j1970))*86400), 0).UTC() } func toDays(date time.Time) float64 { return toJulian(date) - j2000 } func rightAscension(l, b float64) float64 { return math.Atan2(math.Sin(l)*math.Cos(e)-math.Tan(b)*math.Sin(e), math.Cos(l)) } func declination(l, b float64) float64 { return math.Asin(math.Sin(b)*math.Cos(e) + math.Cos(b)*math.Sin(e)*math.Sin(l)) } func solarMeanAnomaly(d float64) float64 { return rad * (357.5291 + 0.98560028*d) } func eclipticLongitude(M float64) float64 { C := rad * (1.9148*math.Sin(M) + 0.02*math.Sin(2*M) + 0.0003*math.Sin(3*M)) P := rad * 102.9372 return M + C + P + math.Pi } func julianCycle(d, lw float64) float64 { return math.Round(d - 0.0009 - lw/(2*math.Pi)) } func approxTransit(Ht, lw, n float64) float64 { return 0.0009 + (Ht+lw)/(2*math.Pi) + n } func solarTransitJ(ds, M, L float64) float64 { return j2000 + ds + 0.0053*math.Sin(M) - 0.0069*math.Sin(2*L) } func hourAngle(h, phi, d float64) float64 { cosH := (math.Sin(h) - math.Sin(phi)*math.Sin(d)) / (math.Cos(phi) * math.Cos(d)) if cosH < -1 || cosH > 1 { return math.NaN() } return math.Acos(cosH) } func getSetJ(h, lw, phi, dec, n, M, L float64) (float64, bool) { w := hourAngle(h, phi, dec) if math.IsNaN(w) { return 0, false } a := approxTransit(w, lw, n) return solarTransitJ(a, M, L), true }