Skip to content

Commit

Permalink
feat(duration): expose get duration procedure
Browse files Browse the repository at this point in the history
  • Loading branch information
uladkasach committed Sep 12, 2024
1 parent 191a0ab commit 87b9d7b
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 22 deletions.
46 changes: 46 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@ehmpathy/error-fns": "1.0.2",
"date-fns": "3.6.0",
"domain-glossaries": "1.0.0",
"test-fns": "1.5.0",
"type-fns": "1.19.0"
},
"devDependencies": {
Expand Down
19 changes: 2 additions & 17 deletions src/domain/UniDuration.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { UnexpectedCodePathError } from '@ehmpathy/error-fns';
import { AsOfGlossary } from 'domain-glossaries';
import { PickOne, isPresent } from 'type-fns';
import { PickAny } from 'type-fns';

export type UniDuration = AsOfGlossary<
PickOne<{
PickAny<{
weeks: number;
days: number;
hours: number;
Expand All @@ -14,17 +13,3 @@ export type UniDuration = AsOfGlossary<
'uni-time',
false
>;

export const toMilliseconds = (duration: UniDuration): number => {
if (isPresent(duration.weeks))
return duration.weeks * 7 * 24 * 60 * 60 * 1000;
if (isPresent(duration.days)) return duration.days * 24 * 60 * 60 * 1000;
if (isPresent(duration.hours)) return duration.hours * 60 * 60 * 1000;
if (isPresent(duration.minutes)) return duration.minutes * 60 * 1000;
if (isPresent(duration.seconds)) return duration.seconds * 1000;
if (isPresent(duration.milliseconds)) return duration.milliseconds;
throw new UnexpectedCodePathError(
'unsupported duration unit to convert to milliseconds',
{ duration },
);
};
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export * from './logic/checks/isUniDate';
export * from './logic/checks/isUniDateTime';
export * from './logic/casts/toMillisecondsSinceEpoch';
export * from './logic/manipulate/addDuration';
export * from './logic/manipulate/getDuration';
export * from './logic/manipulate/subDuration';
export * from './logic/manipulate/toMilliseconds';
export * from './logic/wrappers/waitFor';
export * from './logic/observe/stopwatch/startDurationStopwatch';
3 changes: 2 additions & 1 deletion src/logic/manipulate/addDuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { parseISO, subMilliseconds } from 'date-fns';
import { PickOne } from 'type-fns';

import { UniDate, UniDateTime } from '../../domain/UniDateTime';
import { UniDuration, toMilliseconds } from '../../domain/UniDuration';
import { UniDuration } from '../../domain/UniDuration';
import { asUniDate, isUniDate } from '../checks/isUniDate';
import { asUniDateTime } from '../checks/isUniDateTime';
import { toMilliseconds } from './toMilliseconds';

/**
* subtract a duration from a datetime
Expand Down
128 changes: 128 additions & 0 deletions src/logic/manipulate/getDuration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { given, then, when } from 'test-fns';

import { asUniDateTime } from '../checks/isUniDateTime';
import { getDuration } from './getDuration';
import {
MILLISECONDS_PER_DAY,
MILLISECONDS_PER_HOUR,
MILLISECONDS_PER_WEEK,
} from './toMilliseconds';

describe('getDuration', () => {
given('milliseconds', () => {
when('less than a second', () => {
then('it should accurately define the duration', () => {
const duration = getDuration({ of: { milliseconds: 69 } });
expect(duration).toEqual({ milliseconds: 69 });
});
});
when('less than a minute', () => {
then('it should accurately define the duration', () => {
const duration = getDuration({ of: { milliseconds: 1069 } });
expect(duration).toEqual({ seconds: 1, milliseconds: 69 });
});
});
when('less than an day', () => {
then('it should accurately define the duration', () => {
const duration = getDuration({
of: { milliseconds: 7 * MILLISECONDS_PER_HOUR + 1069 },
});
expect(duration).toEqual({ hours: 7, seconds: 1, milliseconds: 69 });
});
});
when('less than a week', () => {
then('it should accurately define the duration', () => {
const duration = getDuration({
of: {
milliseconds:
3 * MILLISECONDS_PER_DAY + 7 * MILLISECONDS_PER_HOUR + 1069,
},
});
expect(duration).toEqual({
days: 3,
hours: 7,
seconds: 1,
milliseconds: 69,
});
});
});
when('less than a month ', () => {
then('it should accurately define the duration', () => {
const duration = getDuration({
of: {
milliseconds:
5 * MILLISECONDS_PER_WEEK +
3 * MILLISECONDS_PER_DAY +
7 * MILLISECONDS_PER_HOUR +
1069,
},
});
expect(duration).toEqual({
weeks: 5,
days: 3,
hours: 7,
seconds: 1,
milliseconds: 69,
});
});
});
});

given('range', () => {
when('has parts of each time unit', () => {
then('it should accurately define the duration', () => {
const duration = getDuration({
of: {
range: {
since: asUniDateTime('2024-09-09T13:08:21.4269Z'),
until: asUniDateTime('2024-09-17T15:53:31.3157Z'),
},
},
});
expect(duration).toEqual({
weeks: 1,
days: 1,
hours: 2,
minutes: 45,
seconds: 9,
milliseconds: 889,
});
});
});
});

given('a unit to get the duration in', () => {
when('asked to define it in seconds', () => {
then('it should accurately define the duration', () => {
const duration = getDuration({
of: {
range: {
since: asUniDateTime('2024-09-09T13:08:21.4269Z'),
until: asUniDateTime('2024-09-17T15:53:31.3157Z'),
},
},
as: 'seconds',
});
expect(duration).toEqual({
seconds: 701109.889,
});
});
});
when('asked to define it in seconds', () => {
then('it should accurately define the duration', () => {
const duration = getDuration({
of: {
range: {
since: asUniDateTime('2024-09-09T13:08:21.4269Z'),
until: asUniDateTime('2024-09-17T15:53:31.3157Z'),
},
},
as: 'minutes',
});
expect(duration).toEqual({
minutes: 11685.164816666667,
});
});
});
});
});
99 changes: 99 additions & 0 deletions src/logic/manipulate/getDuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { UnexpectedCodePathError } from '@ehmpathy/error-fns';
import { PickOne } from 'type-fns';

import { UniDateTimeRange } from '../../domain/UniDateTime';
import { UniDuration } from '../../domain/UniDuration';
import { toMse } from '../casts/toMillisecondsSinceEpoch';
import {
MILLISECONDS_PER_DAY,
MILLISECONDS_PER_HOUR,
MILLISECONDS_PER_MINUTE,
MILLISECONDS_PER_SECOND,
MILLISECONDS_PER_WEEK,
} from './toMilliseconds';

/**
* .what = calculates the duration of a time range
*/
export const getDuration = (input: {
/**
* what measure of time to extract a duration from
*/
of: PickOne<{ range: UniDateTimeRange; milliseconds: number }>;

/**
* the unit to define the duration in, if desired
*
* note
* - by default, it will define it via all of them
*/
as?: keyof UniDuration;
}): UniDuration => {
// handle range inputs
if (input.of.range)
return getDuration({
of: {
milliseconds: toMse(input.of.range.until) - toMse(input.of.range.since),
},
as: input.as,
});

// handle milliseconds
if (input.of.milliseconds) {
// if asked to define in a specific unit, define it in that unit
if (input.as) {
if (input.as === 'weeks')
return { weeks: input.of.milliseconds / MILLISECONDS_PER_WEEK };
if (input.as === 'days')
return { days: input.of.milliseconds / MILLISECONDS_PER_DAY };
if (input.as === 'hours')
return { hours: input.of.milliseconds / MILLISECONDS_PER_HOUR };
if (input.as === 'minutes')
return { minutes: input.of.milliseconds / MILLISECONDS_PER_MINUTE };
if (input.as === 'seconds')
return { seconds: input.of.milliseconds / MILLISECONDS_PER_SECOND };
if (input.as === 'milliseconds')
return { milliseconds: input.of.milliseconds };
throw new UnexpectedCodePathError(
'input.as does not specify a valid unit',
{ input },
);
}

// otherwise, define it via all of the units
const weeks = Math.floor(input.of.milliseconds / MILLISECONDS_PER_WEEK);
const days = Math.floor(
(input.of.milliseconds % MILLISECONDS_PER_WEEK) / MILLISECONDS_PER_DAY,
);
const hours = Math.floor(
(input.of.milliseconds % MILLISECONDS_PER_DAY) / MILLISECONDS_PER_HOUR,
);
const minutes = Math.floor(
(input.of.milliseconds % MILLISECONDS_PER_HOUR) / MILLISECONDS_PER_MINUTE,
);
const seconds = Math.floor(
(input.of.milliseconds % MILLISECONDS_PER_MINUTE) /
MILLISECONDS_PER_SECOND,
);
const milliseconds = input.of.milliseconds % MILLISECONDS_PER_SECOND;
const durationWithRedundantZeros = {
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
};
const duration = Object.fromEntries(
Object.entries(durationWithRedundantZeros).filter(
([key, val]) => val > 0,
),
) as any as UniDuration;
return duration;
}

// otherwise, unsupported
throw new UnexpectedCodePathError('input.of choice is not supported', {
input,
});
};
3 changes: 2 additions & 1 deletion src/logic/manipulate/subDuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { addMilliseconds, parseISO } from 'date-fns';
import { PickOne } from 'type-fns';

import { UniDate, UniDateTime } from '../../domain/UniDateTime';
import { UniDuration, toMilliseconds } from '../../domain/UniDuration';
import { UniDuration } from '../../domain/UniDuration';
import { asUniDate, isUniDate } from '../checks/isUniDate';
import { asUniDateTime } from '../checks/isUniDateTime';
import { toMilliseconds } from './toMilliseconds';

/**
* add a duration to a datetime
Expand Down
24 changes: 24 additions & 0 deletions src/logic/manipulate/toMilliseconds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { UniDuration } from '../../domain/UniDuration';

export const DAYS_PER_WEEK = 7;
export const HOURS_PER_DAY = 24;
export const MINUTES_PER_HOUR = 60;
export const SECONDS_PER_MINUTE = 60;
export const MILLISECONDS_PER_SECOND = 1000;
export const MILLISECONDS_PER_MINUTE =
MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE;
export const MILLISECONDS_PER_HOUR = MILLISECONDS_PER_MINUTE * MINUTES_PER_HOUR;
export const MILLISECONDS_PER_DAY = MILLISECONDS_PER_HOUR * HOURS_PER_DAY;
export const MILLISECONDS_PER_WEEK = MILLISECONDS_PER_DAY * DAYS_PER_WEEK;

export const toMilliseconds = (duration: UniDuration): number => {
const total = [
(duration.weeks ?? 0) * MILLISECONDS_PER_WEEK,
(duration.days ?? 0) * MILLISECONDS_PER_DAY,
(duration.hours ?? 0) * MILLISECONDS_PER_HOUR,
(duration.minutes ?? 0) * MILLISECONDS_PER_MINUTE,
(duration.seconds ?? 0) * MILLISECONDS_PER_SECOND,
duration.milliseconds ?? 0,
].reduce((totalNow, thisMilliseconds) => totalNow + thisMilliseconds, 0);
return total;
};
3 changes: 2 additions & 1 deletion src/logic/observe/stopwatch/startDurationStopwatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { hrtime } from 'process';
import type { LogLevel } from 'simple-leveled-log-methods';
import type { VisualogicContext } from 'visualogic';

import { UniDuration, toMilliseconds } from '../../../domain/UniDuration';
import { UniDuration } from '../../../domain/UniDuration';
import { toMilliseconds } from '../../manipulate/toMilliseconds';

const roundToHundredths = (num: number) => Math.round(num * 100) / 100; // https://stackoverflow.com/a/14968691/3068233

Expand Down
Loading

0 comments on commit 87b9d7b

Please sign in to comment.