@sanity/util
Version:
Utilities shared across projects of Sanity
281 lines (280 loc) • 11.6 kB
JavaScript
import { TZDateMini } from "@date-fns/tz";
import { UTCDateMini } from "@date-fns/utc";
import { format as format$1, parse as parse$1, parseISO } from "date-fns";
const sanitizeLocale = (locale) => locale.replace(/@posix$/, "");
function getMonthName(date, style = "long", locale = "en-US") {
const validLocale = sanitizeLocale(locale);
return new Intl.DateTimeFormat(validLocale, { month: style }).format(date);
}
function getDayName(date, style = "long", locale = "en-US") {
const validLocale = sanitizeLocale(locale);
return new Intl.DateTimeFormat(validLocale, { weekday: style }).format(date);
}
function getLocalizedDate(date, options, locale = "en-US") {
const validLocale = sanitizeLocale(locale);
return new Intl.DateTimeFormat(validLocale, options).format(date);
}
function zeroPad(num, length) {
return String(num).padStart(length, "0");
}
function getOrdinal(day) {
const j = day % 10, k = day % 100;
return j === 1 && k !== 11 ? `${day}st` : j === 2 && k !== 12 ? `${day}nd` : j === 3 && k !== 13 ? `${day}rd` : `${day}th`;
}
function getISODayOfWeek(date) {
const dow = date.getDay();
return dow === 0 ? 7 : dow;
}
function getISOWeekYear(date) {
const temp = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())), dayOfWeek = getISODayOfWeek(temp);
return temp.setUTCDate(temp.getUTCDate() - dayOfWeek + 4), temp.getUTCFullYear();
}
function getISOWeekNumber(date) {
const temp = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())), dayOfWeek = getISODayOfWeek(temp);
temp.setUTCDate(temp.getUTCDate() - dayOfWeek + 4);
const yearStart = new Date(Date.UTC(temp.getUTCFullYear(), 0, 1));
return Math.ceil(((temp.valueOf() - yearStart.valueOf()) / 864e5 + 1) / 7);
}
function getDayOfYear(date) {
const startOfYear = new Date(Date.UTC(date.getFullYear(), 0, 1)), diff = date.valueOf() - startOfYear.valueOf() + (startOfYear.getTimezoneOffset() - date.getTimezoneOffset()) * 6e4;
return Math.floor(diff / (1e3 * 60 * 60 * 24)) + 1;
}
function getLocaleWeekYear(date) {
return getISOWeekYear(date);
}
function getFractionalSeconds(date, length) {
const ms = zeroPad(date.getMilliseconds(), 3);
return length === 1 ? ms.slice(0, 1) : length === 2 ? ms.slice(0, 2) : length === 3 ? ms : `${ms}0`;
}
function getTimeZoneAbbreviation(date) {
const tz = new Intl.DateTimeFormat(sanitizeLocale("en-US"), {
timeZoneName: "short"
}).formatToParts(date).find((part) => part.type === "timeZoneName");
return tz ? tz.value : "";
}
function formatMomentLike(date, formatStr) {
const escapeSequences = [], escapeToken = "\uE000", processedFormat = formatStr.replace(/\[([^\]]+)\]/g, (_, contents) => (escapeSequences.push(contents), escapeToken)), year = date.getFullYear(), monthIndex = date.getMonth(), dayOfMonth = date.getDate(), dayOfWeek = date.getDay(), hours = date.getHours(), minutes = date.getMinutes(), seconds = date.getSeconds(), isoWeekNum = getISOWeekNumber(date), isoWeekYear = getISOWeekYear(date), localeWeekYear = getLocaleWeekYear(date), unixMs = date.getTime(), unixSec = Math.floor(unixMs / 1e3), tokens = [
// Year
// 1970 1971 ... 2029 2030
{ key: "YYYY", getValue: () => String(year) },
// 70 71 ... 29 30
{ key: "YY", getValue: () => String(year).slice(-2) },
// 1970 1971 ... 9999 +10000 +10001
{ key: "Y", getValue: () => String(year) },
// Expanded years, -001970 -001971 ... +001907 +001971
{ key: "YYYYY", getValue: () => zeroPad(year, 5) },
// ISO week-year
// 1970 1971 ... 2029 2030
{ key: "GGGG", getValue: () => String(isoWeekYear) },
// 70 71 ... 29 30
{ key: "GG", getValue: () => String(isoWeekYear).slice(-2) },
// "locale" week-year
{ key: "gggg", getValue: () => String(localeWeekYear) },
{ key: "gg", getValue: () => String(localeWeekYear).slice(-2) },
// Quarter
{ key: "Q", getValue: () => String(Math.floor(monthIndex / 3) + 1) },
{ key: "Qo", getValue: () => getOrdinal(Math.floor(monthIndex / 3) + 1) },
// --- Month (using Intl) ---
{ key: "MMMM", getValue: () => getMonthName(date, "long") },
// e.g. "January"
{ key: "MMM", getValue: () => getMonthName(date, "short") },
// e.g. "Jan"
// For numeric months, we still do a manual approach:
{ key: "MM", getValue: () => zeroPad(monthIndex + 1, 2) },
{ key: "M", getValue: () => String(monthIndex + 1) },
{ key: "Mo", getValue: () => getOrdinal(monthIndex + 1) },
// Day of Month
{ key: "DD", getValue: () => zeroPad(dayOfMonth, 2) },
{ key: "D", getValue: () => String(dayOfMonth) },
{ key: "Do", getValue: () => getOrdinal(dayOfMonth) },
// --- Day of Week (using Intl) ---
{ key: "dddd", getValue: () => getDayName(date, "long") },
// e.g. "Monday"
{ key: "ddd", getValue: () => getDayName(date, "short") },
// e.g. "Mon"
{
key: "dd",
// e.g. "Mo" => first 2 chars of short day name
getValue: () => getDayName(date, "short").slice(0, 2)
},
{ key: "d", getValue: () => String(dayOfWeek) },
{ key: "do", getValue: () => getOrdinal(dayOfWeek + 1) },
// Day of the year
{ key: "DDDD", getValue: () => zeroPad(getDayOfYear(date), 3) },
{ key: "DDD", getValue: () => String(getDayOfYear(date)) },
{ key: "DDDo", getValue: () => getOrdinal(getDayOfYear(date)) },
// ISO day of week
{ key: "E", getValue: () => String(getISODayOfWeek(date)) },
// Week of the year
// w 1 2 ... 52 53
{ key: "w", getValue: () => zeroPad(isoWeekNum, 2) },
// week 1st 2nd ... 52nd 53rd
{ key: "wo", getValue: () => getOrdinal(isoWeekNum) },
// 01 02 ... 52 53
{ key: "ww", getValue: () => zeroPad(isoWeekNum, 2) },
// ISO Week
{ key: "WW", getValue: () => zeroPad(isoWeekNum, 2) },
{ key: "W", getValue: () => String(isoWeekNum) },
{ key: "Wo", getValue: () => getOrdinal(isoWeekNum) },
// or "locale" week => replace isoWeekNum
// 24h hours
{ key: "HH", getValue: () => zeroPad(hours, 2) },
{ key: "H", getValue: () => String(hours) },
// 12h hours
{ key: "hh", getValue: () => zeroPad((hours + 11) % 12 + 1, 2) },
{ key: "h", getValue: () => String((hours + 11) % 12 + 1) },
// 1 2 ... 23 24
{ key: "k", getValue: () => String(hours || 24) },
// 01 02 ... 23 24
{ key: "kk", getValue: () => zeroPad(hours || 24, 2) },
// Minutes
{ key: "mm", getValue: () => zeroPad(minutes, 2) },
{ key: "m", getValue: () => String(minutes) },
// Seconds
{ key: "ss", getValue: () => zeroPad(seconds, 2) },
{ key: "s", getValue: () => String(seconds) },
// Fractional seconds (S..SSSS) => handled separately
// Timezone offset (Z, ZZ) => handled separately
// AM/PM
{ key: "A", getValue: () => hours < 12 ? "AM" : "PM" },
{ key: "a", getValue: () => hours < 12 ? "am" : "pm" },
// Unix timestamps
{ key: "X", getValue: () => String(unixSec) },
{ key: "x", getValue: () => String(unixMs) },
// Eras BC AD
{ key: "N", getValue: () => year < 0 ? "BC" : "AD" },
{ key: "NN", getValue: () => year < 0 ? "BC" : "AD" },
{ key: "NNN", getValue: () => year < 0 ? "BC" : "AD" },
// Before Christ, Anno Domini
{ key: "NNNN", getValue: () => year < 0 ? "Before Christ" : "Anno Domini" },
{ key: "NNNNN", getValue: () => year < 0 ? "BC" : "AD" },
// Time zone offset
{ key: "z", getValue: () => getTimeZoneAbbreviation(date) },
{ key: "zz", getValue: () => getTimeZoneAbbreviation(date) },
{ key: "Z", getValue: () => format$1(date, "xxx") },
{ key: "ZZ", getValue: () => format$1(date, "xx") },
// Time
{ key: "LTS", getValue: () => getLocalizedDate(date, { timeStyle: "medium" }) },
{ key: "LT", getValue: () => getLocalizedDate(date, { timeStyle: "short" }) },
// Date (uppercase = longer names)
{
key: "LLLL",
getValue: () => getLocalizedDate(date, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric"
})
},
{
key: "LLL",
getValue: () => getLocalizedDate(date, {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric"
})
},
{
key: "LL",
getValue: () => getLocalizedDate(date, { year: "numeric", month: "long", day: "numeric" })
},
{
key: "L",
getValue: () => getLocalizedDate(date, { year: "numeric", month: "2-digit", day: "2-digit" })
},
// Date (lowercase = shorter names)
{
key: "llll",
getValue: () => getLocalizedDate(date, {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric"
})
},
{
key: "lll",
getValue: () => getLocalizedDate(date, {
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric"
})
},
{
key: "ll",
getValue: () => getLocalizedDate(date, { year: "numeric", month: "short", day: "numeric" })
},
{
key: "l",
getValue: () => getLocalizedDate(date, { year: "numeric", month: "numeric", day: "numeric" })
}
];
tokens.sort((a, b) => b.key.length - a.key.length);
const fracSecRegex = new RegExp("(?<!LT)S{1,4}", "g");
let output = processedFormat.replace(fracSecRegex, (match) => getFractionalSeconds(date, match.length));
for (const { key, getValue } of tokens) {
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), tokenRegex = new RegExp(`(^|[^A-Z0-9a-z])(${escapedKey})(?![A-Z0-9a-z])`, "g");
if (output.match(tokenRegex)) {
const value = getValue();
output = output.replace(tokenRegex, `$1${value}`);
}
}
return output = output.replace(new RegExp(escapeToken, "g"), () => escapeSequences.shift() || ""), output;
}
function momentToDateFnsFormat(momentFormat) {
const formatMap = {
YYYY: "yyyy",
YY: "yy",
MMMM: "MMMM",
MMM: "MMM",
MM: "MM",
M: "M",
DD: "dd",
D: "d",
dddd: "EEEE",
ddd: "EEE",
HH: "HH",
H: "H",
hh: "hh",
h: "h",
mm: "mm",
m: "m",
ss: "ss",
s: "s",
A: "a",
a: "a"
};
return Object.keys(formatMap).reduce(
(acc, key) => acc.replace(new RegExp(key, "g"), formatMap[key]),
momentFormat
);
}
const DEFAULT_DATE_FORMAT = "YYYY-MM-DD", DEFAULT_TIME_FORMAT = "HH:mm", DEFAULT_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
function format(input, dateFormat, options = { useUTC: !1, timeZone: void 0 }) {
const { useUTC, timeZone } = options;
return formatMomentLike(useUTC ? new UTCDateMini(input) : timeZone ? new TZDateMini(input, timeZone || DEFAULT_TIMEZONE) : new Date(input), dateFormat);
}
function parse(dateString, dateFormat, timeZone) {
const dnsFormat = dateFormat ? momentToDateFnsFormat(dateFormat) : void 0, parsed = dnsFormat ? parse$1(dateString, dnsFormat, /* @__PURE__ */ new Date()) : parseISO(dateString);
return parsed && !isNaN(parsed.getTime()) ? { isValid: !0, date: timeZone && isValidTimeZoneString(timeZone) ? new TZDateMini(parsed, timeZone) : parsed } : { isValid: !1, error: `Invalid date. Must be on the format "${dateFormat}"` };
}
function isValidTimeZoneString(timeZone) {
return Intl.supportedValuesOf("timeZone").includes(timeZone);
}
export {
DEFAULT_DATE_FORMAT,
DEFAULT_TIME_FORMAT,
format,
isValidTimeZoneString,
parse,
sanitizeLocale
};
//# sourceMappingURL=legacyDateFormat.mjs.map