// Revised version (by www.anb030.de) for the new iOS 16 lockscreen widgets. Version 1.0 - Edit date: 23.11.2022 for adaptation Version 1.1 - Edit date: 27.11.2022 for Icons and Geo /////////////////////////////////////////////////////////////////////// // Quelle: https://gist.github.com/HendrikRunte/4b5d03cb26e31508bc96553ad3c10f47 // Lokalisiert: Deutsch // Datum: 05.11.2020 /////////////////////////////////////////////////////////////////////// // 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 = 26, }) { 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: 26, }); 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 = 'Heute'; let headerColor = Color.white(); const gradient = new LinearGradient(); const gradientByTime = NOW >= todaysSunrise.getTime() - 900000 && NOW < todaysSunset.getTime() + 900000 ? { gradientStart: '#808080', gradientStop: '#404040' } // day : { gradientStart: '#202020', gradientStop: '#606060' }; // 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 = '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: 'sun.and.horizon.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: 'cloud.moon.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: 26, 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();