From bb5be54c26d87ea200369e7a7fd3694f3b621067 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Fri, 10 Apr 2026 22:40:52 +0200 Subject: [PATCH] ui: convert jst to local time --- internal/templates/anime.templ | 2 +- internal/templates/layout.templ | 1 + internal/templates/notifications.templ | 2 +- static/js/timezone.js | 113 +++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 static/js/timezone.js diff --git a/internal/templates/anime.templ b/internal/templates/anime.templ index d602554..07b3f40 100644 --- a/internal/templates/anime.templ +++ b/internal/templates/anime.templ @@ -149,7 +149,7 @@ templ AnimeDetails(anime jikan.Anime, currentStatus string) { if anime.Broadcast.String != "" { } if len(anime.Streaming) > 0 { diff --git a/internal/templates/layout.templ b/internal/templates/layout.templ index 28e6895..ab2a09f 100644 --- a/internal/templates/layout.templ +++ b/internal/templates/layout.templ @@ -14,6 +14,7 @@ templ Layout(title string) { +
diff --git a/internal/templates/notifications.templ b/internal/templates/notifications.templ index 60b4ed6..2d9ad51 100644 --- a/internal/templates/notifications.templ +++ b/internal/templates/notifications.templ @@ -124,7 +124,7 @@ templ NotificationCard(item WatchingAnimeWithDetails) {
if item.Anime.Broadcast.String != "" { - { item.Anime.Broadcast.String } + { item.Anime.Broadcast.String } } if item.Anime.Episodes > 0 { diff --git a/static/js/timezone.js b/static/js/timezone.js new file mode 100644 index 0000000..8e7d01d --- /dev/null +++ b/static/js/timezone.js @@ -0,0 +1,113 @@ +;(function () { + const jstOffsetMinutes = 9 * 60 + + const parseBroadcast = (text) => { + const match = text.match(/^(.*) at (\d{1,2}):(\d{2}) \(JST\)$/i) + if (!match) { + return null + } + + const day = match[1].trim() + const hour = Number.parseInt(match[2], 10) + const minute = Number.parseInt(match[3], 10) + + if (Number.isNaN(hour) || Number.isNaN(minute)) { + return null + } + + return { day, hour, minute } + } + + const normalizeDay = (day) => { + const key = day.trim().toLowerCase().replace(/s$/, '') + const days = { + mon: 1, + monday: 1, + tue: 2, + tues: 2, + tuesday: 2, + wed: 3, + wednesday: 3, + thu: 4, + thur: 4, + thurs: 4, + thursday: 4, + fri: 5, + friday: 5, + sat: 6, + saturday: 6, + sun: 0, + sunday: 0, + } + + if (typeof days[key] !== 'number') { + return null + } + + return days[key] + } + + const convertToLocal = (parsed, localOffsetMinutes) => { + const sourceMinutes = parsed.hour * 60 + parsed.minute + const diff = jstOffsetMinutes - localOffsetMinutes + const localTotal = sourceMinutes - diff + + const dayShift = Math.floor(localTotal / 1440) + const normalizedMinutes = ((localTotal % 1440) + 1440) % 1440 + const localHour = Math.floor(normalizedMinutes / 60) + const localMinute = normalizedMinutes % 60 + + const sourceDayIndex = normalizeDay(parsed.day) + if (sourceDayIndex === null) { + return null + } + + const localDayIndex = ((sourceDayIndex + dayShift) % 7 + 7) % 7 + const localDay = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][localDayIndex] + + const time = `${localHour.toString().padStart(2, '0')}:${localMinute.toString().padStart(2, '0')}` + return `${localDay} at ${time} (Local)` + } + + const updateNode = (node, localOffsetMinutes) => { + const source = node.getAttribute('data-jst-text') + if (!source) { + return + } + + const parsed = parseBroadcast(source) + if (!parsed) { + return + } + + const converted = convertToLocal(parsed, localOffsetMinutes) + if (!converted) { + return + } + + node.textContent = converted + } + + const hideJstSuffix = () => { + document.querySelectorAll('.notification-broadcast, [data-jst-text]').forEach((node) => { + if (!(node instanceof HTMLElement)) { + return + } + + const text = node.textContent || '' + if (text.includes('(JST)')) { + node.textContent = text.replace(/\s*\(JST\)/gi, ' (Local)') + } + }) + } + + const updateAll = () => { + const localOffsetMinutes = -new Date().getTimezoneOffset() + const nodes = document.querySelectorAll('[data-jst-text]') + nodes.forEach((node) => updateNode(node, localOffsetMinutes)) + hideJstSuffix() + } + + document.addEventListener('DOMContentLoaded', updateAll) + document.body.addEventListener('htmx:afterSwap', updateAll) +})()