|
| 1 | +import { toZonedTime } from 'date-fns-tz'; |
| 2 | +import { format } from 'date-fns'; |
| 3 | + |
1 | 4 | export type Dateish = string | Date | number; |
2 | 5 | export const OSLO_TIME_ZONE = 'Europe/Oslo'; |
3 | 6 |
|
4 | | -const dateFormatter = new Intl.DateTimeFormat('nb-NO', { |
5 | | - day: '2-digit', |
6 | | - month: '2-digit', |
7 | | - year: 'numeric', |
8 | | - timeZone: OSLO_TIME_ZONE |
9 | | -}); |
10 | | - |
11 | | -const timeFormatter = new Intl.DateTimeFormat('nb-NO', { |
12 | | - hour: '2-digit', |
13 | | - minute: '2-digit', |
14 | | - hour12: false, |
15 | | - timeZone: OSLO_TIME_ZONE |
16 | | -}); |
17 | | - |
18 | | -const dateTimeFormatter = new Intl.DateTimeFormat('en-CA', { |
19 | | - year: 'numeric', |
20 | | - month: '2-digit', |
21 | | - day: '2-digit', |
22 | | - hour: '2-digit', |
23 | | - minute: '2-digit', |
24 | | - second: '2-digit', |
25 | | - hour12: false, |
26 | | - timeZone: OSLO_TIME_ZONE |
27 | | -}); |
28 | | - |
29 | | -const dateTimeFormatterCache = new Map<string, Intl.DateTimeFormat>(); |
30 | | - |
31 | | -const timeZoneRegex = /(?:Z|[+-]\d{2}:?\d{2})$/i; |
32 | | - |
33 | | -type DateTimeParts = { |
34 | | - year: string; |
35 | | - month: string; |
36 | | - day: string; |
37 | | - hour: string; |
38 | | - minute: string; |
39 | | - second: string; |
40 | | -}; |
41 | | - |
42 | 7 | const ensureDate = (date: Dateish): Date => { |
43 | 8 | return date instanceof Date ? date : new Date(date); |
44 | 9 | }; |
45 | 10 |
|
46 | | -const getFormatterParts = ( |
47 | | - date: Date, |
48 | | - formatter: Intl.DateTimeFormat |
49 | | -): Partial<Record<Intl.DateTimeFormatPartTypes, string>> => { |
50 | | - return formatter |
51 | | - .formatToParts(date) |
52 | | - .reduce<Partial<Record<Intl.DateTimeFormatPartTypes, string>>>((acc, part) => { |
53 | | - if (part.type !== 'literal') { |
54 | | - acc[part.type] = part.value; |
55 | | - } |
56 | | - return acc; |
57 | | - }, {}); |
58 | | -}; |
59 | | - |
60 | | -const getDateTimeParts = (date: Date): DateTimeParts => { |
61 | | - const parts = getFormatterParts(date, dateTimeFormatter); |
62 | | - return { |
63 | | - year: parts.year ?? '0000', |
64 | | - month: parts.month ?? '01', |
65 | | - day: parts.day ?? '01', |
66 | | - hour: parts.hour ?? '00', |
67 | | - minute: parts.minute ?? '00', |
68 | | - second: parts.second ?? '00' |
69 | | - }; |
70 | | -}; |
71 | | - |
72 | | -const getTimeZoneFormatter = (timeZone: string) => { |
73 | | - if (!dateTimeFormatterCache.has(timeZone)) { |
74 | | - dateTimeFormatterCache.set( |
75 | | - timeZone, |
76 | | - new Intl.DateTimeFormat('en-CA', { |
77 | | - year: 'numeric', |
78 | | - month: '2-digit', |
79 | | - day: '2-digit', |
80 | | - hour: '2-digit', |
81 | | - minute: '2-digit', |
82 | | - second: '2-digit', |
83 | | - hour12: false, |
84 | | - timeZone |
85 | | - }) |
86 | | - ); |
87 | | - } |
88 | | - |
89 | | - return dateTimeFormatterCache.get(timeZone)!; |
90 | | -}; |
91 | | - |
92 | | -const getTimeZoneOffset = (date: Date, timeZone: string): number => { |
93 | | - const formatter = getTimeZoneFormatter(timeZone); |
94 | | - const parts = getFormatterParts(date, formatter); |
95 | | - const inZoneUtc = Date.UTC( |
96 | | - Number(parts.year ?? '0'), |
97 | | - Number(parts.month ?? '1') - 1, |
98 | | - Number(parts.day ?? '1'), |
99 | | - Number(parts.hour ?? '0'), |
100 | | - Number(parts.minute ?? '0'), |
101 | | - Number(parts.second ?? '0') |
102 | | - ); |
103 | | - |
104 | | - return inZoneUtc - date.getTime(); |
105 | | -}; |
106 | | - |
107 | 11 | export const formatDate = (date: Dateish) => { |
108 | | - return dateFormatter.format(ensureDate(date)); |
| 12 | + const utcDate = ensureDate(date); |
| 13 | + const osloTime = toZonedTime(utcDate, OSLO_TIME_ZONE); |
| 14 | + return format(osloTime, 'dd.MM.yyyy'); |
109 | 15 | }; |
110 | 16 |
|
111 | 17 | export const time = (date: Dateish) => { |
112 | | - return timeFormatter.format(ensureDate(date)); |
| 18 | + const utcDate = ensureDate(date); |
| 19 | + const osloTime = toZonedTime(utcDate, OSLO_TIME_ZONE); |
| 20 | + return format(osloTime, 'HH:mm'); |
113 | 21 | }; |
114 | 22 |
|
115 | 23 | export const normalDate = (date: Dateish) => { |
116 | | - return `${formatDate(date)} ${time(date)}`; |
| 24 | + const utcDate = ensureDate(date); |
| 25 | + const osloTime = toZonedTime(utcDate, OSLO_TIME_ZONE); |
| 26 | + return format(osloTime, 'dd.MM.yyyy HH:mm'); |
117 | 27 | }; |
118 | 28 |
|
119 | | -export const ISOStandard = (date: Dateish) => { |
120 | | - const parts = getDateTimeParts(ensureDate(date)); |
121 | | - return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}`; |
| 29 | +export const toLocalDateTimeString = (date: Dateish): string => { |
| 30 | + const utcDate = ensureDate(date); |
| 31 | + const osloTime = toZonedTime(utcDate, OSLO_TIME_ZONE); |
| 32 | + return format(osloTime, "yyyy-MM-dd'T'HH:mm"); |
122 | 33 | }; |
123 | 34 |
|
124 | | -export const parseDateTimeLocal = (value: string, timeZone: string = OSLO_TIME_ZONE): Date => { |
125 | | - const trimmedValue = value.trim(); |
126 | | - |
127 | | - if (!trimmedValue) { |
128 | | - return new Date(trimmedValue); |
129 | | - } |
130 | | - |
131 | | - if (timeZoneRegex.test(trimmedValue) || !trimmedValue.includes('T')) { |
132 | | - return new Date(trimmedValue); |
133 | | - } |
134 | | - |
135 | | - const [datePart, timePart] = trimmedValue.split('T'); |
136 | | - if (!datePart || !timePart) { |
137 | | - return new Date(trimmedValue); |
138 | | - } |
139 | | - |
140 | | - const [year, month, day] = datePart.split('-').map((part) => Number(part)); |
141 | | - const timeSegments = timePart.split(':'); |
142 | | - const hour = Number(timeSegments[0] ?? '0'); |
143 | | - const minute = Number(timeSegments[1] ?? '0'); |
144 | | - const second = Number((timeSegments[2] ?? '0').split('.')[0]); |
145 | | - |
146 | | - const targetUtc = Date.UTC(year, (month || 1) - 1, day || 1, hour, minute, second); |
147 | | - let result = new Date(targetUtc); |
148 | | - let offset = getTimeZoneOffset(result, timeZone); |
149 | | - let adjusted = targetUtc - offset; |
150 | | - |
151 | | - if (adjusted !== targetUtc) { |
152 | | - result = new Date(adjusted); |
153 | | - offset = getTimeZoneOffset(result, timeZone); |
154 | | - adjusted = targetUtc - offset; |
155 | | - } |
| 35 | +export const parseDateTimeLocal = (value: string): Date => { |
| 36 | + return new Date(value); |
| 37 | +}; |
156 | 38 |
|
157 | | - return new Date(adjusted); |
| 39 | +export const toUtcISOStringFromLocal = (value: string) => { |
| 40 | + return new Date(value).toISOString(); |
158 | 41 | }; |
159 | 42 |
|
160 | | -export const toUtcISOStringFromLocal = (value: string, timeZone: string = OSLO_TIME_ZONE) => { |
161 | | - return parseDateTimeLocal(value, timeZone).toISOString(); |
| 43 | +export const ISOStandard = (date: Dateish) => { |
| 44 | + return toLocalDateTimeString(date); |
162 | 45 | }; |
0 commit comments