Files
weerscherm/public/format.js
T
2026-09-11 14:42:06 +02:00

125 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Pure formatteer- en rekenfuncties voor het weerscherm. Geen DOM, geen fetch: los testbaar met node --test.
* Notatie nl-NL met decimale komma; tijden in de tijdzone van het station.
*/
const DASH = '–';
const WMO_ICONS = [
[[0], 'sun'],
[[1, 2], 'partly'],
[[3], 'cloud'],
[[45, 48], 'fog'],
[[51, 52, 53, 54, 55, 56, 57], 'drizzle'],
[[61, 62, 63, 64, 65, 66, 67], 'rain'],
[[80, 81, 82], 'showers'],
[[71, 72, 73, 74, 75, 76, 77, 85, 86], 'snow'],
[[95, 96, 97, 98, 99], 'thunder'],
];
const TRENDS = { up: '↗ stijgend', down: '↘ dalend', stable: '→ stabiel' };
function isNum(v) {
return typeof v === 'number' && Number.isFinite(v);
}
function cap(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
/** 16.4 -> "16,4"; null -> "–" */
export function f1(v) {
return isNum(v) ? v.toFixed(1).replace('.', ',') : DASH;
}
/** 18.3 -> "18"; null -> "–" */
export function fInt(v) {
return isNum(v) ? String(Math.round(v)) : DASH;
}
/** 0 -> "0 mm"; 0.4 -> "0,4 mm"; null -> "–" */
export function fmm(v) {
if (!isNum(v)) return DASH;
return v === 0 ? '0 mm' : f1(v) + ' mm';
}
/** Positie van de marker op de min/max-balk, 0–100. Bij max == min of onbekend: 50. */
export function markerPos(temp, min, max) {
if (!isNum(temp) || !isNum(min) || !isNum(max) || max === min) return 50;
const pos = ((temp - min) / (max - min)) * 100;
return Math.min(100, Math.max(0, pos));
}
/** Hoogte per staaf in px: mm / max(waarden) * maxPx, minimaal minPx. Alles 0 of null -> allemaal minPx. */
export function barHeights(values, maxPx = 70, minPx = 2) {
const mm = values.map((v) => (isNum(v) ? v : 0));
const top = Math.max(...mm, 0);
return mm.map((v) => (top > 0 ? Math.max(minPx, (v / top) * maxPx) : minPx));
}
/** WMO-weercode -> icoonnaam. Onbekend -> "cloud". */
export function wmoIcon(code) {
for (const [codes, name] of WMO_ICONS) {
if (codes.includes(code)) return name;
}
return 'cloud';
}
/** "up" -> "↗ stijgend" enz.; onbekend -> "" */
export function trendLabel(trend) {
return TRENDS[trend] || '';
}
/** "Vrijdag 11 september" */
export function formatDate(date, tz) {
const s = new Intl.DateTimeFormat('nl-NL', { weekday: 'long', day: 'numeric', month: 'long', timeZone: tz }).format(date);
return cap(s);
}
/** ISO-tijdstip -> "06:10" in de tijdzone; null -> "–" */
export function formatTime(iso, tz) {
const d = iso instanceof Date ? iso : new Date(iso);
if (!iso || Number.isNaN(d.getTime())) return DASH;
return new Intl.DateTimeFormat('nl-NL', { hour: '2-digit', minute: '2-digit', hourCycle: 'h23', timeZone: tz }).format(d);
}
/** "2026-09-12" -> "Za" */
export function dayAbbr(dateStr, tz) {
const d = new Date(dateStr + 'T12:00:00Z');
if (Number.isNaN(d.getTime())) return DASH;
const s = new Intl.DateTimeFormat('nl-NL', { weekday: 'short', timeZone: 'UTC' }).format(d);
return cap(s.replace('.', '')).slice(0, 2);
}
/** "September" */
export function monthName(date, tz) {
return cap(new Intl.DateTimeFormat('nl-NL', { month: 'long', timeZone: tz }).format(date));
}
/** Is het ISO-tijdstip ouder dan `minutes` minuten ten opzichte van `now`? Onbekend telt als verouderd. */
export function isOlderThan(iso, now, minutes) {
if (!iso) return true;
const t = new Date(iso).getTime();
if (Number.isNaN(t)) return true;
return now.getTime() - t > minutes * 60000;
}
/** Lokale tijd (uur, minuut, seconde) van `now` in de tijdzone. */
function localParts(now, tz) {
const parts = new Intl.DateTimeFormat('en-GB', {
hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23', timeZone: tz,
}).formatToParts(now);
const get = (type) => Number(parts.find((p) => p.type === type).value);
return { h: get('hour'), m: get('minute'), s: get('second') };
}
/** Milliseconden tot de volgende keer dat het `hour`:`minute` is in de tijdzone (minimaal 1 s). */
export function msUntil(now, tz, hour, minute = 0) {
const { h, m, s } = localParts(now, tz);
const target = hour * 3600 + minute * 60;
const current = h * 3600 + m * 60 + s;
let diff = target - current;
if (diff <= 0) diff += 86400;
return Math.max(1000, diff * 1000 - now.getMilliseconds());
}