summaryrefslogtreecommitdiff
path: root/src/duration.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/duration.ts')
-rw-r--r--src/duration.ts155
1 files changed, 155 insertions, 0 deletions
diff --git a/src/duration.ts b/src/duration.ts
new file mode 100644
index 0000000..3d1a44c
--- /dev/null
+++ b/src/duration.ts
@@ -0,0 +1,155 @@
+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),
+ );
+};