topkat-utils
Version:
A comprehensive collection of TypeScript/JavaScript utility functions for common programming tasks. Includes validation, object manipulation, date handling, string formatting, and more. Zero dependencies, fully typed, and optimized for performance.
384 lines (332 loc) • 18.8 kB
text/typescript
//----------------------------------------
// DATE UTILS
//----------------------------------------
import { isset } from './isset'
import { pad } from './math-utils'
import { DescriptiveError } from './error-utils'
import { err422IfNotSet } from './error-utils'
const int = parseInt
export function getDateAsInt12(dateAllFormat?: Date | string | number, errIfNotValid?: any): string { return getDateAsInt(dateAllFormat, errIfNotValid, true) } // alias
export function humanReadableTimestamp(dateAllFormat: DateAllFormat): number {
if (isset(dateAllFormat)) dateAllFormat = getDateAsObject(dateAllFormat)
return parseInt(getDateAsInt12(dateAllFormat) + pad(((dateAllFormat as Date) || new Date()).getUTCSeconds()) + pad(((dateAllFormat as Date) || new Date()).getUTCMilliseconds(), 3))
}
// type DateAllFormat = Date | string | number
/** format for 6/8/2018 => 20180806
* @param dateAllFormat multiple format allowed 2012, 20120101, 201201011200, new Date(), "2019-12-08T16:19:10.341Z" and all string that new Date() can parse
*/
export function getDateAsInt(dateAllFormat: DateAllFormat = new Date(), errIfNotValid$ = false, withHoursAndMinutes$ = false): string { // optional
let dateInt
const isString = typeof dateAllFormat === 'string'
if (isString && /^\d{4}-\d{2}-\d{2}$/.test(dateAllFormat)) {
dateInt = (dateAllFormat.split('-').join('') + '0000').substring(0, 12)
} else if (isString && dateAllFormat.includes('/')) {
// 01/01/2020 format
const [d, m, y] = dateAllFormat.split('/')
return y + m.toString().padStart(2, '0') + d.toString().padStart(2, '0')
} else if (isDateIntOrStringValid(dateAllFormat)) {
// we can pass an int or string format (20180106)
dateInt = (dateAllFormat + '00000000').substr(0, 12) // add default 000000 for "month days minutes:sec" if not set
} else {
let date: any = dateAllFormat
if (typeof date === 'string') date = new Date(date)
const realDate: Date = date
//let dateArr = dateAllFormat.toString().split(); // we cannot use ISOString
dateInt = '' + realDate.getUTCFullYear() + pad(realDate.getUTCMonth() + 1) + pad(realDate.getUTCDate()) + pad(realDate.getUTCHours()) + pad(realDate.getUTCMinutes())
}
isDateIntOrStringValid(dateInt, errIfNotValid$)
return (withHoursAndMinutes$ ? dateInt : dateInt.substr(0, 8))
}
export function getMonthAsInt(dateAllFormat: DateAllFormat = new Date()): number {
let dateInt
if (isDateIntOrStringValid(dateAllFormat)) {
// we can pass an int or string format (20180106)
dateInt = (dateAllFormat + '').substr(0, 6)
} else {
let date: any = dateAllFormat
if (typeof date === 'string') date = new Date(date)
//let dateArr = dateAllFormat.toString().split(); // we cannot use ISOString
dateInt = '' + date.getUTCFullYear() + pad(date.getUTCMonth() + 1)
}
return int(dateInt)
}
/**
* @param dateAllFormat multiple format allowed 2012, 20120101, 201201011200, new Date(), "2019-12-08T16:19:10.341Z" and all string that new Date() can parse
*/
export function getDateAsObject(dateAllFormat: DateAllFormat = new Date(), errIfNotValid$ = true): Date {
let dateObj = dateAllFormat
if (isDateIntOrStringValid(dateAllFormat)) {
const [y, M, d, h, m] = dateStringToArray(dateAllFormat as string)
dateObj = new Date(`${y}-${M}-${d}T${h}:${m}`)
} else if (typeof dateAllFormat === 'string') {
dateObj = new Date(dateAllFormat)
} else {
dateObj = new Date((dateAllFormat as Date).getTime()) // clone
}
isDateIsoOrObjectValid(dateObj, errIfNotValid$)
return dateObj
}
/** [2018,01,06] */
export function dateStringToArray(strOrInt: string | number) {
err422IfNotSet({ strOrInt })
const dateStr = strOrInt.toString()
return [
dateStr.substr(0, 4), // Y
dateStr.substr(4, 2) || '01', // M
dateStr.substr(6, 2) || '01', // D
dateStr.substr(8, 2) || '12', // H 12 default to avoid time shift when converting to dateObj
dateStr.substr(10, 2) || '00', // M
dateStr.substr(12, 2) || '00', // S
dateStr.substr(14, 3) || '000', // MS
]
}
/**
* @param dateAllFormat default: actualDate
* @returns ['01', '01', '2019'] OR **string** if separator is provided */
export function dateArray(dateAllFormat: DateAllFormat = getDateAsInt()): [string, string, string] {
const dateStr = getDateAsInt(dateAllFormat).toString()
return [
dateStr.substr(6, 2), // D
dateStr.substr(4, 2), // M
dateStr.substr(0, 4), // Y
]
}
/**
* @param dateAllFormat default: actualDate
* @returns ['01', '01', '2019'] OR **string** if separator is provided */
export function dateArrayInt(dateAllFormat: DateAllFormat = getDateAsInt()): [number, number, number] {
const dateStr = getDateAsInt(dateAllFormat).toString()
return [
int(dateStr.substr(6, 2)), // D
int(dateStr.substr(4, 2)), // M
int(dateStr.substr(0, 4)), // Y
]
}
/**
* @return 01/01/2012 (alias of dateArrayFormatted(date, '/'))
*/
export function dateFormatted(dateAllFormat: DateAllFormat, separator = '/') { return dateArray(dateAllFormat).join(separator) }
/** Date with custom offset (Ex: +2 for France) */
export function dateOffset(offsetHours: number, dateObj = new Date()) {
const utc = Date.UTC(dateObj.getUTCFullYear(), dateObj.getUTCMonth(), dateObj.getUTCDate(),
dateObj.getUTCHours(), dateObj.getUTCMinutes(), dateObj.getUTCSeconds())
return new Date(utc + (3600000 * offsetHours))
}
//----------------------------------------
// TIME UTILS
//----------------------------------------
/** */
export function getTimeAsInt(timeOrDateInt: any = getDateAsInt12()) {
if (isDateIntOrStringValid(timeOrDateInt)) {
const tl = timeOrDateInt.toString().length
return int(timeOrDateInt.toString().substring(tl - 4, tl))
} else if (typeof timeOrDateInt === 'string' && timeOrDateInt.length === 5 && timeOrDateInt.includes(':'))
return int(timeOrDateInt.replace(':', ''))
else return 'dateInvalid'
}
/**
* @param {timeInt|dateInt12} Eg: 2222 OR 201201012222. Default, actual dateInt12
* @param {String} separator default: ":"
*/
export function getIntAsTime(intOrDateTimeInt = getDateAsInt12(), separator = ':') {
const time = intOrDateTimeInt.toString().padStart(4, '0')
const tl = time.length
return time.substring(tl - 4, tl - 2) + separator + time.substring(tl - 2, tl)
}
export function isTimeStringValid(timeStr: string, outputAnError$ = false) {
const timeArr = timeStr.split(':')
const h = int(timeArr[0])
const m = int(timeArr[1])
const test1 = h >= 0 && h < 24
const test2 = m >= 0 && m < 60
if (outputAnError$ && !(test1 && test2)) throw new DescriptiveError('timeStringOutOfRange', { code: 422, origin: 'Time validator' })
else return test1 && test2
}
//----------------------------------------
// DURATIONS
//----------------------------------------
export function getDuration(startDate: any, endDate: any, inMinutes = false) {
const startDateO = getDateAsObject(startDate)
const endDateO = getDateAsObject(endDate)
const diffInSec = Math.floor(endDateO.getTime() / 1000) - Math.floor(startDateO.getTime() / 1000)
if (inMinutes) return Math.floor(diffInSec / 60)
else return [
Math.floor(diffInSec / (24 * 3600)), // D
Math.floor((diffInSec % (24 * 3600)) / 3600), // H
Math.floor(((diffInSec % (24 * 3600)) % 3600) / 60), // M
]
}
type DateEvent = { startDate: any, endDate: any }
/** compare two object with DATE INT, if they overlap return true
* @param {Object} event1 {startDate, endDate}
* @param {Object} event2 {startDate, endDate}
* @param {String} fieldNameForStartDate$ replace startDate with this string
* @param {String} fieldNameForEndDate$ replace endDate with this string
* @param {Boolean} allowNull$ if false, retrun false if any of the startdates or enddates are not set
* @param {Boolean} strict$ if true,
*/
export function doDateOverlap(event1: DateEvent, event2: DateEvent, fieldNameForStartDate$ = 'startDate' as const, fieldNameForEndDate$ = 'endDate' as const, allowNull$ = true, strict$ = false) {
if (!allowNull$ && !isset(event1[fieldNameForStartDate$], event1[fieldNameForEndDate$], event2[fieldNameForStartDate$], event2[fieldNameForEndDate$])) return false
if (strict$)
return (!event2[fieldNameForEndDate$] || event1[fieldNameForStartDate$] < event2[fieldNameForEndDate$]) && (!event1[fieldNameForEndDate$] || event1[fieldNameForEndDate$] > event2[fieldNameForStartDate$])
return (!event2[fieldNameForEndDate$] || event1[fieldNameForStartDate$] <= event2[fieldNameForEndDate$]) && (!event1[fieldNameForEndDate$] || event1[fieldNameForEndDate$] >= event2[fieldNameForStartDate$])
}
type DateAllFormat = Date | string | number
type DateAllFormatString = DateObjectFormat | DateStringFormats
type DateStringFormats = 'dateInt8' | 'dateInt12' | 'humanReadableTimestamp'
type DateObjectFormat = 'date'
export function nextWeekDay(fromDate: any, weekDayInt?: 0 | 1 | 2 | 3 | 4 | 5 | 6, outputFormat?: DateStringFormats, sameDayAllowed?: boolean): number
export function nextWeekDay(fromDate: any, weekDayInt?: 0 | 1 | 2 | 3 | 4 | 5 | 6, outputFormat?: DateObjectFormat, sameDayAllowed?: boolean): Date
export function nextWeekDay(fromDate: any, weekDayInt?: 0 | 1 | 2 | 3 | 4 | 5 | 6, outputFormat = 'date', sameDayAllowed = false): any {
const date = getDateAsObject(fromDate)
if (typeof weekDayInt === 'undefined') weekDayInt = (date.getDay() as 0 | 1 | 2 | 3 | 4 | 5 | 6)
const toAdd = !sameDayAllowed && date.getDay() === weekDayInt ? 7 : 0
date.setUTCDate(date.getUTCDate() + toAdd + (7 + weekDayInt - date.getUTCDay()) % 7)
return getDateAs(date, outputFormat as any)
}
/**
* @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
*/
export function addDays(dateAllFormat?: Date | string | number, numberOfDays?: number, outputFormat?: 'dateInt8' | 'dateInt12' | 'humanReadableTimestamp'): string
export function addDays(dateAllFormat?: Date | string | number, numberOfDays?: number, outputFormat?: 'date'): Date
export function addDays(dateAllFormat: DateAllFormat = getDateAsInt(), numberOfDays = 1, outputFormat: DateAllFormatString = 'date'): any {
const date = getDateAsObject(dateAllFormat)
date.setTime(date.getTime() + numberOfDays * 24 * 60 * 60 * 1000)
return getDateAs(date, outputFormat as any)
}
/**
* @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
*/
export function addMinutes(dateAllFormat?: Date | string | number, numberOfMinutes?: number, outputFormat?: DateStringFormats): string
export function addMinutes(dateAllFormat?: Date | string | number, numberOfMinutes?: number, outputFormat?: DateObjectFormat): Date
export function addMinutes(dateAllFormat: DateAllFormat = getDateAsInt(), numberOfMinutes = 1, outputFormat: DateAllFormatString = 'date'): any {
const date = getDateAsObject(dateAllFormat)
date.setTime(date.getTime() + 1 * numberOfMinutes * 60 * 1000)
return getDateAs(date, outputFormat as any)
}
/**
* @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
*/
export function addHours(dateAllFormat?: Date | string | number, numberOfHours?: number, outputFormat?: 'dateInt8' | 'dateInt12' | 'humanReadableTimestamp'): string
export function addHours(dateAllFormat?: Date | string | number, numberOfHours?: number, outputFormat?: 'date'): Date
export function addHours(dateAllFormat: DateAllFormat = getDateAsInt(), numberOfHours = 1, outputFormat: DateAllFormatString = 'date'): any {
const date = getDateAsObject(dateAllFormat)
date.setTime(date.getTime() + 1 * numberOfHours * 60 * 60 * 1000)
return getDateAs(date, outputFormat as any)
}
/**
* @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
*/
export function addMonths(dateAllFormat?: Date | string | number, numberOfMonths?: number, outputFormat?: 'dateInt8' | 'dateInt12' | 'humanReadableTimestamp'): string
export function addMonths(dateAllFormat?: Date | string | number, numberOfMonths?: number, outputFormat?: 'date'): Date
export function addMonths(dateAllFormat: DateAllFormat = getDateAsInt(), numberOfMonths = 1, outputFormat: DateAllFormatString = 'date'): any {
const date = getDateAsObject(dateAllFormat)
date.setUTCMonth(date.getUTCMonth() + numberOfMonths)
return getDateAs(date, outputFormat as any)
}
/**
* @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
*/
export function addYears(dateAllFormat: DateAllFormat = getDateAsInt(), numberOfYears = 1, outputFormat: DateAllFormatString = 'date') {
const date = getDateAsObject(dateAllFormat)
date.setUTCFullYear(date.getUTCFullYear() + numberOfYears)
return getDateAs(date, outputFormat as any)
}
export function getDayOfMonth(dateAllFormat: DateAllFormat = getDateAsInt()) {
const dateAsInt = getDateAsInt(dateAllFormat)
const [, , d] = dateStringToArray(dateAsInt)
return d
}
export function getYear(dateAllFormat: DateAllFormat = getDateAsInt()) {
const dateAsInt = getDateAsInt(dateAllFormat)
const [y] = dateStringToArray(dateAsInt)
return y
}
export function getHours(dateAllFormat: DateAllFormat = getDateAsInt()) {
const dateAsInt = getDateAsInt(dateAllFormat)
const [, , , h] = dateStringToArray(dateAsInt)
return h
}
export function getMinutes(dateAllFormat: DateAllFormat = getDateAsInt()) {
const dateAsInt = getDateAsInt(dateAllFormat)
const [, , , , m] = dateStringToArray(dateAsInt)
return m
}
/**
* @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
*/
export function lastDayOfMonth(dateAllFormat: DateAllFormat = getDateAsInt(), outputFormat: DateAllFormatString = 'date') {
const date = getDateAsObject(dateAllFormat)
const lastDay = new Date(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)
lastDay.setUTCHours(date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds())
return getDateAs(lastDay, outputFormat as any)
}
/**
* @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
*/
export function firstDayOfMonth(dateAllFormat: DateAllFormat = getDateAsInt(), outputFormat: DateAllFormatString = 'date') {
const date = getDateAsObject(dateAllFormat)
const firstDay = new Date(date.getUTCFullYear(), date.getUTCMonth(), 1)
firstDay.setUTCHours(date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds())
return getDateAs(firstDay, outputFormat as any)
}
export function differenceInMilliseconds(startDateAllFormat: any, endDateAllFormat: any) {
const startDate = getDateAsObject(startDateAllFormat)
const endDate = getDateAsObject(endDateAllFormat)
return endDate.getTime() - startDate.getTime()
}
export function differenceInSeconds(startDateAllFormat: any, endDateAllFormat: any) {
return differenceInMilliseconds(startDateAllFormat, endDateAllFormat) / 1000
}
export function differenceInMinutes(startDateAllFormat: any, endDateAllFormat: any) {
return differenceInSeconds(startDateAllFormat, endDateAllFormat) / 60
}
export function differenceInHours(startDateAllFormat: any, endDateAllFormat: any) {
return differenceInMinutes(startDateAllFormat, endDateAllFormat) / 60
}
export function differenceInDays(startDateAllFormat: any, endDateAllFormat: any) {
return differenceInHours(startDateAllFormat, endDateAllFormat) / 24
}
export function differenceInWeeks(startDateAllFormat: any, endDateAllFormat: any) {
return differenceInDays(startDateAllFormat, endDateAllFormat) / 7
}
/**
* @param {String} outputDateFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
*/
export function getDateAs(dateAllFormat?: Date | string | number, outputFormat?: 'dateInt8' | 'dateInt12' | 'humanReadableTimestamp'): string
export function getDateAs(dateAllFormat?: Date | string | number, outputFormat?: 'date'): Date
export function getDateAs(dateAllFormat: DateAllFormat = new Date(), outputDateFormat: DateAllFormatString = 'date') {
switch (outputDateFormat) {
case 'dateInt8':
return getDateAsInt(dateAllFormat)
case 'dateInt12':
return getDateAsInt12(dateAllFormat)
case 'humanReadableTimestamp':
return humanReadableTimestamp(dateAllFormat)
case 'date':
default:
return getDateAsObject(dateAllFormat)
}
}
export function isDateIntOrStringValid(dateStringOrInt: DateAllFormat, outputAnError = false, length?: number): boolean {
if (!isset(dateStringOrInt)) return false
const dateStr = dateStringOrInt.toString()
if (length && dateStr.length !== length) throw new DescriptiveError(`wrongLengthForDateInt`, { code: 422, origin: 'Date Int validator', dateStringOrInt: dateStringOrInt, extraInfo: `${dateStringOrInt} length !== ${length}` })
if ((typeof dateStringOrInt === 'object' && isNaN(int(dateStr))) || ![4, 6, 8, 10, 12, 17].includes(dateStr.length)) return false
const dateArr = dateStringToArray(dateStringOrInt as string)
const [y, M, d, h, m] = dateArr
const test1 = dateArr.length >= 3 && int(y) >= 1000 // Y
const test2 = int(M) <= 12 && int(M) > 0 // M
const test3 = !isset(d) || int(d) <= 31 && int(d) > 0 // D
const test4 = !isset(h) || (int(h) <= 23 && int(h) >= 0) // H
const test5 = !isset(m) || (int(m) <= 59 && int(m) >= 0) // M
if (outputAnError && !(test1 && test2 && test3 && test4 && test5)) throw new DescriptiveError(`dateStringOrIntFormatInvalid`, { code: 422, origin: 'Date Int validator', dateStringOrInt: dateStringOrInt, extraInfo: 'Needs YYYYMMDD[HHMM] between 100001010000 and 999912312359', dateArr, isYearValid: test1, isMonthValid: test2, isDayValid: test3, isHourValid: test4, isMinutesValid: test5 })
return true
}
export function isDateIsoOrObjectValid(dateIsoOrObj: any, outputAnError = false) {
let dateObj: Date | number | string = dateIsoOrObj
if (typeof dateIsoOrObj === 'string') dateObj = new Date(dateIsoOrObj)
const valid = dateObj instanceof Date
if (outputAnError && !valid) throw new DescriptiveError('dateIsoStringOrObjectIsNotValid', { code: 422, origin: 'Date Object validator', isoDate: dateIsoOrObj })
return valid
}