@zag-js/date-utils
Version:
Date utilities for zag.js
561 lines (552 loc) • 20.1 kB
JavaScript
import { startOfYear, startOfMonth, startOfWeek, maxDate, toCalendarDate, minDate, isSameDay, toCalendarDateTime, today, DateFormatter, endOfMonth, endOfWeek, getWeeksInMonth, getLocalTimeZone, toCalendar, CalendarDate, now, endOfYear } from '@internationalized/date';
// src/constrain.ts
function alignCenter(date, duration, locale, min, max) {
const halfDuration = {};
for (let prop in duration) {
const key = prop;
const value = duration[key];
if (value == null) continue;
halfDuration[key] = Math.floor(value / 2);
if (halfDuration[key] > 0 && value % 2 === 0) {
halfDuration[key]--;
}
}
const aligned = alignStart(date, duration, locale).subtract(halfDuration);
return constrainStart(date, aligned, duration, locale, min, max);
}
function alignStart(date, duration, locale, min, max) {
let aligned = date;
if (duration.years) {
aligned = startOfYear(date);
} else if (duration.months) {
aligned = startOfMonth(date);
} else if (duration.weeks) {
aligned = startOfWeek(date, locale);
}
return constrainStart(date, aligned, duration, locale, min, max);
}
function alignEnd(date, duration, locale, min, max) {
let d = { ...duration };
if (d.days) {
d.days--;
} else if (d.weeks) {
d.weeks--;
} else if (d.months) {
d.months--;
} else if (d.years) {
d.years--;
}
let aligned = alignStart(date, duration, locale).subtract(d);
return constrainStart(date, aligned, duration, locale, min, max);
}
function constrainStart(date, aligned, duration, locale, min, max) {
if (min && date.compare(min) >= 0) {
aligned = maxDate(aligned, alignStart(toCalendarDate(min), duration, locale));
}
if (max && date.compare(max) <= 0) {
aligned = minDate(aligned, alignEnd(toCalendarDate(max), duration, locale));
}
return aligned;
}
function constrainValue(date, minValue, maxValue) {
let constrainedDate = toCalendarDate(date);
if (minValue) {
constrainedDate = maxDate(constrainedDate, toCalendarDate(minValue));
}
if (maxValue) {
constrainedDate = minDate(constrainedDate, toCalendarDate(maxValue));
}
return constrainedDate;
}
// src/align.ts
function alignDate(date, alignment, duration, locale, min, max) {
switch (alignment) {
case "start":
return alignStart(date, duration, locale, min, max);
case "end":
return alignEnd(date, duration, locale, min, max);
case "center":
default:
return alignCenter(date, duration, locale, min, max);
}
}
function alignStartDate(date, startDate, endDate, duration, locale, min, max) {
if (date.compare(startDate) < 0) {
return alignEnd(date, duration, locale, min, max);
}
if (date.compare(endDate) > 0) {
return alignStart(date, duration, locale, min, max);
}
return startDate;
}
function isDateEqual(dateA, dateB) {
return dateB != null && isSameDay(dateA, dateB);
}
function isDateUnavailable(date, isUnavailable, locale, minValue, maxValue) {
if (!date) return false;
if (isUnavailable?.(date, locale)) return true;
return isDateOutsideRange(date, minValue, maxValue);
}
function isDateOutsideRange(date, startDate, endDate) {
return startDate != null && date.compare(startDate) < 0 || endDate != null && date.compare(endDate) > 0;
}
function isPreviousRangeInvalid(startDate, minValue, maxValue) {
const prevDate = startDate.subtract({ days: 1 });
return isSameDay(prevDate, startDate) || isDateOutsideRange(prevDate, minValue, maxValue);
}
function isNextRangeInvalid(endDate, minValue, maxValue) {
const nextDate = endDate.add({ days: 1 });
return isSameDay(nextDate, endDate) || isDateOutsideRange(nextDate, minValue, maxValue);
}
// src/duration.ts
function getUnitDuration(duration) {
let clone = { ...duration };
for (let key in clone) clone[key] = 1;
return clone;
}
function getEndDate(startDate, duration) {
let clone = { ...duration };
if (clone.days) clone.days--;
else clone.days = -1;
return startDate.add(clone);
}
// src/get-era-format.ts
function getEraFormat(date) {
return date?.calendar.identifier === "gregory" && date.era === "BC" ? "short" : void 0;
}
// src/formatter.ts
function getDayFormatter(locale, timeZone) {
const date = toCalendarDateTime(today(timeZone));
return new DateFormatter(locale, {
weekday: "long",
month: "long",
year: "numeric",
day: "numeric",
era: getEraFormat(date),
timeZone
});
}
function getMonthFormatter(locale, timeZone) {
const date = today(timeZone);
return new DateFormatter(locale, {
month: "long",
year: "numeric",
era: getEraFormat(date),
calendar: date?.calendar.identifier,
timeZone
});
}
// src/format.ts
function formatRange(startDate, endDate, formatter, toString, timeZone) {
let parts = formatter.formatRangeToParts(startDate.toDate(timeZone), endDate.toDate(timeZone));
let separatorIndex = -1;
for (let i = 0; i < parts.length; i++) {
let part = parts[i];
if (part.source === "shared" && part.type === "literal") {
separatorIndex = i;
} else if (part.source === "endRange") {
break;
}
}
let start = "";
let end = "";
for (let i = 0; i < parts.length; i++) {
if (i < separatorIndex) {
start += parts[i].value;
} else if (i > separatorIndex) {
end += parts[i].value;
}
}
return toString(start, end);
}
function formatSelectedDate(startDate, endDate, locale, timeZone) {
let start = startDate;
let end = endDate ?? startDate;
let formatter = getDayFormatter(locale, timeZone);
if (isSameDay(start, end)) {
return formatter.format(start.toDate(timeZone));
}
return formatRange(start, end, formatter, (start2, end2) => `${start2} \u2013 ${end2}`, timeZone);
}
function formatVisibleRange(startDate, endDate, locale, timeZone) {
const start = startDate;
const end = endDate ?? startDate;
const dayFormatter = getDayFormatter(locale, timeZone);
if (!isSameDay(start, startOfMonth(start))) {
return dayFormatter.formatRange(start.toDate(timeZone), end.toDate(timeZone));
}
const monthFormatter = getMonthFormatter(locale, timeZone);
if (isSameDay(end, endOfMonth(start))) {
return monthFormatter.format(start.toDate(timeZone));
}
if (isSameDay(end, endOfMonth(end))) {
return monthFormatter.formatRange(start.toDate(timeZone), end.toDate(timeZone));
}
return "";
}
var daysOfTheWeek = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
function normalizeFirstDayOfWeek(firstDayOfWeek) {
return firstDayOfWeek != null ? daysOfTheWeek[firstDayOfWeek] : void 0;
}
function getStartOfWeek(date, locale, firstDayOfWeek) {
const firstDay = normalizeFirstDayOfWeek(firstDayOfWeek);
return startOfWeek(date, locale, firstDay);
}
function getEndOfWeek(date, locale, firstDayOfWeek = 0) {
const firstDay = normalizeFirstDayOfWeek(firstDayOfWeek);
return endOfWeek(date, locale, firstDay);
}
function getDaysInWeek(weekIndex, from, locale, firstDayOfWeek) {
const weekDate = from.add({ weeks: weekIndex });
const dates = [];
let date = getStartOfWeek(weekDate, locale, firstDayOfWeek);
while (dates.length < 7) {
dates.push(date);
let nextDate = date.add({ days: 1 });
if (isSameDay(date, nextDate)) break;
date = nextDate;
}
return dates;
}
function getMonthDays(from, locale, numOfWeeks, firstDayOfWeek) {
const firstDay = normalizeFirstDayOfWeek(firstDayOfWeek);
const monthWeeks = numOfWeeks ?? getWeeksInMonth(from, locale, firstDay);
const weeks = [...new Array(monthWeeks).keys()];
return weeks.map((week) => getDaysInWeek(week, from, locale, firstDayOfWeek));
}
function getWeekdayFormats(locale, timeZone) {
const longFormat = new DateFormatter(locale, { weekday: "long", timeZone });
const shortFormat = new DateFormatter(locale, { weekday: "short", timeZone });
const narrowFormat = new DateFormatter(locale, { weekday: "narrow", timeZone });
return (value) => {
const date = value instanceof Date ? value : value.toDate(timeZone);
return {
value,
short: shortFormat.format(date),
long: longFormat.format(date),
narrow: narrowFormat.format(date)
};
};
}
function getWeekDays(date, startOfWeekProp, timeZone, locale) {
const firstDayOfWeek = getStartOfWeek(date, locale, startOfWeekProp);
const weeks = [...new Array(7).keys()];
const format = getWeekdayFormats(locale, timeZone);
return weeks.map((index) => format(firstDayOfWeek.add({ days: index })));
}
function getMonthNames(locale, format = "long") {
const date = new Date(2021, 0, 1);
const monthNames = [];
for (let i = 0; i < 12; i++) {
monthNames.push(date.toLocaleString(locale, { month: format }));
date.setMonth(date.getMonth() + 1);
}
return monthNames;
}
// src/date-year.ts
function getYearsRange(range) {
const years = [];
for (let year = range.from; year <= range.to; year += 1) years.push(year);
return years;
}
var FUTURE_YEAR_COERCION = 10;
function normalizeYear(year) {
if (!year) return;
if (year.length === 3) return year.padEnd(4, "0");
if (year.length === 2) {
const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
const currentCentury = Math.floor(currentYear / 100) * 100;
const twoDigitYear = parseInt(year.slice(-2), 10);
const fullYear = currentCentury + twoDigitYear;
return fullYear > currentYear + FUTURE_YEAR_COERCION ? (fullYear - 100).toString() : fullYear.toString();
}
return year;
}
function getDecadeRange(year) {
const computedYear = year - year % 10 - 1;
const years = [];
for (let i = 0; i < 12; i += 1) {
const value = computedYear + i;
years.push(value);
}
return years;
}
function getTodayDate(timeZone) {
return today(timeZone ?? getLocalTimeZone());
}
function setCalendar(date, calendar) {
return toCalendar(toCalendarDateTime(date), calendar);
}
function setDate(date, startDate, isDateUnavailable2, locale, minValue, maxValue) {
let result;
result = constrainValue(date, minValue, maxValue);
result = getPreviousAvailableDate(date, startDate, locale, isDateUnavailable2);
return result;
}
function getPreviousAvailableDate(date, minValue, locale, isDateUnavailable2) {
if (!isDateUnavailable2) {
return date;
}
while (date.compare(minValue) >= 0 && isDateUnavailable2(date, locale)) {
date = date.subtract({ days: 1 });
}
if (date.compare(minValue) >= 0) {
return date;
}
}
function getAdjustedDateFn(visibleDuration, locale, minValue, maxValue) {
return function getDate(options) {
const { startDate, focusedDate } = options;
const endDate = getEndDate(startDate, visibleDuration);
if (isDateOutsideRange(focusedDate, minValue, maxValue)) {
return {
startDate,
focusedDate: constrainValue(focusedDate, minValue, maxValue),
endDate
};
}
if (focusedDate.compare(startDate) < 0) {
return {
startDate: alignEnd(focusedDate, visibleDuration, locale, minValue, maxValue),
focusedDate: constrainValue(focusedDate, minValue, maxValue),
endDate
};
}
if (focusedDate.compare(endDate) > 0) {
return {
startDate: alignStart(focusedDate, visibleDuration, locale, minValue, maxValue),
endDate,
focusedDate: constrainValue(focusedDate, minValue, maxValue)
};
}
return {
startDate,
endDate,
focusedDate: constrainValue(focusedDate, minValue, maxValue)
};
};
}
function getNextPage(focusedDate, startDate, visibleDuration, locale, minValue, maxValue) {
const adjust = getAdjustedDateFn(visibleDuration, locale, minValue, maxValue);
const start = startDate.add(visibleDuration);
return adjust({
focusedDate: focusedDate.add(visibleDuration),
startDate: alignStart(
constrainStart(focusedDate, start, visibleDuration, locale, minValue, maxValue),
visibleDuration,
locale
)
});
}
function getPreviousPage(focusedDate, startDate, visibleDuration, locale, minValue, maxValue) {
const adjust = getAdjustedDateFn(visibleDuration, locale, minValue, maxValue);
let start = startDate.subtract(visibleDuration);
return adjust({
focusedDate: focusedDate.subtract(visibleDuration),
startDate: alignStart(
constrainStart(focusedDate, start, visibleDuration, locale, minValue, maxValue),
visibleDuration,
locale
)
});
}
function getNextRow(focusedDate, startDate, visibleDuration, locale, minValue, maxValue) {
const adjust = getAdjustedDateFn(visibleDuration, locale, minValue, maxValue);
if (visibleDuration.days) {
return getNextPage(focusedDate, startDate, visibleDuration, locale, minValue, maxValue);
}
if (visibleDuration.weeks || visibleDuration.months || visibleDuration.years) {
return adjust({
focusedDate: focusedDate.add({ weeks: 1 }),
startDate
});
}
}
function getPreviousRow(focusedDate, startDate, visibleDuration, locale, minValue, maxValue) {
const adjust = getAdjustedDateFn(visibleDuration, locale, minValue, maxValue);
if (visibleDuration.days) {
return getPreviousPage(focusedDate, startDate, visibleDuration, locale, minValue, maxValue);
}
if (visibleDuration.weeks || visibleDuration.months || visibleDuration.years) {
return adjust({
focusedDate: focusedDate.subtract({ weeks: 1 }),
startDate
});
}
}
function getSectionStart(focusedDate, startDate, visibleDuration, locale, minValue, maxValue) {
const adjust = getAdjustedDateFn(visibleDuration, locale, minValue, maxValue);
if (visibleDuration.days) {
return adjust({
focusedDate: startDate,
startDate
});
}
if (visibleDuration.weeks) {
return adjust({
focusedDate: startOfWeek(focusedDate, locale),
startDate
});
}
if (visibleDuration.months || visibleDuration.years) {
return adjust({
focusedDate: startOfMonth(focusedDate),
startDate
});
}
}
function getSectionEnd(focusedDate, startDate, visibleDuration, locale, minValue, maxValue) {
const adjust = getAdjustedDateFn(visibleDuration, locale, minValue, maxValue);
const endDate = getEndDate(startDate, visibleDuration);
if (visibleDuration.days) {
return adjust({
focusedDate: endDate,
startDate
});
}
if (visibleDuration.weeks) {
return adjust({
focusedDate: endOfWeek(focusedDate, locale),
startDate
});
}
if (visibleDuration.months || visibleDuration.years) {
return adjust({
focusedDate: endOfMonth(focusedDate),
startDate
});
}
}
function getNextSection(focusedDate, startDate, larger, visibleDuration, locale, minValue, maxValue) {
const adjust = getAdjustedDateFn(visibleDuration, locale, minValue, maxValue);
if (!larger && !visibleDuration.days) {
return adjust({
focusedDate: focusedDate.add(getUnitDuration(visibleDuration)),
startDate
});
}
if (visibleDuration.days) {
return getNextPage(focusedDate, startDate, visibleDuration, locale, minValue, maxValue);
}
if (visibleDuration.weeks) {
return adjust({
focusedDate: focusedDate.add({ months: 1 }),
startDate
});
}
if (visibleDuration.months || visibleDuration.years) {
return adjust({
focusedDate: focusedDate.add({ years: 1 }),
startDate
});
}
}
function getPreviousSection(focusedDate, startDate, larger, visibleDuration, locale, minValue, maxValue) {
const adjust = getAdjustedDateFn(visibleDuration, locale, minValue, maxValue);
if (!larger && !visibleDuration.days) {
return adjust({
focusedDate: focusedDate.subtract(getUnitDuration(visibleDuration)),
startDate
});
}
if (visibleDuration.days) {
return getPreviousPage(focusedDate, startDate, visibleDuration, locale, minValue, maxValue);
}
if (visibleDuration.weeks) {
return adjust({
focusedDate: focusedDate.subtract({ months: 1 }),
startDate
});
}
if (visibleDuration.months || visibleDuration.years) {
return adjust({
focusedDate: focusedDate.subtract({ years: 1 }),
startDate
});
}
}
var isValidYear = (year) => year != null && year.length === 4;
var isValidMonth = (month) => month != null && parseFloat(month) <= 12;
var isValidDay = (day) => day != null && parseFloat(day) <= 31;
function parseDateString(date, locale, timeZone) {
const regex = createRegex(locale, timeZone);
let { year, month, day } = extract(regex, date) ?? {};
const hasMatch = year != null || month != null || day != null;
if (hasMatch) {
const curr = /* @__PURE__ */ new Date();
year || (year = curr.getFullYear().toString());
month || (month = (curr.getMonth() + 1).toString());
day || (day = curr.getDate().toString());
}
if (!isValidYear(year)) {
year = normalizeYear(year);
}
if (isValidYear(year) && isValidMonth(month) && isValidDay(day)) {
return new CalendarDate(+year, +month, +day);
}
const time = Date.parse(date);
if (!isNaN(time)) {
const date2 = new Date(time);
return new CalendarDate(date2.getFullYear(), date2.getMonth() + 1, date2.getDate());
}
}
function createRegex(locale, timeZone) {
const formatter = new DateFormatter(locale, { day: "numeric", month: "numeric", year: "numeric", timeZone });
const parts = formatter.formatToParts(new Date(2e3, 11, 25));
return parts.map(({ type, value }) => type === "literal" ? `${value}?` : `((?!=<${type}>)\\d+)?`).join("");
}
function extract(pattern, str) {
const matches = str.match(pattern);
return pattern.toString().match(/<(.+?)>/g)?.map((group) => {
const groupMatches = group.match(/<(.+)>/);
if (!groupMatches || groupMatches.length <= 0) {
return null;
}
return group.match(/<(.+)>/)?.[1];
}).reduce((acc, curr, index) => {
if (!curr) return acc;
if (matches && matches.length > index) {
acc[curr] = matches[index + 1];
} else {
acc[curr] = null;
}
return acc;
}, {});
}
function getDateRangePreset(preset, locale, timeZone) {
const today3 = toCalendarDate(now(timeZone));
switch (preset) {
case "thisWeek":
return [startOfWeek(today3, locale), endOfWeek(today3, locale)];
case "thisMonth":
return [startOfMonth(today3), today3];
case "thisQuarter":
return [startOfMonth(today3).add({ months: -today3.month % 3 }), today3];
case "thisYear":
return [startOfYear(today3), today3];
case "last3Days":
return [today3.add({ days: -2 }), today3];
case "last7Days":
return [today3.add({ days: -6 }), today3];
case "last14Days":
return [today3.add({ days: -13 }), today3];
case "last30Days":
return [today3.add({ days: -29 }), today3];
case "last90Days":
return [today3.add({ days: -89 }), today3];
case "lastMonth":
return [startOfMonth(today3.add({ months: -1 })), endOfMonth(today3.add({ months: -1 }))];
case "lastQuarter":
return [
startOfMonth(today3.add({ months: -today3.month % 3 - 3 })),
endOfMonth(today3.add({ months: -today3.month % 3 - 1 }))
];
case "lastWeek":
return [startOfWeek(today3, locale).add({ weeks: -1 }), endOfWeek(today3, locale).add({ weeks: -1 })];
case "lastYear":
return [startOfYear(today3.add({ years: -1 })), endOfYear(today3.add({ years: -1 }))];
default:
throw new Error(`Invalid date range preset: ${preset}`);
}
}
export { alignCenter, alignDate, alignEnd, alignStart, alignStartDate, constrainStart, constrainValue, formatRange, formatSelectedDate, formatVisibleRange, getAdjustedDateFn, getDateRangePreset, getDayFormatter, getDaysInWeek, getDecadeRange, getEndDate, getEndOfWeek, getMonthDays, getMonthFormatter, getMonthNames, getNextPage, getNextRow, getNextSection, getPreviousAvailableDate, getPreviousPage, getPreviousRow, getPreviousSection, getSectionEnd, getSectionStart, getStartOfWeek, getTodayDate, getUnitDuration, getWeekDays, getWeekdayFormats, getYearsRange, isDateEqual, isDateOutsideRange, isDateUnavailable, isNextRangeInvalid, isPreviousRangeInvalid, normalizeYear, parseDateString, setCalendar, setDate };