-
-
Save ParoTheParrot/c545b0aebadff4557937152f9c2c535b to your computer and use it in GitHub Desktop.
Scriptable.app widget displaying the exact time of today's sunrise and sunset. Which comes in handy in the wintertime … #scriptable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: orange; icon-glyph: sun-2; | |
/////////////////////////////////////////////////////////////////////// | |
// dawn2dusk.js | |
// Take it and have fun. | |
// Hendrik Runte, Oct 24, 2020. | |
/////////////////////////////////////////////////////////////////////// | |
// Extending the JavaScritp Date object. | |
// Usage: | |
// const sunriseDateObject = new Date().sunrise(lat, long); | |
// const sunsetDateObject = new Date().sunrise(lat, long); | |
// All the other methods just help. | |
Date.prototype.sunrise = function (latitude, longitude, zenith) { | |
return this.setSun(latitude, longitude, true, zenith); | |
}; | |
Date.prototype.sunset = function (latitude, longitude, zenith) { | |
return this.setSun(latitude, longitude, false, zenith); | |
}; | |
Date.prototype.setSun = function (latitude, longitude, isSunrise, zenith) { | |
zenith = zenith || 90.8333; | |
const DEGREES_PER_HOUR = 360 / 24; | |
const hoursFromMeridian = longitude / DEGREES_PER_HOUR; | |
const dayOfYear = this.getDayOfYear(); | |
const approxTimeOfEventInDays = isSunrise | |
? dayOfYear + (6 - hoursFromMeridian) / 24 | |
: dayOfYear + (18 - hoursFromMeridian) / 24; | |
const sunMeanAnomaly = 0.9856 * approxTimeOfEventInDays - 3.289; | |
const sunTrueLongitude = Math.mod( | |
sunMeanAnomaly + | |
1.916 * Math.sinDeg(sunMeanAnomaly) + | |
0.02 * Math.sinDeg(2 * sunMeanAnomaly) + | |
282.634, | |
360 | |
); | |
const ascension = 0.91764 * Math.tanDeg(sunTrueLongitude); | |
let rightAscension = (360 / (2 * Math.PI)) * Math.atan(ascension); | |
rightAscension = Math.mod((360 / (2 * Math.PI)) * Math.atan(ascension), 360); | |
const lQuadrant = Math.floor(sunTrueLongitude / 90) * 90; | |
const raQuadrant = Math.floor(rightAscension / 90) * 90; | |
rightAscension = rightAscension + (lQuadrant - raQuadrant); | |
rightAscension /= DEGREES_PER_HOUR; | |
const sinDec = 0.39782 * Math.sinDeg(sunTrueLongitude); | |
const cosDec = Math.cosDeg(Math.asinDeg(sinDec)); | |
const cosLocalHourAngle = | |
(Math.cosDeg(zenith) - sinDec * Math.sinDeg(latitude)) / | |
(cosDec * Math.cosDeg(latitude)); | |
const localHourAngle = Math.acosDeg(cosLocalHourAngle); | |
const localHour = isSunrise | |
? (360 - localHourAngle) / DEGREES_PER_HOUR | |
: localHourAngle / DEGREES_PER_HOUR; | |
const localMeanTime = | |
localHour + rightAscension - 0.06571 * approxTimeOfEventInDays - 6.622; | |
let time = localMeanTime - longitude / DEGREES_PER_HOUR; | |
time = Math.mod(time, 24); | |
const midnight = new Date(0); | |
midnight.setUTCFullYear(this.getUTCFullYear()); | |
midnight.setUTCMonth(this.getUTCMonth()); | |
midnight.setUTCDate(this.getUTCDate()); | |
const milli = midnight.getTime() + time * 60 * 60 * 1000; | |
return new Date(milli); | |
}; | |
// Utility functions | |
Date.prototype.getDayOfYear = function () { | |
return Math.ceil((this - new Date(this.getFullYear(), 0, 1)) / 86400000); | |
}; | |
Math.degToRad = function (num) { | |
return (num * Math.PI) / 180; | |
}; | |
Math.radToDeg = function (radians) { | |
return (radians * 180.0) / Math.PI; | |
}; | |
Math.sinDeg = function (deg) { | |
return Math.sin((deg * 2.0 * Math.PI) / 360.0); | |
}; | |
Math.acosDeg = function (x) { | |
return (Math.acos(x) * 360.0) / (2 * Math.PI); | |
}; | |
Math.asinDeg = function (x) { | |
return (Math.asin(x) * 360.0) / (2 * Math.PI); | |
}; | |
Math.tanDeg = function (deg) { | |
return Math.tan((deg * 2.0 * Math.PI) / 360.0); | |
}; | |
Math.cosDeg = function (deg) { | |
return Math.cos((deg * 2.0 * Math.PI) / 360.0); | |
}; | |
Math.mod = function (a, b) { | |
let result = a % b; | |
if (result < 0) { | |
result += b; | |
} | |
return result; | |
}; | |
/////////////////////////////////////////////////////////////////////// | |
// Here comes the actual Scriptable widget stuff. | |
/////////////////////////////////////////////////////////////////////// | |
const widget = await createWidget(); | |
if (!config.runsInWidget) { | |
await widget.presentSmall(); | |
} | |
Script.setWidget(widget); | |
Script.complete(); | |
async function createWidget(items) { | |
const list = new ListWidget(); | |
const location = await getLocation(); | |
const gradient = new LinearGradient(); | |
gradient.locations = [0, 1]; | |
gradient.colors = [new Color('#2D1925'), new Color('#402938')]; | |
list.backgroundGradient = gradient; | |
const today = new Date(); | |
const header = list.addText('Sonnenlauf'.toUpperCase()); | |
const sunrise = today.sunrise(location.latitude, location.longitude); | |
const sunset = today.sunset(location.latitude, location.longitude); | |
header.font = Font.mediumSystemFont(11); | |
header.textColor = Color.white(); | |
list.addSpacer(12); | |
// Sunrise | |
const sunriseStack = list.addStack(); | |
addSymbol({ | |
symbol: 'sunrise.fill', | |
stack: sunriseStack, | |
color: Color.orange(), | |
size: 28, | |
}); | |
sunriseStack.addSpacer(); | |
const sunriseLabel = sunriseStack.addText( | |
` ${('0' + sunrise.getHours()).slice(-2)}:${( | |
'0' + sunrise.getMinutes() | |
).slice(-2)}` | |
); | |
sunriseLabel.font = Font.boldSystemFont(28); | |
sunriseLabel.textColor = Color.orange(); | |
// Sunset | |
const sunsetStack = list.addStack(); | |
addSymbol({ | |
symbol: 'sunset.fill', | |
stack: sunsetStack, | |
color: Color.orange(), | |
size: 28, | |
}); | |
sunsetStack.addSpacer(); | |
const sunsetLabel = sunsetStack.addText( | |
` ${('0' + sunset.getHours()).slice(-2)}:${( | |
'0' + sunset.getMinutes() | |
).slice(-2)}` | |
); | |
sunsetLabel.font = Font.boldSystemFont(28); | |
sunsetLabel.textColor = Color.orange(); | |
list.addSpacer(12); | |
// Geo location: | |
const geoStack = list.addStack(); | |
addSymbol({ | |
symbol: 'location.fill', | |
stack: geoStack, | |
color: Color.white(), | |
size: 11, | |
}); | |
geoStack.addSpacer(); | |
const geoLabel = geoStack.addText( | |
` ${location.latitude | |
.toString() | |
.substr(0, 6)}, ${location.longitude.toString().substr(0, 6)}` | |
); | |
geoLabel.font = Font.thinSystemFont(11); | |
geoLabel.textColor = Color.white(); | |
// render | |
return list; | |
} | |
// Locate yourself or you params. | |
async function getLocation() { | |
try { | |
if (args.widgetParameter) { | |
const fixedCoordinates = args.widgetParameter.split(',').map(parseFloat); | |
return { latitude: fixedCoordinates[0], longitude: fixedCoordinates[1] }; | |
} else { | |
Location.setAccuracyToThreeKilometers(); | |
return await Location.current(); | |
} | |
} catch (e) { | |
return null; | |
} | |
} | |
// Helps adding icons from SF Symbols. | |
function addSymbol({ | |
symbol = 'applelogo', | |
stack, | |
color = Color.white(), | |
size = 20, | |
}) { | |
const sym = SFSymbol.named(symbol); | |
const icon = stack.addImage(sym.image); | |
icon.tintColor = color; | |
icon.imageSize = new Size(size, size); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment