UNPKG

ivt

Version:

Ivt Components Library

1,324 lines (1,292 loc) 185 kB
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