From 5144864b227dced213db6566230915b872f1f215 Mon Sep 17 00:00:00 2001 From: "lightwalker.eth" <126201998+lightwalker-eth@users.noreply.github.com> Date: Fri, 3 May 2024 19:07:23 +0400 Subject: [PATCH] feat: Enhance use of Duration --- src/time.test.ts | 73 +++++++++++++++++++++++++++++++- src/time.ts | 105 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 143 insertions(+), 35 deletions(-) diff --git a/src/time.test.ts b/src/time.test.ts index 100d9d4..6231578 100644 --- a/src/time.test.ts +++ b/src/time.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest"; -import { addSeconds, buildTimePeriod, buildTimestamp, buildTimestampMs, buildDateFromTimestamp, timestampMsToTimestamp, now, buildTimestampFromDate, buildDuration, isOverlappingTimestamp, subtractSeconds, formatTimestamp, FormatTimestampOptions, formatTimestampAsDistance, formatTimestampAsDistanceToNow } from "./time"; +import { addSeconds, buildTimePeriod, buildTimestamp, buildTimestampMs, buildDateFromTimestamp, timestampMsToTimestamp, now, buildTimestampFromDate, buildDuration, isOverlappingTimestamp, subtractSeconds, formatTimestamp, FormatTimestampOptions, formatTimestampAsDistance, formatTimestampAsDistanceToNow, scaleDuration, SECONDS_PER_DAY, DAYS_PER_YEAR } from "./time"; describe("timestampMsToTimestamp() function", () => { it("Correctly returns 0s for less than 1000ms params", () => { @@ -59,6 +59,77 @@ describe("buildDuration() function", () => { }); }); +describe("scaleDuration() function", () => { + + it("throws when scaled by a negative number", () => { + + const seconds = 1000n; + const duration = buildDuration(seconds); + const scalar = -1; + + expect(() => {scaleDuration(duration, scalar)}).toThrow(); + }); + + it("throws when scaled by an invalid number", () => { + + const seconds = 1000n; + const duration = buildDuration(seconds); + const scalar = Infinity; + + expect(() => {scaleDuration(duration, scalar)}).toThrow(); + }); + + it("scale by 0n", () => { + + const seconds = 1000n; + const duration = buildDuration(seconds); + const scalar = 0n; + const result = scaleDuration(duration, scalar); + + expect(result.seconds).toStrictEqual(0n); + }); + + it("scale by 0", () => { + + const seconds = 1000n; + const duration = buildDuration(seconds); + const scalar = 0; + const result = scaleDuration(duration, scalar); + + expect(result.seconds).toStrictEqual(0n); + }); + + it("scale by 1000n", () => { + + const seconds = 1000n; + const duration = buildDuration(seconds); + const scalar = 1000n; + const result = scaleDuration(duration, scalar); + + expect(result.seconds).toStrictEqual(1000000n); + }); + + it("scale by 1000", () => { + + const seconds = 1000n; + const duration = buildDuration(seconds); + const scalar = 1000; + const result = scaleDuration(duration, scalar); + + expect(result.seconds).toStrictEqual(1000000n); + }); + + it("scale by fractional scalar", () => { + + const duration = SECONDS_PER_DAY; + const scalar = DAYS_PER_YEAR; + const result = scaleDuration(duration, scalar); + + expect(result.seconds).toStrictEqual(31556952n); + }); + +}); + describe("addSeconds() function", () => { it("add zero duration", () => { diff --git a/src/time.ts b/src/time.ts index 5152105..e544888 100644 --- a/src/time.ts +++ b/src/time.ts @@ -1,23 +1,89 @@ import { formatDistanceStrict } from "date-fns"; import { enUS } from 'date-fns/locale'; +/** + * A Duration represents a length of time in seconds. + */ +export interface Duration { + + /** + * The number of seconds in the Duration. + * Must be non-negative. + */ + seconds: bigint; +} + +/** + * Builds a Duration. + * + * @param seconds - The number of seconds in the Duration. + * @returns The resulting Duration. + */ +export const buildDuration = ( + seconds: bigint +): Duration => { + if (seconds < 0n) + throw new Error(`Error in buildDuration. seconds ${seconds}} is less than 0.`); + + return { + seconds + }; +}; + +/** + * Scales a Duration by the given scalar. + * + * @param duration The Duration to scale. + * @param scalar The scalar to scale the Duration by. + * @returns The scaled Duration. + * @throws If scalar is negative. + * @throws If scalar is of type number and cannot be converted to bigint. + */ +export const scaleDuration = ( + duration: Duration, + scalar: bigint | number +): Duration => { + + let newSeconds: bigint; + + if (typeof scalar === 'number') { + try { + newSeconds = BigInt(Number(duration.seconds) * scalar); + } catch (error) { + throw new Error(`Error in scaleDuration. scalar ${scalar}} is not a valid number.`); + } + } else { + newSeconds = duration.seconds * scalar; + } + + if (newSeconds < 0n) { + throw new Error(`Error in scaleDuration. scalar ${scalar}} must be a non-negative number.`); + } + + return buildDuration(newSeconds); +} + export const MILLISECONDS_PER_SECOND = 1000n; -export const SECONDS_PER_MINUTE = 60n; + +/** + * 60n seconds + */ +export const SECONDS_PER_MINUTE = buildDuration(60n); /** * 3,600n seconds */ -export const SECONDS_PER_HOUR = 60n * SECONDS_PER_MINUTE; +export const SECONDS_PER_HOUR = scaleDuration(SECONDS_PER_MINUTE, 60n); /** * 86,400n seconds */ -export const SECONDS_PER_DAY = 24n * SECONDS_PER_HOUR; +export const SECONDS_PER_DAY = scaleDuration(SECONDS_PER_HOUR, 24n); /** * 604,800n seconds */ -export const SECONDS_PER_WEEK = 7n * SECONDS_PER_DAY; +export const SECONDS_PER_WEEK = scaleDuration(SECONDS_PER_DAY, 7n); /** * The average Gregorian calendar year is 365.2425 days in length @@ -27,7 +93,7 @@ export const DAYS_PER_YEAR = 365.2425; /** * 31,556,952n seconds */ -export const SECONDS_PER_YEAR = BigInt(Number(SECONDS_PER_DAY) * DAYS_PER_YEAR); +export const SECONDS_PER_YEAR = scaleDuration(SECONDS_PER_DAY, DAYS_PER_YEAR); /** * A moment in time measured in seconds. @@ -169,35 +235,6 @@ export const now = (): Timestamp => { return timestampMsToTimestamp(nowMs()); }; -/** - * A Duration represents a length of time in seconds. - */ -export interface Duration { - - /** - * The number of seconds in the Duration. - * Must be non-negative. - */ - seconds: bigint; -} - -/** - * Builds a Duration. - * - * @param seconds - The number of seconds in the Duration. - * @returns The resulting Duration. - */ -export const buildDuration = ( - seconds: bigint -): Duration => { - if (seconds < 0n) - throw new Error(`Error in buildDuration. seconds ${seconds}} is less than 0.`); - - return { - seconds - }; -}; - /** * Builds a new Timestamp that is incremented by the provided Duration. *