From 09cc6f07e428dc29d0b55167a7a71b27e735beee Mon Sep 17 00:00:00 2001
From: Aaron Presley <155617+AaronPresley@users.noreply.github.com>
Date: Thu, 16 Feb 2023 09:16:22 -0800
Subject: [PATCH] Booking page now displays localized dates, times, and
timezones (#7130)
---
apps/web/pages/booking/[uid].tsx | 39 ++++++++++++++--------
packages/lib/date-fns/index.ts | 55 ++++++++++++++++++++++++++++++++
2 files changed, 80 insertions(+), 14 deletions(-)
diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx
index 4c0f2cfa45..f88475b53c 100644
--- a/apps/web/pages/booking/[uid].tsx
+++ b/apps/web/pages/booking/[uid].tsx
@@ -28,7 +28,11 @@ import {
import { parseRecurringEvent } from "@calcom/lib";
import CustomBranding from "@calcom/lib/CustomBranding";
import { APP_NAME } from "@calcom/lib/constants";
-import { formatTime } from "@calcom/lib/date-fns";
+import {
+ formatToLocalizedDate,
+ formatToLocalizedTime,
+ formatToLocalizedTimezone,
+} from "@calcom/lib/date-fns";
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
@@ -820,7 +824,10 @@ export function RecurringBookings({
isCancelled,
}: RecurringBookingsProps) {
const [moreEventsVisible, setMoreEventsVisible] = useState(false);
- const { t } = useLocale();
+ const {
+ t,
+ i18n: { language },
+ } = useLocale();
const recurringBookingsSorted = recurringBookings
? recurringBookings.sort((a: ConfigType, b: ConfigType) => (dayjs(a).isAfter(dayjs(b)) ? 1 : -1))
@@ -843,11 +850,13 @@ export function RecurringBookings({
{eventType.recurringEvent?.count &&
recurringBookingsSorted.slice(0, 4).map((dateStr: string, idx: number) => (
- {dayjs.tz(dateStr, timeZone()).format("dddd, DD MMMM YYYY")}
+ {formatToLocalizedDate(dayjs.tz(dateStr, timeZone()), language, "full")}
- {formatTime(dateStr, is24h ? 24 : 12, timeZone().toLowerCase())} -{" "}
- {formatTime(dayjs(dateStr).add(duration, "m"), is24h ? 24 : 12, timeZone().toLowerCase())}{" "}
- ({timeZone()})
+ {formatToLocalizedTime(dayjs(dateStr), language, undefined, !is24h)} -{" "}
+ {formatToLocalizedTime(dayjs(dateStr).add(duration, "m"), language, undefined, !is24h)}{" "}
+
+ ({formatToLocalizedTimezone(dayjs(dateStr), language)})
+
))}
{recurringBookingsSorted.length > 4 && (
@@ -861,11 +870,13 @@ export function RecurringBookings({
{eventType.recurringEvent?.count &&
recurringBookingsSorted.slice(4).map((dateStr: string, idx: number) => (
- {dayjs.tz(dateStr, timeZone()).format("dddd, DD MMMM YYYY")}
+ {formatToLocalizedDate(dayjs.tz(date, timeZone()), language, "full")}
- {formatTime(dateStr, is24h ? 24 : 12, timeZone().toLowerCase())} -{" "}
- {formatTime(dayjs(dateStr).add(duration, "m"), is24h ? 24 : 12, timeZone().toLowerCase())}{" "}
- ({timeZone()})
+ {formatToLocalizedTime(date, language, undefined, !is24h)} -{" "}
+ {formatToLocalizedTime(dayjs(date).add(duration, "m"), language, undefined, !is24h)}{" "}
+
+ ({formatToLocalizedTimezone(dayjs(dateStr), language)})
+
))}
@@ -877,11 +888,11 @@ export function RecurringBookings({
return (
- {dayjs.tz(date, timeZone()).format("dddd, DD MMMM YYYY")}
+ {formatToLocalizedDate(dayjs.tz(date, timeZone()), language, "full")}
- {formatTime(date, is24h ? 24 : 12, timeZone().toLowerCase())} -{" "}
- {formatTime(dayjs(date).add(duration, "m"), is24h ? 24 : 12, timeZone().toLowerCase())}{" "}
- ({timeZone()})
+ {formatToLocalizedTime(date, language, undefined, !is24h)} -{" "}
+ {formatToLocalizedTime(dayjs(date).add(duration, "m"), language, undefined, !is24h)}{" "}
+ ({formatToLocalizedTimezone(date, language)})
);
}
diff --git a/packages/lib/date-fns/index.ts b/packages/lib/date-fns/index.ts
index f816377583..d1bab2bf7a 100644
--- a/packages/lib/date-fns/index.ts
+++ b/packages/lib/date-fns/index.ts
@@ -29,6 +29,61 @@ export const formatTime = (
: dayjs(date).format(timeFormat === 12 ? "h:mma" : "HH:mm");
};
+/**
+ * Returns a localized and translated date or time, based on the native
+ * Intl.DateTimeFormat available to JS. Undefined values mean the browser's
+ * locale will be used.
+ */
+export const formatLocalizedDateTime = (
+ date: Date | Dayjs,
+ options: Intl.DateTimeFormatOptions = {},
+ locale: string | undefined = undefined
+) => {
+ const theDate = date instanceof dayjs ? (date as Dayjs).toDate() : (date as Date);
+ return Intl.DateTimeFormat(locale, options).format(theDate);
+};
+
+/**
+ * Returns a localized and translated calendar day based on the
+ * given Date object and locale. Undefined values mean the defaults
+ * associated with the browser's current locale will be used.
+ */
+export const formatToLocalizedDate = (
+ date: Date | Dayjs,
+ locale: string | undefined = undefined,
+ dateStyle: Intl.DateTimeFormatOptions["dateStyle"] = "long"
+) => formatLocalizedDateTime(date, { dateStyle }, locale);
+
+/**
+ * Returns a localized and translated time of day based on the
+ * given Date object and locale. Undefined values mean the defaults
+ * associated with the browser's current locale will be used.
+ */
+export const formatToLocalizedTime = (
+ date: Date | Dayjs,
+ locale: string | undefined = undefined,
+ timeStyle: Intl.DateTimeFormatOptions["timeStyle"] = "short",
+ hour12: Intl.DateTimeFormatOptions["hour12"] = undefined
+) => formatLocalizedDateTime(date, { timeStyle, hour12 }, locale);
+
+/**
+ * Returns a translated timezone based on the given Date object and
+ * locale. Undefined values mean the browser's current locale
+ * will be used.
+ */
+export const formatToLocalizedTimezone = (
+ date: Date | Dayjs,
+ locale: string | undefined = undefined,
+ timeZoneName: Intl.DateTimeFormatOptions["timeZoneName"] = "long"
+) => {
+ // Intl.DateTimeFormat doesn't format into a timezone only, so we must
+ // formatToParts() and return the piece we want
+ const theDate = date instanceof dayjs ? (date as Dayjs).toDate() : (date as Date);
+ return Intl.DateTimeFormat(locale, { timeZoneName })
+ .formatToParts(theDate)
+ .find((d) => d.type == "timeZoneName")?.value;
+};
+
/**
* Sorts two timezones by their offset from GMT.
*/