sunrisesunset.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. // Package sunrisesunset should be used to calculate the apparent sunrise and sunset based on the latitude, longitude, UTC offset and date.
  2. // All calculations (formulas) were extracted from the Solar Calculation Details of the Earth System Research Laboratory:
  3. // https://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html
  4. package utils
  5. import (
  6. "errors"
  7. "math"
  8. "time"
  9. )
  10. // The Parameters struct can also be used to manipulate
  11. // the data and get the sunrise and sunset
  12. type Parameters struct {
  13. Latitude float64
  14. Longitude float64
  15. UtcOffset float64
  16. Date time.Time
  17. }
  18. // Just call the 'general' GetSunriseSunset function and return the results
  19. func (p *Parameters) GetSunriseSunset() (time.Time, time.Time, error) {
  20. return GetSunriseSunset(p.Latitude, p.Longitude, p.UtcOffset, p.Date)
  21. }
  22. // Convert radians to degrees
  23. func rad2deg(radians float64) float64 {
  24. return radians * (180.0 / math.Pi)
  25. }
  26. // Convert degrees to radians
  27. func deg2rad(degrees float64) float64 {
  28. return degrees * (math.Pi / 180.0)
  29. }
  30. // Creates a vector with the seconds normalized to the range 0~1.
  31. // seconds - The number of seconds will be normalized to 1
  32. // Return A vector with the seconds normalized to 0~1
  33. func createSecondsNormalized(seconds int) (vector []float64) {
  34. for index := 0; index < seconds; index++ {
  35. temp := float64(index) / float64(seconds-1)
  36. vector = append(vector, temp)
  37. }
  38. return
  39. }
  40. // Calculate Julian Day based on the formula: nDays+2415018.5+secondsNorm-UTCoff/24
  41. // numDays - The number of days calculated in the calculate function
  42. // secondsNorm - Seconds normalized calculated by the createSecondsNormalized function
  43. // utcOffset - UTC offset defined by the user
  44. // Return Julian day slice
  45. func calcJulianDay(numDays int64, secondsNorm []float64, utcOffset float64) (julianDay []float64) {
  46. for index := 0; index < len(secondsNorm); index++ {
  47. temp := float64(numDays) + 2415018.5 + secondsNorm[index] - utcOffset/24.0
  48. julianDay = append(julianDay, temp)
  49. }
  50. return
  51. }
  52. // Calculate the Julian Century based on the formula: (julianDay - 2451545.0) / 36525.0
  53. // julianDay - Julian day vector calculated by the calcJulianDay function
  54. // Return Julian century slice
  55. func calcJulianCentury(julianDay []float64) (julianCentury []float64) {
  56. for index := 0; index < len(julianDay); index++ {
  57. temp := (julianDay[index] - 2451545.0) / 36525.0
  58. julianCentury = append(julianCentury, temp)
  59. }
  60. return
  61. }
  62. // Calculate the Geom Mean Long Sun in degrees based on the formula: 280.46646 + julianCentury * (36000.76983 + julianCentury * 0.0003032)
  63. // julianCentury - Julian century calculated by the calcJulianCentury function
  64. // Return The Geom Mean Long Sun slice
  65. func calcGeomMeanLongSun(julianCentury []float64) (geomMeanLongSun []float64) {
  66. for index := 0; index < len(julianCentury); index++ {
  67. a := 280.46646 + julianCentury[index]*(36000.76983+julianCentury[index]*0.0003032)
  68. temp := math.Mod(a, 360.0)
  69. geomMeanLongSun = append(geomMeanLongSun, temp)
  70. }
  71. return
  72. }
  73. // Calculate the Geom Mean Anom Sun in degrees based on the formula: 357.52911 + julianCentury * (35999.05029 - 0.0001537 * julianCentury)
  74. // julianCentury - Julian century calculated by the calcJulianCentury function
  75. // Return The Geom Mean Anom Sun slice
  76. func calcGeomMeanAnomSun(julianCentury []float64) (geomMeanAnomSun []float64) {
  77. for index := 0; index < len(julianCentury); index++ {
  78. temp := 357.52911 + julianCentury[index]*(35999.05029-0.0001537*julianCentury[index])
  79. geomMeanAnomSun = append(geomMeanAnomSun, temp)
  80. }
  81. return
  82. }
  83. // Calculate the Eccent Earth Orbit based on the formula: 0.016708634 - julianCentury * (0.000042037 + 0.0000001267 * julianCentury)
  84. // julianCentury - Julian century calculated by the calcJulianCentury function
  85. // Return The Eccent Earth Orbit slice
  86. func calcEccentEarthOrbit(julianCentury []float64) (eccentEarthOrbit []float64) {
  87. for index := 0; index < len(julianCentury); index++ {
  88. temp := 0.016708634 - julianCentury[index]*(0.000042037+0.0000001267*julianCentury[index])
  89. eccentEarthOrbit = append(eccentEarthOrbit, temp)
  90. }
  91. return
  92. }
  93. // 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;
  94. // julianCentury - Julian century calculated by the calcJulianCentury function
  95. // geomMeanAnomSun - Geom Mean Anom Sun calculated by the calcGeomMeanAnomSun function
  96. // Return The Sun Eq Ctr slice
  97. func calcSunEqCtr(julianCentury []float64, geomMeanAnomSun []float64) (sunEqCtr []float64) {
  98. if len(julianCentury) != len(geomMeanAnomSun) {
  99. return
  100. }
  101. for index := 0; index < len(julianCentury); index++ {
  102. 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
  103. sunEqCtr = append(sunEqCtr, temp)
  104. }
  105. return
  106. }
  107. // Calculate the Sun True Long in degrees based on the formula: sunEqCtr + geomMeanLongSun
  108. // sunEqCtr - Sun Eq Ctr calculated by the calcSunEqCtr function
  109. // geomMeanLongSun - Geom Mean Long Sun calculated by the calcGeomMeanLongSun function
  110. // Return The Sun True Long slice
  111. func calcSunTrueLong(sunEqCtr []float64, geomMeanLongSun []float64) (sunTrueLong []float64) {
  112. if len(sunEqCtr) != len(geomMeanLongSun) {
  113. return
  114. }
  115. for index := 0; index < len(sunEqCtr); index++ {
  116. temp := sunEqCtr[index] + geomMeanLongSun[index]
  117. sunTrueLong = append(sunTrueLong, temp)
  118. }
  119. return
  120. }
  121. // Calculate the Sun App Long in degrees based on the formula: sunTrueLong-0.00569-0.00478*sin(deg2rad(125.04-1934.136*julianCentury))
  122. // sunTrueLong - Sun True Long calculated by the calcSunTrueLong function
  123. // julianCentury - Julian century calculated by the calcJulianCentury function
  124. // Return The Sun App Long slice
  125. func calcSunAppLong(sunTrueLong []float64, julianCentury []float64) (sunAppLong []float64) {
  126. if len(sunTrueLong) != len(julianCentury) {
  127. return
  128. }
  129. for index := 0; index < len(sunTrueLong); index++ {
  130. temp := sunTrueLong[index] - 0.00569 - 0.00478*math.Sin(deg2rad(125.04-1934.136*julianCentury[index]))
  131. sunAppLong = append(sunAppLong, temp)
  132. }
  133. return
  134. }
  135. // 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
  136. // julianCentury - Julian century calculated by the calcJulianCentury function
  137. // Return the Mean Obliq Ecliptic slice
  138. func calcMeanObliqEcliptic(julianCentury []float64) (meanObliqEcliptic []float64) {
  139. for index := 0; index < len(julianCentury); index++ {
  140. temp := 23.0 + (26.0+(21.448-julianCentury[index]*(46.815+julianCentury[index]*(0.00059-julianCentury[index]*0.001813)))/60.0)/60.0
  141. meanObliqEcliptic = append(meanObliqEcliptic, temp)
  142. }
  143. return
  144. }
  145. // Calculate the Obliq Corr in degrees based on the formula: meanObliqEcliptic+0.00256*cos(deg2rad(125.04-1934.136*julianCentury))
  146. // meanObliqEcliptic - Mean Obliq Ecliptic calculated by the calcMeanObliqEcliptic function
  147. // julianCentury - Julian century calculated by the calcJulianCentury function
  148. // Return the Obliq Corr slice
  149. func calcObliqCorr(meanObliqEcliptic []float64, julianCentury []float64) (obliqCorr []float64) {
  150. if len(meanObliqEcliptic) != len(julianCentury) {
  151. return
  152. }
  153. for index := 0; index < len(julianCentury); index++ {
  154. temp := meanObliqEcliptic[index] + 0.00256*math.Cos(deg2rad(125.04-1934.136*julianCentury[index]))
  155. obliqCorr = append(obliqCorr, temp)
  156. }
  157. return
  158. }
  159. // Calculate the Sun Declination in degrees based on the formula: rad2deg(asin(sin(deg2rad(obliqCorr))*sin(deg2rad(sunAppLong))))
  160. // obliqCorr - Obliq Corr calculated by the calcObliqCorr function
  161. // sunAppLong - Sun App Long calculated by the calcSunAppLong function
  162. // Return the sun declination slice
  163. func calcSunDeclination(obliqCorr []float64, sunAppLong []float64) (sunDeclination []float64) {
  164. if len(obliqCorr) != len(sunAppLong) {
  165. return
  166. }
  167. for index := 0; index < len(obliqCorr); index++ {
  168. temp := rad2deg(math.Asin(math.Sin(deg2rad(obliqCorr[index])) * math.Sin(deg2rad(sunAppLong[index]))))
  169. sunDeclination = append(sunDeclination, temp)
  170. }
  171. return
  172. }
  173. // Calculate the equation of time (minutes) based on the formula:
  174. // 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)))
  175. // multiFactor - The Multi Factor vector calculated in the calculate function
  176. // geomMeanLongSun - The Geom Mean Long Sun vector calculated by the calcGeomMeanLongSun function
  177. // eccentEarthOrbit - The Eccent Earth vector calculated by the calcEccentEarthOrbit function
  178. // geomMeanAnomSun - The Geom Mean Anom Sun vector calculated by the calcGeomMeanAnomSun function
  179. // Return the equation of time slice
  180. func calcEquationOfTime(multiFactor []float64, geomMeanLongSun []float64, eccentEarthOrbit []float64, geomMeanAnomSun []float64) (equationOfTime []float64) {
  181. if len(multiFactor) != len(geomMeanLongSun) ||
  182. len(multiFactor) != len(eccentEarthOrbit) ||
  183. len(multiFactor) != len(geomMeanAnomSun) {
  184. return
  185. }
  186. for index := 0; index < len(multiFactor); index++ {
  187. a := multiFactor[index] * math.Sin(2.0*deg2rad(geomMeanLongSun[index]))
  188. b := 2.0 * eccentEarthOrbit[index] * math.Sin(deg2rad(geomMeanAnomSun[index]))
  189. c := 4.0 * eccentEarthOrbit[index] * multiFactor[index] * math.Sin(deg2rad(geomMeanAnomSun[index]))
  190. d := math.Cos(2.0 * deg2rad(geomMeanLongSun[index]))
  191. e := 0.5 * multiFactor[index] * multiFactor[index] * math.Sin(4.0*deg2rad(geomMeanLongSun[index]))
  192. f := 1.25 * eccentEarthOrbit[index] * eccentEarthOrbit[index] * math.Sin(2.0*deg2rad(geomMeanAnomSun[index]))
  193. temp := 4.0 * rad2deg(a-b+c*d-e-f)
  194. equationOfTime = append(equationOfTime, temp)
  195. }
  196. return
  197. }
  198. // 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))))
  199. // latitude - The latitude defined by the user
  200. // sunDeclination - The Sun Declination calculated by the calcSunDeclination function
  201. // Return the HaSunrise slice
  202. func calcHaSunrise(latitude float64, sunDeclination []float64) (haSunrise []float64) {
  203. for index := 0; index < len(sunDeclination); index++ {
  204. 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]))))
  205. haSunrise = append(haSunrise, temp)
  206. }
  207. return
  208. }
  209. // Calculate the Solar Noon based on the formula: (720 - 4 * longitude - equationOfTime + utcOffset * 60) * 60
  210. // longitude - The longitude is defined by the user
  211. // equationOfTime - The Equation of Time slice is calculated by the calcEquationOfTime function
  212. // utcOffset - The UTC offset is defined by the user
  213. // Return the Solar Noon slice
  214. func calcSolarNoon(longitude float64, equationOfTime []float64, utcOffset float64) (solarNoon []float64) {
  215. for index := 0; index < len(equationOfTime); index++ {
  216. temp := (720.0 - 4.0*longitude - equationOfTime[index] + utcOffset*60.0) * 60.0
  217. solarNoon = append(solarNoon, temp)
  218. }
  219. return
  220. }
  221. // Check if the latitude is valid. Range: -90 - 90
  222. func checkLatitude(latitude float64) bool {
  223. if latitude < -90.0 || latitude > 90.0 {
  224. return false
  225. }
  226. return true
  227. }
  228. // Check if the longitude is valid. Range: -180 - 180
  229. func checkLongitude(longitude float64) bool {
  230. if longitude < -180.0 || longitude > 180.0 {
  231. return false
  232. }
  233. return true
  234. }
  235. // Check if the UTC offset is valid. Range: -12 - 14
  236. func checkUtcOffset(utcOffset float64) bool {
  237. if utcOffset < -12.0 || utcOffset > 14.0 {
  238. return false
  239. }
  240. return true
  241. }
  242. // Check if the date is valid.
  243. func checkDate(date time.Time) bool {
  244. minDate := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)
  245. maxDate := time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC)
  246. if date.Before(minDate) || date.After(maxDate) {
  247. return false
  248. }
  249. return true
  250. }
  251. // Compute the number of days between two dates
  252. func diffDays(date1, date2 time.Time) int64 {
  253. return int64(date2.Sub(date1) / (24 * time.Hour))
  254. }
  255. // Find the index of the minimum value
  256. func minIndex(slice []float64) int {
  257. if len(slice) == 0 {
  258. return -1
  259. }
  260. min := slice[0]
  261. minIndex := 0
  262. for index := 0; index < len(slice); index++ {
  263. if slice[index] < min {
  264. min = slice[index]
  265. minIndex = index
  266. }
  267. }
  268. return minIndex
  269. }
  270. // Convert each value to the absolute value
  271. func abs(slice []float64) []float64 {
  272. var newSlice []float64
  273. for _, value := range slice {
  274. if value < 0.0 {
  275. value = math.Abs(value)
  276. }
  277. newSlice = append(newSlice, value)
  278. }
  279. return newSlice
  280. }
  281. func round(value float64) int {
  282. if value < 0 {
  283. return int(value - 0.5)
  284. }
  285. return int(value + 0.5)
  286. }
  287. // GetSunriseSunset function is responsible for calculate the apparent Sunrise and Sunset times.
  288. // If some parameter is wrong it will return an error.
  289. func GetSunriseSunset(latitude float64, longitude float64, utcOffset float64, date time.Time) (sunrise time.Time, sunset time.Time, err error) {
  290. // Check latitude
  291. if !checkLatitude(latitude) {
  292. err = errors.New("Invalid latitude")
  293. return
  294. }
  295. // Check longitude
  296. if !checkLongitude(longitude) {
  297. err = errors.New("Invalid longitude")
  298. return
  299. }
  300. // Check UTC offset
  301. if !checkUtcOffset(utcOffset) {
  302. err = errors.New("Invalid UTC offset")
  303. return
  304. }
  305. // Check date
  306. if !checkDate(date) {
  307. err = errors.New("Invalid date")
  308. return
  309. }
  310. // The number of days since 30/12/1899
  311. since := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
  312. numDays := diffDays(since, date)
  313. // Seconds of a full day 86400
  314. seconds := 24 * 60 * 60
  315. // Creates a vector that represents each second in the range 0~1
  316. secondsNorm := createSecondsNormalized(seconds)
  317. // Calculate Julian Day
  318. julianDay := calcJulianDay(numDays, secondsNorm, utcOffset)
  319. // Calculate Julian Century
  320. julianCentury := calcJulianCentury(julianDay)
  321. // Geom Mean Long Sun (deg)
  322. geomMeanLongSun := calcGeomMeanLongSun(julianCentury)
  323. // Geom Mean Anom Sun (deg)
  324. geomMeanAnomSun := calcGeomMeanAnomSun(julianCentury)
  325. // Eccent Earth Orbit
  326. eccentEarthOrbit := calcEccentEarthOrbit(julianCentury)
  327. // Sun Eq of Ctr
  328. sunEqCtr := calcSunEqCtr(julianCentury, geomMeanAnomSun)
  329. // Sun True Long (deg)
  330. sunTrueLong := calcSunTrueLong(sunEqCtr, geomMeanLongSun)
  331. // Sun App Long (deg)
  332. sunAppLong := calcSunAppLong(sunTrueLong, julianCentury)
  333. // Mean Obliq Ecliptic (deg)
  334. meanObliqEcliptic := calcMeanObliqEcliptic(julianCentury)
  335. // Obliq Corr (deg)
  336. obliqCorr := calcObliqCorr(meanObliqEcliptic, julianCentury)
  337. // Sun Declin (deg)
  338. sunDeclination := calcSunDeclination(obliqCorr, sunAppLong)
  339. // var y
  340. var multiFactor []float64
  341. for index := 0; index < len(obliqCorr); index++ {
  342. temp := math.Tan(deg2rad(obliqCorr[index]/2.0)) * math.Tan(deg2rad(obliqCorr[index]/2.0))
  343. multiFactor = append(multiFactor, temp)
  344. }
  345. // Eq of Time (minutes)
  346. equationOfTime := calcEquationOfTime(multiFactor, geomMeanLongSun, eccentEarthOrbit, geomMeanAnomSun)
  347. // HA Sunrise (deg)
  348. haSunrise := calcHaSunrise(latitude, sunDeclination)
  349. // Solar Noon (LST)
  350. solarNoon := calcSolarNoon(longitude, equationOfTime, utcOffset)
  351. // Sunrise and Sunset Times (LST)
  352. var tempSunrise []float64
  353. var tempSunset []float64
  354. for index := 0; index < len(solarNoon); index++ {
  355. tempSunrise = append(tempSunrise, (solarNoon[index] - float64(round(haSunrise[index]*4.0*60.0)) - float64(seconds)*secondsNorm[index]))
  356. tempSunset = append(tempSunset, (solarNoon[index] + float64(round(haSunrise[index]*4.0*60.0)) - float64(seconds)*secondsNorm[index]))
  357. }
  358. // Get the sunrise and sunset in seconds
  359. sunriseSeconds := minIndex(abs(tempSunrise))
  360. sunsetSeconds := minIndex(abs(tempSunset))
  361. // Convert the seconds to time
  362. defaultTime := new(time.Time)
  363. sunrise = defaultTime.Add(time.Duration(sunriseSeconds) * time.Second)
  364. sunset = defaultTime.Add(time.Duration(sunsetSeconds) * time.Second)
  365. return
  366. }
  367. func SunriseSunsetForChina(latitude, longitude float64) (string, string, error) {
  368. p := Parameters{
  369. Latitude: latitude,
  370. Longitude: longitude,
  371. UtcOffset: 8.0,
  372. Date: New(MlNow()).BeginningOfDay(),
  373. }
  374. // Get sunrise and sunset in seconds
  375. sunrise, sunset, err := p.GetSunriseSunset()
  376. if err != nil {
  377. return "", "", err
  378. }
  379. if sunrise.Second() >= 30 {
  380. sunrise = sunrise.Add(time.Minute)
  381. }
  382. if sunset.Second() >= 30 {
  383. sunset = sunset.Add(time.Minute)
  384. }
  385. //return "16:17", "16:16", nil
  386. return sunrise.Format("15:04"), sunset.Format("15:04"), nil
  387. }