Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 137 additions & 159 deletions src/common/datetime/format_date.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,96 @@
import type { HassConfig } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { format } from "date-fns";
import { TZDate } from "@date-fns/tz";
import type { FrontendLocaleData } from "../../data/translation";
import { DateFormat } from "../../data/translation";
import { resolveTimeZone } from "./resolve-time-zone";

// Helper to get date in target timezone
const toTimeZone = (date: Date, timeZone: string): Date => {
try {
return new TZDate(date, timeZone);
} catch {
return date;
}
};

// Helper to get format string based on date preference
const formatForDatePreference = (
template: { DMY: string; MDY: string; YMD: string },
locale: FrontendLocaleData
): string => {
if (
locale.date_format === DateFormat.language ||
locale.date_format === DateFormat.system
) {
return template.MDY; // Default to MDY for browser locale
}

const pattern =
template[locale.date_format as unknown as keyof typeof template];
return pattern || template.MDY; // Fallback to MDY if not found
};

// Tuesday, August 10
export const formatDateWeekdayDay = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateWeekdayDayMem(locale, config.time_zone).format(dateObj);

const formatDateWeekdayDayMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
month: "long",
day: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
const pattern = formatForDatePreference(
{ DMY: "EEEE, d MMMM", MDY: "EEEE, MMMM d", YMD: "EEEE, MMMM d" },
locale
);
return format(zonedDate, pattern);
};

// August 10, 2021
export const formatDate = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateMem(locale, config.time_zone).format(dateObj);

const formatDateMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
const pattern = formatForDatePreference(
{
DMY: "d MMMM, yyyy",
MDY: "MMMM d, yyyy",
YMD: "yyyy, MMMM d",
},
locale
);
return format(zonedDate, pattern);
};

// Aug 10, 2021
export const formatDateShort = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateShortMem(locale, config.time_zone).format(dateObj);

const formatDateShortMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "short",
day: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
const pattern = formatForDatePreference(
{
DMY: "d MMM, yyyy",
MDY: "MMM d, yyyy",
YMD: "yyyy, MMM d",
},
locale
);
return format(zonedDate, pattern);
};

// 10/08/2021
export const formatDateNumeric = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => {
const formatter = formatDateNumericMem(locale, config.time_zone);
const formatter = createDateNumericFormatter(locale, config.time_zone);

if (
locale.date_format === DateFormat.language ||
Expand Down Expand Up @@ -93,193 +122,142 @@ export const formatDateNumeric = (
return formats[locale.date_format];
};

const formatDateNumericMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) => {
const localeString =
locale.date_format === DateFormat.system ? undefined : locale.language;

if (
locale.date_format === DateFormat.language ||
locale.date_format === DateFormat.system
) {
return new Intl.DateTimeFormat(localeString, {
year: "numeric",
month: "numeric",
day: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
});
}

return new Intl.DateTimeFormat(localeString, {
year: "numeric",
month: "numeric",
day: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
});
}
);
const createDateNumericFormatter = (
locale: FrontendLocaleData,
serverTimeZone: string
) => {
const localeString =
locale.date_format === DateFormat.system ? undefined : locale.language;
return new Intl.DateTimeFormat(localeString, {
year: "numeric",
month: "numeric",
day: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
});
};

// Aug 10
export const formatDateVeryShort = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateVeryShortMem(locale, config.time_zone).format(dateObj);

const formatDateVeryShortMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
day: "numeric",
month: "short",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
const pattern = formatForDatePreference(
{ DMY: "d MMM", MDY: "MMM d", YMD: "MMM d" },
locale
);
return format(zonedDate, pattern);
};

// August 2021
export const formatDateMonthYear = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateMonthYearMem(locale, config.time_zone).format(dateObj);

const formatDateMonthYearMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
year: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
const pattern = formatForDatePreference(
{
DMY: "MMMM yyyy",
MDY: "MMMM yyyy",
YMD: "yyyy MMMM",
},
locale
);
return format(zonedDate, pattern);
};

// August
export const formatDateMonth = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateMonthMem(locale, config.time_zone).format(dateObj);

const formatDateMonthMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
return format(zonedDate, "MMMM");
};

// Aug
export const formatDateMonthShort = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateMonthShortMem(locale, config.time_zone).format(dateObj);

const formatDateMonthShortMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
month: "short",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
return format(zonedDate, "MMM");
};

// 2021
export const formatDateYear = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateYearMem(locale, config.time_zone).format(dateObj);

const formatDateYearMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
return format(zonedDate, "yyyy");
};

// Monday
export const formatDateWeekday = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateWeekdayMem(locale, config.time_zone).format(dateObj);

const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
return format(zonedDate, "EEEE");
};

// Mon
export const formatDateWeekdayShort = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateWeekdayShortMem(locale, config.time_zone).format(dateObj);

const formatDateWeekdayShortMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "short",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
return format(zonedDate, "EEE");
};

// Mon, Aug 10
export const formatDateWeekdayVeryShortDate = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) =>
formatDateWeekdayVeryShortDateMem(locale, config.time_zone).format(dateObj);

const formatDateWeekdayVeryShortDateMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "short",
month: "short",
day: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
return format(zonedDate, "EEE, MMM d");
};

// Mon, Aug 10, 2021
export const formatDateWeekdayShortDate = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateWeekdayShortDateMem(locale, config.time_zone).format(dateObj);

const formatDateWeekdayShortDateMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "short",
month: "short",
day: "numeric",
year: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const zonedDate = toTimeZone(dateObj, timeZone);
return format(zonedDate, "EEE, MMM d, yyyy");
};

/**
* Format a date as YYYY-MM-DD. Uses "en-CA" because it's the only
* Intl locale that natively outputs ISO 8601 date format.
* Locale/config are only used to resolve the time zone.
* Format a date as YYYY-MM-DD
*/
export const formatISODateOnly = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const formatter = new Intl.DateTimeFormat("en-CA", {
year: "numeric",
month: "2-digit",
day: "2-digit",
timeZone,
});
return formatter.format(dateObj);
const zonedDate = toTimeZone(dateObj, timeZone);
return format(zonedDate, "yyyy-MM-dd");
};

// 2026-08-10/2026-08-15
Expand Down
Loading
Loading