@quasar/quasar-ui-qcalendar
Version:
QCalendar - Day/Month/Week Calendars, Popups, Date Pickers, Schedules, Agendas, Planners and Tasks for your Vue Apps
1,441 lines (1,317 loc) • 50.8 kB
text/typescript
export const PARSE_DATETIME =
/^(\d{4})-(\d{1,2})(-(\d{1,2}))?([^\d]+(\d{1,2}))?(:(\d{1,2}))?(:(\d{1,2}))?(.(\d{1,3}))?$/
export const PARSE_DATE = /^(\d{4})-(\d{1,2})(-(\d{1,2}))?/
export const PARSE_TIME = /(\d\d?)(:(\d\d?)|)(:(\d\d?)|)/
export const DAYS_IN_MONTH = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
export const DAYS_IN_MONTH_LEAP = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
export const TIME_CONSTANTS = {
MILLISECONDS_IN: {
SECOND: 1000,
MINUTE: 60000,
HOUR: 3600000,
DAY: 86400000,
WEEK: 604800000,
},
SECONDS_IN: {
MINUTE: 60,
HOUR: 3600,
DAY: 86400,
WEEK: 604800,
},
MINUTES_IN: {
MINUTE: 1,
HOUR: 60,
DAY: 1440,
WEEK: 10080,
},
HOURS_IN: {
DAY: 24,
WEEK: 168,
},
DAYS_IN: {
WEEK: 7,
},
}
export const DAYS_IN_MONTH_MIN = 28
export const DAYS_IN_MONTH_MAX = 31
export const MONTH_MAX = 12
export const MONTH_MIN = 1
export const DAY_MIN = 1
export const FIRST_HOUR = 0
/**
* @typedef {Object} Timestamp The Timestamp object
* @property {string=} Timestamp.date Date string in format 'YYYY-MM-DD'
* @property {string=} Timestamp.time Time string in format 'HH:MM'
* @property {number} Timestamp.year The numeric year
* @property {number} Timestamp.month The numeric month (Jan = 1, ...)
* @property {number} Timestamp.day The numeric day
* @property {number} Timestamp.weekday The numeric weekday (Sun = 0, ..., Sat = 6)
* @property {number=} Timestamp.hour The numeric hour
* @property {number} Timestamp.minute The numeric minute
* @property {number=} Timestamp.doy The numeric day of the year (doy)
* @property {number=} Timestamp.workweek The numeric workweek
* @property {boolean} Timestamp.hasDay True if Timestamp.date is filled in and usable
* @property {boolean} Timestamp.hasTime True if Timestamp.time is filled in and usable
* @property {boolean=} Timestamp.past True if the Timestamp is in the past
* @property {boolean=} Timestamp.current True if Timestamp is current day (now)
* @property {boolean=} Timestamp.future True if Timestamp is in the future
* @property {boolean=} Timestamp.disabled True if this is a disabled date
* @property {boolean=} Timestamp.currentWeekday True if this date corresponds to current weekday
*/
// export const Timestamp = {
// date: '', // YYYY-MM-DD
// time: '', // HH:MM (optional)
// year: 0, // YYYY
// month: 0, // MM (Jan = 1, etc)
// day: 0, // day of the month
// weekday: 0, // week day (0=Sunday...6=Saturday)
// hour: 0, // 24-hr format
// minute: 0, // mm
// doy: 0, // day of year
// workweek: 0, // workweek number
// hasDay: false, // if this timestamp is supposed to have a date
// hasTime: false, // if this timestamp is supposed to have a time
// past: false, // if timestamp is in the past (based on `now` property)
// current: false, // if timestamp is current date (based on `now` property)
// future: false, // if timestamp is in the future (based on `now` property)
// disabled: false, // if timestamp is disabled
// currentWeekday: false, // if this date corresponds to current weekday
// }
// export const TimeObject = {
// hour: 0, // Number
// minute: 0, // Number
// }
export interface Timestamp {
date: string // YYYY-MM-DD
hasDay: boolean // if this timestamp is supposed to have a date
year: number // YYYY
month: number // MM (Jan = 1, etc)
day: number // day of the month
time?: string // HH:MM (optional)
hasTime: boolean // if this timestamp is supposed to have a time
hour: number // 24-hr format
minute: number // mm
weekday?: number // week day (0=Sunday...6=Saturday)
doy?: number // day of year
workweek?: number // workweek number
past?: boolean // if timestamp is in the past (based on `now` property)
current?: boolean // if timestamp is current date (based on `now` property)
future?: boolean // if timestamp is in the future (based on `now` property)
disabled?: boolean // if timestamp is disabled
currentWeekday?: boolean // if this date corresponds to current weekday
}
export interface TimeObject {
hour: number // Number
minute: number // Number
}
/**
* Validates the passed input ('YYY-MM-DD') as a date or ('YYY-MM-DD HH:MM') date time combination
* @param {string} input A string in the form 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM'
* @returns {boolean} True if parseable
*/
export function validateTimestamp(input: string): boolean {
if (typeof input !== 'string') return false
return PARSE_DATETIME.test(input)
}
/**
* Fast low-level parser for a date string ('YYYY-MM-DD'). Does not update formatted or relative date.
* Use 'parseTimestamp' for formatted and relative updates
* @param {string} input In the form 'YYYY-MM-DD hh:mm:ss' (seconds are optional, but not used)
* @returns {Timestamp} This {@link Timestamp} is minimally filled in. The {@link Timestamp.date} and {@link Timestamp.time} as well as relative data will not be filled in.
*/
export function parsed(input: string): Timestamp | null {
if (typeof input !== 'string') return null
const parts = PARSE_DATETIME.exec(input)
if (!parts || !parts[1] || !parts[2]) return null
const year = parseInt(parts[1], 10)
const month = parseInt(parts[2], 10)
const day = parseInt(parts[4] || '1', 10)
const hour = parseInt(parts[6] || '0', 10)
const minute = parseInt(parts[8] || '0', 10)
return {
date: input,
time: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`,
year,
month,
day,
hour,
minute,
hasDay: !!parts[4],
hasTime: true, // time is always present, even if '00:00'
past: false,
current: false,
future: false,
disabled: false,
weekday: 0,
doy: 0,
workweek: 0,
}
}
/**
* Takes a JavaScript Date and returns a {@link Timestamp}. The {@link Timestamp} is not updated with relative information.
* @param {Date} date JavaScript Date
* @param {boolean} utc If set the {@link Timestamp} will parse the Date as UTC
* @returns {Timestamp} A minimal {@link Timestamp} without updated or relative updates.
*/
export function parseDate(date: Date, utc = false): Timestamp | null {
if (!(date instanceof Date)) return null
const UTC = utc ? 'UTC' : ''
return updateFormatted({
date:
padNumber(date[`get${UTC}FullYear`](), 4) +
'-' +
padNumber(date[`get${UTC}Month`]() + 1, 2) +
'-' +
padNumber(date[`get${UTC}Date`](), 2),
time:
padNumber(date[`get${UTC}Hours`]() || 0, 2) +
':' +
padNumber(date[`get${UTC}Minutes`]() || 0, 2),
year: date[`get${UTC}FullYear`](),
month: date[`get${UTC}Month`]() + 1,
day: date[`get${UTC}Date`](),
hour: date[`get${UTC}Hours`](),
minute: date[`get${UTC}Minutes`](),
weekday: 0,
doy: 0,
workweek: 0,
hasDay: true,
hasTime: true, // Date always has time, even if it is '00:00'
past: false,
current: false,
future: false,
disabled: false,
} as Timestamp)
}
/**
* Padds a passed in number to length (converts to a string). Good for converting '5' as '05'.
* @param {number} x The number to pad
* @param {number} length The length of the required number as a string
* @returns {string} The padded number (as a string). (ie: 5 = '05')
*/
export function padNumber(x: number, length: number): string {
let padded = String(x)
while (padded.length < length) {
padded = '0' + padded
}
return padded
}
/**
* Returns if the passed year is a leap year
* @param {number} year The year to check (ie: 1999, 2020)
* @returns {boolean} True if the year is a leap year
*/
export function isLeapYear(year: number): boolean {
// A year is a Gregorian leap year if it is divisible by 4,
// but not by 100, unless it is also divisible by 400.
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
}
/**
* Returns the days of the specified month in a year
* @param {number} year The year (ie: 1999, 2020)
* @param {number} month The month (zero-based)
* @returns {number} The number of days in the month (corrected for leap years)
*/
export function daysInMonth(year: number, month: number): number {
return (isLeapYear(year) ? DAYS_IN_MONTH_LEAP[month] : DAYS_IN_MONTH[month]) as number
}
/**
* Returns a {@link Timestamp} of next day from passed in {@link Timestamp}
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @returns {Timestamp} The modified {@link Timestamp} as the next day
*/
export function nextDay(timestamp: Timestamp): Timestamp {
const date = new Date(timestamp.year, timestamp.month - 1, timestamp.day + 1)
return updateFormatted(
normalizeTimestamp({
...timestamp,
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate(),
}),
)
}
/**
* Returns a {@link Timestamp} of previous day from passed in {@link Timestamp}
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @returns {Timestamp} The modified {@link Timestamp} as the previous day
*/
export function prevDay(timestamp: Timestamp): Timestamp {
const date = new Date(timestamp.year, timestamp.month - 1, timestamp.day - 1)
return updateFormatted(
normalizeTimestamp({
...timestamp,
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate(),
}),
)
}
/**
* Returns today's date
* @returns {string} Date string in the form 'YYYY-MM-dd'
*/
export function today(): string {
const d = new Date(),
month = d.getMonth() + 1,
day = d.getDate(),
year = d.getFullYear()
return [year, padNumber(month, 2), padNumber(day, 2)].join('-')
}
/**
* Takes a date string ('YYYY-MM-DD') and validates if it is today's date
* @param {string} date Date string in the form 'YYYY-MM-DD'
* @returns {boolean} True if the date is today's date
*/
export function isToday(date: string): boolean {
return date === today()
}
/**
* Returns the start of the week give a {@link Timestamp} and weekdays (in which it finds the day representing the start of the week).
* If today {@link Timestamp} is passed in then this is used to update relative information in the returned {@link Timestamp}.
* @param {Timestamp} timestamp The {@link Timestamp} to use to find the start of the week
* @param {number[]} weekdays The array is [0,1,2,3,4,5,6] where 0=Sunday and 6=Saturday
* @param {Timestamp=} today If passed in then the {@link Timestamp} is updated with relative information
* @returns {Timestamp} The {@link Timestamp} representing the start of the week
*/
export function getStartOfWeek(
timestamp: Timestamp,
weekdays: number[],
today: Timestamp,
): Timestamp {
let start = copyTimestamp(timestamp)
if (!weekdays) {
return start
}
if (start.day === 1 || start.weekday === 0) {
while (!weekdays.includes(Number(start.weekday))) {
start = nextDay(start)
}
}
start = findWeekday(start, weekdays[0], prevDay)
start = updateFormatted(start)
if (today) {
start = updateRelative(start, today, start.hasTime)
}
return start
}
/**
* Returns the end of the week give a {@link Timestamp} and weekdays (in which it finds the day representing the last of the week).
* If today {@link Timestamp} is passed in then this is used to update relative information in the returned {@link Timestamp}.
* @param {Timestamp} timestamp The {@link Timestamp} to use to find the end of the week
* @param {number[]} weekdays The array is [0,1,2,3,4,5,6] where 0=Sunday and 6=Saturday
* @param {Timestamp=} today If passed in then the {@link Timestamp} is updated with relative information
* @returns {Timestamp} The {@link Timestamp} representing the end of the week
*/
export function getEndOfWeek(
timestamp: Timestamp,
weekdays: number[],
today: Timestamp,
): Timestamp {
let end = copyTimestamp(timestamp)
if (!weekdays || !Array.isArray(weekdays)) {
return end
}
// is last day of month?
const lastDay = daysInMonth(end.year, end.month)
if (lastDay === end.day || end.weekday === weekdays[weekdays.length - 1]) {
while (!weekdays.includes(Number(end.weekday))) {
end = prevDay(end)
}
}
end = findWeekday(end, weekdays[weekdays.length - 1]!, nextDay)
end = updateFormatted(end)
if (today) {
end = updateRelative(end, today, end.hasTime)
}
return end
}
/**
* Finds the start of the month based on the passed in {@link Timestamp}
* @param {Timestamp} timestamp The {@link Timestamp} to use to find the start of the month
* @returns {Timestamp} A {@link Timestamp} of the start of the month
*/
export function getStartOfMonth(timestamp: Timestamp): Timestamp {
let start = copyTimestamp(timestamp)
start.day = DAY_MIN
start = updateFormatted(start)
return start
}
/**
* Finds the end of the month based on the passed in {@link Timestamp}
* @param {Timestamp} timestamp The {@link Timestamp} to use to find the end of the month
* @returns {Timestamp} A {@link Timestamp} of the end of the month
*/
export function getEndOfMonth(timestamp: Timestamp): Timestamp {
let end = copyTimestamp(timestamp)
end.day = daysInMonth(end.year, end.month)
end = updateFormatted(end)
return end
}
// returns minutes since midnight
export function parseTime(
input: number | string | { hour: number; minute: number },
): number | false {
const type = Object.prototype.toString.call(input)
switch (type) {
case '[object Number]':
// when a number is given, it's minutes since 12:00am
return input as number
case '[object String]': {
// when a string is given, it's a hh:mm:ss format where seconds are optional, but not used
const parts = PARSE_TIME.exec(input as string)
if (!parts) {
return false
}
return parseInt(parts[1]!, 10) * 60 + parseInt(parts[3] || '0', 10)
}
case '[object Object]':
// when an object is given, it must have hour and minute
if (
typeof input !== 'object' ||
typeof input.hour !== 'number' ||
typeof input.minute !== 'number'
) {
return false
}
if (typeof input === 'object' && 'hour' in input && 'minute' in input) {
return input.hour * 60 + input.minute
}
return false
}
return false
}
/**
* Compares two {@link Timestamp}s for exactness
* @param {Timestamp} ts1 The first {@link Timestamp}
* @param {Timestamp} ts2 The second {@link Timestamp}
* @returns {boolean} True if the two {@link Timestamp}s are an exact match
*/
export function compareTimestamps(ts1: Timestamp, ts2: Timestamp): boolean {
if (!ts1 || !ts2) return false
return (
ts1.year === ts2.year &&
ts1.month === ts2.month &&
ts1.day === ts2.day &&
ts1.hour === ts2.hour &&
ts1.minute === ts2.minute
)
}
/**
* Compares the date of two {@link Timestamp}s that have been updated with relative data
* @param {Timestamp} ts1 The first {@link Timestamp}
* @param {Timestamp} ts2 The second {@link Timestamp}
* @returns {boolean} True if the two dates are the same
*/
export function compareDate(ts1: Timestamp, ts2: Timestamp): boolean {
return getDate(ts1) === getDate(ts2)
}
/**
* Compares the time of two {@link Timestamp}s that have been updated with relative data
* @param {Timestamp} ts1 The first {@link Timestamp}
* @param {Timestamp} ts2 The second {@link Timestamp}
* @returns {boolean} True if the two times are an exact match
*/
export function compareTime(ts1: Timestamp, ts2: Timestamp): boolean {
return getTime(ts1) === getTime(ts2)
}
/**
* Compares the date and time of two {@link Timestamp}s that have been updated with relative data
* @param {Timestamp} ts1 The first {@link Timestamp}
* @param {Timestamp} ts2 The second {@link Timestamp}
* @returns {boolean} True if the date and time are an exact match
*/
export function compareDateTime(ts1: Timestamp, ts2: Timestamp): boolean {
return getDateTime(ts1) === getDateTime(ts2)
}
/**
* High-level parser that converts the passed in string to {@link Timestamp} and uses 'now' to update relative information.
* @param {string} input In the form 'YYYY-MM-DD hh:mm:ss' (seconds are optional, but not used)
* @param {Timestamp} now A {@link Timestamp} to use for relative data updates
* @returns {Timestamp} The {@link Timestamp.date} will be filled in as well as the {@link Timestamp.time} if a time is supplied and formatted fields (doy, weekday, workweek, etc). If 'now' is supplied, then relative data will also be updated.
*/
export function parseTimestamp(input: string, now: Timestamp | null = null): Timestamp | null {
let timestamp = parsed(input)
if (!timestamp) return null
timestamp = updateFormatted(timestamp as Timestamp)
if (now) {
timestamp = updateRelative(timestamp as Timestamp, now, timestamp.hasTime)
}
return timestamp as Timestamp
}
/**
* Converts a {@link Timestamp} into a numeric date identifier based on the passed {@link Timestamp}'s date
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @returns {number} The numeric date identifier
*/
export function getDayIdentifier(timestamp: Timestamp): number {
return (
(timestamp.year ?? 0) * 100000000 +
(timestamp.month ?? 0) * 1000000 +
(timestamp.day ?? 0) * 10000
)
}
/**
* Converts a {@link Timestamp} into a numeric time identifier based on the passed {@link Timestamp}'s time
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @returns {number} The numeric time identifier
*/
export function getTimeIdentifier(timestamp: Timestamp): number {
return (timestamp.hour ?? 0) * 100 + (timestamp.minute ?? 0)
}
/**
* Converts a {@link Timestamp} into a numeric date and time identifier based on the passed {@link Timestamp}'s date and time
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @returns {number} The numeric date+time identifier
*/
export function getDayTimeIdentifier(timestamp: Timestamp): number {
return getDayIdentifier(timestamp) + getTimeIdentifier(timestamp)
}
/**
* Returns the difference between two {@link Timestamp}s
* @param {Timestamp} ts1 The first {@link Timestamp}
* @param {Timestamp} ts2 The second {@link Timestamp}
* @param {boolean=} strict Optional flag to not to return negative numbers
* @returns {number} The difference
*/
export function diffTimestamp(ts1: Timestamp, ts2: Timestamp, strict = false): number {
const utc1 = Date.UTC(
ts1.year ?? 0,
(ts1.month ?? 1) - 1,
ts1.day ?? 1,
ts1.hour ?? 0,
ts1.minute ?? 0,
)
const utc2 = Date.UTC(
ts2.year ?? 0,
(ts2.month ?? 1) - 1,
ts2.day ?? 1,
ts2.hour ?? 0,
ts2.minute ?? 0,
)
if (strict === true && utc2 < utc1) {
// Not negative number
// utc2 - utc1 < 0 -> utc2 < utc1 -> NO: utc1 >= utc2
return 0
}
return utc2 - utc1
}
/**
* Updates a {@link Timestamp} with relative data (past, current and future)
* @param {Timestamp} timestamp The {@link Timestamp} that needs relative data updated
* @param {Timestamp} now {@link Timestamp} that represents the current date (optional time)
* @param {boolean=} time Optional flag to include time ('timestamp' and 'now' params should have time values)
* @returns {Timestamp} A new {@link Timestamp}
*/
export function updateRelative(timestamp: Timestamp, now: Timestamp, time = false): Timestamp {
let ts = copyTimestamp(timestamp as Timestamp)
let a = getDayIdentifier(now)
let b = getDayIdentifier(ts)
let current = a === b
if (ts.hasTime && time && current) {
a = getTimeIdentifier(now)
b = getTimeIdentifier(ts)
current = a === b
}
ts.past = b < a
ts.current = current
ts.future = b > a
ts.currentWeekday = ts.weekday === now.weekday
return ts
}
/**
* Sets a Timestamp{@link Timestamp} to number of minutes past midnight (modifies hour and minutes if needed)
* @param {Timestamp} timestamp The {@link Timestamp} to modify
* @param {number} minutes The number of minutes to set from midnight
* @param {Timestamp=} now Optional {@link Timestamp} representing current date and time
* @returns {Timestamp} A new {@link Timestamp}
*/
export function updateMinutes(
timestamp: Timestamp,
minutes: number,
now: Timestamp | null = null,
): Timestamp {
let ts = copyTimestamp(timestamp)
ts.hasTime = true
ts.hour = Math.floor(minutes / TIME_CONSTANTS.MINUTES_IN.HOUR)
ts.minute = minutes % TIME_CONSTANTS.MINUTES_IN.HOUR
ts.time = getTime(ts)
if (now) {
ts = updateRelative(ts, now, true)
}
return ts
}
/**
* Updates the {@link Timestamp} with the weekday
* @param {Timestamp} timestamp The {@link Timestamp} to modify
* @returns A new Timestamp
*/
export function updateWeekday(timestamp: Timestamp): Timestamp {
let ts = copyTimestamp(timestamp)
ts.weekday = getWeekday(ts)
return ts
}
/**
* Updates the {@link Timestamp} with the day of the year (doy)
* @param {Timestamp} timestamp The {@link Timestamp} to modify
* @returns A new Timestamp
*/
export function updateDayOfYear(timestamp: Timestamp): Timestamp {
let ts = copyTimestamp(timestamp)
ts.doy = getDayOfYear(ts) || 0
return ts
}
/**
* Updates the {@link Timestamp} with the workweek
* @param {Timestamp} timestamp The {@link Timestamp} to modify
* @returns A new {@link Timestamp}
*/
export function updateWorkWeek(timestamp: Timestamp): Timestamp {
let ts = copyTimestamp(timestamp)
ts.workweek = getWorkWeek(ts)
return ts
}
/**
* Updates the passed {@link Timestamp} with disabled, if needed
* @param {Timestamp} timestamp The {@link Timestamp} to modify
* @param {string} [disabledBefore] In 'YYY-MM-DD' format
* @param {string} [disabledAfter] In 'YYY-MM-DD' format
* @param {number[]} [disabledWeekdays] An array of numbers representing weekdays [0 = Sun, ..., 6 = Sat]
* @param {string[]|string[][]} [disabledDays] An array of days in 'YYYY-MM-DD' format. If an array with a pair of dates is in first array, then this is treated as a range.
* @returns A new {@link Timestamp}
*/
export function updateDisabled(
timestamp: Timestamp,
disabledBefore?: string,
disabledAfter?: string,
disabledWeekdays?: number[],
disabledDays?: string[] | string[][],
): Timestamp {
let ts = copyTimestamp(timestamp)
const t = getDayIdentifier(ts)
if (disabledBefore !== undefined) {
const disabledDay = parsed(disabledBefore)
if (disabledDay) {
const before = getDayIdentifier(disabledDay)
if (t <= before) {
ts.disabled = true
}
}
}
if (ts.disabled !== true && disabledAfter !== undefined) {
const disabledDay = parsed(disabledAfter!)
if (disabledDay) {
const after = getDayIdentifier(disabledDay)
if (t >= after) {
ts.disabled = true
}
}
}
if (ts.disabled !== true && Array.isArray(disabledWeekdays) && disabledWeekdays.length > 0) {
for (const weekday in disabledWeekdays) {
if (disabledWeekdays[weekday] === ts.weekday) {
ts.disabled = true
break
}
}
}
if (ts.disabled !== true && Array.isArray(disabledDays) && disabledDays.length > 0) {
for (const day in disabledDays) {
if (
Array.isArray(disabledDays[day]) &&
disabledDays[day].length === 2 &&
disabledDays[day][0] &&
disabledDays[day][1]
) {
const start = parsed(disabledDays[day][0])
const end = parsed(disabledDays[day][1])
if (start && end && isBetweenDates(ts, start, end)) {
ts.disabled = true
break
}
} else {
const disabledDayOrRange = disabledDays[day]
// handle ranges with multiple days
if (Array.isArray(disabledDayOrRange)) {
for (const range of disabledDayOrRange) {
const disabledDay = parseTimestamp(range)
if (disabledDay) {
const d = getDayIdentifier(disabledDay)
if (d === t) {
ts.disabled = true
break
}
}
}
} else if (disabledDayOrRange) {
const disabledDay = parseTimestamp(disabledDayOrRange)
if (disabledDay) {
const d = getDayIdentifier(disabledDay)
if (d === t) {
ts.disabled = true
}
}
}
}
}
}
return ts
}
/**
* Updates the passed {@link Timestamp} with formatted data (time string, date string, weekday, day of year and workweek)
* @param {Timestamp} timestamp The {@link Timestamp} to modify
* @returns A new {@link Timestamp}
*/
export function updateFormatted(timestamp: Timestamp): Timestamp {
let ts = copyTimestamp(timestamp)
ts.hasTime = true
ts.time = getTime(ts)
ts.date = getDate(ts)
ts.weekday = getWeekday(ts)
ts.doy = getDayOfYear(ts) || 0
ts.workweek = getWorkWeek(ts)
return ts
}
/**
* Returns day of the year (doy) for the passed in {@link Timestamp}
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @returns {number} The day of the year
*/
export function getDayOfYear(timestamp: Timestamp): number | void {
if (timestamp.year === 0) return
return (
(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day) -
Date.UTC(timestamp.year, 0, 0)) /
24 /
60 /
60 /
1000
)
}
/**
* Returns workweek for the passed in {@link Timestamp}
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @returns {number} The work week
*/
export function getWorkWeek(timestamp: Timestamp): number {
let ts = copyTimestamp(timestamp)
if (ts.year === 0) {
const parsedToday = parseTimestamp(today())
if (parsedToday) {
ts = parsedToday as Timestamp
}
}
// Remove time components of date
const weekday = new Date(Date.UTC(ts.year, ts.month - 1, ts.day))
// Adjust the date to the correct day of the week
const dayAdjustment = 4 // thursday is 4
weekday.setUTCDate(weekday.getUTCDate() - ((weekday.getUTCDay() + 6) % 7) + dayAdjustment)
// Set to nearest Thursday: current date + 4 - current day number
// Make Sunday's day number 7
weekday.setUTCDate(weekday.getUTCDate() + dayAdjustment - (weekday.getUTCDay() || 7))
// Get first day of year
var yearStart = new Date(Date.UTC(weekday.getUTCFullYear(), 0, 1))
// Calculate full weeks to nearest Thursday
var weekNumber = Math.ceil(((weekday.valueOf() - yearStart.valueOf()) / 86400000 + 1) / 7)
return weekNumber
}
/**
* Returns weekday for the passed in {@link Timestamp}
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @returns {number} The weekday
*/
export function getWeekday(timestamp: Timestamp): number {
let weekday = timestamp.weekday
if (timestamp.hasDay) {
const floor = Math.floor
const day = timestamp.day
const month = ((timestamp.month + 9) % MONTH_MAX) + 1
const century = floor(timestamp.year / 100)
const year = (timestamp.year % 100) - (timestamp.month <= 2 ? 1 : 0)
weekday =
(((day +
floor(2.6 * month - 0.2) -
2 * century +
year +
floor(year / 4) +
floor(century / 4)) %
7) +
7) %
7
}
return weekday ?? 0
}
/**
* Makes a copy of the passed in {@link Timestamp}
* @param {Timestamp} timestamp The original {@link Timestamp}
* @returns {Timestamp} A copy of the original {@link Timestamp}
*/
export function copyTimestamp(timestamp: Timestamp): Timestamp {
return { ...timestamp }
}
/**
* Used internally to convert {@link Timestamp} used with 'parsed' or 'parseDate' so the 'date' portion of the {@link Timestamp} is correct.
* @param {Timestamp} timestamp The (raw) {@link Timestamp}
* @returns {string} A formatted date ('YYYY-MM-DD')
*/
export function getDate(timestamp: Timestamp): string {
let str = `${padNumber(timestamp.year, 4)}-${padNumber(timestamp.month, 2)}`
if (timestamp.hasDay) str += `-${padNumber(timestamp.day, 2)}`
return str
}
/**
* Used intenally to convert {@link Timestamp} with 'parsed' or 'parseDate' so the 'time' portion of the {@link Timestamp} is correct.
* @param {Timestamp} timestamp The (raw) {@link Timestamp}
* @returns {string} A formatted time ('hh:mm')
*/
export function getTime(timestamp: Timestamp): string {
if (!timestamp.hasTime) {
return ''
}
return `${padNumber(timestamp.hour, 2)}:${padNumber(timestamp.minute, 2)}`
}
/**
* Returns a formatted string date and time ('YYYY-YY-MM hh:mm')
* @param {Timestamp} timestamp The {@link Timestamp}
* @returns {string} A formatted date time ('YYYY-MM-DD HH:mm')
*/
export function getDateTime(timestamp: Timestamp): string {
return getDate(timestamp) + ' ' + (timestamp.hasTime ? getTime(timestamp) : '00:00')
}
/**
* An alias for {relativeDays}
* @param {Timestamp} timestamp The {@link Timestamp} to modify
* @param {function} [mover=nextDay] The mover function to use (ie: {nextDay} or {prevDay}).
* @param {number} [days=1] The number of days to move.
* @param {number[]} [allowedWeekdays=[ 0, 1, 2, 3, 4, 5, 6 ]] An array of numbers representing the weekdays. ie: [0 = Sun, ..., 6 = Sat].
* @returns The modified {@link Timestamp}
*/
export function moveRelativeDays(
timestamp: Timestamp,
mover = nextDay,
days = 1,
allowedWeekdays = [0, 1, 2, 3, 4, 5, 6],
): Timestamp {
const ts = copyTimestamp(timestamp)
return relativeDays(ts, mover, days, allowedWeekdays)
}
/**
* Moves the {@link Timestamp} the number of relative days
* @param {Timestamp} timestamp The {@link Timestamp} to modify
* @param {function} [mover=nextDay] The mover function to use (ie: {nextDay} or {prevDay}).
* @param {number} [days=1] The number of days to move.
* @param {number[]} [allowedWeekdays=[ 0, 1, 2, 3, 4, 5, 6 ]] An array of numbers representing the weekdays. ie: [0 = Sun, ..., 6 = Sat].
* @returns A new {@link Timestamp}
*/
export function relativeDays(
timestamp: Timestamp,
mover = nextDay,
days = 1,
allowedWeekdays = [0, 1, 2, 3, 4, 5, 6],
): Timestamp {
let ts = copyTimestamp(timestamp)
if (!allowedWeekdays.includes(Number(ts.weekday)) && ts.weekday === 0 && mover === nextDay) {
++days
}
while (--days >= 0) {
ts = mover(ts)
if (allowedWeekdays.length < 7 && !allowedWeekdays.includes(Number(ts.weekday))) {
++days
}
}
return ts
}
/**
* Finds the specified weekday (forward or back) based on the {@link Timestamp}
* @param {Timestamp} timestamp The {@link Timestamp} to modify
* @param {number} weekday The weekday number (Sun = 0, ..., Sat = 6)
* @param {function} [mover=nextDay] The function to use ({prevDay} or {nextDay}).
* @param {number} [maxDays=6] The number of days to look forward or back.
* @returns A new {@link Timestamp}
*/
export function findWeekday(
timestamp: Timestamp,
weekday: number,
mover = nextDay,
maxDays = 6,
): Timestamp {
let ts = copyTimestamp(timestamp)
while (ts.weekday !== weekday && --maxDays >= 0) ts = mover(ts)
return ts
}
/**
* Creates an array of {@link Timestamp}s based on start and end params
* @param {Timestamp} start The starting {@link Timestamp}
* @param {Timestamp} end The ending {@link Timestamp}
* @param {Timestamp} now The relative day
* @param {number[]} weekdays An array of numbers (representing days of the week) that are 0 (=Sunday) to 6 (=Saturday)
* @param {string} [disabledBefore] Days before this date are disabled (YYYY-MM-DD)
* @param {string} [disabledAfter] Days after this date are disabled (YYYY-MM-DD)
* @param {number[]} [disabledWeekdays] An array representing weekdays that are disabled [0 = Sun, ..., 6 = Sat]
* @param {string[]} [disabledDays] An array of days in 'YYYY-MM-DD' format. If an array with a pair of dates is in first array, then this is treated as a range.
* @param {number} [max=42] Max days to do
* @param {number} [min=0] Min days to do
* @returns {Timestamp[]} The requested array of {@link Timestamp}s
*/
export function createDayList(
start: Timestamp,
end: Timestamp,
now: Timestamp,
weekdays: number[] = [0, 1, 2, 3, 4, 5, 6],
disabledBefore: string | undefined = undefined,
disabledAfter: string | undefined = undefined,
disabledWeekdays: number[] = [],
disabledDays: string[] = [],
max = 42,
min = 0,
): Timestamp[] {
const begin = getDayIdentifier(start)
const stop = getDayIdentifier(end)
const days: Timestamp[] = []
let current = copyTimestamp(start)
let currentIdentifier = 0
let stopped = currentIdentifier === stop
if (stop < begin) {
return days
}
while ((!stopped || days.length < min) && days.length < max) {
currentIdentifier = getDayIdentifier(current)
stopped = stopped || (currentIdentifier > stop && days.length >= min)
if (stopped) {
break
}
if (!weekdays.includes(Number(current.weekday))) {
current = relativeDays(current, nextDay)
continue
}
let day = copyTimestamp(current)
day = updateFormatted(day)
day = updateRelative(day, now)
day = updateDisabled(day, disabledBefore, disabledAfter, disabledWeekdays, disabledDays)
days.push(day)
current = relativeDays(current, nextDay)
}
return days
}
/**
* Creates an array of interval {@link Timestamp}s based on params
* @param {Timestamp} timestamp The starting {@link Timestamp}
* @param {number} first The starting interval time
* @param {number} minutes How many minutes between intervals (ie: 60, 30, 15 would be common ones)
* @param {number} count The number of intervals needed
* @param {Timestamp} now A relative {@link Timestamp} with time
* @returns {Timestamp[]} The requested array of interval {@link Timestamp}s
*/
export function createIntervalList(
timestamp: Timestamp,
first: number,
minutes: number,
count: number,
now: Timestamp,
): Timestamp[] {
const intervals = []
for (let i = 0; i < count; ++i) {
const mins = (first + i) * minutes
const ts = copyTimestamp(timestamp)
intervals.push(updateMinutes(ts, mins, now))
}
return intervals
}
export type LocaleFormatter = (_timestamp: Timestamp, _short: boolean) => Intl.DateTimeFormatOptions
export type WeekdayFormatter = (
_weekday: keyof typeof weekdayDateMap,
_type: string,
_locale?: string,
) => string
export type MonthFormatter = (_month: number, _type: string, _locale?: string) => string
/**
* @callback getOptions
* @param {Timestamp} timestamp A {@link Timestamp} object
* @param {boolean} short True if using short options
* @returns {Object} An Intl object representing optioons to be used
*/
/**
* @callback formatter
* @param {Timestamp} timestamp The {@link Timestamp} being used
* @param {boolean} short If short format is being requested
* @returns {string} The localized string of the formatted {@link Timestamp}
*/
/**
* Returns a function that uses Intl.DateTimeFormat formatting
* @param {string} locale The locale to use (ie: en-US)
* @param {getOptions} cb The function to call for options. This function should return an Intl formatted object. The function is passed (timestamp, short).
* @returns {formatter} The function has params (timestamp, short). The short is to use the short options.
*/
export function createNativeLocaleFormatter(
locale: string,
cb: LocaleFormatter,
): (_timestamp: Timestamp, _short: boolean) => string {
const emptyFormatter = (): string => ''
/* istanbul ignore next */
if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') {
return emptyFormatter
}
return (timestamp: Timestamp, short: boolean): string => {
try {
const intlFormatter = new Intl.DateTimeFormat(locale || undefined, cb(timestamp, short))
return intlFormatter.format(makeDateTime(timestamp))
} catch (e) /* istanbul ignore next */ {
console.error(`Intl.DateTimeFormat: ${(e as Error).message} -> ${getDateTime(timestamp)}`)
return ''
}
}
}
/**
* Makes a JavaScript Date from the passed {@link Timestamp}
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @param {boolean} utc True to get Date object using UTC
* @returns {Date} A JavaScript Date
*/
export function makeDate(timestamp: Timestamp, utc = true): Date {
if (utc) return new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day, 0, 0))
return new Date(timestamp.year, timestamp.month - 1, timestamp.day, 0, 0)
}
/**
* Makes a JavaScript Date from the passed {@link Timestamp} (with time)
* @param {Timestamp} timestamp The {@link Timestamp} to use
* @param {boolean} utc True to get Date object using UTC
* @returns {Date} A JavaScript Date
*/
export function makeDateTime(timestamp: Timestamp, utc = true): Date {
if (utc)
return new Date(
Date.UTC(
timestamp.year,
timestamp.month - 1,
timestamp.day,
timestamp.hour,
timestamp.minute,
),
)
return new Date(
timestamp.year,
timestamp.month - 1,
timestamp.day,
timestamp.hour,
timestamp.minute,
)
}
/**
* Validates if the input is a finite number.
*
* @param input - The value to be validated. Can be a string or a number.
* @returns A boolean indicating whether the input is a finite number.
* Returns true if the input is a finite number, false otherwise.
*/
export function validateNumber(input: string | number): boolean {
return isFinite(Number(input))
}
/**
* Given an array of {@link Timestamp}s, finds the max date (and possible time)
* @param {Timestamp[]} timestamps This is an array of {@link Timestamp}s
* @param {boolean=} useTime Default false; if true, uses time in the comparison as well
* @returns The {@link Timestamp} with the highest date (and possibly time) value
*/
export function maxTimestamp(timestamps: Timestamp[], useTime = false): Timestamp {
const func = useTime === true ? getDayTimeIdentifier : getDayIdentifier
return timestamps.reduce((prev, cur) => {
return Math.max(func(prev), func(cur)) === func(prev) ? prev : cur
})
}
/**
* Given an array of {@link Timestamp}s, finds the min date (and possible time)
* @param {Timestamp[]} timestamps This is an array of {@link Timestamp}s
* @param {boolean=} useTime Default false; if true, uses time in the comparison as well
* @returns The {@link Timestamp} with the lowest date (and possibly time) value
*/
export function minTimestamp(timestamps: Timestamp[], useTime = false): Timestamp {
const func = useTime === true ? getDayTimeIdentifier : getDayIdentifier
return timestamps.reduce((prev, cur) => {
return Math.min(func(prev), func(cur)) === func(prev) ? prev : cur
})
}
/**
* Determines if the passed {@link Timestamp} is between (or equal) to two {@link Timestamp}s (range)
* @param {Timestamp} timestamp The {@link Timestamp} for testing
* @param {Timestamp} startTimestamp The starting {@link Timestamp}
* @param {Timestamp} endTimestamp The ending {@link Timestamp}
* @param {boolean=} useTime If true, use time from the {@link Timestamp}s
* @returns {boolean} True if {@link Timestamp} is between (or equal) to two {@link Timestamp}s (range)
*/
export function isBetweenDates(
timestamp: Timestamp,
startTimestamp: Timestamp,
endTimestamp: Timestamp,
useTime = false,
): boolean {
const cd = getDayIdentifier(timestamp) + (useTime === true ? getTimeIdentifier(timestamp) : 0)
const sd =
getDayIdentifier(startTimestamp) + (useTime === true ? getTimeIdentifier(startTimestamp) : 0)
const ed =
getDayIdentifier(endTimestamp) + (useTime === true ? getTimeIdentifier(endTimestamp) : 0)
return cd >= sd && cd <= ed
}
/**
* Determine if two ranges of {@link Timestamp}s overlap each other
* @param {Timestamp} startTimestamp The starting {@link Timestamp} of first range
* @param {Timestamp} endTimestamp The endinging {@link Timestamp} of first range
* @param {Timestamp} firstTimestamp The starting {@link Timestamp} of second range
* @param {Timestamp} lastTimestamp The ending {@link Timestamp} of second range
* @returns {boolean} True if the two ranges overlap each other
*/
export function isOverlappingDates(
startTimestamp: Timestamp,
endTimestamp: Timestamp,
firstTimestamp: Timestamp,
lastTimestamp: Timestamp,
): boolean {
const start = getDayIdentifier(startTimestamp)
const end = getDayIdentifier(endTimestamp)
const first = getDayIdentifier(firstTimestamp)
const last = getDayIdentifier(lastTimestamp)
return (
(start >= first && start <= last) || // overlap left
(end >= first && end <= last) || // overlap right
(first >= start && end >= last) // surrounding
)
}
export interface AddToDateOptions {
year?: number
month?: number
day?: number
hour?: number
minute?: number
}
/**
* Add or decrements years, months, days, hours or minutes to a timestamp
* @param {Timestamp} timestamp The {@link Timestamp} object
* @param {Object} options configuration data
* @param {number=} options.year If positive, adds years. If negative, removes years.
* @param {number=} options.month If positive, adds months. If negative, removes month.
* @param {number=} options.day If positive, adds days. If negative, removes days.
* @param {number=} options.hour If positive, adds hours. If negative, removes hours.
* @param {number=} options.minute If positive, adds minutes. If negative, removes minutes.
* @returns {Timestamp} A modified copy of the passed in {@link Timestamp}
*/
export function addToDate(timestamp: Timestamp, options: AddToDateOptions): Timestamp {
const ts = copyTimestamp(timestamp)
if (options.year) ts.year += options.year
if (options.month) ts.month += options.month
if (options.day) ts.day += options.day
if (options.hour) ts.hour += options.hour
if (options.minute) ts.minute += options.minute
return updateFormatted(normalizeTimestamp(ts))
}
/**
* Normalizes a timestamp object by creating a JavaScript Date object and extracting standardized values.
* This function ensures that the timestamp values are consistent and correctly represent a valid date and time.
*
* @param {Object} ts - The timestamp object to normalize.
* @param {number} ts.year - The year of the timestamp.
* @param {number} ts.month - The month of the timestamp (1-12).
* @param {number} ts.day - The day of the month.
* @param {number} ts.hour - The hour of the day (0-23).
* @param {number} ts.minute - The minute of the hour (0-59).
* @returns {Object} A new object with normalized timestamp values.
* The returned object includes all properties from the input object,
* with year, month, day, hour, and minute properties updated to normalized values.
*/
function normalizeTimestamp(ts: Timestamp): Timestamp {
const date = new Date(ts.year, ts.month - 1, ts.day, ts.hour, ts.minute)
return {
...ts,
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate(),
hour: date.getHours(),
minute: date.getMinutes(),
}
}
/**
* Returns number of days between two {@link Timestamp}s
* @param {Timestamp} ts1 The first {@link Timestamp}
* @param {Timestamp} ts2 The second {@link Timestamp}
* @returns Number of days
*/
export function daysBetween(ts1: Timestamp, ts2: Timestamp): number {
const diff = diffTimestamp(ts1, ts2, true)
return Math.floor(diff / TIME_CONSTANTS.MILLISECONDS_IN.DAY)
}
/**
* Returns number of weeks between two {@link Timestamp}s
* @param {Timestamp} ts1 The first {@link Timestamp}
* @param {Timestamp} ts2 The second {@link Timestamp}
*/
export function weeksBetween(ts1: Timestamp, ts2: Timestamp): number {
let t1 = copyTimestamp(ts1)
let t2 = copyTimestamp(ts2)
t1 = findWeekday(t1, 0)
t2 = findWeekday(t2, 6)
return Math.ceil(daysBetween(t1, t2) / TIME_CONSTANTS.DAYS_IN.WEEK)
}
// Known dates
const weekdayDateMap = {
Sun: new Date('2020-01-05T00:00:00.000Z'),
Mon: new Date('2020-01-06T00:00:00.000Z'),
Tue: new Date('2020-01-07T00:00:00.000Z'),
Wed: new Date('2020-01-08T00:00:00.000Z'),
Thu: new Date('2020-01-09T00:00:00.000Z'),
Fri: new Date('2020-01-10T00:00:00.000Z'),
Sat: new Date('2020-01-11T00:00:00.000Z'),
}
/**
* Returns a function that uses Intl.DateTimeFormat to format weekdays.
*
* @function getWeekdayFormatter
* @returns {function} A function that formats weekdays.
*
* @example
* const formatWeekday = getWeekdayFormatter();
* console.log(formatWeekday('Mon', 'long', 'en-US')); // "Monday"
* console.log(formatWeekday('Mon', 'short', 'fr-FR')); // "lun."
*
* @param {string} weekday - The abbreviation of the weekday (e.g., 'Mon', 'Tue', 'Wed', etc.).
* @param {string} [type='long'] - The type of formatting to use ('narrow', 'short', or 'long').
* @param {string} [locale=''] - The locale to use for formatting.
*
* @returns {string} The formatted weekday.
*/
export function getWeekdayFormatter(): WeekdayFormatter {
const emptyFormatter = (): string => ''
const options = {
long: { timeZone: 'UTC', weekday: 'long' },
short: { timeZone: 'UTC', weekday: 'short' },
narrow: { timeZone: 'UTC', weekday: 'narrow' },
}
/* istanbul ignore next */
if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') {
return emptyFormatter as WeekdayFormatter
}
/**
* Formats a given weekday into a localized string based on the specified type and locale.
*
* @param {number} weekday - The day of the week (0 for Sunday, 1 for Monday, etc.).
* @param {string} type - The format type (e.g., 'narrow', 'short', 'long') to use for formatting.
* @param {string} [locale] - The locale string (e.g., 'en-US') to use for formatting. Defaults to the user's locale if not provided.
* @returns {string} The formatted weekday string.
*/
function weekdayFormatter(
weekday: keyof typeof weekdayDateMap,
type: string,
locale?: string,
): string {
try {
const intlFormatter = new Intl.DateTimeFormat(
locale || undefined,
/// @ts-expect-error ignore for now
options[type] || options['long'],
)
return intlFormatter.format(weekdayDateMap[weekday])
} catch (e) /* istanbul ignore next */ {
if (e instanceof Error) {
console.error(`Intl.DateTimeFormat: ${e.message} -> day of week: ${weekday}`)
}
return ''
}
}
return weekdayFormatter
}
/**
* Retrieves an array of localized weekday names.
*
* @param {string} type - The format type for the weekday names. Can be 'narrow', 'short', or 'long'.
* @param {string} [locale] - The locale to use for formatting. If not provided, the default locale is used.
* @returns {string[]} An array of localized weekday names in the specified format.
*/
export function getWeekdayNames(type: string, locale: string): string[] {
const shortWeekdays = Object.keys(weekdayDateMap)
const weekdayFormatter = getWeekdayFormatter()
return shortWeekdays.map((weekday) =>
String(weekdayFormatter(weekday as keyof typeof weekdayDateMap, type, locale)),
)
}
/**
* Creates and returns a function for formatting month names based on locale and format type.
*
* @returns {Function} A function that formats month names.
* The returned function accepts the following parameters:
* @param {number} month - The month to format (0-11, where 0 is January).
* @param {string} [type='long'] - The format type: 'narrow', 'short', or 'long'.
* @param {string} [locale] - The locale to use for formatting. If not provided, the default locale is used.
* @returns {string} The formatted month name.
*
* @throws {Error} If Intl or Intl.DateTimeFormat is not supported in the environment.
*/
export function getMonthFormatter(): MonthFormatter {
const emptyFormatter = (): string => ''
const options: Record<'long' | 'short' | 'narrow', { timeZone: string; month: string }> = {
long: { timeZone: 'UTC', month: 'long' },
short: { timeZone: 'UTC', month: 'short' },
narrow: { timeZone: 'UTC', month: 'narrow' },
}
/* istanbul ignore next */
if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') {
return emptyFormatter
}
/**
* Formats a given month into a string based on the specified type and locale.
*
* @param {number} month - The month to format (0 for January, 11 for December).
* @param {string} type - The format type (e.g., 'narrow', 'long', 'short', etc.).
* @param {string} [locale] - The locale to use for formatting (defaults to the system locale if not provided).
* @returns {string} The formatted month string.
*/
function monthFormatter(month: number, type: string, locale?: string): string {
try {
const intlFormatter = new Intl.DateTimeFormat(
locale || undefined,
/// @ts-expect-error ignore for now
options[type] || options['long'],
)
const date = new Date()
date.setDate(1)
date.setMonth(month)
return intlFormatter.format(date)
} catch (e: unknown) /* istanbul ignore next */ {
if (e instanceof Error) {
console.error(`Intl.DateTimeFormat: ${e.message} -> month: ${month}`)
}
return ''
}
}
return monthFormatter
}
/**
* Retrieves an array of localized month names.
*
* @param {string} type - The format type for the month names. Can be 'narrow', 'short', or 'long'.
* @param {string} [locale] - The locale to use for formatting. If not provided, the default locale is used.
* @returns {string[]} An array of localized month names in the specified format.
*/
export function getMonthNames(type: string, locale: string): string[] {
const monthFormatter = getMonthFormatter()
return [...Array(12).keys()].map((month) => monthFormatter(month, type, locale))
}
// the exports...
export default {
PARSE_DATETIME,
PARSE_DATE,
PARSE_TIME,
DAYS_IN_MONTH,
DAYS_IN_MONTH_LEAP,
DAYS_IN_MONTH_MIN,
DAYS_IN_MONTH_MAX,
MONTH_MAX,
MONTH_MIN,
DAY_MIN,
TIME_CONSTANTS,
FIRST_HOUR,
// Timestamp,
// TimeObject,
today,
getStartOfWeek,
getEndOfWeek,
getStartOfMonth,
getEndOfMonth,
parseTime,
valid