diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 07a42f0..7b161ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,6 +148,9 @@ importers: date-fns: specifier: 4.1.0 version: 4.1.0 + date-fns-tz: + specifier: ^3.2.0 + version: 3.2.0(date-fns@4.1.0) drizzle-orm: specifier: 0.44.4 version: 0.44.4(@cloudflare/workers-types@4.20250823.0) @@ -8868,6 +8871,14 @@ packages: resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} dev: false + /date-fns-tz@3.2.0(date-fns@4.1.0): + resolution: {integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==} + peerDependencies: + date-fns: ^3.0.0 || ^4.0.0 + dependencies: + date-fns: 4.1.0 + dev: false + /date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} diff --git a/programmerbar-web/package.json b/programmerbar-web/package.json index 3ec0e51..52611e4 100644 --- a/programmerbar-web/package.json +++ b/programmerbar-web/package.json @@ -72,6 +72,7 @@ "class-variance-authority": "0.7.1", "clsx": "2.1.1", "date-fns": "4.1.0", + "date-fns-tz": "^3.2.0", "drizzle-orm": "0.44.4", "groq": "4.5.0", "ky": "^1.9.0", diff --git a/programmerbar-web/src/lib/date.ts b/programmerbar-web/src/lib/date.ts index e848d6d..d0c1406 100644 --- a/programmerbar-web/src/lib/date.ts +++ b/programmerbar-web/src/lib/date.ts @@ -1,162 +1,45 @@ +import { toZonedTime } from 'date-fns-tz'; +import { format } from 'date-fns'; + export type Dateish = string | Date | number; export const OSLO_TIME_ZONE = 'Europe/Oslo'; -const dateFormatter = new Intl.DateTimeFormat('nb-NO', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - timeZone: OSLO_TIME_ZONE -}); - -const timeFormatter = new Intl.DateTimeFormat('nb-NO', { - hour: '2-digit', - minute: '2-digit', - hour12: false, - timeZone: OSLO_TIME_ZONE -}); - -const dateTimeFormatter = new Intl.DateTimeFormat('en-CA', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false, - timeZone: OSLO_TIME_ZONE -}); - -const dateTimeFormatterCache = new Map(); - -const timeZoneRegex = /(?:Z|[+-]\d{2}:?\d{2})$/i; - -type DateTimeParts = { - year: string; - month: string; - day: string; - hour: string; - minute: string; - second: string; -}; - const ensureDate = (date: Dateish): Date => { return date instanceof Date ? date : new Date(date); }; -const getFormatterParts = ( - date: Date, - formatter: Intl.DateTimeFormat -): Partial> => { - return formatter - .formatToParts(date) - .reduce>>((acc, part) => { - if (part.type !== 'literal') { - acc[part.type] = part.value; - } - return acc; - }, {}); -}; - -const getDateTimeParts = (date: Date): DateTimeParts => { - const parts = getFormatterParts(date, dateTimeFormatter); - return { - year: parts.year ?? '0000', - month: parts.month ?? '01', - day: parts.day ?? '01', - hour: parts.hour ?? '00', - minute: parts.minute ?? '00', - second: parts.second ?? '00' - }; -}; - -const getTimeZoneFormatter = (timeZone: string) => { - if (!dateTimeFormatterCache.has(timeZone)) { - dateTimeFormatterCache.set( - timeZone, - new Intl.DateTimeFormat('en-CA', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false, - timeZone - }) - ); - } - - return dateTimeFormatterCache.get(timeZone)!; -}; - -const getTimeZoneOffset = (date: Date, timeZone: string): number => { - const formatter = getTimeZoneFormatter(timeZone); - const parts = getFormatterParts(date, formatter); - const inZoneUtc = Date.UTC( - Number(parts.year ?? '0'), - Number(parts.month ?? '1') - 1, - Number(parts.day ?? '1'), - Number(parts.hour ?? '0'), - Number(parts.minute ?? '0'), - Number(parts.second ?? '0') - ); - - return inZoneUtc - date.getTime(); -}; - export const formatDate = (date: Dateish) => { - return dateFormatter.format(ensureDate(date)); + const utcDate = ensureDate(date); + const osloTime = toZonedTime(utcDate, OSLO_TIME_ZONE); + return format(osloTime, 'dd.MM.yyyy'); }; export const time = (date: Dateish) => { - return timeFormatter.format(ensureDate(date)); + const utcDate = ensureDate(date); + const osloTime = toZonedTime(utcDate, OSLO_TIME_ZONE); + return format(osloTime, 'HH:mm'); }; export const normalDate = (date: Dateish) => { - return `${formatDate(date)} ${time(date)}`; + const utcDate = ensureDate(date); + const osloTime = toZonedTime(utcDate, OSLO_TIME_ZONE); + return format(osloTime, 'dd.MM.yyyy HH:mm'); }; -export const ISOStandard = (date: Dateish) => { - const parts = getDateTimeParts(ensureDate(date)); - return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}`; +export const toLocalDateTimeString = (date: Dateish): string => { + const utcDate = ensureDate(date); + const osloTime = toZonedTime(utcDate, OSLO_TIME_ZONE); + return format(osloTime, "yyyy-MM-dd'T'HH:mm"); }; -export const parseDateTimeLocal = (value: string, timeZone: string = OSLO_TIME_ZONE): Date => { - const trimmedValue = value.trim(); - - if (!trimmedValue) { - return new Date(trimmedValue); - } - - if (timeZoneRegex.test(trimmedValue) || !trimmedValue.includes('T')) { - return new Date(trimmedValue); - } - - const [datePart, timePart] = trimmedValue.split('T'); - if (!datePart || !timePart) { - return new Date(trimmedValue); - } - - const [year, month, day] = datePart.split('-').map((part) => Number(part)); - const timeSegments = timePart.split(':'); - const hour = Number(timeSegments[0] ?? '0'); - const minute = Number(timeSegments[1] ?? '0'); - const second = Number((timeSegments[2] ?? '0').split('.')[0]); - - const targetUtc = Date.UTC(year, (month || 1) - 1, day || 1, hour, minute, second); - let result = new Date(targetUtc); - let offset = getTimeZoneOffset(result, timeZone); - let adjusted = targetUtc - offset; - - if (adjusted !== targetUtc) { - result = new Date(adjusted); - offset = getTimeZoneOffset(result, timeZone); - adjusted = targetUtc - offset; - } +export const parseDateTimeLocal = (value: string): Date => { + return new Date(value); +}; - return new Date(adjusted); +export const toUtcISOStringFromLocal = (value: string) => { + return new Date(value).toISOString(); }; -export const toUtcISOStringFromLocal = (value: string, timeZone: string = OSLO_TIME_ZONE) => { - return parseDateTimeLocal(value, timeZone).toISOString(); +export const ISOStandard = (date: Dateish) => { + return toLocalDateTimeString(date); }; diff --git a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte index 101369a..ecbaec5 100644 --- a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte +++ b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte @@ -1,5 +1,4 @@