140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
import { flow, pipe } from "fp-ts/function";
|
|
import * as E from "fp-ts/lib/Either";
|
|
import * as S from "fp-ts/lib/string";
|
|
import * as O from "fp-ts/lib/Option";
|
|
import * as R from "fp-ts/lib/ReadonlyArray";
|
|
|
|
export type Duration = number;
|
|
|
|
export enum DurationUnit {
|
|
MILLISECOND,
|
|
SECOND,
|
|
MINUTE,
|
|
HOUR
|
|
}
|
|
const durationUnitMap: Record<string, DurationUnit> = {
|
|
ms: DurationUnit.MILLISECOND,
|
|
milliseconds: DurationUnit.MILLISECOND,
|
|
sec: DurationUnit.SECOND,
|
|
seconds: DurationUnit.SECOND,
|
|
min: DurationUnit.MINUTE,
|
|
minutes: DurationUnit.MINUTE,
|
|
hr: DurationUnit.HOUR,
|
|
hour: DurationUnit.HOUR,
|
|
hours: DurationUnit.HOUR
|
|
};
|
|
const getDurationUnit = (key: string): O.Option<DurationUnit> => O.fromNullable(durationUnitMap[key.toLowerCase()]);
|
|
|
|
export const getMs = (duration: Duration): number => duration;
|
|
export const getSeconds = (duration: Duration): number => duration / 1000;
|
|
export const getMinutes = (duration: Duration): number => getSeconds(duration) / 60;
|
|
export const getHours = (duration: Duration): number => getMinutes(duration) / 60;
|
|
export const format = (duration: Duration): string => {
|
|
const ms = getMs(duration) % 1000;
|
|
const seconds = getSeconds(duration) % 60;
|
|
const minutes = getMinutes(duration) % 60;
|
|
const hours = getHours(duration);
|
|
|
|
return (
|
|
[hours, minutes, seconds].map((x) => Math.floor(x).toString().padStart(2, "0")).join(":") +
|
|
"." +
|
|
ms.toString().padStart(3, "0")
|
|
);
|
|
};
|
|
|
|
export interface DurationBuilder {
|
|
readonly millis: number;
|
|
readonly seconds: number;
|
|
readonly minutes: number;
|
|
readonly hours: number;
|
|
}
|
|
export const createDurationBuilder = (): DurationBuilder => ({
|
|
millis: 0,
|
|
seconds: 0,
|
|
minutes: 0,
|
|
hours: 0
|
|
});
|
|
|
|
export type DurationBuilderField<T> = (arg: T) => (builder: DurationBuilder) => DurationBuilder;
|
|
|
|
export const withMillis: DurationBuilderField<number> = (millis) => (builder) => ({
|
|
...builder,
|
|
millis
|
|
});
|
|
|
|
export const withSeconds: DurationBuilderField<number> = (seconds) => (builder) => ({
|
|
...builder,
|
|
seconds
|
|
});
|
|
|
|
export const withMinutes: DurationBuilderField<number> = (minutes) => (builder) => ({
|
|
...builder,
|
|
minutes
|
|
});
|
|
|
|
export const withHours: DurationBuilderField<number> = (hours) => (builder) => ({
|
|
...builder,
|
|
hours
|
|
});
|
|
|
|
export const build = (builder: DurationBuilder): Duration =>
|
|
builder.millis + builder.seconds * 1000 + builder.minutes * 60 * 1000 + builder.hours * 60 * 60 * 1000;
|
|
|
|
export const parse = (duration: string): E.Either<string, Duration> => {
|
|
const parts = pipe(
|
|
duration,
|
|
S.split(" "),
|
|
R.map(S.trim),
|
|
R.filter((part) => !S.isEmpty(part))
|
|
);
|
|
|
|
const valueUnitPairs = pipe(
|
|
parts,
|
|
R.mapWithIndex((i, part) => {
|
|
const isUnit = i % 2 !== 0;
|
|
if (!isUnit) return E.right(O.none);
|
|
|
|
const value = Number(parts[i - 1]);
|
|
if (isNaN(value)) return E.left(`bad value: "${parts[i - 1]}"`);
|
|
|
|
const unit = getDurationUnit(part);
|
|
if (O.isNone(unit)) return E.left(`unknown duration type: ${part}`);
|
|
|
|
return E.right(O.some([unit.value, value] as [DurationUnit, number]));
|
|
}),
|
|
E.sequenceArray,
|
|
E.map(
|
|
flow(
|
|
R.filter(O.isSome),
|
|
R.map(({ value }) => value)
|
|
)
|
|
)
|
|
);
|
|
|
|
return pipe(
|
|
valueUnitPairs,
|
|
E.flatMap(
|
|
R.reduce(E.of<string, DurationBuilder>(createDurationBuilder()), (builderEither, [unit, value]) =>
|
|
pipe(
|
|
builderEither,
|
|
E.chain((builder) => {
|
|
switch (unit) {
|
|
case DurationUnit.MILLISECOND:
|
|
return E.right(withMillis(value)(builder));
|
|
case DurationUnit.SECOND:
|
|
return E.right(withSeconds(value)(builder));
|
|
case DurationUnit.MINUTE:
|
|
return E.right(withMinutes(value)(builder));
|
|
case DurationUnit.HOUR:
|
|
return E.right(withHours(value)(builder));
|
|
default:
|
|
return E.left(`unknown unit: ${unit}`);
|
|
}
|
|
})
|
|
)
|
|
)
|
|
),
|
|
E.map(build)
|
|
);
|
|
};
|