mirror of
https://github.com/lukevella/rallly.git
synced 2025-04-30 02:36:30 +02:00
212 lines
5.9 KiB
TypeScript
212 lines
5.9 KiB
TypeScript
import { Option } from "@prisma/client";
|
|
import {
|
|
differenceInHours,
|
|
differenceInMinutes,
|
|
format,
|
|
formatDuration,
|
|
isSameDay,
|
|
} from "date-fns";
|
|
import { formatInTimeZone } from "date-fns-tz";
|
|
import spacetime from "spacetime";
|
|
|
|
import {
|
|
DateTimeOption,
|
|
TimeOption,
|
|
} from "../components/forms/poll-options-form";
|
|
|
|
export const getBrowserTimeZone = () =>
|
|
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
|
|
export const encodeDateOption = (option: DateTimeOption) => {
|
|
return option.type === "timeSlot"
|
|
? `${option.start}/${option.end}`
|
|
: option.date;
|
|
};
|
|
|
|
export interface ParsedDateOption {
|
|
type: "date";
|
|
optionId: string;
|
|
day: string;
|
|
dow: string;
|
|
month: string;
|
|
}
|
|
|
|
export interface ParsedTimeSlotOption {
|
|
type: "timeSlot";
|
|
optionId: string;
|
|
day: string;
|
|
dow: string;
|
|
month: string;
|
|
startTime: string;
|
|
endTime: string;
|
|
duration: string;
|
|
}
|
|
|
|
export type ParsedDateTimeOpton = ParsedDateOption | ParsedTimeSlotOption;
|
|
|
|
const isTimeSlot = (value: string) => value.indexOf("/") !== -1;
|
|
|
|
const getDuration = (startTime: Date, endTime: Date) => {
|
|
const hours = Math.floor(differenceInHours(endTime, startTime));
|
|
const minutes = Math.floor(
|
|
differenceInMinutes(endTime, startTime) - hours * 60,
|
|
);
|
|
return formatDuration({ hours, minutes });
|
|
};
|
|
|
|
export const decodeOptions = (
|
|
options: Option[],
|
|
timeZone: string | null,
|
|
targetTimeZone: string,
|
|
):
|
|
| { pollType: "date"; options: ParsedDateOption[] }
|
|
| { pollType: "timeSlot"; options: ParsedTimeSlotOption[] } => {
|
|
const pollType = isTimeSlot(options[0].value) ? "timeSlot" : "date";
|
|
|
|
if (pollType === "timeSlot") {
|
|
return {
|
|
pollType,
|
|
options: options.map((option) =>
|
|
parseTimeSlotOption(option, timeZone, targetTimeZone),
|
|
),
|
|
};
|
|
} else {
|
|
return {
|
|
pollType,
|
|
options: options.map((option) => parseDateOption(option)),
|
|
};
|
|
}
|
|
};
|
|
|
|
const parseDateOption = (option: Option): ParsedDateOption => {
|
|
const dateString =
|
|
option.value.indexOf("T") === -1
|
|
? // we add the time because otherwise Date will assume UTC time which might change the day for some time zones
|
|
option.value + "T00:00:00"
|
|
: option.value;
|
|
const date = new Date(dateString);
|
|
return {
|
|
type: "date",
|
|
optionId: option.id,
|
|
day: format(date, "d"),
|
|
dow: format(date, "E"),
|
|
month: format(date, "MMM"),
|
|
};
|
|
};
|
|
|
|
const parseTimeSlotOption = (
|
|
option: Option,
|
|
timeZone: string | null,
|
|
targetTimeZone: string,
|
|
): ParsedTimeSlotOption => {
|
|
const [start, end] = option.value.split("/");
|
|
if (timeZone && targetTimeZone) {
|
|
const startDate = spacetime(start, timeZone).toNativeDate();
|
|
const endDate = spacetime(end, timeZone).toNativeDate();
|
|
return {
|
|
type: "timeSlot",
|
|
optionId: option.id,
|
|
startTime: formatInTimeZone(startDate, targetTimeZone, "hh:mm a"),
|
|
endTime: formatInTimeZone(endDate, targetTimeZone, "hh:mm a"),
|
|
day: formatInTimeZone(startDate, targetTimeZone, "d"),
|
|
dow: formatInTimeZone(startDate, targetTimeZone, "E"),
|
|
month: formatInTimeZone(startDate, targetTimeZone, "MMM"),
|
|
duration: getDuration(startDate, endDate),
|
|
};
|
|
} else {
|
|
const startDate = new Date(start);
|
|
const endDate = new Date(end);
|
|
return {
|
|
type: "timeSlot",
|
|
optionId: option.id,
|
|
startTime: format(startDate, "hh:mm a"),
|
|
endTime: format(endDate, "hh:mm a"),
|
|
day: format(startDate, "d"),
|
|
dow: format(startDate, "E"),
|
|
month: format(startDate, "MMM"),
|
|
duration: getDuration(startDate, endDate),
|
|
};
|
|
}
|
|
};
|
|
|
|
export const decodeDateOption = (
|
|
option: Option,
|
|
timeZone: string | null,
|
|
targetTimeZone: string,
|
|
): ParsedDateTimeOpton => {
|
|
const isTimeRange = option.value.indexOf("/") !== -1;
|
|
// option can either be an ISO date (ex. 2000-01-01)
|
|
// or a time range (ex. 2000-01-01T08:00:00/2000-01-01T09:00:00)
|
|
if (isTimeRange) {
|
|
const [start, end] = option.value.split("/");
|
|
|
|
if (timeZone && targetTimeZone) {
|
|
const startDate = spacetime(start, timeZone).toNativeDate();
|
|
const endDate = spacetime(end, timeZone).toNativeDate();
|
|
return {
|
|
type: "timeSlot",
|
|
optionId: option.id,
|
|
startTime: formatInTimeZone(startDate, targetTimeZone, "hh:mm a"),
|
|
endTime: formatInTimeZone(endDate, targetTimeZone, "hh:mm a"),
|
|
day: formatInTimeZone(startDate, targetTimeZone, "d"),
|
|
dow: formatInTimeZone(startDate, targetTimeZone, "E"),
|
|
month: formatInTimeZone(startDate, targetTimeZone, "MMM"),
|
|
duration: getDuration(startDate, endDate),
|
|
};
|
|
} else {
|
|
const startDate = new Date(start);
|
|
const endDate = new Date(end);
|
|
return {
|
|
type: "timeSlot",
|
|
optionId: option.id,
|
|
startTime: format(startDate, "hh:mm a"),
|
|
endTime: format(endDate, "hh:mm a"),
|
|
day: format(startDate, "d"),
|
|
dow: format(startDate, "E"),
|
|
month: format(startDate, "MMM"),
|
|
duration: getDuration(startDate, endDate),
|
|
};
|
|
}
|
|
}
|
|
|
|
// we add the time because otherwise Date will assume UTC time which might change the day for some time zones
|
|
const dateString =
|
|
option.value.indexOf("T") === -1
|
|
? option.value + "T00:00:00"
|
|
: option.value;
|
|
const date = new Date(dateString);
|
|
return {
|
|
type: "date",
|
|
optionId: option.id,
|
|
day: format(date, "d"),
|
|
dow: format(date, "E"),
|
|
month: format(date, "MMM"),
|
|
};
|
|
};
|
|
|
|
export const removeAllOptionsForDay = (
|
|
options: DateTimeOption[],
|
|
date: Date,
|
|
) => {
|
|
return options.filter((option) => {
|
|
const optionDate = spacetime(
|
|
option.type === "date" ? option.date : option.start,
|
|
).toNativeDate();
|
|
return !isSameDay(date, optionDate);
|
|
});
|
|
};
|
|
|
|
export const getDateProps = (date: Date) => {
|
|
return {
|
|
day: format(date, "d"),
|
|
dow: format(date, "E"),
|
|
month: format(date, "MMM"),
|
|
};
|
|
};
|
|
|
|
export const expectTimeOption = (d: DateTimeOption): TimeOption => {
|
|
if (d.type === "date") {
|
|
throw new Error("Expected timeSlot but got date instead");
|
|
}
|
|
return d;
|
|
};
|