"time in Europe/Berlin" dropped the second word

Two independent traps. The noise filter only keeps word tokens right
after a conversion token (in/as/...); any other token type resets
that, so the "/" in a slash-form IANA id ("Europe/Berlin") reset it
before the parser ever saw "Berlin". The word-collecting loop in
parseTarget() had the same blind spot even once the token was there.
Both needed the same exception, or "Europe/Berlin" still degrades to
just "Europe".
This commit is contained in:
Horizontal42 2026-07-03 11:25:56 +03:00
parent ef6244dc84
commit a9aaff5490
2 changed files with 21 additions and 2 deletions

View file

@ -232,4 +232,7 @@ describe("review fixes", () => {
expect(calc("123456 in scientific")).toBe("1.23e5"); expect(calc("123456 in scientific")).toBe("1.23e5");
expect(calc("123456 in exponent")).toBe("1.23e5"); expect(calc("123456 in exponent")).toBe("1.23e5");
}); });
it("timezone names with a slash resolve as IANA ids", () => {
expect(calc("time in Europe/Berlin")).toMatch(/^\d{2}:\d{2}$/);
});
}); });

View file

@ -72,6 +72,12 @@ export function parseLine(tokens: Token[], knownVars: Set<string>, line: string)
if (knownVars.has(tk.raw) || afterConv) filtered.push(tk); if (knownVars.has(tk.raw) || afterConv) filtered.push(tk);
continue; continue;
} }
// a bare "/" inside a timezone name ("Europe/Berlin") must not break the
// word-keeping chain, otherwise the second half gets filtered as noise
if (afterConv && tk.t === "op" && tk.op === "div") {
filtered.push(tk);
continue;
}
afterConv = tk.t === "conv"; afterConv = tk.t === "conv";
filtered.push(tk); filtered.push(tk);
} }
@ -256,8 +262,18 @@ class Parser {
} }
if (tk.t === "word") { if (tk.t === "word") {
const words: string[] = []; const words: string[] = [];
while (this.peek()?.t === "word" && words.length < 4) { for (;;) {
words.push((this.next() as Token & { raw: string }).raw); const w = this.peek();
if (w?.t === "word" && words.length < 4) {
words.push((this.next() as Token & { raw: string }).raw);
continue;
}
// "Europe/Berlin": a slash between timezone words is part of the name
if (w?.t === "op" && w.op === "div" && words.length > 0 && this.toks[this.i + 1]?.t === "word") {
this.next();
continue;
}
break;
} }
if (words.length > 0) return { type: "tz", words }; if (words.length > 0) return { type: "tz", words };
} }