Files
iptvnator/docs/architecture/date-handling.md
T
4grayandClaude Fable 5.1 512e9787a8 perf(web): load Angular date locales lazily per language (#1695)
Plan thread C1, journey **J1 `launch`**, counter **`renderer.initialBytes`**. Stacked on #1694 → #1693 → #1692; merge in order.

`apps/web/src/app/app-date-locales.ts` imported the locale data of all 18 supported languages eagerly, so every user shipped and parsed all of it at startup. Each locale is now a dynamic import keyed by the Angular locale id that `normalizeDateLocale()` derives from the app language (`by` → `be`, `ary` → `ar-MA`, `zhtw` → `zh-Hant`); English needs no data.

Ordering is preserved so no template renders a locale whose data has not arrived (Angular throws in that case):
- `main.ts` awaits the initial language's data (from `getInitialLanguage()`) before `bootstrapApplication`.
- Both `TranslateService.use()` call sites, `AppComponent.initSettings()` and `SettingsFormFacade.applySavedSettings()`, register the data first through the new `AppDateLocaleService`.
- A failed load never leaves the locale without data: English formatting is registered under the requested id (eager 1.1 KB `@angular/common/locales/en`), so `DatePipe` renders instead of throwing; the locale is not marked registered, so the next call retries and a success replaces the fallback (review follow-up).
- `AppDateLocaleService.use()` orders switches by request, not by completion: a switch whose data arrives after a newer request is dropped, so the language chosen last wins (review follow-up).

No kill switch: behavior is identical once the locale resolves, and the only new failure mode (a same-origin chunk failing to load) is shared with every lazy route.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-26 13:44:29 +02:00

2.7 KiB

Date Handling

Rules

  • Use native Date, ISO strings, and epoch timestamps as the stored/runtime values.
  • Use date-fns for parsing, arithmetic, and normalization logic.
  • Use Angular DatePipe in templates when the value is already a Date, ISO string, or epoch timestamp.
  • Use cached Intl.DateTimeFormat helpers in TypeScript-only formatting paths where Angular pipes are not available.
  • Do not add new moment usage. The app no longer depends on it.

Locale Strategy

  • Date display should follow the user-selected app language from TranslateService.
  • When a template renders localized month or weekday names, pass the normalized app locale explicitly to DatePipe.
  • Angular locale data is loaded lazily per language by registerAppDateLocale() in apps/web/src/app/app-date-locales.ts: main.ts awaits the initial language's data before bootstrapping, and every TranslateService.use() call site registers the data first (through AppDateLocaleService), because the switch re-renders dates with the new locale and a locale without data throws. English needs no data. AppDateLocaleService.use() applies only the language requested last when switches overlap, and a locale whose chunk fails to load falls back to English formatting under that locale id until a later call loads it.
  • App language aliases are normalized in libs/ui/pipes/src/lib/date-format.util.ts:
    • ary -> ar-MA
    • by -> be
    • zhtw -> zh-Hant

Parsing Boundaries

  • Normalize provider-specific or legacy date strings to ISO as early as possible.
  • Keep optional epoch fields such as startTimestamp and stopTimestamp when the provider already supplies them.
  • Avoid new non-standard Date.parse(...) usage for provider formats; prefer explicit date-fns parsing when the input is not ISO.
  • Xtream added / last_modified values are provider-supplied epoch fields. Normalize them through toXtreamRecentlyAddedTimestamp() / toXtreamRecentlyAddedEpochSeconds() from @iptvnator/shared/interfaces before ranking or storing recently-added content. Values more than 24 hours in the future are treated as invalid so provider placeholders such as 2030-01-01 cannot permanently pin the top of recently-added rails. Recently-added ranking uses added before last_modified for live/VOD content and last_modified before added for series. The VOD/live fallback is intentional for providers that omit added but still expose a valid last_modified epoch. Legacy cached millisecond epochs in content.added are migrated to epoch seconds during database startup so indexed dashboard queries can continue to compare and sort text timestamps directly.