123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- // Package sunrisesunset should be used to calculate the apparent sunrise and sunset based on the latitude, longitude, UTC offset and date.
- // All calculations (formulas) were extracted from the Solar Calculation Details of the Earth System Research Laboratory:
- // https://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html
- package utils
- import (
- "errors"
- "math"
- "time"
- )
- // The Parameters struct can also be used to manipulate
- // the data and get the sunrise and sunset
- type Parameters struct {
- Latitude float64
- Longitude float64
- UtcOffset float64
- Date time.Time
- }
- // Just call the 'general' GetSunriseSunset function and return the results
- func (p *Parameters) GetSunriseSunset() (time.Time, time.Time, error) {
- return GetSunriseSunset(p.Latitude, p.Longitude, p.UtcOffset, p.Date)
- }
- // Convert radians to degrees
- func rad2deg(radians float64) float64 {
- return radians * (180.0 / math.Pi)
- }
- // Convert degrees to radians
- func deg2rad(degrees float64) float64 {
- return degrees * (math.Pi / 180.0)
- }
- // Creates a vector with the seconds normalized to the range 0~1.
- // seconds - The number of seconds will be normalized to 1
- // Return A vector with the seconds normalized to 0~1
- func createSecondsNormalized(seconds int) (vector []float64) {
- for index := 0; index < seconds; index++ {
- temp := float64(index) / float64(seconds-1)
- vector = append(vector, temp)
- }
- return
- }
- // Calculate Julian Day based on the formula: nDays+2415018.5+secondsNorm-UTCoff/24
- // numDays - The number of days calculated in the calculate function
- // secondsNorm - Seconds normalized calculated by the createSecondsNormalized function
- // utcOffset - UTC offset defined by the user
- // Return Julian day slice
- func calcJulianDay(numDays int64, secondsNorm []float64, utcOffset float64) (julianDay []float64) {
- for index := 0; index < len(secondsNorm); index++ {
- temp := float64(numDays) + 2415018.5 + secondsNorm[index] - utcOffset/24.0
- julianDay = append(julianDay, temp)
- }
- return
- }
- // Calculate the Julian Century based on the formula: (julianDay - 2451545.0) / 36525.0
- // julianDay - Julian day vector calculated by the calcJulianDay function
- // Return Julian century slice
- func calcJulianCentury(julianDay []float64) (julianCentury []float64) {
- for index := 0; index < len(julianDay); index++ {
- temp := (julianDay[index] - 2451545.0) / 36525.0
- julianCentury = append(julianCentury, temp)
- }
- return
- }
- // Calculate the Geom Mean Long Sun in degrees based on the formula: 280.46646 + julianCentury * (36000.76983 + julianCentury * 0.0003032)
- // julianCentury - Julian century calculated by the calcJulianCentury function
- // Return The Geom Mean Long Sun slice
- func calcGeomMeanLongSun(julianCentury []float64) (geomMeanLongSun []float64) {
- for index := 0; index < len(julianCentury); index++ {
- a := 280.46646 + julianCentury[index]*(36000.76983+julianCentury[index]*0.0003032)
- temp := math.Mod(a, 360.0)
- geomMeanLongSun = append(geomMeanLongSun, temp)
- }
- return
- }
- // Calculate the Geom Mean Anom Sun in degrees based on the formula: 357.52911 + julianCentury * (35999.05029 - 0.0001537 * julianCentury)
- // julianCentury - Julian century calculated by the calcJulianCentury function
- // Return The Geom Mean Anom Sun slice
- func calcGeomMeanAnomSun(julianCentury []float64) (geomMeanAnomSun []float64) {
- for index := 0; index < len(julianCentury); index++ {
- temp := 357.52911 + julianCentury[index]*(35999.05029-0.0001537*julianCentury[index])
- geomMeanAnomSun = append(geomMeanAnomSun, temp)
- }
- return
- }
- // Calculate the Eccent Earth Orbit based on the formula: 0.016708634 - julianCentury * (0.000042037 + 0.0000001267 * julianCentury)
- // julianCentury - Julian century calculated by the calcJulianCentury function
- // Return The Eccent Earth Orbit slice
- func calcEccentEarthOrbit(julianCentury []float64) (eccentEarthOrbit []float64) {
- for index := 0; index < len(julianCentury); index++ {
- temp := 0.016708634 - julianCentury[index]*(0.000042037+0.0000001267*julianCentury[index])
- eccentEarthOrbit = append(eccentEarthOrbit, temp)
- }
- return
- }
- // Calculate the Sun Eq Ctr based on the formula: sin(deg2rad(geomMeanAnomSun))*(1.914602-julianCentury*(0.004817+0.000014*julianCentury))+sin(deg2rad(2*geomMeanAnomSun))*(0.019993-0.000101*julianCentury)+sin(deg2rad(3*geomMeanAnomSun))*0.000289;
- // julianCentury - Julian century calculated by the calcJulianCentury function
- // geomMeanAnomSun - Geom Mean Anom Sun calculated by the calcGeomMeanAnomSun function
- // Return The Sun Eq Ctr slice
- func calcSunEqCtr(julianCentury []float64, geomMeanAnomSun []float64) (sunEqCtr []float64) {
- if len(julianCentury) != len(geomMeanAnomSun) {
- return
- }
- for index := 0; index < len(julianCentury); index++ {
- temp := math.Sin(deg2rad(geomMeanAnomSun[index]))*(1.914602-julianCentury[index]*(0.004817+0.000014*julianCentury[index])) + math.Sin(deg2rad(2*geomMeanAnomSun[index]))*(0.019993-0.000101*julianCentury[index]) + math.Sin(deg2rad(3*geomMeanAnomSun[index]))*0.000289
- sunEqCtr = append(sunEqCtr, temp)
- }
- return
- }
- // Calculate the Sun True Long in degrees based on the formula: sunEqCtr + geomMeanLongSun
- // sunEqCtr - Sun Eq Ctr calculated by the calcSunEqCtr function
- // geomMeanLongSun - Geom Mean Long Sun calculated by the calcGeomMeanLongSun function
- // Return The Sun True Long slice
- func calcSunTrueLong(sunEqCtr []float64, geomMeanLongSun []float64) (sunTrueLong []float64) {
- if len(sunEqCtr) != len(geomMeanLongSun) {
- return
- }
- for index := 0; index < len(sunEqCtr); index++ {
- temp := sunEqCtr[index] + geomMeanLongSun[index]
- sunTrueLong = append(sunTrueLong, temp)
- }
- return
- }
- // Calculate the Sun App Long in degrees based on the formula: sunTrueLong-0.00569-0.00478*sin(deg2rad(125.04-1934.136*julianCentury))
- // sunTrueLong - Sun True Long calculated by the calcSunTrueLong function
- // julianCentury - Julian century calculated by the calcJulianCentury function
- // Return The Sun App Long slice
- func calcSunAppLong(sunTrueLong []float64, julianCentury []float64) (sunAppLong []float64) {
- if len(sunTrueLong) != len(julianCentury) {
- return
- }
- for index := 0; index < len(sunTrueLong); index++ {
- temp := sunTrueLong[index] - 0.00569 - 0.00478*math.Sin(deg2rad(125.04-1934.136*julianCentury[index]))
- sunAppLong = append(sunAppLong, temp)
- }
- return
- }
- // Calculate the Mean Obliq Ecliptic in degrees based on the formula: 23+(26+((21.448-julianCentury*(46.815+julianCentury*(0.00059-julianCentury*0.001813))))/60)/60
- // julianCentury - Julian century calculated by the calcJulianCentury function
- // Return the Mean Obliq Ecliptic slice
- func calcMeanObliqEcliptic(julianCentury []float64) (meanObliqEcliptic []float64) {
- for index := 0; index < len(julianCentury); index++ {
- temp := 23.0 + (26.0+(21.448-julianCentury[index]*(46.815+julianCentury[index]*(0.00059-julianCentury[index]*0.001813)))/60.0)/60.0
- meanObliqEcliptic = append(meanObliqEcliptic, temp)
- }
- return
- }
- // Calculate the Obliq Corr in degrees based on the formula: meanObliqEcliptic+0.00256*cos(deg2rad(125.04-1934.136*julianCentury))
- // meanObliqEcliptic - Mean Obliq Ecliptic calculated by the calcMeanObliqEcliptic function
- // julianCentury - Julian century calculated by the calcJulianCentury function
- // Return the Obliq Corr slice
- func calcObliqCorr(meanObliqEcliptic []float64, julianCentury []float64) (obliqCorr []float64) {
- if len(meanObliqEcliptic) != len(julianCentury) {
- return
- }
- for index := 0; index < len(julianCentury); index++ {
- temp := meanObliqEcliptic[index] + 0.00256*math.Cos(deg2rad(125.04-1934.136*julianCentury[index]))
- obliqCorr = append(obliqCorr, temp)
- }
- return
- }
- // Calculate the Sun Declination in degrees based on the formula: rad2deg(asin(sin(deg2rad(obliqCorr))*sin(deg2rad(sunAppLong))))
- // obliqCorr - Obliq Corr calculated by the calcObliqCorr function
- // sunAppLong - Sun App Long calculated by the calcSunAppLong function
- // Return the sun declination slice
- func calcSunDeclination(obliqCorr []float64, sunAppLong []float64) (sunDeclination []float64) {
- if len(obliqCorr) != len(sunAppLong) {
- return
- }
- for index := 0; index < len(obliqCorr); index++ {
- temp := rad2deg(math.Asin(math.Sin(deg2rad(obliqCorr[index])) * math.Sin(deg2rad(sunAppLong[index]))))
- sunDeclination = append(sunDeclination, temp)
- }
- return
- }
- // Calculate the equation of time (minutes) based on the formula:
- // 4*rad2deg(multiFactor*sin(2*deg2rad(geomMeanLongSun))-2*eccentEarthOrbit*sin(deg2rad(geomMeanAnomSun))+4*eccentEarthOrbit*multiFactor*sin(deg2rad(geomMeanAnomSun))*cos(2*deg2rad(geomMeanLongSun))-0.5*multiFactor*multiFactor*sin(4*deg2rad(geomMeanLongSun))-1.25*eccentEarthOrbit*eccentEarthOrbit*sin(2*deg2rad(geomMeanAnomSun)))
- // multiFactor - The Multi Factor vector calculated in the calculate function
- // geomMeanLongSun - The Geom Mean Long Sun vector calculated by the calcGeomMeanLongSun function
- // eccentEarthOrbit - The Eccent Earth vector calculated by the calcEccentEarthOrbit function
- // geomMeanAnomSun - The Geom Mean Anom Sun vector calculated by the calcGeomMeanAnomSun function
- // Return the equation of time slice
- func calcEquationOfTime(multiFactor []float64, geomMeanLongSun []float64, eccentEarthOrbit []float64, geomMeanAnomSun []float64) (equationOfTime []float64) {
- if len(multiFactor) != len(geomMeanLongSun) ||
- len(multiFactor) != len(eccentEarthOrbit) ||
- len(multiFactor) != len(geomMeanAnomSun) {
- return
- }
- for index := 0; index < len(multiFactor); index++ {
- a := multiFactor[index] * math.Sin(2.0*deg2rad(geomMeanLongSun[index]))
- b := 2.0 * eccentEarthOrbit[index] * math.Sin(deg2rad(geomMeanAnomSun[index]))
- c := 4.0 * eccentEarthOrbit[index] * multiFactor[index] * math.Sin(deg2rad(geomMeanAnomSun[index]))
- d := math.Cos(2.0 * deg2rad(geomMeanLongSun[index]))
- e := 0.5 * multiFactor[index] * multiFactor[index] * math.Sin(4.0*deg2rad(geomMeanLongSun[index]))
- f := 1.25 * eccentEarthOrbit[index] * eccentEarthOrbit[index] * math.Sin(2.0*deg2rad(geomMeanAnomSun[index]))
- temp := 4.0 * rad2deg(a-b+c*d-e-f)
- equationOfTime = append(equationOfTime, temp)
- }
- return
- }
- // Calculate the HaSunrise in degrees based on the formula: rad2deg(acos(cos(deg2rad(90.833))/(cos(deg2rad(latitude))*cos(deg2rad(sunDeclination)))-tan(deg2rad(latitude))*tan(deg2rad(sunDeclination))))
- // latitude - The latitude defined by the user
- // sunDeclination - The Sun Declination calculated by the calcSunDeclination function
- // Return the HaSunrise slice
- func calcHaSunrise(latitude float64, sunDeclination []float64) (haSunrise []float64) {
- for index := 0; index < len(sunDeclination); index++ {
- temp := rad2deg(math.Acos(math.Cos(deg2rad(90.833))/(math.Cos(deg2rad(latitude))*math.Cos(deg2rad(sunDeclination[index]))) - math.Tan(deg2rad(latitude))*math.Tan(deg2rad(sunDeclination[index]))))
- haSunrise = append(haSunrise, temp)
- }
- return
- }
- // Calculate the Solar Noon based on the formula: (720 - 4 * longitude - equationOfTime + utcOffset * 60) * 60
- // longitude - The longitude is defined by the user
- // equationOfTime - The Equation of Time slice is calculated by the calcEquationOfTime function
- // utcOffset - The UTC offset is defined by the user
- // Return the Solar Noon slice
- func calcSolarNoon(longitude float64, equationOfTime []float64, utcOffset float64) (solarNoon []float64) {
- for index := 0; index < len(equationOfTime); index++ {
- temp := (720.0 - 4.0*longitude - equationOfTime[index] + utcOffset*60.0) * 60.0
- solarNoon = append(solarNoon, temp)
- }
- return
- }
- // Check if the latitude is valid. Range: -90 - 90
- func checkLatitude(latitude float64) bool {
- if latitude < -90.0 || latitude > 90.0 {
- return false
- }
- return true
- }
- // Check if the longitude is valid. Range: -180 - 180
- func checkLongitude(longitude float64) bool {
- if longitude < -180.0 || longitude > 180.0 {
- return false
- }
- return true
- }
- // Check if the UTC offset is valid. Range: -12 - 14
- func checkUtcOffset(utcOffset float64) bool {
- if utcOffset < -12.0 || utcOffset > 14.0 {
- return false
- }
- return true
- }
- // Check if the date is valid.
- func checkDate(date time.Time) bool {
- minDate := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)
- maxDate := time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC)
- if date.Before(minDate) || date.After(maxDate) {
- return false
- }
- return true
- }
- // Compute the number of days between two dates
- func diffDays(date1, date2 time.Time) int64 {
- return int64(date2.Sub(date1) / (24 * time.Hour))
- }
- // Find the index of the minimum value
- func minIndex(slice []float64) int {
- if len(slice) == 0 {
- return -1
- }
- min := slice[0]
- minIndex := 0
- for index := 0; index < len(slice); index++ {
- if slice[index] < min {
- min = slice[index]
- minIndex = index
- }
- }
- return minIndex
- }
- // Convert each value to the absolute value
- func abs(slice []float64) []float64 {
- var newSlice []float64
- for _, value := range slice {
- if value < 0.0 {
- value = math.Abs(value)
- }
- newSlice = append(newSlice, value)
- }
- return newSlice
- }
- func round(value float64) int {
- if value < 0 {
- return int(value - 0.5)
- }
- return int(value + 0.5)
- }
- // GetSunriseSunset function is responsible for calculate the apparent Sunrise and Sunset times.
- // If some parameter is wrong it will return an error.
- func GetSunriseSunset(latitude float64, longitude float64, utcOffset float64, date time.Time) (sunrise time.Time, sunset time.Time, err error) {
- // Check latitude
- if !checkLatitude(latitude) {
- err = errors.New("Invalid latitude")
- return
- }
- // Check longitude
- if !checkLongitude(longitude) {
- err = errors.New("Invalid longitude")
- return
- }
- // Check UTC offset
- if !checkUtcOffset(utcOffset) {
- err = errors.New("Invalid UTC offset")
- return
- }
- // Check date
- if !checkDate(date) {
- err = errors.New("Invalid date")
- return
- }
- // The number of days since 30/12/1899
- since := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
- numDays := diffDays(since, date)
- // Seconds of a full day 86400
- seconds := 24 * 60 * 60
- // Creates a vector that represents each second in the range 0~1
- secondsNorm := createSecondsNormalized(seconds)
- // Calculate Julian Day
- julianDay := calcJulianDay(numDays, secondsNorm, utcOffset)
- // Calculate Julian Century
- julianCentury := calcJulianCentury(julianDay)
- // Geom Mean Long Sun (deg)
- geomMeanLongSun := calcGeomMeanLongSun(julianCentury)
- // Geom Mean Anom Sun (deg)
- geomMeanAnomSun := calcGeomMeanAnomSun(julianCentury)
- // Eccent Earth Orbit
- eccentEarthOrbit := calcEccentEarthOrbit(julianCentury)
- // Sun Eq of Ctr
- sunEqCtr := calcSunEqCtr(julianCentury, geomMeanAnomSun)
- // Sun True Long (deg)
- sunTrueLong := calcSunTrueLong(sunEqCtr, geomMeanLongSun)
- // Sun App Long (deg)
- sunAppLong := calcSunAppLong(sunTrueLong, julianCentury)
- // Mean Obliq Ecliptic (deg)
- meanObliqEcliptic := calcMeanObliqEcliptic(julianCentury)
- // Obliq Corr (deg)
- obliqCorr := calcObliqCorr(meanObliqEcliptic, julianCentury)
- // Sun Declin (deg)
- sunDeclination := calcSunDeclination(obliqCorr, sunAppLong)
- // var y
- var multiFactor []float64
- for index := 0; index < len(obliqCorr); index++ {
- temp := math.Tan(deg2rad(obliqCorr[index]/2.0)) * math.Tan(deg2rad(obliqCorr[index]/2.0))
- multiFactor = append(multiFactor, temp)
- }
- // Eq of Time (minutes)
- equationOfTime := calcEquationOfTime(multiFactor, geomMeanLongSun, eccentEarthOrbit, geomMeanAnomSun)
- // HA Sunrise (deg)
- haSunrise := calcHaSunrise(latitude, sunDeclination)
- // Solar Noon (LST)
- solarNoon := calcSolarNoon(longitude, equationOfTime, utcOffset)
- // Sunrise and Sunset Times (LST)
- var tempSunrise []float64
- var tempSunset []float64
- for index := 0; index < len(solarNoon); index++ {
- tempSunrise = append(tempSunrise, (solarNoon[index] - float64(round(haSunrise[index]*4.0*60.0)) - float64(seconds)*secondsNorm[index]))
- tempSunset = append(tempSunset, (solarNoon[index] + float64(round(haSunrise[index]*4.0*60.0)) - float64(seconds)*secondsNorm[index]))
- }
- // Get the sunrise and sunset in seconds
- sunriseSeconds := minIndex(abs(tempSunrise))
- sunsetSeconds := minIndex(abs(tempSunset))
- // Convert the seconds to time
- defaultTime := new(time.Time)
- sunrise = defaultTime.Add(time.Duration(sunriseSeconds) * time.Second)
- sunset = defaultTime.Add(time.Duration(sunsetSeconds) * time.Second)
- return
- }
- func SunriseSunsetForChina(latitude, longitude float64) (string, string, error) {
- p := Parameters{
- Latitude: latitude,
- Longitude: longitude,
- UtcOffset: 8.0,
- Date: New(MlNow()).BeginningOfDay(),
- }
- // Get sunrise and sunset in seconds
- sunrise, sunset, err := p.GetSunriseSunset()
- if err != nil {
- return "", "", err
- }
- if sunrise.Second() >= 30 {
- sunrise = sunrise.Add(time.Minute)
- }
- if sunset.Second() >= 30 {
- sunset = sunset.Add(time.Minute)
- }
- //return "16:17", "16:16", nil
- return sunrise.Format("15:04"), sunset.Format("15:04"), nil
- }
|