From 9d2867a443d196e67f70cd88960230aa1afdded5 Mon Sep 17 00:00:00 2001 From: deirdreobyrne Date: Sun, 10 Jul 2022 19:13:46 +0100 Subject: [PATCH] Widget to set dst rules --- apps/widdst/ChangeLog | 1 + apps/widdst/README.md | 34 +++++++ apps/widdst/icon.png | Bin 0 -> 8056 bytes apps/widdst/metadata.json | 15 ++++ apps/widdst/settings.js | 184 ++++++++++++++++++++++++++++++++++++++ apps/widdst/widget.js | 65 ++++++++++++++ 6 files changed, 299 insertions(+) create mode 100644 apps/widdst/ChangeLog create mode 100644 apps/widdst/README.md create mode 100644 apps/widdst/icon.png create mode 100644 apps/widdst/metadata.json create mode 100644 apps/widdst/settings.js create mode 100644 apps/widdst/widget.js diff --git a/apps/widdst/ChangeLog b/apps/widdst/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/widdst/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/widdst/README.md b/apps/widdst/README.md new file mode 100644 index 000000000..ca41c2bed --- /dev/null +++ b/apps/widdst/README.md @@ -0,0 +1,34 @@ +# Daylight savings time widget + +This widget will set the daylight saving rules on your watch. Note you may need a firmware update before this can work. + +## Settings + +You need to set your timezone, and your daylight savings rules, in `Settings -> Apps -> Daylight Savings`. The settings are + +**Enabled** enables or disables the widget + +**Icon** enables or disables the showing of a small "DST" icon when daylight savings is in effect + +**Base TZ** is your "base" timezone - i.e. the timezone you keep when daylight savings is not in effect. It is positive east. + +**Change** is the size of the daylight savings change, which is +1 hour almost everywhere. + +**DST Start, DST End** set the rules for the start and end of daylight savings. + +## Setting the daylight savings rules + +The page for setting **DST Start** and **DST End** ask you to describe the rules for daylight savings changes. For instance, in the UK, the rules are + +**DST Start** `The [last] [Sun] of [Mar] minus [0 days] at [01:00]` + +**DST End** `The [last] [Sun] of [Oct] minus [0 days] at [02:00]` + +In most of the US, the rules are + +**DST Start** `The [2nd] [Sun] of [Mar] minus [0 days] at [02:00]` + +**DST End** `The [1st] [Sun] of [Nov] minus [0 days] at [02:00]` + + + diff --git a/apps/widdst/icon.png b/apps/widdst/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..aa714295927d6e28c2abbaad6a4c793b7132ce69 GIT binary patch literal 8056 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 aofk~Db{+-}&L%X!0Y>^}dL=qeVgCngrE(eo literal 0 HcmV?d00001 diff --git a/apps/widdst/metadata.json b/apps/widdst/metadata.json new file mode 100644 index 000000000..04be425af --- /dev/null +++ b/apps/widdst/metadata.json @@ -0,0 +1,15 @@ +{ "id": "widdst", + "name": "Daylight Saving", + "version":"0.01", + "description": "Widget to set daylight saving rules. See the instructions below for more information.", + "icon": "icon.png", + "type": "widget", + "tags": "widget,tool", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"widdst.wid.js","url":"widget.js"}, + {"name":"widdst.settings.js","url":"settings.js"} + ], + "data": [{"name":"widdst.json"}] +} diff --git a/apps/widdst/settings.js b/apps/widdst/settings.js new file mode 100644 index 000000000..9a7e579b7 --- /dev/null +++ b/apps/widdst/settings.js @@ -0,0 +1,184 @@ +(function(back) { + + var dows = require("date_utils").dows(0,1); + var months = require("date_utils").months(1); + + var settings = Object.assign({ + has_dst: false, + show_icon: true, + tz: 0, + dst_size: 1, + dst_start: { + dow_number: 4, // "1st", "2nd", "3rd", "4th", "last" + dow: 0, // "Sun", "Mon", ... + month: 2, + day_offset: 0, + at: 1 + }, + dst_end: { + dow_number: 4, + dow: 0, + month: 9, + day_offset: 0, + at: 2 + } + }, require("Storage").readJSON("widdst.json", true) || {}); + + var dst_start_end = { + is_start: true, + day_offset: 0, + dow_number: 0, + dow: 0, + month: 0, + at: 0 + }; + + function writeSettings() { + require('Storage').writeJSON("widdst.json", settings); + } + + function writeSubMenuSettings() { + if (dst_start_end.is_start) { + settings.dst_start.day_offset = dst_start_end.day_offset; + settings.dst_start.dow_number = dst_start_end.dow_number; + settings.dst_start.dow = dst_start_end.dow; + settings.dst_start.month = dst_start_end.month; + settings.dst_start.at = dst_start_end.at; + } else { + settings.dst_end.day_offset = dst_start_end.day_offset; + settings.dst_end.dow_number = dst_start_end.dow_number; + settings.dst_end.dow = dst_start_end.dow; + settings.dst_end.month = dst_start_end.month; + settings.dst_end.at = dst_start_end.at; + } + writeSettings(); + } + + function hoursToString(h) { + return (h|0) + ':' + (((6*h)%6)|0) + (((60*h)%10)|0); + } + + function getDSTStartEndMenu(start) { + dst_start_end.is_start = start; + if (start) { + dst_start_end.day_offset = settings.dst_start.day_offset; + dst_start_end.dow_number = settings.dst_start.dow_number; + dst_start_end.dow = settings.dst_start.dow; + dst_start_end.month = settings.dst_start.month; + dst_start_end.at = settings.dst_start.at; + } else { + dst_start_end.day_offset = settings.dst_end.day_offset; + dst_start_end.dow_number = settings.dst_end.dow_number; + dst_start_end.dow = settings.dst_end.dow; + dst_start_end.month = settings.dst_end.month; + dst_start_end.at = settings.dst_end.at; + } + return { + "": { + "Title": start ? /*LANG*/"DST Start" : /*LANG*/"DST End" + }, + "< Back": () => E.showMenu(dstMenu), + /*LANG*/"The" : { + value: dst_start_end.dow_number, + format: v => [/*LANG*/"1st",/*LANG*/"2nd",/*LANG*/"3rd",/*LANG*/"4th",/*LANG*/"last"][v], + min: 0, + max: 4, + onchange: v => { + dst_start_end.dow_number = v; + writeSubMenuSettings(); + } + }, + " -" : { + value: dst_start_end.dow, + format: v => dows[v], + min:0, + max:6, + onchange: v => { + dst_start_end.dow = v; + writeSubMenuSettings(); + } + }, + /*LANG*/"of": { + value: dst_start_end.month, + format: v => months[v], + min: 0, + max: 11, + onchange: v => { + dst_start_end.month = v; + writeSubMenuSettings(); + } + }, + /*LANG*/"minus" : { + value: dst_start_end.day_offset, + format: v => v + ((v == 1) ? /*LANG*/" day" : /*LANG*/" days"), + min: 0, + max: 7, + onchange: v => { + dst_start_end.day_offset = v; + writeSubMenuSettings(); + } + }, + /*LANG*/"at": { + value: dst_start_end.at, + format: v => hoursToString(v), + min: 0, + max: 23, + // step: 0.05, // every 3 minutes - FOR DEVELOPMENT PURPOSES + onchange: v => { + dst_start_end.at = v; + writeSubMenuSettings(); + } + } + } + } + + var dstMenu = { + "": { + "Title": /*LANG*/"Daylight Saving" + }, + "< Back": () => back(), + /*LANG*/"Enabled": { + value: settings.has_dst, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: v => { + settings.has_dst = v; + writeSettings(); + } + }, + /*LANG*/"Icon": { + value: settings.show_icon, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: v => { + settings.show_icon = v; + writeSettings(); + } + }, + /*LANG*/"Base TZ": { + value: settings.tz, + format: v => (v >= 0 ? '+' + hoursToString(v) : '-' + hoursToString(-v)), + onchange: v => { + settings.tz = v; + writeSettings(); + }, + min: -13, + max: 13, + step: 0.25 + }, + /*LANG*/"Change": { + value: settings.dst_size, + format: v => (v >= 0 ? '+' + hoursToString(v): '-' + hoursToString(-v)), + min: -1, + max: 1, + step: 0.5, + onchange: v=> { + settings.dst_size = v; + writeSettings(); + } + }, + /*LANG*/"DST Start": () => E.showMenu(getDSTStartEndMenu(true)), + /*LANG*/"DST End": () => E.showMenu(getDSTStartEndMenu(false)) + }; + + E.showMenu(dstMenu); + +}); diff --git a/apps/widdst/widget.js b/apps/widdst/widget.js new file mode 100644 index 000000000..f9a5b7f81 --- /dev/null +++ b/apps/widdst/widget.js @@ -0,0 +1,65 @@ +(() => { + + // our setTimeout() return value for the function that periodically check the status of DST + var check_timeout = undefined; + + // Called by draw() when we are not in DST or when we are not displaying the icon + function clear() { + if (this.width) { + this.width = 0; + Bangle.drawWidgets(); + } + } + + // draw, or erase, our little "dst" icon in the widgets area + function draw() { + var dstSettings = require('Storage').readJSON('widdst.json',1)||{}; + if ((dstSettings.has_dst) && (dstSettings.show_icon)) { + var now = new Date(); + if (now.getIsDST()) { + if (this.width) { + g.drawImage( + { + width : 24, height : 24, bpp : 1, palette: new Uint16Array([g.theme.bg, g.theme.fg]), + buffer : atob("AAAAAAAAPAAAIgAAIQAAIQAAIQAAIQAAIngAPIQAAIAAAPAAAAwAAAQAAIX8AHggAAAgAAAgAAAgAAAgAAAgAAAgAAAAAAAA") + }, this.x, this.y + ); + } else { + this.width = 24; + Bangle.drawWidgets(); + } + } else { + clear(); + } + if (check_timeout) clearTimeout(check_timeout); + check_timeout = setTimeout( function() { + check_timeout = undefined; + draw(); + }, 3600000 - (now.getTime() % 3600000)); // Check every hour. + } else { + clear(); + } + } + + function setDst() { + var dstSettings = require('Storage').readJSON('widdst.json',1)||{}; + if (dstSettings.has_dst) { + E.setDST(60*dstSettings.dst_size, 60*dstSettings.tz, dstSettings.dst_start.dow_number, dstSettings.dst_start.dow, + dstSettings.dst_start.month, dstSettings.dst_start.day_offset, 60*dstSettings.dst_start.at, + dstSettings.dst_end.dow_number, dstSettings.dst_end.dow, dstSettings.dst_end.month, dstSettings.dst_end.day_offset, + 60*dstSettings.dst_end.at); + } else { + E.setDST(0,0,0,0,0,0,0,0,0,0,0,0); + } + } + + setDst(); + + // Register ourselves + WIDGETS["widdst"] = { + area: "tl", + width: 0, + draw: draw + }; + +})()