Skip to content

Commit 5e73c0c

Browse files
committed
Extract sun.js to own repo
0 parents  commit 5e73c0c

12 files changed

Lines changed: 455 additions & 0 deletions

File tree

.github/jshint.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"asi": true,
3+
"esversion": 6,
4+
"evil": true,
5+
"laxcomma": true,
6+
"maxdepth": 6,
7+
"node": true,
8+
"nonbsp": true,
9+
"nonew": true,
10+
"quotmark": "double",
11+
"shadow": "outer",
12+
"undef": true,
13+
"unused": true
14+
}

.github/litejs.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"lint": {
3+
"jshint": ".github/jshint.json",
4+
"_": [
5+
"*.json",
6+
".github/*.json",
7+
"*.js"
8+
]
9+
}
10+
}

.github/workflows/test.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Test
2+
on: [ push, pull_request, workflow_dispatch ]
3+
jobs:
4+
Coverage:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v5
8+
- uses: actions/setup-node@v5
9+
- run: npm install --no-audit
10+
- run: npm audit --audit-level=low
11+
- run: npx c8 -r lcovonly -r text --check-coverage --100 npm test
12+
- run: node --import=@litejs/cli/test.js test/load.mjs
13+
- run: npx lj lint
14+
- run: npx tsc -p test/tsconfig.json --noEmit
15+
- uses: coverallsapp/github-action@v2
16+
name: Upload to coveralls.io
17+
with:
18+
github-token: ${{ github.token }}
19+
Bun:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v5
23+
- uses: oven-sh/setup-bun@v2
24+
- run: bun i
25+
- run: bun x npm t
26+
Test:
27+
strategy:
28+
matrix:
29+
include:
30+
- { node: 8, os: ubuntu-24.04, arch: x86 }
31+
- { node: 10, os: windows-2022, arch: x86 }
32+
- { node: 16, os: ubuntu-latest, arch: x64 }
33+
- { node: 22, os: macos-latest, arch: arm64 }
34+
runs-on: ${{ matrix.os }}
35+
name: Node ${{matrix.node}} (${{matrix.os}} ${{matrix.arch}})
36+
steps:
37+
- if: matrix.os == 'windows-2022'
38+
run: |
39+
git config --global core.autocrlf false
40+
tzutil /s "FLE Standard Time"
41+
- uses: actions/checkout@v5
42+
- uses: actions/setup-node@v5
43+
with:
44+
node-version: ${{ matrix.node }}
45+
architecture: ${{ matrix.arch }}
46+
- run: npm install --no-audit
47+
- run: npm test
48+
Analyze:
49+
uses: litejs/.github/.github/workflows/analyze.yml@main
50+
Release:
51+
if: startsWith(github.ref, 'refs/tags/v')
52+
needs: [Analyze, Coverage, Test]
53+
uses: litejs/.github/.github/workflows/release.yml@main
54+
secrets: inherit

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.nyc_output/
2+
coverage/
3+
node_modules/

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
[1]: https://badgen.net/coveralls/c/github/litejs/sun
3+
[2]: https://coveralls.io/r/litejs/sun
4+
[3]: https://packagephobia.now.sh/badge?p=@litejs/sun
5+
[4]: https://packagephobia.now.sh/result?p=@litejs/sun
6+
[5]: https://badgen.net/badge/icon/Buy%20Me%20A%20Tea/orange?icon=kofi&label
7+
[6]: https://www.buymeacoffee.com/lauriro
8+
9+
10+
LiteJS Sun – [![Coverage][1]][2] [![Size][3]][4] [![Buy Me A Tea][5]][6]
11+
==========
12+
13+
Sun position and timing calculations based on NOAA solar equations.
14+
15+
```sh
16+
$ npm install @litejs/sun
17+
```
18+
19+
```javascript
20+
import { sun } from "@litejs/sun"
21+
22+
let data = sun(59.437, 24.753, "2025-03-20T12:00:00Z")
23+
console.log("Sunrise:", data.rise.time, "Sunset:", data.set.time)
24+
// Sunrise: 2025-03-20T04:22:24.000Z Sunset: 2025-03-20T16:35:43.000Z
25+
console.log("Azimuth:", data.azimuth, "Elevation:", data.el)
26+
// Azimuth: 206.16 Elevation: 28.01
27+
```
28+
29+
## API
30+
31+
### sun(lat, lon, time)
32+
33+
Returns an object with the following properties:
34+
35+
| Property | Description |
36+
|----------------|----------------------------------------------|
37+
| `time` | Date object for the given time |
38+
| `decl` | Solar declination (degrees) |
39+
| `dur` | Daylight duration (minutes) |
40+
| `el` | Solar elevation with refraction (degrees) |
41+
| `elTrue` | Solar elevation without refraction (degrees) |
42+
| `eqTime` | Equation of time (minutes) |
43+
| `hourAngle` | Solar hour angle (degrees) |
44+
| `azimuth` | Solar azimuth (degrees, lazy) |
45+
| `dist` | Distance to sun in AU (lazy) |
46+
| `rightAsc` | Right ascension (degrees, lazy) |
47+
| `noon` | Solar noon data (lazy) |
48+
| `rise` | Sunrise data (lazy) |
49+
| `set` | Sunset data (lazy) |
50+
51+
The `noon`, `rise`, and `set` getters return objects with the same structure, with `time` refined to the exact event.
52+
During polar day or polar night, `rise` and `set` find the nearest event, which may be days or months away.
53+
Returns `null` only at extreme latitudes (e.g., the poles) where no sunrise or sunset occurs within a year.
54+
55+
## Contributing
56+
57+
Follow [Coding Style Guide](https://github.com/litejs/litejs/wiki/Style-Guide),
58+
run tests `npm install; npm test`.
59+
60+
61+
> Copyright (c) 2025 Lauri Rooden <lauri@rooden.ee>
62+
[MIT License](https://litejs.com/MIT-LICENSE.txt) |
63+
[GitHub repo](https://github.com/litejs/sun) |
64+
[npm package](https://npmjs.org/package/@litejs/sun) |
65+
[Buy Me A Tea][6]
66+

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@litejs/sun",
3+
"version": "25.0.0",
4+
"description": "Sun position and timing calculations based on NOAA solar equations",
5+
"license": "MIT",
6+
"author": "Lauri Rooden <lauri@rooden.ee>",
7+
"keywords": [
8+
"astronomy",
9+
"sun",
10+
"sunrise",
11+
"sunset",
12+
"litejs"
13+
],
14+
"main": "sun.js",
15+
"files": [],
16+
"scripts": {
17+
"test": "lj t test/*.js"
18+
},
19+
"repository": {
20+
"type": "git",
21+
"url": "https://github.com/litejs/sun.git"
22+
},
23+
"devDependencies": {
24+
"@litejs/cli": "25.1.0"
25+
}
26+
}

sun.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
!function(exports, Math) {
2+
exports.sun = function sun(lat, lon, time, recalc) {
3+
time = new Date(time)
4+
5+
var tmp
6+
, acos = Math.acos
7+
, cos = Math.cos
8+
, sin = Math.sin
9+
, tan = Math.tan
10+
, PI = Math.PI
11+
, rad = PI / 180
12+
, dayMs = 86400000
13+
, startOfDay = Math.floor(time / dayMs) * dayMs
14+
15+
// Julian Century
16+
, JC = (time / dayMs - 10957.5) / 36525
17+
, cosLat = cos(rad * lat)
18+
, sinLat = sin(rad * lat)
19+
20+
// Mean Longitude of the Sun
21+
, lonMean = rad * (280.46646 + JC * (36000.76983 + JC*0.0003032))
22+
// Mean Anomaly of the Sun
23+
, anomMean = rad * (357.52911 + JC * (35999.05029 - 0.0001537 * JC))
24+
// Mean Obliquity of the Ecliptic (degrees)
25+
, obliqMean = 23 + (26 + (21.448 - JC*(46.8150 + JC*(0.00059 - JC*0.001813)))/60)/60
26+
// Corrected Obliquity
27+
, obliqCorr = rad * (obliqMean + 0.00256 * cos(tmp = rad * (125.04 - 1934.136 * JC)))
28+
29+
// Sun's Equation of Center
30+
, sunEqCtr = sin(anomMean) * (1.914602 - JC * (0.004817 + 0.000014 * JC)) + sin(2 * anomMean) * (0.019993 - 0.000101 * JC) + sin(3 * anomMean) * 0.000289
31+
32+
// Apparent Longitude of the Sun
33+
, lonApp = lonMean + rad * (sunEqCtr - 0.00569 - 0.00478 * sin(tmp))
34+
35+
// Declination of the Sun
36+
, declRad = Math.asin(sin(obliqCorr) * sin(lonApp))
37+
, sinDecl = sin(declRad)
38+
, cosDecl = cos(declRad)
39+
40+
// Eccentricity of Earth's Orbit
41+
, earthEccent = 0.016708634 - JC * (0.000042037 + 0.0000001267 * JC)
42+
, varY = (tmp = tan(obliqCorr / 2)) * tmp
43+
44+
// Equation of Time (minutes)
45+
, eqTime = 4 * (
46+
varY * sin(2 * lonMean) -
47+
2 * earthEccent * sin(anomMean) +
48+
4 * earthEccent * varY * sin(anomMean) * cos(2 * lonMean) -
49+
0.5 * varY * varY * sin(4 * lonMean) -
50+
1.25 * earthEccent * earthEccent * sin(2 * anomMean)
51+
) / rad
52+
53+
// Hour Angle at Sunrise (degrees)
54+
, hourAngleRise = acos((cos(rad * 90.833) - sinLat * sinDecl) / (cosLat * cosDecl)) / rad
55+
56+
// True Solar Time (minutes)
57+
, sunTime = (time / 60000 + eqTime + 4 * lon) % 1440
58+
, hourAngle = sunTime < 0 ? sunTime / 4 + 180 : sunTime / 4 - 180
59+
60+
// Solar Zenith/Elevation
61+
, cosZenith = sinLat * sinDecl + cosLat * cosDecl * cos(rad * hourAngle)
62+
, elTrue = 90 - acos(cosZenith) / rad
63+
// Atmospheric Refraction Correction (degrees)
64+
, atmCorr = (
65+
elTrue > 85 ? 0 : (tmp = tan(rad * elTrue),
66+
elTrue > 5 ? 58.1 / tmp - 0.07 / (tmp*tmp*tmp) + 0.000086 / (tmp*tmp*tmp*tmp*tmp) :
67+
elTrue > -0.575 ? 1735 + elTrue * (-518.2 + elTrue * (103.4 + elTrue * (-12.79 + elTrue * 0.711))) :
68+
-20.774 / tmp
69+
) / 3600
70+
)
71+
72+
// Solar Noon (minutes)
73+
, noon = 720 - 4 * lon - eqTime
74+
// Sunrise & Sunset Time (minutes)
75+
, rise = noon - hourAngleRise * 4
76+
, set = noon + hourAngleRise * 4
77+
78+
if (recalc) {
79+
tmp = Math.round((recalc === "rise" ? rise : recalc === "set" ? set : noon) * 60) * 1000
80+
time.setTime(startOfDay + tmp)
81+
}
82+
83+
return {
84+
time,
85+
decl: declRad / rad,
86+
dur: 8 * hourAngleRise, // Total Sunlight Duration (minutes)
87+
el: elTrue + atmCorr, // Solar Elevation (Corrected for Refraction)
88+
elTrue,
89+
eqTime,
90+
hourAngle,
91+
get azimuth() {
92+
return (tmp = acos((sinLat * cosZenith - sinDecl) / (cosLat * Math.sqrt(1 - cosZenith * cosZenith))), hourAngle > 0 ? tmp + PI : 3 * PI - tmp) * 180 / PI % 360
93+
},
94+
get dist() {
95+
return 1.000001018 * (1 - earthEccent * earthEccent) / (1 + earthEccent * cos(anomMean + rad * sunEqCtr)) // Distance to Sun (Astronomical Units)
96+
},
97+
get rightAsc() {
98+
return Math.atan2(cos(obliqCorr) * sin(lonApp), cos(lonApp)) / rad // Right Ascension of the Sun (degrees)
99+
},
100+
get noon() {
101+
return findNext("noon", noon, -1)
102+
},
103+
get rise() {
104+
return findNext("rise", rise, 1)
105+
},
106+
get set() {
107+
return findNext("set", set, -1)
108+
},
109+
}
110+
111+
function findNext(key, minutes, inc) {
112+
if (minutes === minutes) return sun(lat, lon, startOfDay + minutes * 60000, key)
113+
var day = startOfDay, i = 366, d = (time - Date.UTC(time.getUTCFullYear(), 0, 0)) / dayMs
114+
for (inc *= (lat > 66.4 && d > 79 && d < 267 || lat < -66.4 && (d < 83 || d > 263)) ? -dayMs : dayMs; i--; )
115+
if ((d = sun(lat, lon, day += inc)).dur === d.dur) return d[key]
116+
return null
117+
}
118+
}
119+
}(this, Math) // jshint ignore:line

test/load.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
import { sun } from "../sun.js"
3+
4+
describe("Run as ESM module", () => {
5+
it("should have function createZip", assert => {
6+
assert.type(sun, "function")
7+
assert.end()
8+
})
9+
})
10+

0 commit comments

Comments
 (0)