From d266a8258d58dc6e56c706e5bb913cca1460ee1f Mon Sep 17 00:00:00 2001 From: Bram Date: Sat, 7 Nov 2026 16:30:47 +0100 Subject: [PATCH] timezone issues --- .kilo/plans/1793988235237-xtream-ppv-bundler.md | 8 +++++--- Dockers/xtream-ppv-bundler/Dockerfile | 1 + Dockers/xtream-ppv-bundler/README.md | 7 ++++++- .../docker-compose.example.yml | 1 + Dockers/xtream-ppv-bundler/server.js | 1 + Dockers/xtream-ppv-bundler/src/scheduler.js | 2 +- Dockers/xtream-ppv-bundler/src/settings.js | 8 +++++--- .../test/channel-name.test.js | 17 +++++++++++------ Dockers/xtream-ppv-bundler/test/events.test.js | 2 +- 9 files changed, 32 insertions(+), 15 deletions(-) diff --git a/.kilo/plans/1793988235237-xtream-ppv-bundler.md b/.kilo/plans/1793988235237-xtream-ppv-bundler.md index ef9f355..3a90162 100644 --- a/.kilo/plans/1793988235237-xtream-ppv-bundler.md +++ b/.kilo/plans/1793988235237-xtream-ppv-bundler.md @@ -55,7 +55,8 @@ View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (202 | `EVENT_DURATION_MIN` | `150` | nee | Duur waarna een event het slot verlaat | | `START_GRACE_MIN` | `10` | nee | Hoeveel minuten voor aanvang een event eligibel wordt | | `REFRESH_INTERVAL_SEC` | `60` | nee | Interval voor upstream-refresh | -| `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone van de tijdstempels in kanaalnamen | +| `SOURCE_TIMEZONE` | `UTC` | nee | Tijdzone van de tijdstempels in kanaalnamen | +| `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone voor weergave in playlist en EPG | | `LISTEN_PORT` | `8080` | nee | HTTP-poort | | `UPSTREAM_TIMEOUT_MS` | `15000` | nee | Timeout voor upstream-verzoeken | | `PROXY_INSECURE_TLS` | `0` | nee | `1` = self-signed upstream-certificaten accepteren | @@ -95,8 +96,9 @@ Ondersteun minimaal het opgegeven DAZN-formaat: - Neem de laatste `[...]` als inhoud; splits op de eerste `|` in `title` en `rest`. - Parse uit `rest`: league, optionele taalcode en verplichte starttijd. - `title`, `league`, `language`, `startAt` (UTC) en `endAt`. -- Tijdzone via `TIMEZONE`, correct omgaan met zomertijd (Intl-offsetberekening, geen - afhankelijkheid van de proces-TZ). +- Tijdstempels worden standaard als UTC gelezen (`SOURCE_TIMEZONE`), correct omgaan met + zomertijd (Intl-offsetberekening, geen afhankelijkheid van de proces-TZ). Weergave in + playlist/EPG gebeurt in `TIMEZONE`. - Als een matchende naam niet parsebaar is: stream overslaan, één keer loggen en in `/status` als waarschuwing tonen. Geen gokken met "nu actief", om toekomstige streams niet te vroeg te tonen. diff --git a/Dockers/xtream-ppv-bundler/Dockerfile b/Dockers/xtream-ppv-bundler/Dockerfile index 76a84eb..adbfd1b 100644 --- a/Dockers/xtream-ppv-bundler/Dockerfile +++ b/Dockers/xtream-ppv-bundler/Dockerfile @@ -20,6 +20,7 @@ ENV LISTEN_PORT=8080 \ EVENT_DURATION_MIN=150 \ START_GRACE_MIN=10 \ REFRESH_INTERVAL_SEC=60 \ + SOURCE_TIMEZONE=UTC \ TIMEZONE=Europe/Brussels \ UPSTREAM_TIMEOUT_MS=15000 \ PROXY_INSECURE_TLS=0 \ diff --git a/Dockers/xtream-ppv-bundler/README.md b/Dockers/xtream-ppv-bundler/README.md index d3a728a..c10cd23 100644 --- a/Dockers/xtream-ppv-bundler/README.md +++ b/Dockers/xtream-ppv-bundler/README.md @@ -12,6 +12,10 @@ competitie, taal en starttijd, bijvoorbeeld: View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (2026-11-06 19:25:00)] ``` +De tijdstempels in de kanaalnamen worden standaard als **UTC** gelezen. De app toont ze +vervolgens in `TIMEZONE` (standaard `Europe/Brussels`), dus `19:25:00` UTC wordt in de +playlist en EPG als `20:25` getoond. + Alleen streams met taalcode `(nl)` of zonder taalcode worden gebruikt. Een expliciete `(fr)`- of andere taalcode wordt overgeslagen. @@ -68,7 +72,8 @@ staat. | `EVENT_DURATION_MIN` | `150` | nee | Duur waarna een event het slot verlaat | | `START_GRACE_MIN` | `10` | nee | Minuten voor aanvang dat een event eligibel wordt | | `REFRESH_INTERVAL_SEC` | `60` | nee | Hoe vaak kanaalnamen opnieuw worden gelezen | -| `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone van de tijdstempels in kanaalnamen | +| `SOURCE_TIMEZONE` | `UTC` | nee | Tijdzone van de tijdstempels in de kanaalnamen | +| `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone voor weergave in playlist en EPG | | `LISTEN_PORT` | `8080` | nee | HTTP-poort | | `UPSTREAM_TIMEOUT_MS` | `15000` | nee | Timeout voor upstream-verzoeken | | `PROXY_INSECURE_TLS` | `0` | nee | `1` = self-signed upstream-certificaten accepteren | diff --git a/Dockers/xtream-ppv-bundler/docker-compose.example.yml b/Dockers/xtream-ppv-bundler/docker-compose.example.yml index af740c2..426d291 100644 --- a/Dockers/xtream-ppv-bundler/docker-compose.example.yml +++ b/Dockers/xtream-ppv-bundler/docker-compose.example.yml @@ -14,6 +14,7 @@ services: EVENT_DURATION_MIN: "150" START_GRACE_MIN: "10" REFRESH_INTERVAL_SEC: "600" + SOURCE_TIMEZONE: "UTC" TIMEZONE: "Europe/Brussels" # PROXY_INSECURE_TLS: "1" # alleen nodig bij self-signed upstream-certificaten # LOG_LEVEL: "debug" diff --git a/Dockers/xtream-ppv-bundler/server.js b/Dockers/xtream-ppv-bundler/server.js index c19efc5..d1cfbf6 100644 --- a/Dockers/xtream-ppv-bundler/server.js +++ b/Dockers/xtream-ppv-bundler/server.js @@ -40,6 +40,7 @@ function statusPayload() { eventDurationMin: settings.eventDurationMin, startGraceMin: settings.startGraceMin, refreshIntervalSec: settings.refreshIntervalSec, + sourceTimezone: settings.sourceTimezone, timezone: settings.timezone, }, }; diff --git a/Dockers/xtream-ppv-bundler/src/scheduler.js b/Dockers/xtream-ppv-bundler/src/scheduler.js index bc26881..691c77b 100644 --- a/Dockers/xtream-ppv-bundler/src/scheduler.js +++ b/Dockers/xtream-ppv-bundler/src/scheduler.js @@ -160,7 +160,7 @@ function createScheduler() { const { events, warnings } = buildEvents(streams, { searchTerm: settings.searchTerm, language: settings.language, - timezone: settings.timezone, + timezone: settings.sourceTimezone, eventDurationMs: settings.eventDurationMs, }); const now = Date.now(); diff --git a/Dockers/xtream-ppv-bundler/src/settings.js b/Dockers/xtream-ppv-bundler/src/settings.js index ecfa52b..b14d795 100644 --- a/Dockers/xtream-ppv-bundler/src/settings.js +++ b/Dockers/xtream-ppv-bundler/src/settings.js @@ -18,6 +18,7 @@ const settings = { eventDurationMin: readNumber(process.env.EVENT_DURATION_MIN, 150, 1, 1440), startGraceMin: readNumber(process.env.START_GRACE_MIN, 10, 0, 1440), refreshIntervalSec: readNumber(process.env.REFRESH_INTERVAL_SEC, 60, 10, 3600), + sourceTimezone: (process.env.SOURCE_TIMEZONE || 'UTC').trim() || 'UTC', timezone: (process.env.TIMEZONE || 'Europe/Brussels').trim() || 'Europe/Brussels', listenPort: Math.round(readNumber(process.env.LISTEN_PORT, 8080, 1, 65535)), upstreamTimeoutMs: readNumber(process.env.UPSTREAM_TIMEOUT_MS, 15000, 1000, 120000), @@ -32,11 +33,11 @@ const settings = { settings.eventDurationMs = settings.eventDurationMin * 60 * 1000; settings.startGraceMs = settings.startGraceMin * 60 * 1000; -function validateTimezone(timezone) { +function validateTimezone(timezone, envName) { try { new Intl.DateTimeFormat('en-US', { timeZone: timezone }); } catch { - throw new Error(`Invalid TIMEZONE: ${timezone}`); + throw new Error(`Invalid ${envName}: ${timezone}`); } } @@ -60,7 +61,8 @@ function validate() { throw new Error('XTREAM_URL must use http or https'); } - validateTimezone(settings.timezone); + validateTimezone(settings.sourceTimezone, 'SOURCE_TIMEZONE'); + validateTimezone(settings.timezone, 'TIMEZONE'); if (!settings.language) { throw new Error('LANGUAGE must not be empty'); diff --git a/Dockers/xtream-ppv-bundler/test/channel-name.test.js b/Dockers/xtream-ppv-bundler/test/channel-name.test.js index ddfe615..e395f24 100644 --- a/Dockers/xtream-ppv-bundler/test/channel-name.test.js +++ b/Dockers/xtream-ppv-bundler/test/channel-name.test.js @@ -7,18 +7,23 @@ const { parseChannelName, normalizeText } = require('../src/channel-name'); const sample = 'View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (2026-11-06 19:25:00)]'; -test('parses the DAZN sample', () => { - const parsed = parseChannelName(sample, { timezone: 'Europe/Brussels' }); +test('parses the DAZN sample with UTC source timestamps', () => { + const parsed = parseChannelName(sample, { timezone: 'UTC' }); assert.equal(parsed.title, 'Standard de Liège - SK Beveren'); assert.equal(parsed.league, 'Jupiler Pro League'); assert.equal(parsed.language, 'nl'); + assert.equal(parsed.startAt.toISOString(), '2026-11-06T19:25:00.000Z'); +}); + +test('can parse the same timestamp in another source timezone', () => { + const parsed = parseChannelName(sample, { timezone: 'Europe/Brussels' }); assert.equal(parsed.startAt.toISOString(), '2026-11-06T18:25:00.000Z'); }); test('parses a French stream', () => { const parsed = parseChannelName( 'View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (fr) (2026-11-06 19:25:00)]', - { timezone: 'Europe/Brussels' } + { timezone: 'UTC' } ); assert.equal(parsed.language, 'fr'); }); @@ -26,14 +31,14 @@ test('parses a French stream', () => { test('allows a missing language code', () => { const parsed = parseChannelName( 'DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (2026-11-06 19:25:00)]', - { timezone: 'Europe/Brussels' } + { timezone: 'UTC' } ); assert.equal(parsed.language, null); }); test('returns null for unparseable names', () => { - assert.equal(parseChannelName('DAZN 03', { timezone: 'Europe/Brussels' }), null); - assert.equal(parseChannelName('', { timezone: 'Europe/Brussels' }), null); + assert.equal(parseChannelName('DAZN 03', { timezone: 'UTC' }), null); + assert.equal(parseChannelName('', { timezone: 'UTC' }), null); }); test('normalizes diacritics and whitespace', () => { diff --git a/Dockers/xtream-ppv-bundler/test/events.test.js b/Dockers/xtream-ppv-bundler/test/events.test.js index 3543463..2b44123 100644 --- a/Dockers/xtream-ppv-bundler/test/events.test.js +++ b/Dockers/xtream-ppv-bundler/test/events.test.js @@ -4,7 +4,7 @@ const test = require('node:test'); const assert = require('node:assert/strict'); const { buildEvents, compareEvents, isEligible } = require('../src/events'); -const timezone = 'Europe/Brussels'; +const timezone = 'UTC'; function stream(id, name) { return { stream_id: id, name };