UNPKG

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
//---------------------------------------- // 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 }