// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: orange; icon-glyph: sun; | |
/////////////////////////////////////////////////////////////////////// | |
// dawn2dusk.js | |
// Origin: | |
// https://gist.github.com/HendrikRunte/4b5d03cb26e31508bc96553ad3c10f47 | |
// Take it and have fun. | |
// Hendrik Runte, Nov 12, 2020, 17:33. | |
/////////////////////////////////////////////////////////////////////// | |
// 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. | |
/////////////////////////////////////////////////////////////////////// | |
function getMoonphase(dateObj) { | |
// Bluntly copied from https://gist.github.com/endel/dfe6bb2fbe679781948c | |
let c = 0; | |
let e = 0; | |
let jd = 0; | |
let b = 0; | |
let year = dateObj.getFullYear(); | |
let month = dateObj.getMonth() + 1; | |
let day = dateObj.getDate(); | |
if (month < 3) { | |
year--; | |
month += 12; | |
} | |
++month; | |
c = 365.25 * year; | |
e = 30.6 * month; | |
jd = c + e + day - 694039.09; // jd is total days elapsed | |
jd /= 29.5305882; // divide by the moon cycle | |
b = parseInt(jd); // int(jd) -> b, take integer part of jd | |
jd -= b; // subtract integer part to leave fractional part of original jd | |
b = Math.round(jd * 8); // scale fraction from 0-8 and round | |
if (b >= 8) { | |
b = 0; // 0 and 8 are the same so turn 8 into 0 | |
} | |
return b; | |
} | |
// Helps adding icons from SF Symbols. | |
function addSymbol({ | |
symbolName = 'applelogo', | |
stack, | |
color = Color.white(), | |
size = 20, | |
}) { | |
const icon = stack.addImage(SFSymbol.named(symbolName).image); | |
icon.tintColor = color; | |
icon.imageSize = new Size(size, size); | |
} | |
function getSunriseAndSunset(date, location) { | |
return { | |
location: location, | |
todaysSunrise: date.sunrise(location.latitude, location.longitude), | |
todaysSunset: date.sunset(location.latitude, location.longitude), | |
}; | |
} | |
function displayLoadingIndicator() { | |
const listWidget = new ListWidget(); | |
const gradient = new LinearGradient(); | |
gradient.locations = [0, 1]; | |
gradient.colors = [new Color('#000618'), new Color('#121A34')]; | |
listWidget.backgroundGradient = gradient; | |
const iconStack = listWidget.addStack(); | |
addSymbol({ | |
symbolName: 'text.bubble', | |
stack: iconStack, | |
color: Color.white(), | |
size: 32, | |
}); | |
listWidget.addSpacer(10); | |
const header = listWidget.addText('Das Widget'); | |
header.font = Font.regularRoundedSystemFont(FONTSETTINGS.medium); | |
header.textColor = Color.white(); | |
listWidget.addSpacer(2); | |
const footer = listWidget.addText('wird geladen …'); | |
footer.font = Font.regularRoundedSystemFont(FONTSETTINGS.medium); | |
footer.textColor = Color.white(); | |
return listWidget; | |
} | |
async function displaySunriseAndSunset( | |
{ location, todaysSunrise, todaysSunset }, | |
locality = null | |
) { | |
const listWidget = new ListWidget(); | |
let todaysDate = new Date(NOW); | |
let headerText = 'Sonnenlauf, heute'; | |
let headerColor = Color.white(); | |
const gradient = new LinearGradient(); | |
const gradientByTime = | |
NOW >= todaysSunrise.getTime() - 900000 && | |
NOW < todaysSunset.getTime() + 900000 | |
? { gradientStart: '#093199', gradientStop: '#4C95FE' } // day | |
: { gradientStart: '#000618', gradientStop: '#121A34' }; // night | |
gradient.locations = [0, 1]; | |
gradient.colors = [ | |
new Color(gradientByTime.gradientStart), | |
new Color(gradientByTime.gradientStop), | |
]; | |
listWidget.backgroundGradient = gradient; | |
// Is it before midnight but later than today's sunset | |
// we'll look at tomorrow: | |
if ( | |
NOW <= todaysDate.setHours(23, 59, 59, 999) && | |
NOW > todaysSunset.getTime() | |
) { | |
todaysDate = new Date(new Date().setDate(todaysDate.getDate() + 1)); // tomorrow | |
headerText = 'Sonnenlauf, morgen'; | |
todaysSunrise = todaysDate.sunrise(location.latitude, location.longitude); | |
todaysSunset = todaysDate.sunset(location.latitude, location.longitude); | |
headerColor = Color.white(); | |
} | |
const header = listWidget.addText(headerText.toUpperCase()); | |
header.font = Font.regularRoundedSystemFont(FONTSETTINGS.small); | |
header.textColor = headerColor; | |
listWidget.addSpacer(12); | |
// Sunrise | |
const sunriseStack = listWidget.addStack(); | |
const sunriseStackColor = | |
todaysSunrise.getTime() < NOW ? new Color('#ffffff99') : Color.white(); | |
addSymbol({ | |
symbolName: 'sunrise.fill', | |
stack: sunriseStack, | |
color: sunriseStackColor, | |
size: 26, | |
}); | |
sunriseStack.addSpacer(); | |
const sunriseLabel = sunriseStack.addText( | |
` ${todaysSunrise | |
.getHours() | |
.toString() | |
.replace(/^0(?:0:0?)?/, '')}:${('0' + todaysSunrise.getMinutes()).slice( | |
-2 | |
)}` | |
); | |
sunriseLabel.font = Font.mediumRoundedSystemFont(FONTSETTINGS.big); | |
sunriseLabel.textColor = sunriseStackColor; | |
// Sunset | |
const sunsetStack = listWidget.addStack(); | |
const sunsetStackColor = | |
todaysSunset.getTime() < NOW ? new Color('#ffffff99') : Color.white(); | |
addSymbol({ | |
symbolName: 'sunset.fill', | |
stack: sunsetStack, | |
color: sunsetStackColor, | |
size: 26, | |
}); | |
sunsetStack.addSpacer(); | |
const sunsetLabel = sunsetStack.addText( | |
` ${todaysSunset | |
.getHours() | |
.toString() | |
.replace(/^0(?:0:0?)?/, '')}:${('0' + todaysSunset.getMinutes()).slice( | |
-2 | |
)}` | |
); | |
sunsetLabel.font = Font.mediumRoundedSystemFont(FONTSETTINGS.big); | |
sunsetLabel.textColor = sunsetStackColor; | |
listWidget.addSpacer(12); | |
// Footer: | |
const footerStack = listWidget.addStack(); | |
addSymbol({ | |
symbolName: locality ? 'location.fill' : 'arrowtriangle.right.circle', | |
stack: footerStack, | |
color: Color.white(), | |
size: 12, | |
}); | |
const footerLabel = locality | |
? footerStack.addText(` ${locality.toUpperCase()}`) | |
: footerStack.addText( | |
` ${todaysDate.toLocaleDateString(undefined, { | |
weekday: 'short', | |
})}., ${todaysDate.toLocaleDateString(undefined, { | |
year: 'numeric', | |
month: 'numeric', | |
day: 'numeric', | |
})}` | |
); | |
footerStack.addSpacer(); | |
footerStack.addText(MOONICONS[getMoonphase(new Date())]); | |
footerLabel.font = Font.regularRoundedSystemFont(FONTSETTINGS.small); | |
footerLabel.textColor = Color.white(); | |
// render | |
return listWidget; | |
} | |
// Locate yourself or use 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; | |
} | |
} | |
async function getLocality(geolocation) { | |
let locality = null; | |
try { | |
// Location.reverseGeocode returns an array with object properties. | |
// Uses Apple CLLocation. | |
const address = await Location.reverseGeocode( | |
geolocation.latitude, | |
geolocation.longitude | |
); | |
// The order is relevant for processing the | |
// address properties. | |
const cascade = [ | |
'ocean', | |
'inlandWater', | |
'administrativeArea', | |
'subAdministrativeArea', | |
'locality', | |
'subLocality', | |
]; | |
if (address.length) { | |
cascade.forEach((prop) => { | |
locality = address[0][prop] ? address[0][prop] : locality; | |
}); | |
} | |
return locality; | |
} catch (e) { | |
return null; | |
} | |
} | |
/////////////////////////////////////////////////////////////////////// | |
let widget = {}; | |
const FONTSETTINGS = { | |
big: 30, | |
medium: 16, | |
small: 9, | |
}; | |
const NOW = +new Date(); | |
const MOONICONS = ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘']; | |
const location = await getLocation(); | |
const locality = await getLocality(location); | |
if (location) { | |
const sunriseAndSunset = getSunriseAndSunset(new Date(NOW), location); | |
widget = await displaySunriseAndSunset(sunriseAndSunset, locality); | |
} else { | |
console.error(location); | |
console.error(locality); | |
widget = await displayLoadingIndicator(); | |
} | |
if (!config.runsInWidget) { | |
await widget.presentSmall(); | |
} | |
Script.setWidget(widget); | |
Script.complete(); |
This comment has been minimized.
This comment has been minimized.
Das ist schade, ich kann es leider nicht reproduzieren. Du kannst checken, ob Du in Zeile 160 ein Leerzeichen mehr eingefügt hast, in dem Fall würde es so aussehen. |
This comment has been minimized.
This comment has been minimized.
Super Widget. Eine Frage habe ich: Kann man vielleicht eine Stunde nach Sonnenuntergang dann die Daten vom nächsten Tag anzeigen? |
This comment has been minimized.
This comment has been minimized.
Danke! Ja, ich könnte mir gut vorstellen, dass man in dem Fall »morgen« drüber schreibt, und die Zeiten vom nächsten Tag anzeigt. |
This comment has been minimized.
This comment has been minimized.
Thank you so much for this! I can see myself finally stop using a very-very hacky Shortcut I built a while ago to get data from suncalc.org. A question: SunCalc provides also Sunrise and Dusk times, roughly 30 minutes after Dawn and Sunset respectively. |
This comment has been minimized.
This comment has been minimized.
Glad to see you like it! At the time of this writing my algorithm provides only sunrise and sunset times, I'm afraid. |
This comment has been minimized.
This comment has been minimized.
I imagined this was the case. Thanks again for the amazing script! |
This comment has been minimized.
This comment has been minimized.
Made some smaller adjustments style-wise. Hope you like it. |
This comment has been minimized.
This comment has been minimized.
Ja, sieht gut aus. Ist es möglich, dass anstatt der Koordinaten dort der Name der Stadt steht? Ich habe es bei den COVID Widgets so gesehen. |
This comment has been minimized.
This comment has been minimized.
Danke. Wenn ich das richtig sehe, nimmt das Inzidenz-Widget von @kevinkub https://gist.github.com/kevinkub/46caebfebc7e26be63403a7f0587f664 den Ortsnamen aus den Daten, die das Widget aus den Daten des RKI erhält. Theoretisch könnte man natürlich auch diesen Dienst zusätzlich einbauen nur für die Ermittlung des Ortsnamen nehmen, aber das wäre vielleicht etwas unelegant. Was es braucht, wäre ein Dienst, der die Geo-Koordinaten in einen Ortsnamen übersetzt. Es sieht so aus, als könnte Scriptable eine Apple-API ansprechen, aber das müsste ich erst mal testen :) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Funktioniert bei mir auch perfekt! Vielen Dank! |
This comment has been minimized.
This comment has been minimized.
Hey @wodkasreineseele, jetzt werden Sonnenauf- und -untergang des Folgetages angezeigt, wenn die Sonne mal unten ist. Test doch einmal, ob es bei Dir auch funktioniert. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Sieht gut aus der Farbverlauf, Top. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Wow, Sehr stylish - sieht wirklich gut aus! Tolle Arbeit, die du da geleistet hast! |
This comment has been minimized.
This comment has been minimized.
Freut mich, wenn's gefällt! |
This comment has been minimized.
This comment has been minimized.
Update: Der Hintergrund wechselte erst nach Stunden zu spät, da war wohl eine 0 zu viel. Bitte die letzte Version verwenden. |
This comment has been minimized.
This comment has been minimized.
Update: Ab Mitternacht wurde nicht auf »heute« gewechselt. |
This comment has been minimized.
This comment has been minimized.
Ich bekomme leider beim Ausführen des Skripts eine Fehlermeldung: |
This comment has been minimized.
This comment has been minimized.
Hi @frollein-ike, ich habe da so einen Verdacht, aber zunächst ein paar Fragen: Gestattest Du Deinem iPhone, Deine Position zu ermitteln? Gestattest Du der App Scritptable, Deine Position zu ermitteln? –– Bis bald, Hendrik. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Update: Eventuell wird nun eine fehlende Verortung besser aufgefangen. @frollein-ike: Für Dich habe ich noch etwas eingebaut, mit dem Du mir helfen könntest, wenn Du Lust hast: Aktualisiere das Script mit dem neuen Code von heute (5.11.20). Öffne die Quelltext-Ansicht des Widgets in Scriptable und tippe auf den Play-Button rechts unten. Nun wird das Widget (oder die Fehlermeldung) angezeigt. Schließe diese Ansicht links oben mit »Close«. Nun steht rechts unten neben dem Play-Button eine eingerahmte »1«. Tipp darauf und Du bekommst eine Log-Ausgabe. Kannst Du das herauskopieren und mir per E-Mail schicken? (Wenn Du es hier veröffentlichst, steht möglicherweise Deine Privat-Adresse drin). Schreibe an hendrik.runte+scriptable@gmail.com. Danke! |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Hallo Hendrik, bei mir funktioniert auf jeden Fall jetzt alles ganz wunderbar. Ich danke dir für die Arbeit, die du da reingesteckt hast! |
This comment has been minimized.
This comment has been minimized.
Yipiieh :) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Ich bekomme auch manchmal die gleiche Meldung. |
This comment has been minimized.
This comment has been minimized.
Ja, die bekomme ich auch, wenn ich das Widget längere Zeit nicht aufgerufen hatte, allerdings nur für einen Augenblick, dann erscheint der gewohnte Anblick. |
This comment has been minimized.
This comment has been minimized.
Ich forsche. |
This comment has been minimized.
This comment has been minimized.
Benutze das Widget in einem Stapel und habe das mehrmals täglich wenn ich innerhalb des Stapels zu dem Widget schalte. Nachdem ich das Script in der Scriptable-App ausführe, wird es normal angezeigt. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Ja, vielen Dank. Das beobachte ich ähnlich. Allerdings ist bei mir die Fehlermeldung nach einem Augenblick verschwunden und das Widget funktioniert dann normal. Ich vermute, dass Scriptable irgendwann den Location-Service stoppt. Aber mal sehen, ich teste gerade eine andere Version. |
This comment has been minimized.
This comment has been minimized.
Jetzt wird ein solcher Fehler, wenn er auftritt, mit einer Hinweismeldung abgefangen. Kurz danach lädt das Widget dann normal. |
This comment has been minimized.
This comment has been minimized.
Okay 👍🏻 Danke für die Mühe. Sieht jetzt besser aus als eine Fehlermeldung mit rotem Text. Wenn es an der Ortung liegt weswegen die Fehler auftreten, wäre es eine Option dass man den Ort selbst als Parameter in den Widget-Einstellungen eingibt und das Widget auf diesen Ort zurückgreift sobald die Ortung fehlschlägt? |
This comment has been minimized.
This comment has been minimized.
Das sollte bereits funktionieren. Gib die Koordinaten mit Dezimalpunkt, getrennt durch Komma an. |
This comment has been minimized.
This comment has been minimized.
Update: Die Ladeanzeige ist nun ein wenig schicker. |
This comment has been minimized.
This comment has been minimized.
Update: Die Ortsbestimmung ist nun wieder eingebaut (aber technisch etwas anders). Feedback Welcome! |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Bei mir funktioniert es in der neuen Version prima. |
This comment has been minimized.
This comment has been minimized.
Danke, @frollein-ike, schön zu hören! Grüße in den Südwesten! |
This comment has been minimized.
This comment has been minimized.
Läuft weiterhin alles stabil. |
This comment has been minimized.
This comment has been minimized.
Danke für die Rückmeldung! |
This comment has been minimized.
This comment has been minimized.
Hi, ist es möglich eine Benachrichtigung einzubauen? Ich möchte gerne das bei Sonnenuntergang eine Pushcut gesendet wird. So könnte ich Automationen erstellen. If sunset then
Kann mir jemand sagen was ich wo einfügen müsste? |
This comment has been minimized.
This comment has been minimized.
Hi, das Widget hat derzeit kein Event wie »onSunset()«. Soweit ich weiß, wird es auch kein zuverlässiges EventHandling geben, wenn das Widget nicht gerade erst angezeigt wurde, da kein Hintergrundprozess läuft. |
This comment has been minimized.
Die letzten beiden Ziffern sind abgeschnitten