├── .gitattributes ├── .gitignore ├── README.md ├── favicon.ico ├── icons ├── favicon_old.ico ├── icon.png ├── icon.svg ├── icon_128.png ├── icon_192.png ├── icon_256.png ├── icon_32.png ├── icon_512.png ├── icon_64.png ├── icon_maskable.svg └── icon_old.png ├── index.html ├── libs ├── astronomy │ ├── LICENSE.txt │ ├── astronomy.browser.js │ └── astronomy.browser.min.js └── suncalc │ ├── LICENSE │ ├── README.md │ └── suncalc.js ├── manifest.json ├── pop.html ├── resources ├── icon-generator.svg └── icons │ ├── info.svg │ ├── info2.svg │ ├── info3.svg │ ├── info4.svg │ ├── settings.svg │ ├── settings2.svg │ └── settings3.svg ├── scripts ├── app.js ├── calendar.js └── clock.js ├── styles ├── colors.css └── main.css └── worker.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | resources 2 | test 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sun Clock 2 | 3 | A 24-hour clock that shows sunrise, sunset, golden hour, and twilight times for your 4 | current location. It also shows the current position and phase of the moon, and its 5 | rising and setting times. 6 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/favicon.ico -------------------------------------------------------------------------------- /icons/favicon_old.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/icons/favicon_old.ico -------------------------------------------------------------------------------- /icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/icons/icon.png -------------------------------------------------------------------------------- /icons/icon.svg: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /icons/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/icons/icon_128.png -------------------------------------------------------------------------------- /icons/icon_192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/icons/icon_192.png -------------------------------------------------------------------------------- /icons/icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/icons/icon_256.png -------------------------------------------------------------------------------- /icons/icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/icons/icon_32.png -------------------------------------------------------------------------------- /icons/icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/icons/icon_512.png -------------------------------------------------------------------------------- /icons/icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/icons/icon_64.png -------------------------------------------------------------------------------- /icons/icon_maskable.svg: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /icons/icon_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualgeoff/sunclock/a33effb73b353d497b0667c69329fb1ab9ca3d0a/icons/icon_old.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |Getting location…
180 | 181 | 182 | 183 |Sun Clock is a 24-hour clock that displays the position of the sun, and times of sunrise, solar noon, sunset, golden hour, and twilight for your current location.
195 |It also shows the position and phase of the moon, and its rising and setting times.
196 | 197 | 198 |In the Northern Hemisphere the Sun moves across the sky in a clockwise direction; in the Southern Hemisphere, it moves anti-clockwise. Sun Clock matches this by setting its direction of rotation based on your latitude1. You can change it in the settings if you wish.
199 |1. Ideally you want the clock to turn in the same direction as the sun, regardless of which hemisphere you are in. If you are facing North when at your computer set it to anti-clockwise.
200 | 201 |Tap on or hover over the segments to get their start and end times. You can also tap/hover on the moon, the hour hand, and the centre dot.
203 |See updates for change history.
204 | 205 |Sun Clock is free to use, and contains no advertising. If you would like to help support Sun Clock, please —
207 |208 | 209 | 215 | 216 |
217 | 218 |We collect aggregate user stats only. Your location and settings are stored in your web browser and are not sent to the server. No cookies are saved or sent.
220 | 221 |223 | Feedback 〜 224 | Source 〜 225 | SunCalc 226 |
227 | 228 | ✕ 229 |Added the option to show the odd numbers on the clock face.
238 |The "use 12-hour times" option now applies to the numbers on the clock face also.
239 | 240 |Added an annual calendar. Try it out. Feedback welcome!
242 | 243 |Sun Clock is now a Progressive Web App. This means you can install it on your device homepage and it will be available when your are offline.
245 | 246 |Added auto-color mode (dynamic colors that change with the time periods.)
248 | 249 |Added dark mode.
251 | 252 |Live!
254 | 255 | ✕ 256 |Times update at Solar Midnight
263 |Event | 267 |Time | 268 |
---|
${App.formatDate(date)}
${formatDelta(date, now)}
`; 248 | } 249 | 250 | function updateAngle() { 251 | // update the angle of dateHand2 and text 252 | let date = angleToDate(angleDegrees); 253 | let str, date2, dayOfYear; 254 | 255 | if (snap) { 256 | date2 = new Date(thisYear, date.getMonth(), date.getDate(), 0, 0, 0, 0); // round down 257 | // rotate indicator line 258 | $('#dateHand2').setAttribute('transform', `rotate(${ dateToAngle(date2) })`); 259 | } else { 260 | date2 = date; 261 | // rotate indicator line 262 | $('#dateHand2').setAttribute('transform', `rotate(${ angleDegrees })`); 263 | } 264 | dayOfYear = Math.round((date2 - yearStart) / msPerDay) + 1; // no day 0 265 | 266 | // update info2 267 | str = `${App.formatDate(date2)}
${formatDelta(date2, now)}
`; 268 | if (debug) { str += `${angleDegrees.toFixed(3)}°
`; } 269 | App.showInfo(str); 270 | } 271 | 272 | function getPointerAngle(e) { 273 | // get pointer position and calculate angle 274 | // note atan2(y,x) gives the counterclockwise angle, in radians, between the +ve x-axis and the point (x,y) 275 | e.preventDefault(); 276 | let x = e.offsetX - $('#calendar').clientWidth/2; 277 | let y = e.offsetY - $('#calendar').clientHeight/2; 278 | angleDegrees = Math.atan2(y,x) * 360/tau + 90; 279 | if (angleDegrees < 0) { angleDegrees += 360; } 280 | updateAngle(); 281 | } 282 | 283 | function showPointer(e) { 284 | // show dateHand2 285 | $('#dateHand2').style.display = 'block'; 286 | getPointerAngle(e); 287 | } 288 | 289 | function hidePointer(e) { 290 | // hide dateHand2 and clear info2 291 | if (App.supportsHover) { 292 | $('#dateHand2').style.display = 'none'; 293 | App.hideInfo(); 294 | } 295 | } 296 | 297 | function init() { 298 | getDate(); 299 | drawFace(); 300 | update(); 301 | 302 | // mouse & touch 303 | $('#calendarOverlay').addEventListener("pointermove", getPointerAngle); 304 | $('#calendarOverlay').addEventListener("pointerover", showPointer); 305 | $('#calendarOverlay').addEventListener("pointerout", hidePointer); 306 | } 307 | 308 | return { 309 | update, 310 | drawFace, 311 | init 312 | }; 313 | })(); 314 | -------------------------------------------------------------------------------- /scripts/clock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Sun Clock 3 | A 24-hour clock that shows sunrise, sunset, golden hour, and twilight times for your current location 4 | 5 | Geoff Pack, May 2022 6 | https://github.com/virtualgeoff/sunclock 7 | */ 8 | 9 | /* jshint esversion: 6 */ 10 | /* globals $, $All, debug, App, SunCalc */ 11 | 12 | var SunClock = (function() { 13 | 'use strict'; 14 | 15 | let now, then, timerStart; 16 | let hours, minutes, seconds; 17 | let hourHand, minuteHand, secondHand; 18 | let clockIconHours, clockIconMinutes; 19 | let sunTimes, sunPosition, noonPosition, nadirPosition, sunAlwaysUp, sunAlwaysDown; 20 | let periodsTemp, currentPeriod, nextPeriodTime; 21 | let moonTimes, moonPosition, moonPhase, moonHand, moonIcon, moonPath; 22 | let radius = 130; 23 | 24 | const periods = [ 25 | // name: from: to: color: darkColor: 26 | ['earlyMorning', 'nadir', 'nightEnd', '#192029', '#030303'], 27 | ['astronomicalMorningTwilight', 'nightEnd', 'nauticalDawn', '#213c66', '#101d33'], 28 | ['nauticalMorningTwilight', 'nauticalDawn', 'dawn', '#4574bc', '#325489'], 29 | ['civilMorningTwilight', 'dawn', 'sunrise', '#88a6d4', '#677ea1'], 30 | ['sunrise', 'sunrise', 'sunriseEnd', '#ff9900', '#cc7a00'], 31 | ['morningGoldenHour', 'sunriseEnd', 'goldenHourEnd', '#ffe988', '#ccba6c'], 32 | ['morning', 'goldenHourEnd', 'solarNoon', '#dceaff', '#b0bbcc'], 33 | ['afternoon', 'solarNoon', 'goldenHour', '#dceaff', '#b0bbcc'], 34 | ['eveningGoldenHour', 'goldenHour', 'sunsetStart', '#ffe988', '#ccba6c'], 35 | ['sunset', 'sunsetStart', 'sunset', '#ff9900', '#cc7a00'], 36 | ['civilEveningTwilight', 'sunset', 'dusk', '#88a6d4', '#677ea1'], 37 | ['nauticalEveningTwilight', 'dusk', 'nauticalDusk', '#4574bc', '#325489'], 38 | ['astronomicalEveningTwilight', 'nauticalDusk', 'night', '#213c66', '#101d33'], 39 | ['lateEvening', 'night', 'nadir2', '#192029', '#030303'] 40 | ]; 41 | const textReplacements = { 42 | 'nadir' : 'Solar Midnight', 43 | 'earlyMorning' : 'Early Morning', 44 | 'nightEnd' : 'Astronomical Dawn', 45 | 'astronomicalMorningTwilight' : 'Astronomical Morning Twilight', 46 | 'nauticalDawn' : 'Nautical Dawn', 47 | 'nauticalMorningTwilight' : 'Nautical Morning Twilight', 48 | 'dawn' : 'Civil Dawn', 49 | 'civilMorningTwilight' : 'Civil Morning Twilight', 50 | 'sunrise' : 'Sunrise', 51 | 'sunriseEnd' : 'End of Sunrise', 52 | 'morningGoldenHour' : 'Morning Golden Hour', 53 | 'goldenHourEnd' : 'End of Golden Hour', 54 | 'morning' : 'Morning', 55 | 'solarNoon' : ' Solar Noon', 56 | 'afternoon' : 'Afternoon', 57 | 'goldenHour' : 'Start of Golden Hour', 58 | 'eveningGoldenHour' : 'Evening Golden Hour', 59 | 'sunsetStart' : 'Beginning of Sunset', 60 | 'sunset' : 'Sunset', 61 | 'civilEveningTwilight' : 'Civil Evening Twilight', 62 | 'dusk' : 'Civil Dusk', 63 | 'nauticalEveningTwilight' : 'Nautical Evening Twilight', 64 | 'nauticalDusk' : 'Nautical Dusk', 65 | 'astronomicalEveningTwilight' : 'Astronomical Evening Twilight', 66 | 'night' : 'Astronomical Dusk', 67 | 'lateEvening' : 'Late Evening', 68 | 'nadir2' : 'Solar Midnight' 69 | }; 70 | 71 | function toDegrees(angle) { 72 | // convert radians to degrees 73 | return (angle / (2 * Math.PI) * 360); 74 | } 75 | 76 | function convertAzimuth(angle) { 77 | // convert azimuth to degrees clockwise from North. SunCalc returns radians clockwise from South 78 | return ((360 + 180 + toDegrees(angle)) % 360); 79 | } 80 | 81 | function getPointFromTime(date) { 82 | // get point on clock perimeter from time 83 | // note: when daylight savings changes, some times may be in a different time zone to current time, so check offsets 84 | let nowOffset = now.getTimezoneOffset(); 85 | let dateOffset = date.getTimezoneOffset(); 86 | let direction = App.settings.direction; 87 | //var angle = ((date.getHours() + date.getMinutes()/60 + date.getSeconds()/3600) / 24 * 2 * Math.PI); // radians 88 | var angle = ((date.getHours() + date.getMinutes()/60 + (dateOffset-nowOffset)/60 + date.getSeconds()/3600) / 24 * 2 * Math.PI); // radians 89 | return `${Math.sin(angle) * radius * -direction}, ${Math.cos(angle) * radius}`; // return as string for svg path attribute 90 | } 91 | 92 | function getEarlier(time) { 93 | // get now - 24 hours 94 | return new Date(time.valueOf() - 86400000); 95 | } 96 | function getLater(time) { 97 | // get now + 24 hours 98 | return new Date(time.valueOf() + 86400000); 99 | } 100 | 101 | function getSunTimes() { 102 | // get times from suncalc.js 103 | let location = App.settings.location; 104 | if (!location) { return; } 105 | 106 | sunTimes = null; 107 | sunTimes = SunCalc.getTimes(now, location.latitude, location.longitude, 0); 108 | // get the sun times for the next day so I can get the next nadir 109 | // (can't just add 24 hrs to first one, or hack SunCalc.js (nadir2: fromJulian(Jnoon + 0.5)) 110 | sunTimes.nadir2 = SunCalc.getTimes(getLater(sunTimes.solarNoon), location.latitude, location.longitude, 0).nadir; 111 | 112 | if (debug) { 113 | console.log(`now: ${now}`); 114 | console.log(`location: ${location.latitude}, ${location.longitude}`); 115 | console.log(sunTimes); 116 | } 117 | 118 | // sometimes now is not in the range of times output by SunCalc (e.g. "2022-04-03T00:59:00+1100") 119 | while (now < sunTimes.nadir) { 120 | if (debug) { console.log('now is earlier than nadir: get earlier sun times'); } 121 | sunTimes = SunCalc.getTimes(getEarlier(sunTimes.solarNoon), location.latitude, location.longitude, 0); 122 | sunTimes.nadir2 = SunCalc.getTimes(getLater(sunTimes.solarNoon), location.latitude, location.longitude, 0).nadir; 123 | } 124 | while (now > sunTimes.nadir2) { 125 | // is this possible? 126 | if (debug) { console.log('now is later than nadir2: get later time sun times'); } 127 | sunTimes = SunCalc.getTimes(getLater(sunTimes.solarNoon), location.latitude, location.longitude, 0); 128 | sunTimes.nadir2 = SunCalc.getTimes(getLater(sunTimes.solarNoon), location.latitude, location.longitude, 0).nadir; 129 | } 130 | 131 | noonPosition = SunCalc.getPosition(sunTimes.solarNoon, location.latitude, location.longitude); 132 | nadirPosition = SunCalc.getPosition(sunTimes.nadir, location.latitude, location.longitude); 133 | sunAlwaysUp = (toDegrees(nadirPosition.altitude) > -0.833) ? true : false; // sun is always above horizon 134 | sunAlwaysDown = (toDegrees(noonPosition.altitude) < -0.833) ? true : false; // sun is always below horizon 135 | 136 | if (debug) { 137 | console.log(sunTimes); 138 | console.log(`sunAlwaysUp: ${sunAlwaysUp}, sunAlwaysDown: ${sunAlwaysDown}`); 139 | } 140 | 141 | // write times to table and below date 142 | writeMainTimes(); 143 | writeAllTimes(); 144 | 145 | // draw time period arcs on clock face 146 | drawTimePeriods(); 147 | if (App.settings.colorScheme === 'dynamic') { updateDynamicTheme(); } 148 | } 149 | 150 | function clearSunTimes() { 151 | // clear all times - called by App.clearLocation(); 152 | sunTimes = null; 153 | clearTimePeriods(); 154 | if (App.settings.colorScheme === 'dynamic') { updateDynamicTheme(); } 155 | } 156 | 157 | function writeMainTimes() { 158 | // write subset of times below date 159 | let subset = ['sunrise', 'solarNoon', 'sunset']; // subset of times to show below location 160 | if (!sunTimes) { return; } 161 | 162 | $('#mainTimes').innerHTML = ''; 163 | for (let i=0; i${textReplacements[p[1]]}
${App.formatTime(sunTimes[p[1]])}
— to —
326 |${textReplacements[p[2]]}
${App.formatTime(sunTimes[p[2]])}
Altitude: ${toDegrees(sunPosition.altitude).toFixed(2)}°
342 | Azimuth: ${convertAzimuth(sunPosition.azimuth).toFixed(2)}°
Altitude at:
344 | noon: ${toDegrees(noonPosition.altitude).toFixed(2)}°
345 | midnight: ${toDegrees(nadirPosition.altitude).toFixed(2)}°
${getMoonPhaseName(moonPhase).name}
(${(moonPhase * 29.53).toFixed(1)} days old)
Rises: ${App.formatTime(moonTimes.rise)}
Sets: ${App.formatTime(moonTimes.set)}
Sets: ${App.formatTime(moonTimes.set)}
Rises: ${App.formatTime(moonTimes.rise)}
Rises: ${App.formatTime(moonTimes.rise)}
`; 427 | } else if (moonTimes.set) { 428 | str += `Sets: ${App.formatTime(moonTimes.set)}
`; 429 | } else if (moonTimes.alwaysUp) { 430 | str += 'Moon is up all day
'; 431 | } else if (moonTimes.alwaysDown) { 432 | str += 'Moon is down all day
'; 433 | } else { 434 | // ??? 435 | } 436 | } 437 | if (moonPosition) { 438 | str += ` 439 |Altitude: ${toDegrees(moonPosition.altitude).toFixed(2)}°
440 | Azimuth: ${convertAzimuth(moonPosition.azimuth).toFixed(2)}°