@gez/date-time-kit
Version:
145 lines (134 loc) • 5.35 kB
text/typescript
/**
* 字符串类型的别名,防止和其他字面量类型联合时被过度简化。
* 例如:避免 `'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
};