119 lines
3.3 KiB
TypeScript
119 lines
3.3 KiB
TypeScript
import {
|
|
isToday,
|
|
isAfter,
|
|
parse,
|
|
compareAsc,
|
|
addDays,
|
|
isSameDay,
|
|
startOfDay,
|
|
} from "date-fns";
|
|
import { nb } from "date-fns/locale";
|
|
import { toZonedTime, format } from "date-fns-tz";
|
|
import { unique } from "./common";
|
|
|
|
const timeZone = "Europe/Oslo";
|
|
|
|
export const compactDateTimeFormat = "dd.MM.yyyy 'kl.' HH:mm";
|
|
export const compactDateFormat = "dd.MM.yyyy";
|
|
|
|
export function toLocalTime(date: Date | string | number) {
|
|
return toZonedTime(date, timeZone);
|
|
}
|
|
|
|
export function formatDate(date: Date | string | number, formatStr: string) {
|
|
return format(toLocalTime(date), formatStr, { timeZone, locale: nb });
|
|
}
|
|
|
|
export function formatYearMonth(yearMonth: string) {
|
|
// full name of month if year is current year, otherwise name of month + year
|
|
const parsed = parse(yearMonth, "yyyy-MM", new Date());
|
|
if (parsed.getFullYear === new Date().getFullYear) {
|
|
return formatDate(parsed, "MMMM");
|
|
}
|
|
return formatDate(parsed, "MMMM yyyy");
|
|
}
|
|
|
|
export function formatExtendedDateTime(
|
|
date: Date | string | number,
|
|
dateOnly: boolean = false,
|
|
alwaysIncludeYear: boolean = false
|
|
) {
|
|
// wide date with weekday and month name
|
|
// year included if not current year
|
|
const parsed = toLocalTime(date);
|
|
const timePart = dateOnly ? "" : " 'kl.' HH:mm";
|
|
const isCurrentYear = parsed.getFullYear() === new Date().getFullYear();
|
|
const yearPart = (!isCurrentYear || alwaysIncludeYear) ? " yyyy" : "";
|
|
const formatStr = `EEEE d. MMMM${yearPart}${timePart}`;
|
|
const formatted = format(parsed, formatStr, { timeZone, locale: nb });
|
|
return formatted;
|
|
}
|
|
|
|
export function isTodayOrFuture(
|
|
date: Date | string | number,
|
|
timeZone = "UTC"
|
|
) {
|
|
const now = new Date();
|
|
const zonedNow = toZonedTime(now, timeZone);
|
|
const zonedDate = toLocalTime(date);
|
|
return isToday(zonedDate) || isAfter(zonedDate, zonedNow);
|
|
}
|
|
|
|
export function compareDates(a: Date | string, b: Date | string) {
|
|
return compareAsc(new Date(a), new Date(b));
|
|
}
|
|
|
|
export function isConsecutiveDays(a: Date, b: Date): boolean {
|
|
const nextDay = addDays(a, 1);
|
|
return isSameDay(nextDay, b);
|
|
}
|
|
|
|
export function groupConsecutiveDates(dates: string[]): string[][] {
|
|
const groupedDates: string[][] = [];
|
|
const uniqueDays = unique(
|
|
dates.map((date) => format(startOfDay(toLocalTime(date)), "yyyy-MM-dd"))
|
|
).sort();
|
|
let group: string[] = [];
|
|
|
|
for (let i = 0; i < uniqueDays.length; i++) {
|
|
if (
|
|
i === 0 ||
|
|
isSameDay(uniqueDays[i - 1], uniqueDays[i]) ||
|
|
isConsecutiveDays(uniqueDays[i - 1], uniqueDays[i])
|
|
) {
|
|
group.push(uniqueDays[i]);
|
|
} else {
|
|
groupedDates.push(group);
|
|
group = [uniqueDays[i]];
|
|
}
|
|
}
|
|
|
|
if (group.length > 0) {
|
|
groupedDates.push(group);
|
|
}
|
|
|
|
return groupedDates;
|
|
}
|
|
|
|
export function formatDateRange(dates: string[]): string {
|
|
if (dates.length === 1) {
|
|
return formatDate(dates[0], "d. MMMM");
|
|
}
|
|
|
|
const firstDate = toLocalTime(dates[0]);
|
|
const lastDate = toLocalTime(dates[dates.length - 1]);
|
|
|
|
if (firstDate.getMonth() === lastDate.getMonth()) {
|
|
// same month
|
|
return `${firstDate.getDate()}.—${lastDate.getDate()}. ${formatDate(
|
|
firstDate,
|
|
"MMMM"
|
|
)}`;
|
|
}
|
|
|
|
// continues into next month
|
|
const formattedFirstDate = formatDate(firstDate, "d. MMMM");
|
|
const formattedLastDate = formatDate(lastDate, "d. MMMM");
|
|
return `${formattedFirstDate}—${formattedLastDate}`;
|
|
}
|