ivt
Version:
Ivt Components Library
1,324 lines (1,292 loc) • 185 kB
JavaScript
import * as React from 'react';
import React__default, { createContext, useContext, useCallback, useRef, useLayoutEffect, useState, useEffect, useMemo } from 'react';
import { c as cn } from './utils-05LlW3Cl.mjs';
import { t as toDate, c as constructFrom, j as addDays, y as normalizeDates, z as startOfDay, g as getDefaultOptions, A as differenceInCalendarDays, f as format, i as getISOWeek, h as getWeek, B as isDate, e as startOfISOWeek, s as startOfWeek, C as startOfYear, l as enUS } from './format-Cn5wls3k.mjs';
import { C as ChevronLeft } from './chevron-left-D7OzIyKL.mjs';
import { C as ChevronRight } from './chevron-right-mC6NR8jW.mjs';
import { C as ChevronDown } from './chevron-down-DNXEgdv9.mjs';
import { b as buttonVariants, B as Button$1 } from './button-Co_1yLv6.mjs';
/**
* The {@link addMonths} function options.
*/ /**
* @name addMonths
* @category Month Helpers
* @summary Add the specified number of months to the given date.
*
* @description
* Add the specified number of months to the given date.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param date - The date to be changed
* @param amount - The amount of months to be added.
* @param options - The options object
*
* @returns The new date with the months added
*
* @example
* // Add 5 months to 1 September 2014:
* const result = addMonths(new Date(2014, 8, 1), 5)
* //=> Sun Feb 01 2015 00:00:00
*
* // Add one month to 30 January 2023:
* const result = addMonths(new Date(2023, 0, 30), 1)
* //=> Tue Feb 28 2023 00:00:00
*/ function addMonths(date, amount, options) {
const _date = toDate(date, options?.in);
if (isNaN(amount)) return constructFrom(date, NaN);
if (!amount) {
// If 0 months, no-op to avoid changing times in the hour before end of DST
return _date;
}
const dayOfMonth = _date.getDate();
// The JS Date object supports date math by accepting out-of-bounds values for
// month, day, etc. For example, new Date(2020, 0, 0) returns 31 Dec 2019 and
// new Date(2020, 13, 1) returns 1 Feb 2021. This is *almost* the behavior we
// want except that dates will wrap around the end of a month, meaning that
// new Date(2020, 13, 31) will return 3 Mar 2021 not 28 Feb 2021 as desired. So
// we'll default to the end of the desired month by adding 1 to the desired
// month and using a date of 0 to back up one day to the end of the desired
// month.
const endOfDesiredMonth = constructFrom(date, _date.getTime());
endOfDesiredMonth.setMonth(_date.getMonth() + amount + 1, 0);
const daysInMonth = endOfDesiredMonth.getDate();
if (dayOfMonth >= daysInMonth) {
// If we're already at the end of the month, then this is the correct date
// and we're done.
return endOfDesiredMonth;
} else {
// Otherwise, we now know that setting the original day-of-month value won't
// cause an overflow, so set the desired day-of-month. Note that we can't
// just set the date of `endOfDesiredMonth` because that object may have had
// its time changed in the unusual case where where a DST transition was on
// the last day of the month and its local time was in the hour skipped or
// repeated next to a DST transition. So we use `date` instead which is
// guaranteed to still have the original time.
_date.setFullYear(endOfDesiredMonth.getFullYear(), endOfDesiredMonth.getMonth(), dayOfMonth);
return _date;
}
}
/**
* The {@link addWeeks} function options.
*/ /**
* @name addWeeks
* @category Week Helpers
* @summary Add the specified number of weeks to the given date.
*
* @description
* Add the specified number of weeks to the given date.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param date - The date to be changed
* @param amount - The amount of weeks to be added.
* @param options - An object with options
*
* @returns The new date with the weeks added
*
* @example
* // Add 4 weeks to 1 September 2014:
* const result = addWeeks(new Date(2014, 8, 1), 4)
* //=> Mon Sep 29 2014 00:00:00
*/ function addWeeks(date, amount, options) {
return addDays(date, amount * 7, options);
}
/**
* The {@link addYears} function options.
*/ /**
* @name addYears
* @category Year Helpers
* @summary Add the specified number of years to the given date.
*
* @description
* Add the specified number of years to the given date.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type.
*
* @param date - The date to be changed
* @param amount - The amount of years to be added.
* @param options - The options
*
* @returns The new date with the years added
*
* @example
* // Add 5 years to 1 September 2014:
* const result = addYears(new Date(2014, 8, 1), 5)
* //=> Sun Sep 01 2019 00:00:00
*/ function addYears(date, amount, options) {
return addMonths(date, amount * 12, options);
}
/**
* The {@link max} function options.
*/ /**
* @name max
* @category Common Helpers
* @summary Return the latest of the given dates.
*
* @description
* Return the latest of the given dates.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param dates - The dates to compare
*
* @returns The latest of the dates
*
* @example
* // Which of these dates is the latest?
* const result = max([
* new Date(1989, 6, 10),
* new Date(1987, 1, 11),
* new Date(1995, 6, 2),
* new Date(1990, 0, 1)
* ])
* //=> Sun Jul 02 1995 00:00:00
*/ function max(dates, options) {
let result;
let context = options?.in;
dates.forEach((date)=>{
// Use the first date object as the context function
if (!context && typeof date === "object") context = constructFrom.bind(null, date);
const date_ = toDate(date, context);
if (!result || result < date_ || isNaN(+date_)) result = date_;
});
return constructFrom(context, result || NaN);
}
/**
* The {@link min} function options.
*/ /**
* @name min
* @category Common Helpers
* @summary Returns the earliest of the given dates.
*
* @description
* Returns the earliest of the given dates.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param dates - The dates to compare
*
* @returns The earliest of the dates
*
* @example
* // Which of these dates is the earliest?
* const result = min([
* new Date(1989, 6, 10),
* new Date(1987, 1, 11),
* new Date(1995, 6, 2),
* new Date(1990, 0, 1)
* ])
* //=> Wed Feb 11 1987 00:00:00
*/ function min(dates, options) {
let result;
let context = options?.in;
dates.forEach((date)=>{
// Use the first date object as the context function
if (!context && typeof date === "object") context = constructFrom.bind(null, date);
const date_ = toDate(date, context);
if (!result || result > date_ || isNaN(+date_)) result = date_;
});
return constructFrom(context, result || NaN);
}
/**
* The {@link isSameDay} function options.
*/ /**
* @name isSameDay
* @category Day Helpers
* @summary Are the given dates in the same day (and year and month)?
*
* @description
* Are the given dates in the same day (and year and month)?
*
* @param laterDate - The first date to check
* @param earlierDate - The second date to check
* @param options - An object with options
*
* @returns The dates are in the same day (and year and month)
*
* @example
* // Are 4 September 06:00:00 and 4 September 18:00:00 in the same day?
* const result = isSameDay(new Date(2014, 8, 4, 6, 0), new Date(2014, 8, 4, 18, 0))
* //=> true
*
* @example
* // Are 4 September and 4 October in the same day?
* const result = isSameDay(new Date(2014, 8, 4), new Date(2014, 9, 4))
* //=> false
*
* @example
* // Are 4 September, 2014 and 4 September, 2015 in the same day?
* const result = isSameDay(new Date(2014, 8, 4), new Date(2015, 8, 4))
* //=> false
*/ function isSameDay(laterDate, earlierDate, options) {
const [dateLeft_, dateRight_] = normalizeDates(options?.in, laterDate, earlierDate);
return +startOfDay(dateLeft_) === +startOfDay(dateRight_);
}
/**
* The {@link differenceInCalendarMonths} function options.
*/ /**
* @name differenceInCalendarMonths
* @category Month Helpers
* @summary Get the number of calendar months between the given dates.
*
* @description
* Get the number of calendar months between the given dates.
*
* @param laterDate - The later date
* @param earlierDate - The earlier date
* @param options - An object with options
*
* @returns The number of calendar months
*
* @example
* // How many calendar months are between 31 January 2014 and 1 September 2014?
* const result = differenceInCalendarMonths(
* new Date(2014, 8, 1),
* new Date(2014, 0, 31)
* )
* //=> 8
*/ function differenceInCalendarMonths(laterDate, earlierDate, options) {
const [laterDate_, earlierDate_] = normalizeDates(options?.in, laterDate, earlierDate);
const yearsDiff = laterDate_.getFullYear() - earlierDate_.getFullYear();
const monthsDiff = laterDate_.getMonth() - earlierDate_.getMonth();
return yearsDiff * 12 + monthsDiff;
}
/**
* The {@link endOfMonth} function options.
*/ /**
* @name endOfMonth
* @category Month Helpers
* @summary Return the end of a month for the given date.
*
* @description
* Return the end of a month for the given date.
* The result will be in the local timezone.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param date - The original date
* @param options - An object with options
*
* @returns The end of a month
*
* @example
* // The end of a month for 2 September 2014 11:55:00:
* const result = endOfMonth(new Date(2014, 8, 2, 11, 55, 0))
* //=> Tue Sep 30 2014 23:59:59.999
*/ function endOfMonth(date, options) {
const _date = toDate(date, options?.in);
const month = _date.getMonth();
_date.setFullYear(_date.getFullYear(), month + 1, 0);
_date.setHours(23, 59, 59, 999);
return _date;
}
function normalizeInterval(context, interval) {
const [start, end] = normalizeDates(context, interval.start, interval.end);
return {
start,
end
};
}
/**
* The {@link eachMonthOfInterval} function options.
*/ /**
* The {@link eachMonthOfInterval} function result type. It resolves the proper data type.
*/ /**
* @name eachMonthOfInterval
* @category Interval Helpers
* @summary Return the array of months within the specified time interval.
*
* @description
* Return the array of months within the specified time interval.
*
* @typeParam IntervalType - Interval type.
* @typeParam Options - Options type.
*
* @param interval - The interval.
* @param options - An object with options.
*
* @returns The array with starts of months from the month of the interval start to the month of the interval end
*
* @example
* // Each month between 6 February 2014 and 10 August 2014:
* const result = eachMonthOfInterval({
* start: new Date(2014, 1, 6),
* end: new Date(2014, 7, 10)
* })
* //=> [
* // Sat Feb 01 2014 00:00:00,
* // Sat Mar 01 2014 00:00:00,
* // Tue Apr 01 2014 00:00:00,
* // Thu May 01 2014 00:00:00,
* // Sun Jun 01 2014 00:00:00,
* // Tue Jul 01 2014 00:00:00,
* // Fri Aug 01 2014 00:00:00
* // ]
*/ function eachMonthOfInterval(interval, options) {
const { start, end } = normalizeInterval(options?.in, interval);
let reversed = +start > +end;
const endTime = reversed ? +start : +end;
const date = reversed ? end : start;
date.setHours(0, 0, 0, 0);
date.setDate(1);
let step = 1;
const dates = [];
while(+date <= endTime){
dates.push(constructFrom(start, date));
date.setMonth(date.getMonth() + step);
}
return reversed ? dates.reverse() : dates;
}
/**
* The {@link startOfMonth} function options.
*/ /**
* @name startOfMonth
* @category Month Helpers
* @summary Return the start of a month for the given date.
*
* @description
* Return the start of a month for the given date. The result will be in the local timezone.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments.
* Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed,
* or inferred from the arguments.
*
* @param date - The original date
* @param options - An object with options
*
* @returns The start of a month
*
* @example
* // The start of a month for 2 September 2014 11:55:00:
* const result = startOfMonth(new Date(2014, 8, 2, 11, 55, 0))
* //=> Mon Sep 01 2014 00:00:00
*/ function startOfMonth(date, options) {
const _date = toDate(date, options?.in);
_date.setDate(1);
_date.setHours(0, 0, 0, 0);
return _date;
}
/**
* The {@link endOfYear} function options.
*/ /**
* @name endOfYear
* @category Year Helpers
* @summary Return the end of a year for the given date.
*
* @description
* Return the end of a year for the given date.
* The result will be in the local timezone.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param date - The original date
* @param options - The options
*
* @returns The end of a year
*
* @example
* // The end of a year for 2 September 2014 11:55:00:
* const result = endOfYear(new Date(2014, 8, 2, 11, 55, 0))
* //=> Wed Dec 31 2014 23:59:59.999
*/ function endOfYear(date, options) {
const _date = toDate(date, options?.in);
const year = _date.getFullYear();
_date.setFullYear(year + 1, 0, 0);
_date.setHours(23, 59, 59, 999);
return _date;
}
/**
* The {@link eachYearOfInterval} function options.
*/ /**
* The {@link eachYearOfInterval} function result type. It resolves the proper data type.
* It uses the first argument date object type, starting from the date argument,
* then the start interval date, and finally the end interval date. If
* a context function is passed, it uses the context function return type.
*/ /**
* @name eachYearOfInterval
* @category Interval Helpers
* @summary Return the array of yearly timestamps within the specified time interval.
*
* @description
* Return the array of yearly timestamps within the specified time interval.
*
* @typeParam IntervalType - Interval type.
* @typeParam Options - Options type.
*
* @param interval - The interval.
* @param options - An object with options.
*
* @returns The array with starts of yearly timestamps from the month of the interval start to the month of the interval end
*
* @example
* // Each year between 6 February 2014 and 10 August 2017:
* const result = eachYearOfInterval({
* start: new Date(2014, 1, 6),
* end: new Date(2017, 7, 10)
* })
* //=> [
* // Wed Jan 01 2014 00:00:00,
* // Thu Jan 01 2015 00:00:00,
* // Fri Jan 01 2016 00:00:00,
* // Sun Jan 01 2017 00:00:00
* // ]
*/ function eachYearOfInterval(interval, options) {
const { start, end } = normalizeInterval(options?.in, interval);
let reversed = +start > +end;
const endTime = reversed ? +start : +end;
const date = reversed ? end : start;
date.setHours(0, 0, 0, 0);
date.setMonth(0, 1);
let step = 1;
const dates = [];
while(+date <= endTime){
dates.push(constructFrom(start, date));
date.setFullYear(date.getFullYear() + step);
}
return reversed ? dates.reverse() : dates;
}
/**
* The {@link endOfWeek} function options.
*/ /**
* @name endOfWeek
* @category Week Helpers
* @summary Return the end of a week for the given date.
*
* @description
* Return the end of a week for the given date.
* The result will be in the local timezone.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param date - The original date
* @param options - An object with options
*
* @returns The end of a week
*
* @example
* // The end of a week for 2 September 2014 11:55:00:
* const result = endOfWeek(new Date(2014, 8, 2, 11, 55, 0))
* //=> Sat Sep 06 2014 23:59:59.999
*
* @example
* // If the week starts on Monday, the end of the week for 2 September 2014 11:55:00:
* const result = endOfWeek(new Date(2014, 8, 2, 11, 55, 0), { weekStartsOn: 1 })
* //=> Sun Sep 07 2014 23:59:59.999
*/ function endOfWeek(date, options) {
const defaultOptions = getDefaultOptions();
const weekStartsOn = options?.weekStartsOn ?? options?.locale?.options?.weekStartsOn ?? defaultOptions.weekStartsOn ?? defaultOptions.locale?.options?.weekStartsOn ?? 0;
const _date = toDate(date, options?.in);
const day = _date.getDay();
const diff = (day < weekStartsOn ? -7 : 0) + 6 - (day - weekStartsOn);
_date.setDate(_date.getDate() + diff);
_date.setHours(23, 59, 59, 999);
return _date;
}
/**
* The {@link endOfISOWeek} function options.
*/ /**
* @name endOfISOWeek
* @category ISO Week Helpers
* @summary Return the end of an ISO week for the given date.
*
* @description
* Return the end of an ISO week for the given date.
* The result will be in the local timezone.
*
* ISO week-numbering year: http://en.wikipedia.org/wiki/ISO_week_date
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param date - The original date
* @param options - An object with options
*
* @returns The end of an ISO week
*
* @example
* // The end of an ISO week for 2 September 2014 11:55:00:
* const result = endOfISOWeek(new Date(2014, 8, 2, 11, 55, 0))
* //=> Sun Sep 07 2014 23:59:59.999
*/ function endOfISOWeek(date, options) {
return endOfWeek(date, {
...options,
weekStartsOn: 1
});
}
/**
* The {@link getDaysInMonth} function options.
*/ /**
* @name getDaysInMonth
* @category Month Helpers
* @summary Get the number of days in a month of the given date.
*
* @description
* Get the number of days in a month of the given date, considering the context if provided.
*
* @param date - The given date
* @param options - An object with options
*
* @returns The number of days in a month
*
* @example
* // How many days are in February 2000?
* const result = getDaysInMonth(new Date(2000, 1))
* //=> 29
*/ function getDaysInMonth(date, options) {
const _date = toDate(date, options?.in);
const year = _date.getFullYear();
const monthIndex = _date.getMonth();
const lastDayOfMonth = constructFrom(_date, 0);
lastDayOfMonth.setFullYear(year, monthIndex + 1, 0);
lastDayOfMonth.setHours(0, 0, 0, 0);
return lastDayOfMonth.getDate();
}
/**
* The {@link getMonth} function options.
*/ /**
* @name getMonth
* @category Month Helpers
* @summary Get the month of the given date.
*
* @description
* Get the month of the given date.
*
* @param date - The given date
* @param options - An object with options
*
* @returns The month index (0-11)
*
* @example
* // Which month is 29 February 2012?
* const result = getMonth(new Date(2012, 1, 29))
* //=> 1
*/ function getMonth(date, options) {
return toDate(date, options?.in).getMonth();
}
/**
* The {@link getYear} function options.
*/ /**
* @name getYear
* @category Year Helpers
* @summary Get the year of the given date.
*
* @description
* Get the year of the given date.
*
* @param date - The given date
* @param options - An object with options
*
* @returns The year
*
* @example
* // Which year is 2 July 2014?
* const result = getYear(new Date(2014, 6, 2))
* //=> 2014
*/ function getYear(date, options) {
return toDate(date, options?.in).getFullYear();
}
/**
* @name isAfter
* @category Common Helpers
* @summary Is the first date after the second one?
*
* @description
* Is the first date after the second one?
*
* @param date - The date that should be after the other one to return true
* @param dateToCompare - The date to compare with
*
* @returns The first date is after the second date
*
* @example
* // Is 10 July 1989 after 11 February 1987?
* const result = isAfter(new Date(1989, 6, 10), new Date(1987, 1, 11))
* //=> true
*/ function isAfter(date, dateToCompare) {
return +toDate(date) > +toDate(dateToCompare);
}
/**
* @name isBefore
* @category Common Helpers
* @summary Is the first date before the second one?
*
* @description
* Is the first date before the second one?
*
* @param date - The date that should be before the other one to return true
* @param dateToCompare - The date to compare with
*
* @returns The first date is before the second date
*
* @example
* // Is 10 July 1989 before 11 February 1987?
* const result = isBefore(new Date(1989, 6, 10), new Date(1987, 1, 11))
* //=> false
*/ function isBefore(date, dateToCompare) {
return +toDate(date) < +toDate(dateToCompare);
}
/**
* The {@link isSameMonth} function options.
*/ /**
* @name isSameMonth
* @category Month Helpers
* @summary Are the given dates in the same month (and year)?
*
* @description
* Are the given dates in the same month (and year)?
*
* @param laterDate - The first date to check
* @param earlierDate - The second date to check
* @param options - An object with options
*
* @returns The dates are in the same month (and year)
*
* @example
* // Are 2 September 2014 and 25 September 2014 in the same month?
* const result = isSameMonth(new Date(2014, 8, 2), new Date(2014, 8, 25))
* //=> true
*
* @example
* // Are 2 September 2014 and 25 September 2015 in the same month?
* const result = isSameMonth(new Date(2014, 8, 2), new Date(2015, 8, 25))
* //=> false
*/ function isSameMonth(laterDate, earlierDate, options) {
const [laterDate_, earlierDate_] = normalizeDates(options?.in, laterDate, earlierDate);
return laterDate_.getFullYear() === earlierDate_.getFullYear() && laterDate_.getMonth() === earlierDate_.getMonth();
}
/**
* The {@link isSameYear} function options.
*/ /**
* @name isSameYear
* @category Year Helpers
* @summary Are the given dates in the same year?
*
* @description
* Are the given dates in the same year?
*
* @param laterDate - The first date to check
* @param earlierDate - The second date to check
* @param options - An object with options
*
* @returns The dates are in the same year
*
* @example
* // Are 2 September 2014 and 25 September 2014 in the same year?
* const result = isSameYear(new Date(2014, 8, 2), new Date(2014, 8, 25))
* //=> true
*/ function isSameYear(laterDate, earlierDate, options) {
const [laterDate_, earlierDate_] = normalizeDates(options?.in, laterDate, earlierDate);
return laterDate_.getFullYear() === earlierDate_.getFullYear();
}
/**
* The {@link setMonth} function options.
*/ /**
* @name setMonth
* @category Month Helpers
* @summary Set the month to the given date.
*
* @description
* Set the month to the given date.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param date - The date to be changed
* @param month - The month index to set (0-11)
* @param options - The options
*
* @returns The new date with the month set
*
* @example
* // Set February to 1 September 2014:
* const result = setMonth(new Date(2014, 8, 1), 1)
* //=> Sat Feb 01 2014 00:00:00
*/ function setMonth(date, month, options) {
const _date = toDate(date, options?.in);
const year = _date.getFullYear();
const day = _date.getDate();
const midMonth = constructFrom(date, 0);
midMonth.setFullYear(year, month, 15);
midMonth.setHours(0, 0, 0, 0);
const daysInMonth = getDaysInMonth(midMonth);
// Set the earlier date, allows to wrap Jan 31 to Feb 28
_date.setMonth(month, Math.min(day, daysInMonth));
return _date;
}
/**
* The {@link setYear} function options.
*/ /**
* @name setYear
* @category Year Helpers
* @summary Set the year to the given date.
*
* @description
* Set the year to the given date.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
* @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments.
*
* @param date - The date to be changed
* @param year - The year of the new date
* @param options - An object with options.
*
* @returns The new date with the year set
*
* @example
* // Set year 2013 to 1 September 2014:
* const result = setYear(new Date(2014, 8, 1), 2013)
* //=> Sun Sep 01 2013 00:00:00
*/ function setYear(date, year, options) {
const date_ = toDate(date, options?.in);
// Check if date is Invalid Date because Date.prototype.setFullYear ignores the value of Invalid Date
if (isNaN(+date_)) return constructFrom(date, NaN);
date_.setFullYear(year);
return date_;
}
/**
* Time zone name format.
*/ /**
* The function returns the time zone name for the given date in the specified
* time zone.
*
* It uses the `Intl.DateTimeFormat` API and by default outputs the time zone
* name in a long format, e.g. "Pacific Standard Time" or
* "Singapore Standard Time".
*
* It is possible to specify the format as the third argument using one of the following options
*
* - "short": e.g. "EDT" or "GMT+8".
* - "long": e.g. "Eastern Daylight Time".
* - "shortGeneric": e.g. "ET" or "Singapore Time".
* - "longGeneric": e.g. "Eastern Time" or "Singapore Standard Time".
*
* These options correspond to TR35 tokens `z..zzz`, `zzzz`, `v`, and `vvvv` respectively: https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-zone
*
* @param timeZone - Time zone name (IANA or UTC offset)
* @param date - Date object to get the time zone name for
* @param format - Optional format of the time zone name. Defaults to "long". Can be "short", "long", "shortGeneric", or "longGeneric".
*
* @returns Time zone name (e.g. "Singapore Standard Time")
*/ function tzName(timeZone, date, format = "long") {
return new Intl.DateTimeFormat("en-US", {
// Enforces engine to render the time. Without the option JavaScriptCore omits it.
hour: "numeric",
timeZone: timeZone,
timeZoneName: format
}).format(date).split(/\s/g) // Format.JS uses non-breaking spaces
.slice(2) // Skip the hour and AM/PM parts
.join(" ");
}
const offsetFormatCache = {};
const offsetCache = {};
/**
* The function extracts UTC offset in minutes from the given date in specified
* time zone.
*
* Unlike `Date.prototype.getTimezoneOffset`, this function returns the value
* mirrored to the sign of the offset in the time zone. For Asia/Singapore
* (UTC+8), `tzOffset` returns 480, while `getTimezoneOffset` returns -480.
*
* @param timeZone - Time zone name (IANA or UTC offset)
* @param date - Date to check the offset for
*
* @returns UTC offset in minutes
*/ function tzOffset(timeZone, date) {
try {
var _offsetFormatCache, _timeZone;
const format = (_offsetFormatCache = offsetFormatCache)[_timeZone = timeZone] || (_offsetFormatCache[_timeZone] = new Intl.DateTimeFormat("en-US", {
timeZone,
timeZoneName: "longOffset"
}).format);
const offsetStr = format(date).split("GMT")[1];
if (offsetStr in offsetCache) return offsetCache[offsetStr];
return calcOffset(offsetStr, offsetStr.split(":"));
} catch {
// Fallback to manual parsing if the runtime doesn't support ±HH:MM/±HHMM/±HH
// See: https://github.com/nodejs/node/issues/53419
if (timeZone in offsetCache) return offsetCache[timeZone];
const captures = timeZone?.match(offsetRe);
if (captures) return calcOffset(timeZone, captures.slice(1));
return NaN;
}
}
const offsetRe = /([+-]\d\d):?(\d\d)?/;
function calcOffset(cacheStr, values) {
const hours = +(values[0] || 0);
const minutes = +(values[1] || 0);
// Convert seconds to minutes by dividing by 60 to keep the function return in minutes.
const seconds = +(values[2] || 0) / 60;
return offsetCache[cacheStr] = hours * 60 + minutes > 0 ? hours * 60 + minutes + seconds : hours * 60 - minutes - seconds;
}
class TZDateMini extends Date {
static tz(tz, ...args) {
return args.length ? new TZDateMini(...args, tz) : new TZDateMini(Date.now(), tz);
}
//#endregion
//#region time zone
withTimeZone(timeZone) {
return new TZDateMini(+this, timeZone);
}
getTimezoneOffset() {
const offset = -tzOffset(this.timeZone, this);
// Remove the seconds offset
// use Math.floor for negative GMT timezones and Math.ceil for positive GMT timezones.
return offset > 0 ? Math.floor(offset) : Math.ceil(offset);
}
//#endregion
//#region time
setTime(time) {
Date.prototype.setTime.apply(this, arguments);
syncToInternal(this);
return +this;
}
//#endregion
//#region date-fns integration
[Symbol.for("constructDateFrom")](date) {
return new TZDateMini(+new Date(date), this.timeZone);
}
//#region static
constructor(...args){
super();
if (args.length > 1 && typeof args[args.length - 1] === "string") {
this.timeZone = args.pop();
}
this.internal = new Date();
if (isNaN(tzOffset(this.timeZone, this))) {
this.setTime(NaN);
} else {
if (!args.length) {
this.setTime(Date.now());
} else if (typeof args[0] === "number" && (args.length === 1 || args.length === 2 && typeof args[1] !== "number")) {
this.setTime(args[0]);
} else if (typeof args[0] === "string") {
this.setTime(+new Date(args[0]));
} else if (args[0] instanceof Date) {
this.setTime(+args[0]);
} else {
this.setTime(+new Date(...args));
adjustToSystemTZ(this);
syncToInternal(this);
}
}
}
}
// Assign getters and setters
const re = /^(get|set)(?!UTC)/;
Object.getOwnPropertyNames(Date.prototype).forEach((method)=>{
if (!re.test(method)) return;
const utcMethod = method.replace(re, "$1UTC");
// Filter out methods without UTC counterparts
if (!TZDateMini.prototype[utcMethod]) return;
if (method.startsWith("get")) {
// Delegate to internal date's UTC method
TZDateMini.prototype[method] = function() {
return this.internal[utcMethod]();
};
} else {
// Assign regular setter
TZDateMini.prototype[method] = function() {
Date.prototype[utcMethod].apply(this.internal, arguments);
syncFromInternal(this);
return +this;
};
// Assign UTC setter
TZDateMini.prototype[utcMethod] = function() {
Date.prototype[utcMethod].apply(this, arguments);
syncToInternal(this);
return +this;
};
}
});
/**
* Function syncs time to internal date, applying the time zone offset.
*
* @param {Date} date - Date to sync
*/ function syncToInternal(date) {
date.internal.setTime(+date);
date.internal.setUTCSeconds(date.internal.getUTCSeconds() - Math.round(-tzOffset(date.timeZone, date) * 60));
}
/**
* Function syncs the internal date UTC values to the date. It allows to get
* accurate timestamp value.
*
* @param {Date} date - The date to sync
*/ function syncFromInternal(date) {
// First we transpose the internal values
Date.prototype.setFullYear.call(date, date.internal.getUTCFullYear(), date.internal.getUTCMonth(), date.internal.getUTCDate());
Date.prototype.setHours.call(date, date.internal.getUTCHours(), date.internal.getUTCMinutes(), date.internal.getUTCSeconds(), date.internal.getUTCMilliseconds());
// Now we have to adjust the date to the system time zone
adjustToSystemTZ(date);
}
/**
* Function adjusts the date to the system time zone. It uses the time zone
* differences to calculate the offset and adjust the date.
*
* @param {Date} date - Date to adjust
*/ function adjustToSystemTZ(date) {
// Save the time zone offset before all the adjustments
const baseOffset = tzOffset(date.timeZone, date);
// Remove the seconds offset
// use Math.floor for negative GMT timezones and Math.ceil for positive GMT timezones.
const offset = baseOffset > 0 ? Math.floor(baseOffset) : Math.ceil(baseOffset);
//#region System DST adjustment
// The biggest problem with using the system time zone is that when we create
// a date from internal values stored in UTC, the system time zone might end
// up on the DST hour:
//
// $ TZ=America/New_York node
// > new Date(2020, 2, 8, 1).toString()
// 'Sun Mar 08 2020 01:00:00 GMT-0500 (Eastern Standard Time)'
// > new Date(2020, 2, 8, 2).toString()
// 'Sun Mar 08 2020 03:00:00 GMT-0400 (Eastern Daylight Time)'
// > new Date(2020, 2, 8, 3).toString()
// 'Sun Mar 08 2020 03:00:00 GMT-0400 (Eastern Daylight Time)'
// > new Date(2020, 2, 8, 4).toString()
// 'Sun Mar 08 2020 04:00:00 GMT-0400 (Eastern Daylight Time)'
//
// Here we get the same hour for both 2 and 3, because the system time zone
// has DST beginning at 8 March 2020, 2 a.m. and jumps to 3 a.m. So we have
// to adjust the internal date to reflect that.
//
// However we want to adjust only if that's the DST hour the change happenes,
// not the hour where DST moves to.
// We calculate the previous hour to see if the time zone offset has changed
// and we have landed on the DST hour.
const prevHour = new Date(+date);
// We use UTC methods here as we don't want to land on the same hour again
// in case of DST.
prevHour.setUTCHours(prevHour.getUTCHours() - 1);
// Calculate if we are on the system DST hour.
const systemOffset = -new Date(+date).getTimezoneOffset();
const prevHourSystemOffset = -new Date(+prevHour).getTimezoneOffset();
const systemDSTChange = systemOffset - prevHourSystemOffset;
// Detect the DST shift. System DST change will occur both on
const dstShift = Date.prototype.getHours.apply(date) !== date.internal.getUTCHours();
// Move the internal date when we are on the system DST hour.
if (systemDSTChange && dstShift) date.internal.setUTCMinutes(date.internal.getUTCMinutes() + systemDSTChange);
//#endregion
//#region System diff adjustment
// Now we need to adjust the date, since we just applied internal values.
// We need to calculate the difference between the system and date time zones
// and apply it to the date.
const offsetDiff = systemOffset - offset;
if (offsetDiff) Date.prototype.setUTCMinutes.call(date, Date.prototype.getUTCMinutes.call(date) + offsetDiff);
//#endregion
//#region Seconds System diff adjustment
const systemDate = new Date(+date);
// Set the UTC seconds to 0 to isolate the timezone offset in seconds.
systemDate.setUTCSeconds(0);
// For negative systemOffset, invert the seconds.
const systemSecondsOffset = systemOffset > 0 ? systemDate.getSeconds() : (systemDate.getSeconds() - 60) % 60;
// Calculate the seconds offset based on the timezone offset.
const secondsOffset = Math.round(-(tzOffset(date.timeZone, date) * 60)) % 60;
if (secondsOffset || systemSecondsOffset) {
date.internal.setUTCSeconds(date.internal.getUTCSeconds() + secondsOffset);
Date.prototype.setUTCSeconds.call(date, Date.prototype.getUTCSeconds.call(date) + secondsOffset + systemSecondsOffset);
}
//#endregion
//#region Post-adjustment DST fix
const postBaseOffset = tzOffset(date.timeZone, date);
// Remove the seconds offset
// use Math.floor for negative GMT timezones and Math.ceil for positive GMT timezones.
const postOffset = postBaseOffset > 0 ? Math.floor(postBaseOffset) : Math.ceil(postBaseOffset);
const postSystemOffset = -new Date(+date).getTimezoneOffset();
const postOffsetDiff = postSystemOffset - postOffset;
const offsetChanged = postOffset !== offset;
const postDiff = postOffsetDiff - offsetDiff;
if (offsetChanged && postDiff) {
Date.prototype.setUTCMinutes.call(date, Date.prototype.getUTCMinutes.call(date) + postDiff);
// Now we need to check if got offset change during the post-adjustment.
// If so, we also need both dates to reflect that.
const newBaseOffset = tzOffset(date.timeZone, date);
// Remove the seconds offset
// use Math.floor for negative GMT timezones and Math.ceil for positive GMT timezones.
const newOffset = newBaseOffset > 0 ? Math.floor(newBaseOffset) : Math.ceil(newBaseOffset);
const offsetChange = postOffset - newOffset;
if (offsetChange) {
date.internal.setUTCMinutes(date.internal.getUTCMinutes() + offsetChange);
Date.prototype.setUTCMinutes.call(date, Date.prototype.getUTCMinutes.call(date) + offsetChange);
}
}
//#endregion
}
class TZDate extends TZDateMini {
//#region static
static tz(tz, ...args) {
return args.length ? new TZDate(...args, tz) : new TZDate(Date.now(), tz);
}
//#endregion
//#region representation
toISOString() {
const [sign, hours, minutes] = this.tzComponents();
const tz = `${sign}${hours}:${minutes}`;
return this.internal.toISOString().slice(0, -1) + tz;
}
toString() {
// "Tue Aug 13 2024 07:50:19 GMT+0800 (Singapore Standard Time)";
return `${this.toDateString()} ${this.toTimeString()}`;
}
toDateString() {
// toUTCString returns RFC 7231 ("Mon, 12 Aug 2024 23:36:08 GMT")
const [day, date, month, year] = this.internal.toUTCString().split(" ");
// "Tue Aug 13 2024"
return `${day?.slice(0, -1)} ${month} ${date} ${year}`;
}
toTimeString() {
// toUTCString returns RFC 7231 ("Mon, 12 Aug 2024 23:36:08 GMT")
const time = this.internal.toUTCString().split(" ")[4];
const [sign, hours, minutes] = this.tzComponents();
// "07:42:23 GMT+0800 (Singapore Standard Time)"
return `${time} GMT${sign}${hours}${minutes} (${tzName(this.timeZone, this)})`;
}
toLocaleString(locales, options) {
return Date.prototype.toLocaleString.call(this, locales, {
...options,
timeZone: options?.timeZone || this.timeZone
});
}
toLocaleDateString(locales, options) {
return Date.prototype.toLocaleDateString.call(this, locales, {
...options,
timeZone: options?.timeZone || this.timeZone
});
}
toLocaleTimeString(locales, options) {
return Date.prototype.toLocaleTimeString.call(this, locales, {
...options,
timeZone: options?.timeZone || this.timeZone
});
}
//#endregion
//#region private
tzComponents() {
const offset = this.getTimezoneOffset();
const sign = offset > 0 ? "-" : "+";
const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, "0");
const minutes = String(Math.abs(offset) % 60).padStart(2, "0");
return [
sign,
hours,
minutes
];
}
//#endregion
withTimeZone(timeZone) {
return new TZDate(+this, timeZone);
}
//#region date-fns integration
[Symbol.for("constructDateFrom")](date) {
return new TZDate(+new Date(date), this.timeZone);
}
}
const FIVE_WEEKS = 5;
const FOUR_WEEKS = 4;
/**
* Returns the number of weeks to display in the broadcast calendar for a given
* month.
*
* The broadcast calendar may have either 4 or 5 weeks in a month, depending on
* the start and end dates of the broadcast weeks.
*
* @since 9.4.0
* @param month The month for which to calculate the number of weeks.
* @param dateLib The date library to use for date manipulation.
* @returns The number of weeks in the broadcast calendar (4 or 5).
*/ function getBroadcastWeeksInMonth(month, dateLib) {
// Get the first day of the month
const firstDayOfMonth = dateLib.startOfMonth(month);
// Get the day of the week for the first day of the month (1-7, where 1 is Monday)
const firstDayOfWeek = firstDayOfMonth.getDay() > 0 ? firstDayOfMonth.getDay() : 7;
const broadcastStartDate = dateLib.addDays(month, -firstDayOfWeek + 1);
const lastDateOfLastWeek = dateLib.addDays(broadcastStartDate, FIVE_WEEKS * 7 - 1);
const numberOfWeeks = dateLib.getMonth(month) === dateLib.getMonth(lastDateOfLastWeek) ? FIVE_WEEKS : FOUR_WEEKS;
return numberOfWeeks;
}
/**
* Returns the start date of the week in the broadcast calendar.
*
* The broadcast week starts on Monday. If the first day of the month is not a
* Monday, this function calculates the previous Monday as the start of the
* broadcast week.
*
* @since 9.4.0
* @param date The date for which to calculate the start of the broadcast week.
* @param dateLib The date library to use for date manipulation.
* @returns The start date of the broadcast week.
*/ function startOfBroadcastWeek(date, dateLib) {
const firstOfMonth = dateLib.startOfMonth(date);
const dayOfWeek = firstOfMonth.getDay();
if (dayOfWeek === 1) {
return firstOfMonth;
} else if (dayOfWeek === 0) {
return dateLib.addDays(firstOfMonth, -1 * 6);
} else {
return dateLib.addDays(firstOfMonth, -1 * (dayOfWeek - 1));
}
}
/**
* Returns the end date of the week in the broadcast calendar.
*
* The broadcast week ends on the last day of the last broadcast week for the
* given date.
*
* @since 9.4.0
* @param date The date for which to calculate the end of the broadcast week.
* @param dateLib The date library to use for date manipulation.
* @returns The end date of the broadcast week.
*/ function endOfBroadcastWeek(date, dateLib) {
const startDate = startOfBroadcastWeek(date, dateLib);
const numberOfWeeks = getBroadcastWeeksInMonth(date, dateLib);
const endDate = dateLib.addDays(startDate, numberOfWeeks * 7 - 1);
return endDate;
}
/**
* A wrapper class around [date-fns](http://date-fns.org) that provides utility
* methods for date manipulation and formatting.
*
* @since 9.2.0
* @example
* const dateLib = new DateLib({ locale: es });
* const newDate = dateLib.addDays(new Date(), 5);
*/ class DateLib {
/**
* Generates a mapping of Arabic digits (0-9) to the target numbering system
* digits.
*
* @since 9.5.0
* @returns A record mapping Arabic digits to the target numerals.
*/ getDigitMap() {
const { numerals = "latn" } = this.options;
// Use Intl.NumberFormat to create a formatter with the specified numbering system
const formatter = new Intl.NumberFormat("en-US", {
numberingSystem: numerals
});
// Map Arabic digits (0-9) to the target numerals
const digitMap = {};
for(let i = 0; i < 10; i++){
digitMap[i.toString()] = formatter.format(i);
}
return digitMap;
}
/**
* Replaces Arabic digits in a string with the target numbering system digits.
*
* @since 9.5.0
* @param input The string containing Arabic digits.
* @returns The string with digits replaced.
*/ replaceDigits(input) {
const digitMap = this.getDigitMap();
return input.replace(/\d/g, (digit)=>digitMap[digit] || digit);
}
/**
* Formats a number using the configured numbering system.
*
* @since 9.5.0
* @param value The number to format.
* @returns The formatted number as a string.
*/ formatNumber(value) {
return this.replaceDigits(value.toString());
}
/**
* Returns the preferred ordering for month and year labels for the current
* locale.
*/ getMonthYearOrder() {
const code = this.options.locale?.code;
if (!code) {
return "month-first";
}
return DateLib.yearFirstLocales.has(code) ? "year-first" : "month-first";
}
/**
* Formats the month/year pair respecting locale conventions.
*
* @since 9.11.0
*/ formatMonthYear(date) {
const { locale, timeZone, numerals } = this.options;
const localeCode = locale?.code;
if (localeCode && DateLib.yearFirstLocales.has(localeCode)) {
try {
const intl = new Intl.DateTimeFormat(localeCode, {
month: "long",
year: "numeric",
timeZone,
numberingSystem: numerals
});
const formatted = intl.format(date);
return formatted;
} catch {
// Fallback to date-fns formatting below.
}
}
const pattern = this.getMonthYearOrder() === "year-first" ? "y LLLL" : "LLLL y";
return this.format(date, pattern);
}
/**
* Creates an instance of `DateLib`.
*
* @param options Configuration options for the date library.
* @param overrides Custom overrides for the date library functions.
*/ constructor(options, overrides){
/**
* Reference to the built-in Date constructor.
*
* @deprecated Use `newDate()` or `today()`.
*/ this.Date = Date;
/**
* Creates a new `Date` object representing today's date.
*
* @since 9.5.0
* @returns A `Date` object for today's date.
*/ this.today = ()=>{
if (this.overrides?.today) {
return this.overrides.today();
}
if (this.options.timeZone) {
return TZDate.tz(this.options.timeZone);
}
return new this.Date();
};
/**
* Creates a new `Date` object with the specified year, month, and day.
*
* @since 9.5.0
* @param year The year.
* @param monthIndex The month (0-11).
* @param date The day of the month.
* @returns A new `Date` object.
*/ this.newDate = (year, monthIndex, date)=>{
if (this.overrides?.newDate) {
return this.overrides.newDate(year, monthIndex, date);
}
if (this.options.timeZone) {
return new TZDate(year, monthIndex, date, this.options.timeZone);
}
return new Date(year, monthIndex, date);
};
/**
* Adds the specified number of days to the given date.
*
* @param date The date to add days to.
* @param amount The number of days to add.
* @returns The new date with the days added.
*/ this.addDays = (date, amount)=>{
return this.overrides?.addDays ? this.overrides.addDays(date, amount) : addDays(date, amount);
};
/**
* Adds the specified number of months to the given date.
*
* @param date The date to add months to.
* @param amount The number of months to add.
* @returns The new date with the months added.
*/ this.addMonths = (date, amount)=>{
return this.overrid