From c6562e68824a1c3d7c3de485cef6de9dbb19a61c Mon Sep 17 00:00:00 2001 From: Eliran Sapir <114778791+sapireli@users.noreply.github.com> Date: Thu, 30 Apr 2026 05:58:41 -0400 Subject: [PATCH] fix(xtream): guard against invalid timezone strings in catchup formatter Intl.DateTimeFormat throws RangeError for non-IANA timezone values (e.g. 'UTC+5', offset strings) that some Xtream servers return in server_info.timezone. Wrap the formatter in a try-catch and fall back to client local-time formatting on failure. Co-Authored-By: Claude Sonnet 4.6 --- .../lib/services/xtream-url.service.spec.ts | 15 ++++++++++ .../src/lib/services/xtream-url.service.ts | 28 +++++++++++-------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/libs/portal/xtream/data-access/src/lib/services/xtream-url.service.spec.ts b/libs/portal/xtream/data-access/src/lib/services/xtream-url.service.spec.ts index 2bbc3cea7..2cf32156b 100644 --- a/libs/portal/xtream/data-access/src/lib/services/xtream-url.service.spec.ts +++ b/libs/portal/xtream/data-access/src/lib/services/xtream-url.service.spec.ts @@ -106,5 +106,20 @@ describe('XtreamUrlService', () => { const expected = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}:${pad(date.getHours())}-${pad(date.getMinutes())}`; expect(url).toContain(expected); }); + + it('falls back to client local time when an invalid timezone string is given', () => { + const url = service.constructCatchupUrl( + credentials, + 101, + timestamp, + timestamp + 3600, + 'rest', + 'UTC+5' + ); + const date = new Date(timestamp * 1000); + const pad = (n: number) => String(n).padStart(2, '0'); + const expected = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}:${pad(date.getHours())}-${pad(date.getMinutes())}`; + expect(url).toContain(expected); + }); }); }); diff --git a/libs/portal/xtream/data-access/src/lib/services/xtream-url.service.ts b/libs/portal/xtream/data-access/src/lib/services/xtream-url.service.ts index 65e221a6d..0d63c0937 100644 --- a/libs/portal/xtream/data-access/src/lib/services/xtream-url.service.ts +++ b/libs/portal/xtream/data-access/src/lib/services/xtream-url.service.ts @@ -271,18 +271,22 @@ export class XtreamUrlService { const date = new Date(timestamp * 1000); if (timezone) { - const parts = new Intl.DateTimeFormat('en-CA', { - timeZone: timezone, - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - hour12: false, - }).formatToParts(date); - const get = (type: Intl.DateTimeFormatPartTypes) => - parts.find((p) => p.type === type)?.value ?? '00'; - return `${get('year')}-${get('month')}-${get('day')}:${get('hour')}-${get('minute')}`; + try { + const parts = new Intl.DateTimeFormat('en-CA', { + timeZone: timezone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false, + }).formatToParts(date); + const get = (type: Intl.DateTimeFormatPartTypes) => + parts.find((p) => p.type === type)?.value ?? '00'; + return `${get('year')}-${get('month')}-${get('day')}:${get('hour')}-${get('minute')}`; + } catch { + // Invalid timezone string — fall through to local-time formatting + } } const pad = (value: number) => String(value).padStart(2, '0');