Bugfix/5271 wrong availability displayed on nov 6 (#5365)

* Fixes the localisation issue with recurring events

* Implement DST as Dayjs doesn't

* Fixed generated booking URL when different TZ is set

* manually apply DST offset to times

* Fix type error
This commit is contained in:
Alex van Andel 2022-11-04 10:59:38 -04:00 committed by GitHub
parent 328a354f4d
commit db9911a264
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 37 additions and 33 deletions

View File

@ -83,7 +83,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
pathname: router.pathname.endsWith("/embed") ? "../book" : "book",
query: {
...router.query,
date: dayjs(slot.time).format(),
date: dayjs.utc(slot.time).tz(timeZone()).format(),
type: eventTypeId,
slug: eventTypeSlug,
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */

View File

@ -497,26 +497,17 @@ const BookingPage = ({
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiCalendar className="mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4" />
<div className="text-sm font-medium">
{(rescheduleUid || !eventType.recurringEvent?.freq) &&
`${formatTime(dayjs(date).toDate(), user?.timeFormat, user?.timeZone)}, ${dayjs(
date
).format("dddd, D MMMM YYYY")}`}
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
{!rescheduleUid &&
eventType.recurringEvent?.freq &&
recurringDates.slice(0, 5).map((aDate, key) => {
return (
<p key={key}>{`${formatTime(aDate, user?.timeFormat, user?.timeZone)}, ${dayjs(
aDate
).format("dddd, D MMMM YYYY")}`}</p>
);
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
return <p key={key}>{timeFormatted}</p>;
})}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringDates.slice(5).map((aDate, key) => (
<p key={key}>{`${formatTime(aDate, user?.timeFormat, user?.timeZone)}, ${dayjs(
aDate
).format("dddd, D MMMM YYYY")}`}</p>
content={recurringStrings.slice(5).map((timeFormatted, key) => (
<p key={key}>{timeFormatted}</p>
))}>
<p className="dark:text-darkgray-600 text-sm">
{t("plus_more", { count: recurringStrings.length - 5 })}

View File

@ -20,15 +20,6 @@ export const parseDate = (date: string | null | Dayjs, i18n: I18n) => {
return processDate(date, i18n);
};
// tzid is currently broken in rrule library.
// @see https://github.com/jakubroztocil/rrule/issues/523
const dateWithZone = (d: Date, timeZone?: string) => {
const dateInLocalTZ = new Date(d.toLocaleString("en-US", { timeZone: "UTC" }));
const dateInTargetTZ = new Date(d.toLocaleString("en-US", { timeZone: timeZone || "UTC" }));
const tzOffset = dateInTargetTZ.getTime() - dateInLocalTZ.getTime();
return new Date(d.getTime() - tzOffset);
};
export const parseRecurringDates = (
{
startDate,
@ -48,16 +39,21 @@ export const parseRecurringDates = (
const rule = new RRule({
...restRecurringEvent,
count: recurringCount,
dtstart: dayjs(startDate).utc(true).toDate(),
});
// UTC times with tzOffset applied to account for DST
const times = rule.all().map((t) => dateWithZone(t, timeZone));
const dateStrings = times.map((t) => {
// undo DST diffs for localized display.
return processDate(dayjs.utc(t).tz(timeZone), i18n);
dtstart: new Date(dayjs(startDate).valueOf()),
});
return [dateStrings, times];
const startUtcOffset = dayjs(startDate).utcOffset();
// UTC still need to have DST applied, rrule does not do this.
const times = rule.all().map((t) => {
// applying the DST offset.
return dayjs.utc(t).add(startUtcOffset - dayjs(t).utcOffset(), "minute");
});
const dateStrings = times.map((t) => {
// finally; show in local timeZone again
return processDate(t.tz(timeZone), i18n);
});
return [dateStrings, times.map((t) => t.toDate())];
};
export const extractRecurringDates = (

View File

@ -111,7 +111,24 @@ const getSlots = ({ inviteeDate, frequency, minimumBookingNotice, workingHours,
});
slotsTimeFrameAvailable.forEach((item) => {
const slot = startOfInviteeDay.add(item.startTime, "minute");
// XXX: Hack alert, as dayjs is supposedly not aware of timezone the current slot may have invalid UTC offset.
const timeZone = (startOfInviteeDay as unknown as { $x: { $timezone: string } })["$x"]["$timezone"];
/*
* @calcom/web:dev: 2022-11-06T00:00:00-04:00
* @calcom/web:dev: 2022-11-06T01:00:00-04:00
* @calcom/web:dev: 2022-11-06T01:00:00-04:00 <-- note there is no offset change, but we did lose an hour.
* @calcom/web:dev: 2022-11-06T02:00:00-04:00
* @calcom/web:dev: 2022-11-06T03:00:00-04:00
* ...
*/
let slot = dayjs.tz(
startOfInviteeDay.add(item.startTime, "minute").format("YYYY-MM-DDTHH:mm:ss"),
timeZone
);
// If the startOfInviteeDay has a different UTC offset than the slot, a DST change has occurred.
// As the time has now fallen backwards, or forwards; this difference -
// needs to be manually added as this is not done for us. Usually 0.
slot = slot.add(startOfInviteeDay.utcOffset() - slot.utcOffset(), "minutes");
// Validating slot its not on the past
if (!slot.isBefore(startDate)) {
slots.push(slot);