import Vue from 'vue' // eslint-disable-next-line @typescript-eslint/no-explicit-any Vue.filter('format', (v: any) => { if (typeof v === 'number') { return v || v === 0 ? v.toLocaleString() : '' } else if (v instanceof Date) { return v.toLocaleDateString([], { year: 'numeric', month: 'long', day: 'numeric', weekday: 'short', }) } else if (v && typeof v === 'object') { return JSON.stringify(v) } return v }) // eslint-disable-next-line @typescript-eslint/no-explicit-any Vue.filter('formatDate', (v: any) => { return v ? new Date(v).toLocaleDateString([], { year: 'numeric', month: 'long', day: 'numeric', weekday: 'short', }) : '' }) // eslint-disable-next-line @typescript-eslint/no-explicit-any Vue.filter('duration', (v: any) => { return v ? new Duration(new Date(v), new Date()).toString({ sign: false, granularity: 2, }) : '' }) /** * Possible duration units in this parser */ export type DurationUnit = 'ms' | 's' | 'min' | 'h' | 'd' | 'w' | 'mo' | 'y' export interface IDurationOptions { /** * @default true */ sign?: boolean /** * Number of units plus subunits */ granularity?: number /** * Number of max units shown */ maxUnit?: number /** * Smallest unit shown * * @default 's' */ smallest?: DurationUnit /** * Custom naming for units */ unit?: Partial> } export class Duration { /** * Sign in front of the output toString() */ sign: '+' | '-' | '' = '+' /** * Milliseconds */ ms: number /** * Seconds */ s: number /** * Minutes */ min: number /** * Hours */ h: number /** * Days */ d: number /** * Weeks */ w: number /** * Months */ mo: number /** * Years */ y: number private _dates: [Date, Date] = [new Date(this.from), new Date(this.to)] /** * Parse milliseconds (i.e. epoch) to Duration, based on before present time */ static of(msec: number) { const to = new Date() const output = new this(new Date(+to - msec), to) output.sign = '' return output } constructor( /** * Starting Date */ public from: Date, /** * Ending date */ public to: Date ) { if (from > to) { this.sign = '-' this._dates = this._dates.reverse() as [Date, Date] } this.ms = this._parse((d) => d.getMilliseconds(), { get: (d) => d.getSeconds(), set: (d, v) => d.setSeconds(v), inc: () => 1000, }) this.s = this._parse((d) => d.getSeconds(), { get: (d) => d.getMinutes(), set: (d, v) => d.setMinutes(v), inc: () => 60, }) this.min = this._parse((d) => d.getMinutes(), { get: (d) => d.getHours(), set: (d, v) => d.setHours(v), inc: () => 60, }) this.h = this._parse((d) => d.getHours(), { get: (d) => d.getDate(), set: (d, v) => d.setDate(v), inc: () => 24, }) this.d = this._parse((d) => d.getDate(), { get: (d) => d.getMonth(), set: (d, v) => d.setMonth(v), inc: (d) => { const y = d.getFullYear() let isLeapYear = true if (y % 4) { isLeapYear = false } else if (y % 100) { isLeapYear = true } else if (y % 400) { isLeapYear = false } return [ 31, // Jan isLeapYear ? 29 : 28, // Feb 31, // Mar 30, // Apr 31, // May 30, // Jun 31, // Jul 31, // Aug 30, // Sep 31, // Oct 30, // Nov 31, // Dec ][d.getMonth()]! }, }) this.w = Math.floor(this.d / 7) this.d = this.d % 7 this.mo = this._parse((d) => d.getMonth(), { get: (d) => d.getFullYear(), set: (d, v) => d.setFullYear(v), inc: () => 12, }) this.y = this._parse((d) => d.getFullYear()) } /** * To JSON-serializable OrderedDict */ toOrderedDict(): [DurationUnit, number][] { return [ ['ms', this.ms], ['s', this.s], ['min', this.min], ['h', this.h], ['d', this.d], ['w', this.w], ['mo', this.mo], ['y', this.y], ] } /** * To String * * Works with `${duration}` also */ toString({ sign = true, granularity, maxUnit, smallest = 's', unit = {}, }: IDurationOptions = {}) { const odict = this.toOrderedDict() const smallestIndex = odict.map(([k]) => k).indexOf(smallest) const filteredDict = odict.filter(([, v], i) => v && i >= smallestIndex) const str = filteredDict .slice(granularity ? filteredDict.length - granularity : 0) .reverse() .slice(0, maxUnit) .map(([k, v]) => `${v.toLocaleString()}${unit[k] || k}`) .join(' ') if (sign) { return this.sign + str } return str } private _parse( current: (d: Date) => number, upper?: { get: (d: Date) => number set: (d: Date, v: number) => void inc: (d: Date) => number } ) { let a = current(this._dates[1]) - current(this._dates[0]) if (upper) { while (a < 0) { upper.set(this._dates[1], upper.get(this._dates[1]) - 1) this._dates[1] = new Date(this._dates[1]) a += upper.inc(this._dates[1]) } } return a } } /** * Date adding functions */ export function addDate(d: Date): Record Date> { return { /** * Milliseconds */ ms: (n) => { d.setMilliseconds(d.getMilliseconds() + n) return new Date(d) }, /** * Seconds */ s: (n) => { d.setSeconds(d.getSeconds() + n) return new Date(d) }, /** * Minutes */ min: (n) => { d.setMinutes(d.getMinutes() + n) return new Date(d) }, /** * Hours */ h: (n) => { d.setHours(d.getHours() + n) return new Date(d) }, /** * Date */ d: (n) => { d.setDate(d.getDate() + n) return new Date(d) }, /** * Weeks */ w: (n) => { d.setDate(d.getDate() + n * 7) return new Date(d) }, /** * Months */ mo: (n) => { d.setMonth(d.getMonth() + n) return new Date(d) }, /** * Years */ y: (n) => { d.setFullYear(d.getFullYear() + n) return new Date(d) }, } }