Openstatus
www.openstatus.dev
1import { format, getTimezoneOffset, utcToZonedTime } from "date-fns-tz";
2import { headers } from "next/headers";
3
4export async function getRequestHeaderTimezone() {
5 const headersList = await headers();
6
7 /**
8 * Vercel includes ip timezone to request header
9 */
10 const requestTimezone = headersList.get("x-vercel-ip-timezone");
11
12 return requestTimezone;
13}
14
15export async function convertTimezoneToGMT(defaultTimezone?: string) {
16 const requestTimezone = await getRequestHeaderTimezone();
17
18 /**
19 * Server region timezone as fallback
20 */
21 const clientTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
22
23 const timezone = defaultTimezone || requestTimezone || clientTimezone;
24
25 if (!supportedTimezones.includes(timezone)) return "Etc/UTC";
26
27 const msOffset = getTimezoneOffset(timezone);
28
29 if (Number.isNaN(msOffset)) return "Etc/UTC";
30
31 const hrOffset = Math.round(msOffset / (1000 * 60 * 60)); // avoid weird 30min timezones
32 const offset = hrOffset >= 0 ? `-${hrOffset}` : `+${Math.abs(hrOffset)}`;
33
34 return `Etc/GMT${offset}` as const;
35}
36
37export function getServerTimezoneUTCFormat() {
38 const now = new Date();
39 const now_utc = new Date(now.toUTCString().slice(0, -4)); // remove the GMT end
40
41 return format(now_utc, "LLL dd, y HH:mm:ss (z)", { timeZone: "UTC" });
42}
43
44export function getServerTimezoneFormat() {
45 return format(new Date(), "LLL dd, y HH:mm:ss (z)");
46}
47
48export function formatDate(date: Date) {
49 return format(date, "LLL dd, y", { timeZone: "UTC" });
50}
51
52export function formatDateTime(date: Date) {
53 return format(date, "LLL dd, y HH:mm:ss zzz", { timeZone: "UTC" });
54}
55
56export function formatTime(date: Date) {
57 return format(date, "HH:mm:ss zzz", { timeZone: "UTC" });
58}
59
60/**
61 * All supported browser / node timezones
62 */
63export const supportedTimezones = Intl.supportedValuesOf("timeZone");
64
65export async function getClosestTimezone(defaultTimezone?: string) {
66 const requestTimezone = await getRequestHeaderTimezone();
67
68 /**
69 * Server region timezone as fallback
70 */
71 const clientTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
72
73 const timezone = defaultTimezone || requestTimezone || clientTimezone;
74
75 if (!supportedTimezones.includes(timezone)) return "CET";
76
77 const now = new Date();
78
79 const estTime = utcToZonedTime(now, "America/New_York");
80 const pstTime = utcToZonedTime(now, "America/Los_Angeles");
81 const cetTime = utcToZonedTime(now, "Europe/Paris");
82 const utcTime = utcToZonedTime(now, "UTC");
83
84 const timeDifferences = {
85 EST: Math.abs(now.getTime() - estTime.getTime()),
86 PST: Math.abs(now.getTime() - pstTime.getTime()),
87 CET: Math.abs(now.getTime() - cetTime.getTime()),
88 UTC: Math.abs(now.getTime() - utcTime.getTime()),
89 };
90
91 const closestTimezone = Object.keys(timeDifferences).reduce(
92 (prev, curr) => {
93 if (
94 timeDifferences[curr as keyof typeof timeDifferences] <
95 prev.minDifference
96 ) {
97 return {
98 timezone: curr,
99 minDifference: timeDifferences[curr as keyof typeof timeDifferences],
100 };
101 }
102 return prev;
103 },
104 { timezone: "UTC", minDifference: Number.POSITIVE_INFINITY },
105 );
106
107 return closestTimezone.timezone as keyof typeof timeDifferences;
108}