UNPKG

@gez/date-time-kit

Version:

145 lines (134 loc) 5.35 kB
/** * 字符串类型的别名,防止和其他字面量类型联合时被过度简化。 * 例如:避免 `'utf-8' | string` 被化简成 `string` */ export type Str = string & Record<never, never>; abstract class Granularity<T extends string = string> { public abstract readonly list: readonly T[]; public abstract readonly map: Readonly<Map<T, number>>; public readonly has = (v: string): v is T => this.map.has(v as any); public readonly idx = ((...v: T[]) => v.length === 1 ? this.map.get(v[0])! : v.map((g) => this.map.get(g)!)) as { (v: T): number; (...v: T[]): number[]; }; private readonly _fromIdx = (idx: number): T => { if (idx < 0 || idx >= this.list.length) throw new Error( `${this.constructor.name}.getByIdx: index out of range: ${idx}` ); return this.list[idx]; }; public readonly fromIdx = ((...idx: number[]) => idx.length === 1 ? this._fromIdx(idx[0]) : idx.map((i) => this._fromIdx(i))) as { (idx: number): T; (...indexes: number[]): T[]; }; public readonly cp = (a: T, b: T): number => this.idx(a) - this.idx(b); public readonly lt = (a: T, b: T): boolean => this.cp(a, b) < 0; public readonly lte = (a: T, b: T): boolean => this.cp(a, b) <= 0; public readonly gt = (a: T, b: T): boolean => this.cp(a, b) > 0; public readonly gte = (a: T, b: T): boolean => this.cp(a, b) >= 0; public readonly minmax = (v1: T, v2: T): [T, T] => this.lt(v1, v2) ? [v1, v2] : [v2, v1]; public readonly minmaxIdx = (v1: T, v2: T) => this.idx(...this.minmax(v1, v2)) as [number, number]; public readonly min = (...args: T[]): T => { if (args.length === 0) throw new Error( `${this.constructor.name}.min needs at least one argument` ); return args.reduce((prev, curr) => this.cp(prev, curr) <= 0 ? prev : curr ); }; public readonly max = (...args: T[]): T => { if (args.length === 0) throw new Error( `${this.constructor.name}.max needs at least one argument` ); return args.reduce((prev, curr) => this.cp(prev, curr) >= 0 ? prev : curr ); }; public readonly clamp = ( min: T, val: T | Str, max: T, rule: 'min' | 'max' = 'min' ): T => { [min, max] = this.minmax(min, max); if (!this.has(val)) return rule === 'min' ? min : max; return this.min(this.max(min, val), max); }; } class _DateGranularity extends Granularity<'year' | 'month' | 'day'> { public readonly year = 'year'; public readonly month = 'month'; public readonly day = 'day'; public readonly list = [this.day, this.month, this.year] as const; public readonly map = new Map(this.list.map((v, idx) => [v, idx])); } type DateGran = _DateGranularity['list'][number]; export const dateGranHelper = new _DateGranularity(); class _TimeGranularity extends Granularity< 'hour' | 'minute' | 'second' | 'millisecond' > { public readonly hour = 'hour'; public readonly minute = 'minute'; public readonly second = 'second'; public readonly millisecond = 'millisecond'; public readonly list = [ this.millisecond, this.second, this.minute, this.hour ] as const; public readonly map = new Map(this.list.map((v, idx) => [v, idx])); } type TimeGran = _TimeGranularity['list'][number]; export const timeGranHelper = new _TimeGranularity(); class _DateTimeGranularity extends Granularity<DateGran | TimeGran> { public readonly year = dateGranHelper.year; public readonly month = dateGranHelper.month; public readonly day = dateGranHelper.day; public readonly hour = timeGranHelper.hour; public readonly minute = timeGranHelper.minute; public readonly second = timeGranHelper.second; public readonly millisecond = timeGranHelper.millisecond; public readonly list = [ ...timeGranHelper.list, ...dateGranHelper.list ] as const; public readonly map = new Map(this.list.map((v, idx) => [v, idx])); public readonly toTimeGran = (gran: DateGran | TimeGran): TimeGran => this.clamp(this.hour, gran, this.millisecond) as TimeGran; public readonly toDateGran = (gran: DateGran | TimeGran): DateGran => this.clamp(this.year, gran, this.day) as DateGran; public readonly isTimeGran = ( gran: DateGran | TimeGran | Str ): gran is TimeGran => timeGranHelper.has(gran); public readonly isDateGran = ( gran: DateGran | TimeGran | Str ): gran is DateGran => dateGranHelper.has(gran); } type DateTimeGran = _DateTimeGranularity['list'][number]; export const dateTimeGranHelper = new _DateTimeGranularity(); export const granHelper = { date: dateGranHelper, time: timeGranHelper, dateTime: dateTimeGranHelper, toDateGran: dateTimeGranHelper.toDateGran, toTimeGran: dateTimeGranHelper.toTimeGran, isTimeGran: dateTimeGranHelper.isTimeGran, isDateGran: dateTimeGranHelper.isDateGran } as const; export type { DateGran as DateGranularity, TimeGran as TimeGranularity, DateTimeGran as DateTimeGranularity };