import { ColumnMetadata } from "../metadata/ColumnMetadata" import { parseISO } from "date-fns" /** * Provides utilities to transform hydrated and persisted data. */ export class DateUtils { // ------------------------------------------------------------------------- // Public Static Methods // ------------------------------------------------------------------------- /** * Normalizes date object hydrated from the database. */ static normalizeHydratedDate( mixedDate: Date | string | undefined, ): Date | string | undefined { if (!mixedDate) return mixedDate return typeof mixedDate === "string" ? new Date(mixedDate) : (mixedDate as Date) } /** * Converts given value into date string in a "YYYY-MM-DD" format. */ static mixedDateToDateString(value: string | Date): string { if (value instanceof Date) { return ( this.formatZerolessValue(value.getFullYear(), 4) + "-" + this.formatZerolessValue(value.getMonth() + 1) + "-" + this.formatZerolessValue(value.getDate()) ) } return value } /** * Converts given value into date object. */ static mixedDateToDate( mixedDate: Date | string, toUtc: boolean = false, useMilliseconds = true, ): Date { /** * new Date(ISOString) is not a reliable parser to date strings. * It's better to use 'date-fns' parser to parser string in ISO Format. * * The problem here is with wrong timezone. * * For example: * * ``new Date('2021-04-28')`` will generate `2021-04-28T00:00:00.000Z` * in my timezone, which is not true for my timezone (GMT-0300). It should * be `2021-04-28T03:00:00.000Z` as `new Date(2021, 3, 28)` generates. * * https://stackoverflow.com/a/2587398 */ let date = typeof mixedDate === "string" ? parseISO(mixedDate) : mixedDate if (toUtc) date = new Date( date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds(), ) if (!useMilliseconds) date.setUTCMilliseconds(0) return date } /** * Converts given value into time string in a "HH:mm:ss" format. */ static mixedDateToTimeString( value: Date | any, skipSeconds: boolean = false, ): string | any { if (value instanceof Date) return ( this.formatZerolessValue(value.getHours()) + ":" + this.formatZerolessValue(value.getMinutes()) + (!skipSeconds ? ":" + this.formatZerolessValue(value.getSeconds()) : "") ) return value } /** * Converts given value into time string in a "HH:mm:ss" format. */ static mixedTimeToDate(value: Date | any): string | any { if (typeof value === "string") { const [hours, minutes, seconds] = value.split(":") const date = new Date() if (hours) date.setHours(parseInt(hours)) if (minutes) date.setMinutes(parseInt(minutes)) if (seconds) date.setSeconds(parseInt(seconds)) return date } return value } /** * Converts given string value with "-" separator into a "HH:mm:ss" format. */ static mixedTimeToString( value: string | any, skipSeconds: boolean = false, ): string | any { value = value instanceof Date ? value.getHours() + ":" + value.getMinutes() + (!skipSeconds ? ":" + value.getSeconds() : "") : value if (typeof value === "string") { return value .split(":") .map((v) => (v.length === 1 ? "0" + v : v)) // append zero at beginning if we have a first-zero-less number .join(":") } return value } /** * Converts given value into datetime string in a "YYYY-MM-DD HH-mm-ss" format. */ static mixedDateToDatetimeString( value: Date | any, useMilliseconds?: boolean, ): string | any { if (typeof value === "string") { value = new Date(value) } if (value instanceof Date) { let finalValue = this.formatZerolessValue(value.getFullYear(), 4) + "-" + this.formatZerolessValue(value.getMonth() + 1) + "-" + this.formatZerolessValue(value.getDate()) + " " + this.formatZerolessValue(value.getHours()) + ":" + this.formatZerolessValue(value.getMinutes()) + ":" + this.formatZerolessValue(value.getSeconds()) if (useMilliseconds) finalValue += `.${this.formatMilliseconds( value.getMilliseconds(), )}` value = finalValue } return value } /** * Converts given value into utc datetime string in a "YYYY-MM-DD HH-mm-ss.sss" format. */ static mixedDateToUtcDatetimeString(value: Date | any): string | any { if (typeof value === "string") { value = new Date(value) } if (value instanceof Date) { return ( this.formatZerolessValue(value.getUTCFullYear(), 4) + "-" + this.formatZerolessValue(value.getUTCMonth() + 1) + "-" + this.formatZerolessValue(value.getUTCDate()) + " " + this.formatZerolessValue(value.getUTCHours()) + ":" + this.formatZerolessValue(value.getUTCMinutes()) + ":" + this.formatZerolessValue(value.getUTCSeconds()) + "." + this.formatMilliseconds(value.getUTCMilliseconds()) ) } return value } /** * Converts each item in the given array to string joined by "," separator. */ static simpleArrayToString(value: any[] | any): string[] | any { if (Array.isArray(value)) { return (value as any[]).map((i) => String(i)).join(",") } return value } /** * Converts given string to simple array split by "," separator. */ static stringToSimpleArray(value: string | any): string | any { if (typeof value === "string") { if (value.length > 0) { return value.split(",") } else { return [] } } return value } static simpleJsonToString(value: any): string { return JSON.stringify(value) } static stringToSimpleJson(value: any) { return typeof value === "string" ? JSON.parse(value) : value } static simpleEnumToString(value: any) { return "" + value } static stringToSimpleEnum(value: any, columnMetadata: ColumnMetadata) { if ( columnMetadata.enum && !isNaN(value) && columnMetadata.enum.indexOf(parseInt(value)) >= 0 ) { // convert to number if that exists in poosible enum options value = parseInt(value) } return value } // ------------------------------------------------------------------------- // Private Static Methods // ------------------------------------------------------------------------- /** * Formats given number to "0x" format, e.g. if the totalLength = 2 and the value is 1 then it will return "01". */ private static formatZerolessValue(value: number, totalLength = 2): string { const pad = "0".repeat(totalLength) return String(`${pad}${value}`).slice(-totalLength) } /** * Formats given number to "0x" format, e.g. if it is 1 then it will return "01". */ private static formatMilliseconds(value: number): string { if (value < 10) { return "00" + value } else if (value < 100) { return "0" + value } else { return String(value) } } }