-
Hey chroma. How were you planning on identifying “home”? I've been able to think of at least four methods, each with drawbacks:
- explicit picking from a list, which is more challenging than it initially appears;
- waiting until you're in your home zone and then saying, here, this one now, which is more limiting than some might imagine (though technically savvy users could I suppose force the issue by temporarily overriding their phone's time zone selection);
- having a picker for over on Gadgetbridge and then pulling the results over to the watch, which involves messing a lot with GB;
- logging user behaviour and taking the one that's used most over an (eventually) extended period.
The last is both the hardest and the most magical (though I note there's a security implication if your watch is seized)…
- explicit picking from a list, which is more challenging than it initially appears;
-
-
I see—so the GB() idiom is clearly preferred going forward, and it's acceptable, nay desired, to have time setting move there eventually. I had wondered about that, and it turns out to be the key point :).
Oh, about the Gadgetbridge repo—if I submit Bangle-related changes over there do you get pinged to review them, or is it safest for all if I run them by you in advance on a side channel, or what? I'm not sure how they are set up.
-
[TL;DR: Your idea works, Gordon, I will do your idea (plus a separate Gadgetbridge timezone service) unless the thing at the numbered points below strikes you as better.]
Ah, I hadn't understood that setDST sidesteps the 12 parameter limit. It's documented as taking an array, but when you try it, it doesn't, it takes multiple literal arguments, and I hadn't guessed that the protocol conversion could happen under the hood, so I thought the it ws a documentation error. My ignorance, sorry.
The respect in which I means that setDST might need “rescuing” (other than the parameter count limit I falsely imagined to affect it) is the behaviour of implicitly disabling setTimeZone, which is not merely weird, it means that almost any use of it is a breaking change for someone, and you are clearly very averse to breaking changes. If Gadgetbridge starts calling it, then setTimeZone stops working after disconnection for everyone, for example.
But anyway, here's another idea:
- Give Gadgetbridge a time zone lookup service, and move the main burden to the app. This would even let people implement time zone aware alarms and calendars, for example, and would make it easier to cache your home time zone, would allow for localisation, and so on.
- [optional] Have Gadgetbridge routinely send six parameters to setTimeZone (most of which the current implementation will silently ignore, giving back compatibility):
setTimeZone(offset, [tzID], [nextChangeInstant, nextOffset, nexttzID])
. tzID here is the official identifier, not the user-facing label, giving you a key you can send to the above lookup service. The motivation for sending a single prospective transition is so that you can tolerate a few weeks of disconnection and not miss a DST change, while minimising code and space complexity on the watch. - Have a library that patches in a more sophisticated setTimeZone that uses the extra parameters, for people who want it. Maybe migrate this to the interpreter later if it's seen as good.
I suggest this not because I'm trying to argue with you—treating time updates uniformly in Gadgetbridge is attractive—but because it's a more capable variant of something you suggested yourself earlier, and I think it avoids disabling anything or changing any behaviour without anyone's consent.
On the social side, please understand that when I said I'm not interested in other people's solutions, I wasn't trying to defend being an asshole engineer, I was trying to explain (to third parties on this thread) that telling me to make do with someone else's watch face is kind of pointless because I bought a Bangle so I could write watch faces. “Why don't you have a different hobby” is sort of a non-suggestion in a way that “why don't you propose a better/less disruptive interface” or “have you overlooked such-and-such a consideration” is not.
And don't worry, I'm perfectly aware that I can fork the world, and that the only benefit of checking things in is to benefit others, and that you have stuff to do in your own life. Otherwise you'd have had pull requests some time ago.
My plan now:
I'll move the burden onto Gadgetbridge and do the time request and the timezone service approach, unless you turn out to giving setTimeZone extra args, or you tell me otherwise. It has the downside that my faces will need to disable E.setTimeZone so they can use the service instead, but as you say, that can be seen as prototyping a future, more uniform, interface.
- Give Gadgetbridge a time zone lookup service, and move the main burden to the app. This would even let people implement time zone aware alarms and calendars, for example, and would make it easier to cache your home time zone, would allow for localisation, and so on.
-
Hey, Gordon.
Without disagreeing in any way that the most direct thing to do is
E.setTimeZone(amt, name)
, I took a few more steps down the rabbit hole of looking at scheduled DST changes. It seems there are some peculiarities baked intoE.setDST()
? First, it already has 12 parameters(!), which means that unlikeE.setTimeZone()
it can't straightforwardly take optional time zone name parameters at the end. Second, to the point of not breaking existing uses, it appears from reading the code that widdst depends on the fact thatsetDST
disablessetTimeZone
to prevent further host time zone updates, and thus relies on the host not callingsetDST
, since that would break the override.So … one might wish that the entry had been
setDST(base, delta, startRule, endRule)
wherestartRule
andendRule
are arrays, for example. And while preventing automatic updates is a sensible idea, it might be better to have saidBangle.setEnableAutomaticTimezone(false)
explicitly, rather than accomplishing it by implicitly disablingsetTimeZone
.It seems wrong to set up a competing version of
setDST
, but then again it seems—if my reading of the code is correct, of course—that widdst is relying on being the sole user of the call!Given this, I can imagine several ways forward to ‘rescue’ on-device DST processing, each slightly ugly in its own way:
- Change the interface of
setDST
breakingly, updating any clients that use it, and split off an explicitsetEnableAutomaticTimezone()
entrypoint; - Keep all existing entrypoints unchanged and add a new one that allows setting DST under a slightly different interface, while (like
setTimeZone
) being implicitly disabled bysetDST
; - Extend
setTimeZone
so that it takes an optional label and optional rules to schedule changes (again while maintaining the peculiar interaction withsetDST
, now exactly as documented); - Like (2) or (3), but adding the explicit override mechanism from (1).
What, if anything, would you like for your baby? :) I'm a bit sad to give up prospective DST advice, but if your response is just to leave it and go with the minimal thing, I'll live. ;)
- Change the interface of
-
I think I agree.
Incidentally, although I'm confident that I saw setDST behave inconsistently with getIsDST as I described, other things later started acting strangely. I reset and reloaded my Bangle 2 and I can no longer reproduce the inconsistency—and the source seems to look right. So perhaps I had already broken something.
Oh, thanks for the LogCat pointer. It turns out that I can use that with WiFi debugging too. It's not perfect, but it's quite nice.
-
Oh, believe me, I understand completely about the difference between the issues that will realistically be addressed and … everything else :). I'll take a quick look at the setDST/isDST interaction and see if it feels like a hobbyable fix—though by design setDST is only a DST-aware version of manually setting the time, and may not even be part of a location-aware solution.
As to the idea of mapping from offset to name, from a technical perspective this can't work fully, because there are more timezones than there are offsets: timezones can (and do) differ in whether they observe DST and in what dates they make the change on, for example, not to mention that it might be a political hot potato to tell people in Western Australia that they are in China Standard Time or vice versa. (I had originally thought of making the watch able to display the correct time when completely disconnected, but the geospatial database would be megabyte-scale according to the back of my envelope, so not really feasible on Bangle).
For the sake of other readers I should make my own perspective clear here: I bought my Bangle because I have a long-time (if low-investment) hobby of sketching watch faces and watch features, and Bangle finally provides a low-overhead method to try out ideas. So “why don't you just use this-or-that” is not the answer for me. I'm actually trying to prototype a watch face that helps me deal with the disorientation I feel when the time changes under me, and that's what justifies my effort to myself. But of course I want to do it in a way that it might be useful to others, if there is a choice of methods, and I certainly don't want to break anything.
More generally, I think it might need saying that the experience of timezones in North America is very different from that in Europe. US states cooperate less than European nations(!) and timezone issues are a daily fact of life here; the Central/Eastern divide in the US in particular is highly populated. It's also true that while international travel has not been much of a thing for the last five years, previously (and now, again) waking up on a different continent was very much a normal experience for some of us.
Anyway, I'll try to avoid destabilising anything, and run anything with implications for others by you before trying to check them in.
Incidentally, do you have a clean way of debugging Gadgetbridge/Bangle interactions? Bluetooth seems to require running on real hardware, and there's only one Bluetooth….
-
Looking into doing it ‘right’, I find that E.setDST allows us to set a (single) DST rule prospectively—clearly useful if we are not connected at the moment of transition. There are problems, though:
- Based on a quick experiment in the emulator, it apparently updates the time and the locale but is not fully integrated with Date (it seemingly does not set IsDST or update getTimezoneOffset, the latter of which is probably bad for existing code since it seemingly means that we are out of sync with UTC, the only indication being in the toString-style accessors).
- It encodes the rules in a different way from Java's ZoneOffsetTransitionRule, and in particular doesn't seem to allow for transitions that aren't pinned to a particular day of the week (I don't know how common that is). (The documentation is also unclear on the distinction between transitions at 00:00 and at 24:00, but perhaps the behaviour is well defined and/or finessable.)
- It doesn't provide a clear strategy for the case where a rule is changing.
Is the apparent lack of integration here a reportable bug, the result of a change in design direction, or just me misunderstanding how it fits together?
In light of this, and existing code, is it better to call setDST (massaging its input so that it is at least valid for the upcoming transition, if not subsequent ones) or—my original thought—to ignore it, relying entirely on connected updates (possibly scheduling the next transition for the current zone, so that having only sporadic connection would work nicely)?
- Based on a quick experiment in the emulator, it apparently updates the time and the locale but is not fully integrated with Date (it seemingly does not set IsDST or update getTimezoneOffset, the latter of which is probably bad for existing code since it seemingly means that we are out of sync with UTC, the only indication being in the toString-style accessors).
-
I just read the description of widdst, and it provides a manual interface to set the DST flag. If someone found the need to write it, it kind of suggests that getIsDST() doesn't work out of the box…?
But Android has all the relevant data, whether or not it's a bug that it doesn't share it.
So my proposal is: gadgetbridge should send not just the time and the offset, but also the DST flag, the official time zone ID, and the short code for the time zone; perhaps also the next anticipated DST transition. On the Bangle, the DST flag should be updated when it is received, all these values should be persisted as the offset is already, and an event should be sent if they changed.
-
But, having jetlag at this very moment, it's my objective to change my watchface. Though I grant that given the info one could add a useful widget, for those who like widgets.
To the technical point, does getIsDST actually work? Unless I'm misreading the code, GadgetBridge doesn't set it, Bangle has no manual option for it, and Espruino does not have a time zone subsystem of its own to store the rules (not that you can reverse engineer the isDST state from just an offset, anyway). Though the point that an update to GadgetBridge's time transmission could and probably should set isDST along with sending the zone is a good one.
Having an option to choose +1 rather than CET is not a bad idea, but the reason I want to go to the trouble of providing the abbreviation is that if (e.g.) you are so unfortunate as to live on a timezone boundary, or if you live on the road, you might want to know why your watch is showing an unexpected time—because your phone thinks you are now in Quebec, not because summer time just started….
-
My Bangle correctly follows my phone into a new time zone when appropriate, but I'd like my watch face to be aware of it—adding a small note (e.g. "PDT") on the display and perhaps a shadow hour hand for the pre-change time (if DST) or home time (otherwise).
To a first approximation, the phone appears to have this information in its TimeZone object, and could pass it through GadgetBridge into setting.json at transmitTime, perhaps also firing a Bangle.event("timezone").
There are a couple of wrinkles I know of: first, the familiar short timezone names are informally documented as deprecated because they are ambiguous for input (the deprecation may not be intended to apply to their use as display names but the wording in the documentation doesn't seem clear); and second that Java's TimeZone.getDisplayName() takes a Locale—which will obviously make a difference for long names like “Pacific Standard Time”, but the three letter abbreviations? I dunno. In any case, using the host's UI locale is technically incorrect given that the Bangle could presumably be set differently. But I imagine both these worries could be ignored in reality.
Also, of course, there may be considerations I've never imagined.
Or the info might already be available and I've been looking in the wrong place.
(More broadly it would be nice to get the current time zone's DST data uploaded to the watch, because it would be nice to get notice of DST *in advance*—as long as the world is still using them. Even more broadly it would be nice to get calendar events right in respect of time zones, but that's clearly an undertaking of another order.)
What does anyone think?
-
-
@Gordon—Oh! Mea culpa. I thought I had ruled out internal minification, but I had neglected to defer execution in my experiments, which rather invalidates them :-}. Sorry!
Here are the ones I can think of:
Misinterpreting valid and useful code:
- -
/--
+ +
/++
Wildly misinterpreting technically valid but extremely unlikely code:
/ /
///
⇐ Humorously, the bizarre sequence1 / /a/
does indeed throw an amazing fit, and can produce arbitrarily inappropriate error messages—add a multiline template literal later in the function for total hilarity!Incorrectly allowing syntactically invalid code in an especially confusing way:
/…/ i
//…/i
⇐ The IDE flags/a/ i
as a syntax error, but the runtime sees/a/i
.The obscure example
do x(); while(…);
is already handled correctly, and sequences with- --
and+ ++
display incorrectly in error messages but apparently execute correctly—are the increment and decrement operators internally pretokenised, or something?I can't think of any other examples because alphanumeric tokens already preserve whitespace and JS has few cases where an operator symbol is at the left edge of an expression. Maybe
**
andyield*
, should they ever be implemented, might surprise people carelessly converting C code?HTH.
-
Terminology: a lexer is responsible for tokenisation of the input—breaking it into words, if you will, like
if
,camelVar
,6.02e+23
,{
,'string'
,+
and+=
—while the parser is responsible for syntactic analysis, the grouping of those tokens into statements and expressions (with, admittedly, a few grey areas such as backquotes and regular expressions). It's not a logical necessity that languages be designed with this distinction, but as a practical matter almost all programming languages since the 60s seem to in one way or another. In the C family (to which JS belongs notationally, though not semantically), it's pretty crucial to understanding what's going on, and the formal concept (though not a typical implementation—and certainly not this implementation) is that tokenisation is a separate process that runs ‘before’ parsing. In any case, in JS,--
is one token and- -
is two, just asxx
is different fromx x
and==
is different from= =
, so in this case, the space is significant. The language processor, taken as a whole. is wrong to treat- -
as--
, and would be just as wrong to treat--
as- -
.
Espruino does not always make this mistake—var x
does not get confused withvarx
and the far more obscure exampledo x(); while (!done());
does not get misinterpreted as the (equally legal)dox(); while (!done());
, even during full file interactive upload to the emulator (the context that triggers the bug I was reporting). -
It is obviously not a parsing issue, sensu stricto, both because the distinction between
- -
and--
is lexical, and because, as you point out, the runtime evaluator suffers no such problem when its input is correct. The question that needs to be answered in diagnosing the problem, than, is what could cause the space between the two hyphen-minuses to be elided (or otherwise ignored)? If it is not the minification logic that apparently runs during full-file upload, I'll be surprised. What else is empowered to remove spaces? Why else would the error message actually quote maltransformed input? Why else would the problem vanish on paths where minification is not performed?
I think the key insight here is that the expression1--1
is erroneous (semantically, because1
is not an lvalue, and syntactically, because--
is not an infix operator), but it is not what was written in the source.
I'm sure other explanations are possible, but for now my money is definitely on the assumptions made by the minifier. -
Bangle emulator 2v10.187.
Minimal reproduction:
x = () => print(1 - -1);
Upload into the Bangle.js emulator from the editor panel as full file (not snippet), run
x()
:Uncaught Error: Unable to assign value to non-reference Number at line 1 col 8 print(1--1) ^
The underlying cause has to do with the accidental fusion of the two
-
tokens. This case:x => mod(x- -x, 1)
reports a parse error rather than a semantic error.
+
is similarly affected.As ever, it should be stressed that this is a minimal reproduction and the error also occurs with real, useful code. (Why would I ever explicitly subtract a negative number? Because the numbers I'm using come from tables of data plugged into a regular formula.)
-
-
@Gordon TBH it's as much me trying to speak all languages as close to “natively” as I can, and this was a textbook example of the motivation for the language feature—so I attempted to use it. I don't really do much religion when it comes to language design (none of the religions that I like—total correctness comes to mind—have proven feasible), and I completely understand that there are (a) things that won't fit and (b) more important fish than this to fry.
And sorry if I've come across grumpy—I seem to have come in through the wrong door. I'll go out and come back in again, see if I can do better :). -
@Robin I'm sorry, I misunderstood you! But it's genuinely a place where control constructs are required, since the double loop that wants to terminate early is working on a significant amount of lexically visible state. Of course this can can be skinned in many different ways, I can't look at the design of JS and not think that its own (very different from C) labels were put there for precisely this use case, and they involve a lot less clutter (both visual and conceptual) than any of the many ways of reifying the loop.
@Gordon It's new code, so I'm able to work around the limitations, but it's real code, not a test suite. I have a number of watch faces and related features that I'm working on in the emulator in anticipation of receiving my Bangle 2. When I have real hardware to try them on I'll likely upload some or all of them.
-
I mention C not because I think its decisions are especially defensible—with all due respect to the Bell Labs crew for what they accomplished, even at the time it seemed haphazard (given that the valiant example of Algol68 was already available!)—but because, for good or ill, the brackets, the operators, and the control syntax are all inherited from it, and one of my original bug reports was about the interaction of these three. Specifically, is there any justification, any non-arbitrariness, to suddenly treating the conditions in while loops as syntactically distinct from other control construct conditions, at odds with tradition, ECMA, naive expectation and (I strongly suspect) efficiency of implementation? Sorry, I can't see it.
-
I don't quite understand the question. The comma operator is a normal operator valid in any full Expression (13.16 ). It is historically descended from Algol68's semicolon operator, and (despite the different precedence) semantically related to JS's
&&
operator. Like&&
it processes the left argument and returns the right; unlike&&
it voids the left argument and proceeds unconditionally. In fact, I suspecta, b
could be defined (semantically) as!a < 2 && b
or some such stupidity.Syntactically it has only one oddity, which is that it, like the bare assignment operator, is also used for other things, such as forming VariableDeclarationLists (14.3.2) and ArgumentLists (13.3), which means that there are places where the semantic expectation is an expression but the syntax restricts you to an AssignmentExpression to resolve the ambiguity of which kind of comma is expected.
In any case, none of this has anything to do with the conditions in control constructs, which are uniformly and uncomplicatedly Expressions (14.6, 14.7.2.1, 14.7.3.1, 14.7.4, 14.7.5, 14.12).
There's also no case to be made that it is a semantic restriction of Espruino or its debugging mechanism, since the semantically identical
while ((0, 0));
parses and executes flawlessly. This is nothing but an issue with Espruino's internal grammar, as far as I can see, either a typo (I hope) or an utterly capricious decision to alter the syntax ofwhile ()
while not even making the same change forif ()
.To the argument that this feature of the language is not pretty and adds confusion, all I can say is that today it was recommended to me that I replace
search: while (…) while (…) if (…) break search;
(which Espruino does not implement) with
(() => {while (…) while (…) if (…) return;})();
. It does not appear that prettiness and lack of visual confusion is the driving force behind this project(!). And, for goodness' sake, if you really hate the comma operator so passionately, why allow it inif
andswitch
?Orthogonality and lack of bugs are generally considered virtues in the programming language community; I'm honestly not sure what's going on here. If it's that efficiency trumps usability, then what's the point of Espruino at all? C, or indeed assembly, is easy enough to write; I came here in the expectation that things would “just work” without too much hassle :-}.
-
-
Well, but here's the thing: how can a person write code for this beast if it doesn't follow the spec and doesn't provide any guidance for where it is going to diverge? It's not helping anyone if we have to write and constantly refer to a cheat sheet of arbitrary divergences from even the simplest parts of the language design.
Don't get me wrong, I completely understand the idea of a motivated language subset, but arbitrarily outlawing a => (b) => x while allowing (a) => b => x, or forbidding while (a = b, c) while allowing if (a = b, c), what's that even for? Either these are bugs (and, with several language implementations under my belt, they seem more likely to be typos in the parser source than anything else), or some detailed documentation is needed so that users can know what is going to work and what is not and develop an intuition for why.
As to why I'd appeal to the ECMA spec, it's not demented legalism on my part, it's that I've already noticed that people get yelled at if they don't. Indeed, it happened to me today—I'd assumed that everyone knew what the comma operator does in C-family languages, but apparently chapter and verse is required.
-
@Robin, I am attempting to report a parsing error, not a semantic error, and “while (0,0);” is a minimal reproduction. It is not intended to be a full, useful program; just the shortest and simplest snippet I can concoct that triggers the error, to assist with debugging. I did also explain what real examples would look like and why I care.
I apologise for not including the link https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-while-statement ; I truly didn't think it was necessary, because I thought everyone here would know what the comma operator is and does. Mea culpa.
In any case, the standard, at that link, says this:
Syntax
WhileStatement[Yield, Await, Return] :
while ( Expression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return](and similarly for for statements, which are also affected by the bug).
I don't really understand your point about truthiness. “x = costlyAccessor(i++), p(x.a, x.b)” returns the value of p(…), not the value of x. Per https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-comma-operator-runtime-semantics-evaluation :
13.16.1 Runtime Semantics: Evaluation
Expression : Expression , AssignmentExpression- Let lref be the result of evaluating Expression.
- Perform ? GetValue(lref).
- Let rref be the result of evaluating AssignmentExpression.
- Return ? GetValue(rref).
In short, it does the same thing as in every other language in the C family, evaluating the value on the left, then evaluating and returning the value on the right. The use in loop conditions is generally consider to be the principal motivation for the existence of the operator, since elsewhere you can usually accomplish the same thing with some unpacking and a semicolon. Admittedly it is more commonly used in for loops than in while loops, but again, I was seeking the easiest example to debug the grammar on.
- Let lref be the result of evaluating Expression.
@chroma, on the watch or elsewhere?
@user140377, the problem is an actual timezone map is going to be O(megabytes), and while there are only ~600 reference locations (so using only tens of k you could practically present the user with a list ordered by [N/S biased] distance), I'm not sure where we can get their lat/lon other than trawling the IANA database manually—Java, for example, has chosen to strip it out.
It turns out there's another problem: if you use setTimeZone it's possible to fake temporal objects in other timezones in userland (by subtracting the time zone offsets and then adjusting the underlying ms time by this amount, so that interactions with the time and date fields behave as expected), but you have to do all DST calculations yourself (and possibly break other code's expectations). To the contrary, if you use setDST it will get the calculations right automatically for the main zone, but it becomes impossible to manipulate times in other timezones no matter how hard you try, because there are now 60 (usually) unnamable minutes in every year!
I've been trying to think of a way to address this other than to have a second kind of Date—one that's bound internally to a setDST packet—but I haven't had enough coffee yet.