From bdbcae744841446135623aa6ea41be146d351248 Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Wed, 11 Jun 2025 00:35:55 +0100 Subject: [PATCH 01/12] Fix changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d99c18..4a47630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.2] - 2025-05-06 -### Changes +### Changed - Workflow adjustments (thanks [@simmo]) From 6ea13e9dcbb95b564d7bb16c2a08cf695408420d Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Wed, 11 Jun 2025 00:37:03 +0100 Subject: [PATCH 02/12] Rename constants and fix docs --- CHANGELOG.md | 11 ++++++++++- README.md | 43 +++++++++++++++++++++++++++++++++++++++++-- src/constant.test.ts | 12 ++++++------ src/constant.ts | 6 +++--- src/index.test.ts | 6 +++--- src/toParts.ts | 12 ++++++------ 6 files changed, 69 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a47630..9486f38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Breaking + +- Renames `MICROSECONDS_IN_A_MILLISECOND` to `MICROSECONDS_IN_MILLISECOND` (thanks [@simmo]) +- Renames `MILLISECONDS_IN_A_SECOND` to `MILLISECONDS_IN_SECOND` (thanks [@simmo]) +- Renames `NANOSECONDS_IN_A_MICROSECOND` to `NANOSECONDS_IN_MICROSECOND` (thanks [@simmo]) + +### Fixed + +- Incorrect/missing documentation (thanks [@simmo]) + ## [1.2.0] - 2025-06-10 ### Added @@ -92,5 +102,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [@spyros-uk]: https://github.com/spyros-uk [1.1.1]: https://github.com/simmo/niobe/compare/1.1.1-beta.2...1.1.1 [1.1.2]: https://github.com/simmo/niobe/compare/1.1.2-beta.0...1.1.2 - [1.2.0]: https://github.com/simmo/niobe/releases/tag/1.2.0 diff --git a/README.md b/README.md index 3d06244..c35fa5b 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,9 @@ parseDuration('2m 1s'); parseDuration('1h 2m 3s'); // => 3_723_004 + +parseDuration('1d 2h 3m 4s 5ms 6μs 7ns'); +// => 93_784_005.006_007 ``` ###### Invalid format - Non-strict (Default) @@ -132,14 +135,42 @@ parseDuration('invalid', true); // => throws Error: "invalid" is not a valid duration ``` -#### `toParts(milliseconds: number): { days: number, hours: number, minutes: number, seconds: number, milliseconds: number }` +#### `toParts(milliseconds: number): Parts` + +Converts a duration in milliseconds to a [Parts](#parts) object with properties for each time unit. + +### Types + +#### `Parts` -Converts a duration in milliseconds to an object with properties for each time unit. +Used in the [`toParts`](#topartsmilliseconds-number-parts) function, this type represents the parts of a duration. + +```ts +interface Parts { + days: number; + hours: number; + isNegative: boolean; + nanoseconds: number; + microseconds: number; + milliseconds: number; + minutes: number; + seconds: number; + weeks: number; +} +``` ### Constants These constants are used to represent the number of milliseconds in each time unit. +#### `NANOSECOND` + +One nanosecond in milliseconds. + +#### `MICROSECOND` + +One microsecond in milliseconds. + #### `MILLISECOND` One millisecond. @@ -166,6 +197,14 @@ One day in milliseconds. One week in milliseconds. +#### `NANOSECONDS_IN_MICROSECOND` + +Number of nanoseconds in a second. + +#### `MICROSECONDS_IN_MILLISECOND` + +Number of microseconds in a second. + #### `MILLISECONDS_IN_SECOND` Number of milliseconds in a second. diff --git a/src/constant.test.ts b/src/constant.test.ts index 01ea857..e7d9aaa 100644 --- a/src/constant.test.ts +++ b/src/constant.test.ts @@ -10,13 +10,13 @@ const constantsAndValues: [ ['HOUR', 3_600_000], ['HOURS_IN_DAY', 24], ['MICROSECOND', 0.001], - ['MICROSECONDS_IN_A_MILLISECOND', 1_000], + ['MICROSECONDS_IN_MILLISECOND', 1_000], ['MILLISECOND', 1], - ['MILLISECONDS_IN_A_SECOND', 1_000], + ['MILLISECONDS_IN_SECOND', 1_000], ['MINUTE', 60_000], ['MINUTES_IN_HOUR', 60], ['NANOSECOND', 0.000_001], - ['NANOSECONDS_IN_A_MICROSECOND', 1_000], + ['NANOSECONDS_IN_MICROSECOND', 1_000], ['SECOND', 1_000], ['SECONDS_IN_MINUTE', 60], ['WEEK', 604_800_000], @@ -31,13 +31,13 @@ describe('constants', () => { "HOUR": 3600000, "HOURS_IN_DAY": 24, "MICROSECOND": 0.001, - "MICROSECONDS_IN_A_MILLISECOND": 1000, + "MICROSECONDS_IN_MILLISECOND": 1000, "MILLISECOND": 1, - "MILLISECONDS_IN_A_SECOND": 1000, + "MILLISECONDS_IN_SECOND": 1000, "MINUTE": 60000, "MINUTES_IN_HOUR": 60, "NANOSECOND": 0.000001, - "NANOSECONDS_IN_A_MICROSECOND": 1000, + "NANOSECONDS_IN_MICROSECOND": 1000, "SECOND": 1000, "SECONDS_IN_MINUTE": 60, "WEEK": 604800000, diff --git a/src/constant.ts b/src/constant.ts index 1109f04..3bcf9cc 100644 --- a/src/constant.ts +++ b/src/constant.ts @@ -50,19 +50,19 @@ export const WEEK = 604_800_000; * Number of nanoseconds in a microsecond. */ -export const NANOSECONDS_IN_A_MICROSECOND = 1_000; +export const NANOSECONDS_IN_MICROSECOND = 1_000; /** * Number of microseconds in a millisecond. */ -export const MICROSECONDS_IN_A_MILLISECOND = 1_000; +export const MICROSECONDS_IN_MILLISECOND = 1_000; /** * Number of milliseconds in a second. */ -export const MILLISECONDS_IN_A_SECOND = 1_000; +export const MILLISECONDS_IN_SECOND = 1_000; /** * Number of seconds in a minute. diff --git a/src/index.test.ts b/src/index.test.ts index 098a690..e4e7886 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -10,13 +10,13 @@ describe('api', () => { "HOUR": 3600000, "HOURS_IN_DAY": 24, "MICROSECOND": 0.001, - "MICROSECONDS_IN_A_MILLISECOND": 1000, + "MICROSECONDS_IN_MILLISECOND": 1000, "MILLISECOND": 1, - "MILLISECONDS_IN_A_SECOND": 1000, + "MILLISECONDS_IN_SECOND": 1000, "MINUTE": 60000, "MINUTES_IN_HOUR": 60, "NANOSECOND": 0.000001, - "NANOSECONDS_IN_A_MICROSECOND": 1000, + "NANOSECONDS_IN_MICROSECOND": 1000, "SECOND": 1000, "SECONDS_IN_MINUTE": 60, "WEEK": 604800000, diff --git a/src/toParts.ts b/src/toParts.ts index 1319902..b0d8f3e 100644 --- a/src/toParts.ts +++ b/src/toParts.ts @@ -1,10 +1,10 @@ import { DAYS_IN_WEEK, HOURS_IN_DAY, - MICROSECONDS_IN_A_MILLISECOND, - MILLISECONDS_IN_A_SECOND, + MICROSECONDS_IN_MILLISECOND, + MILLISECONDS_IN_SECOND, MINUTES_IN_HOUR, - NANOSECONDS_IN_A_MICROSECOND, + NANOSECONDS_IN_MICROSECOND, SECONDS_IN_MINUTE, } from './constant.js'; import { @@ -44,12 +44,12 @@ export const toParts = (ms: number): Parts => { hours: Math.floor(hours.from(absoluteMs) % HOURS_IN_DAY), isNegative: ms < 0, nanoseconds: Math.round( - nanoseconds.from(absoluteMs) % NANOSECONDS_IN_A_MICROSECOND, + nanoseconds.from(absoluteMs) % NANOSECONDS_IN_MICROSECOND, ), microseconds: Math.round( - microseconds.from(absoluteMs) % MICROSECONDS_IN_A_MILLISECOND, + microseconds.from(absoluteMs) % MICROSECONDS_IN_MILLISECOND, ), - milliseconds: Math.round(absoluteMs % MILLISECONDS_IN_A_SECOND), + milliseconds: Math.round(absoluteMs % MILLISECONDS_IN_SECOND), minutes: Math.floor(minutes.from(absoluteMs) % MINUTES_IN_HOUR), seconds: Math.floor(seconds.from(absoluteMs) % SECONDS_IN_MINUTE), weeks: Math.floor(weeks.from(absoluteMs)), From 6f48650f8674d00fce391e07e920c3880a267041 Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Wed, 11 Jun 2025 08:11:24 +0100 Subject: [PATCH 03/12] Renames `toParts` to `msToParts` --- CHANGELOG.md | 1 + README.md | 4 ++-- src/index.test.ts | 2 +- src/{toParts.test.ts => msToParts.test.ts} | 12 ++++++------ src/{toParts.ts => msToParts.ts} | 2 +- src/utilities.ts | 2 +- 6 files changed, 12 insertions(+), 11 deletions(-) rename src/{toParts.test.ts => msToParts.test.ts} (81%) rename src/{toParts.ts => msToParts.ts} (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9486f38..a7455dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking +- Renames `toParts` to `msToParts` (thanks [@simmo]) - Renames `MICROSECONDS_IN_A_MILLISECOND` to `MICROSECONDS_IN_MILLISECOND` (thanks [@simmo]) - Renames `MILLISECONDS_IN_A_SECOND` to `MILLISECONDS_IN_SECOND` (thanks [@simmo]) - Renames `NANOSECONDS_IN_A_MICROSECOND` to `NANOSECONDS_IN_MICROSECOND` (thanks [@simmo]) diff --git a/README.md b/README.md index c35fa5b..4b7af95 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ parseDuration('invalid', true); // => throws Error: "invalid" is not a valid duration ``` -#### `toParts(milliseconds: number): Parts` +#### `msToParts(milliseconds: number): Parts` Converts a duration in milliseconds to a [Parts](#parts) object with properties for each time unit. @@ -143,7 +143,7 @@ Converts a duration in milliseconds to a [Parts](#parts) object with properties #### `Parts` -Used in the [`toParts`](#topartsmilliseconds-number-parts) function, this type represents the parts of a duration. +Used in the [`msToParts`](#mstopartsmilliseconds-number-parts) function, this type represents the parts of a duration. ```ts interface Parts { diff --git a/src/index.test.ts b/src/index.test.ts index e4e7886..0099016 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -25,10 +25,10 @@ describe('api', () => { "microseconds": [Function], "milliseconds": [Function], "minutes": [Function], + "msToParts": [Function], "nanoseconds": [Function], "parseDuration": [Function], "seconds": [Function], - "toParts": [Function], "weeks": [Function], } `); diff --git a/src/toParts.test.ts b/src/msToParts.test.ts similarity index 81% rename from src/toParts.test.ts rename to src/msToParts.test.ts index 5d3c9b5..607e6d6 100644 --- a/src/toParts.test.ts +++ b/src/msToParts.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { toParts } from './toParts.js'; +import { msToParts } from './msToParts.js'; -describe('toParts()', () => { +describe('msToParts()', () => { it('returns the correct parts for a positive duration', () => { - const result = toParts(766_606_500); + const result = msToParts(766_606_500); expect(result).toEqual({ days: 1, @@ -19,7 +19,7 @@ describe('toParts()', () => { }); it('returns the correct parts for a negative duration', () => { - const result = toParts(-766_606_500); + const result = msToParts(-766_606_500); expect(result).toEqual({ days: 1, @@ -35,7 +35,7 @@ describe('toParts()', () => { }); it('returns the correct parts for a positive duration with decimal places', () => { - const result = toParts(766_606_500.003002); + const result = msToParts(766_606_500.003002); expect(result).toEqual({ days: 1, @@ -51,7 +51,7 @@ describe('toParts()', () => { }); it('returns the correct parts for a negative duration with decimal places', () => { - const result = toParts(-766_606_500.003002); + const result = msToParts(-766_606_500.003002); expect(result).toEqual({ days: 1, diff --git a/src/toParts.ts b/src/msToParts.ts similarity index 96% rename from src/toParts.ts rename to src/msToParts.ts index b0d8f3e..1098a44 100644 --- a/src/toParts.ts +++ b/src/msToParts.ts @@ -36,7 +36,7 @@ export interface Parts { * @returns An object with properties for each time unit */ -export const toParts = (ms: number): Parts => { +export const msToParts = (ms: number): Parts => { const absoluteMs = Math.abs(ms); return { diff --git a/src/utilities.ts b/src/utilities.ts index a43a039..5b41516 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,2 +1,2 @@ export { parseDuration } from './parseDuration.js'; -export { toParts, type Parts } from './toParts.js'; +export { msToParts, type Parts } from './msToParts.js'; From fe1068872d898c6c1bee1c69fb94dba81ca2337d Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Thu, 12 Jun 2025 00:09:59 +0100 Subject: [PATCH 04/12] Extract Parts and TimeUnit types --- src/index.ts | 6 ++++++ src/interfaces/Parts.ts | 15 +++++++++++++++ src/msToParts.ts | 13 +------------ src/parseDuration.ts | 3 +-- src/types.ts | 2 ++ src/types/TimeUnit.ts | 1 + src/utilities.ts | 2 +- 7 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 src/interfaces/Parts.ts create mode 100644 src/types.ts create mode 100644 src/types/TimeUnit.ts diff --git a/src/index.ts b/src/index.ts index 0920d65..fde9b3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,3 +15,9 @@ export * from './conversion.js'; */ export * from './utilities.js'; + +/** + * Types + */ + +export * from './types.js'; diff --git a/src/interfaces/Parts.ts b/src/interfaces/Parts.ts new file mode 100644 index 0000000..e0e738c --- /dev/null +++ b/src/interfaces/Parts.ts @@ -0,0 +1,15 @@ +/** + * This interface represents the parts of a duration. + */ + +export interface Parts { + days: number; + hours: number; + isNegative: boolean; + nanoseconds: number; + microseconds: number; + milliseconds: number; + minutes: number; + seconds: number; + weeks: number; +} diff --git a/src/msToParts.ts b/src/msToParts.ts index 1098a44..150ca49 100644 --- a/src/msToParts.ts +++ b/src/msToParts.ts @@ -16,18 +16,7 @@ import { seconds, weeks, } from './conversion.js'; - -export interface Parts { - days: number; - hours: number; - isNegative: boolean; - nanoseconds: number; - microseconds: number; - milliseconds: number; - minutes: number; - seconds: number; - weeks: number; -} +import { Parts } from './interfaces/Parts.js'; /** * Converts a duration in milliseconds to an object with properties for each time unit. diff --git a/src/parseDuration.ts b/src/parseDuration.ts index c941961..23343fb 100644 --- a/src/parseDuration.ts +++ b/src/parseDuration.ts @@ -8,8 +8,6 @@ import { } from './conversion.js'; import { UnitConverter } from './utils/createUnitConverter.js'; -type TimeUnit = 'ns' | 'μs' | 'ms' | 's' | 'm' | 'h' | 'd'; - const unitToConverterMap = { ns: nanoseconds, μs: microseconds, @@ -19,6 +17,7 @@ const unitToConverterMap = { h: hours, d: days, } satisfies Record; +import { TimeUnit } from './types/TimeUnit.js'; const regex = new RegExp( `(\\d+(?:\\.\\d+)?)(${Object.keys(unitToConverterMap).join('|')})`, diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..6af6b21 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,2 @@ +export type { Parts } from './interfaces/Parts.js'; +export type { TimeUnit } from './types/TimeUnit.js'; diff --git a/src/types/TimeUnit.ts b/src/types/TimeUnit.ts new file mode 100644 index 0000000..673592b --- /dev/null +++ b/src/types/TimeUnit.ts @@ -0,0 +1 @@ +export type TimeUnit = 'ns' | 'μs' | 'ms' | 's' | 'm' | 'h' | 'd' | 'w'; diff --git a/src/utilities.ts b/src/utilities.ts index 5b41516..e4ae4ff 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,2 +1,2 @@ +export { msToParts } from './msToParts.js'; export { parseDuration } from './parseDuration.js'; -export { msToParts, type Parts } from './msToParts.js'; From 5a31b9b8d36caa4f65673662dee6f03801a6e89d Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Thu, 12 Jun 2025 22:10:07 +0100 Subject: [PATCH 05/12] Fix float conversion --- CHANGELOG.md | 1 + src/conversion.test.ts | 118 ++++++++++++++++++++++---- src/parseDuration.ts | 24 +----- src/utils/createUnitConverter.test.ts | 20 +++++ src/utils/createUnitConverter.ts | 14 ++- src/utils/floatOperation.test.ts | 15 ++++ src/utils/floatOperation.ts | 14 +++ src/utils/getDecimalPlaces.test.ts | 13 +++ src/utils/getDecimalPlaces.ts | 13 +++ src/utils/unitToConverterMap.ts | 23 +++++ 10 files changed, 214 insertions(+), 41 deletions(-) create mode 100644 src/utils/floatOperation.test.ts create mode 100644 src/utils/floatOperation.ts create mode 100644 src/utils/getDecimalPlaces.test.ts create mode 100644 src/utils/getDecimalPlaces.ts create mode 100644 src/utils/unitToConverterMap.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a7455dd..f6f1dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Incorrect/missing documentation (thanks [@simmo]) +- Floating point conversion error (thanks [@simmo]) ## [1.2.0] - 2025-06-10 diff --git a/src/conversion.test.ts b/src/conversion.test.ts index 53ff362..775d0fa 100644 --- a/src/conversion.test.ts +++ b/src/conversion.test.ts @@ -11,29 +11,113 @@ import { } from './conversion.js'; import { UnitConverter } from './utils/createUnitConverter.js'; -const testCases: [unit: string, fn: UnitConverter, milliseconds: number][] = [ - ['nanoseconds', nanoseconds, 0.000_001], - ['microseconds', microseconds, 0.001], - ['milliseconds', milliseconds, 1], - ['seconds', seconds, 1_000], - ['minutes', minutes, 60_000], - ['hours', hours, 3_600_000], - ['days', days, 86_400_000], - ['weeks', weeks, 604_800_000], +const testCases: [ + unit: string, + fn: UnitConverter, + inputs: [amount: number, ms: number][], +][] = [ + [ + 'nanoseconds', + nanoseconds, + [ + [1, 0.000_001], + [2, 0.000_002], + [3, 0.000_003], + [5, 0.000_005], + [8, 0.000_008], + ], + ], + [ + 'microseconds', + microseconds, + [ + [1, 0.001], + [2, 0.002], + [3, 0.003], + [5, 0.005], + [8, 0.008], + ], + ], + [ + 'milliseconds', + milliseconds, + [ + [1, 1], + [2, 2], + [3, 3], + [5, 5], + [8, 8], + ], + ], + [ + 'seconds', + seconds, + [ + [1, 1_000], + [2, 2_000], + [3, 3_000], + [5, 5_000], + [8, 8_000], + ], + ], + [ + 'minutes', + minutes, + [ + [1, 60_000], + [2, 120_000], + [3, 180_000], + [5, 300_000], + [8, 480_000], + ], + ], + [ + 'hours', + hours, + [ + [1, 3_600_000], + [2, 7_200_000], + [3, 10_800_000], + [5, 18_000_000], + [8, 28_800_000], + ], + ], + [ + 'days', + days, + [ + [1, 86_400_000], + [2, 172_800_000], + [3, 259_200_000], + [5, 432_000_000], + [8, 691_200_000], + ], + ], + [ + 'weeks', + weeks, + [ + [1, 604_800_000], + [2, 1_209_600_000], + [3, 1_814_400_000], + [5, 3_024_000_000], + [8, 4_838_400_000], + ], + ], ]; -const testUnitAmounts = [1, 2, 3, 5, 8]; - describe('conversion', () => { - it.each(testCases)('returns milliseconds from %s', (_, fn, milliseconds) => { - testUnitAmounts.forEach(amount => { - expect(fn(amount)).toBe(amount * milliseconds); + it.each(testCases)('returns milliseconds from %s', (_, fn, inputs) => { + inputs.forEach(([amount, milliseconds]) => { + expect(fn(amount), `${amount} >>> ${milliseconds}ms`).toBe(milliseconds); }); }); - it.each(testCases)('returns %s from milliseconds', (_, fn, milliseconds) => { - testUnitAmounts.forEach(amount => { - expect(fn.from(amount * milliseconds)).toBe(amount); + it.each(testCases)('returns %s from milliseconds', (_, fn, inputs) => { + inputs.forEach(([amount, milliseconds]) => { + expect(fn.from(milliseconds), `${milliseconds}ms >>> ${amount}`).toBe( + amount, + ); }); }); }); diff --git a/src/parseDuration.ts b/src/parseDuration.ts index 23343fb..84ac40d 100644 --- a/src/parseDuration.ts +++ b/src/parseDuration.ts @@ -1,23 +1,5 @@ -import { - days, - hours, - microseconds, - minutes, - nanoseconds, - seconds, -} from './conversion.js'; -import { UnitConverter } from './utils/createUnitConverter.js'; - -const unitToConverterMap = { - ns: nanoseconds, - μs: microseconds, - ms: null, - s: seconds, - m: minutes, - h: hours, - d: days, -} satisfies Record; import { TimeUnit } from './types/TimeUnit.js'; +import { unitToConverterMap } from './utils/unitToConverterMap.js'; const regex = new RegExp( `(\\d+(?:\\.\\d+)?)(${Object.keys(unitToConverterMap).join('|')})`, @@ -66,10 +48,8 @@ export const parseDuration = ( const fn = unitToConverterMap[unit as TimeUnit]; if (fn !== undefined) { - const value = parseFloat(num); - total ??= 0; - total += fn === null ? value : fn(value); + total += fn(parseFloat(num)); } } diff --git a/src/utils/createUnitConverter.test.ts b/src/utils/createUnitConverter.test.ts index df6d660..7091f3e 100644 --- a/src/utils/createUnitConverter.test.ts +++ b/src/utils/createUnitConverter.test.ts @@ -17,4 +17,24 @@ describe('createUnitConverter()', () => { expect(fn.from(2_000)).toBe(2); expect(fn.from(3_000)).toBe(3); }); + + it('returns a function that provides the number of milliseconds of a given input with decimals', () => { + const fn = createUnitConverter(0.000_001); + + expect(fn(1)).toBe(0.000_001); + expect(fn(2)).toBe(0.000_002); + expect(fn(3)).toBe(0.000_003); + expect(fn(5)).toBe(0.000_005); + expect(fn(8)).toBe(0.000_008); + }); + + it('returns a method that provides the number of units from a given input with decimals places', () => { + const fn = createUnitConverter(0.000_001); + + expect(fn.from(0.000_001)).toBe(1); + expect(fn.from(0.000_002)).toBe(2); + expect(fn.from(0.000_003)).toBe(3); + expect(fn.from(0.000_005)).toBe(5); + expect(fn.from(0.000_008)).toBe(8); + }); }); diff --git a/src/utils/createUnitConverter.ts b/src/utils/createUnitConverter.ts index 5233b9b..6156998 100644 --- a/src/utils/createUnitConverter.ts +++ b/src/utils/createUnitConverter.ts @@ -1,3 +1,5 @@ +import { floatOperation } from './floatOperation.js'; + export type UnitConverter = { /** * Converts the number of units to milliseconds. @@ -27,9 +29,17 @@ export type UnitConverter = { */ export const createUnitConverter = (unit: number): UnitConverter => { - const fn = (amount: number) => amount * unit; + const fn = (amount: number) => + floatOperation( + [unit], + ([normalisedUnit], factor) => (amount * normalisedUnit) / factor, + ); - fn.from = (ms: number) => ms / unit; + fn.from = (ms: number) => + floatOperation( + [ms, unit], + ([normalisedMs, normalisedUnit]) => normalisedMs / normalisedUnit, + ); return fn; }; diff --git a/src/utils/floatOperation.test.ts b/src/utils/floatOperation.test.ts new file mode 100644 index 0000000..a7618ee --- /dev/null +++ b/src/utils/floatOperation.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from 'vitest'; +import { floatOperation } from './floatOperation.js'; + +describe('floatOperation()', () => { + it('return normalised values and factor from input floats', () => { + const value = floatOperation([0.000_005, 0.000_001], (values, factor) => { + expect(values).toStrictEqual([5, 1]); + expect(factor).toBe(1_000_000); + + return values[0] / values[1]; + }); + + expect(value).toStrictEqual(5); + }); +}); diff --git a/src/utils/floatOperation.ts b/src/utils/floatOperation.ts new file mode 100644 index 0000000..6a74311 --- /dev/null +++ b/src/utils/floatOperation.ts @@ -0,0 +1,14 @@ +import { getDecimalPlaces } from './getDecimalPlaces.js'; + +export const floatOperation = ( + floats: number[], + operation: (normalised: number[], factor: number) => number, +) => { + const maxDecimals = Math.max(...floats.map(getDecimalPlaces)); + const factor = Math.pow(10, maxDecimals); + + return operation( + floats.map(value => Math.round(value * factor)), + factor, + ); +}; diff --git a/src/utils/getDecimalPlaces.test.ts b/src/utils/getDecimalPlaces.test.ts new file mode 100644 index 0000000..93696b4 --- /dev/null +++ b/src/utils/getDecimalPlaces.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest'; +import { getDecimalPlaces } from './getDecimalPlaces.js'; + +describe('getDecimalPlaces()', () => { + it('returns the number of decimal places in a number', () => { + expect(getDecimalPlaces(1.23)).toBe(2); + expect(getDecimalPlaces(1.2345)).toBe(4); + expect(getDecimalPlaces(1)).toBe(0); + expect(getDecimalPlaces(0.001)).toBe(3); + expect(getDecimalPlaces(1e-7)).toBe(7); + expect(getDecimalPlaces(1234567890.123456789)).toBe(7); + }); +}); diff --git a/src/utils/getDecimalPlaces.ts b/src/utils/getDecimalPlaces.ts new file mode 100644 index 0000000..895fe47 --- /dev/null +++ b/src/utils/getDecimalPlaces.ts @@ -0,0 +1,13 @@ +export const getDecimalPlaces = (number: number): number => { + const str = number.toExponential(); // handles cases like 1e-7 + const match = str.match(/e-(\d+)/); + const decimalPart = number.toString().split('.').at(1)?.length || 0; + + if (match) { + const decimals = parseInt(match[1], 10); + + return Math.max(decimals, decimalPart); + } + + return Math.min(7, decimalPart); +}; diff --git a/src/utils/unitToConverterMap.ts b/src/utils/unitToConverterMap.ts new file mode 100644 index 0000000..8837a4c --- /dev/null +++ b/src/utils/unitToConverterMap.ts @@ -0,0 +1,23 @@ +import { + days, + hours, + microseconds, + milliseconds, + minutes, + nanoseconds, + seconds, + weeks, +} from '../conversion.js'; +import { TimeUnit } from '../types/TimeUnit.js'; +import { UnitConverter } from './createUnitConverter.js'; + +export const unitToConverterMap = { + ns: nanoseconds, + μs: microseconds, + ms: milliseconds, + s: seconds, + m: minutes, + h: hours, + d: days, + w: weeks, +} as const satisfies Record; From 04e08cd67e088b133faf2dccd41dd013312134a4 Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Thu, 12 Jun 2025 22:12:49 +0100 Subject: [PATCH 06/12] Add partsToMs --- CHANGELOG.md | 4 +++ README.md | 6 +++- src/index.test.ts | 1 + src/partsToMs.test.ts | 62 +++++++++++++++++++++++++++++++++ src/partsToMs.ts | 37 ++++++++++++++++++++ src/utilities.ts | 1 + src/utils/partToConverterMap.ts | 25 +++++++++++++ 7 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/partsToMs.test.ts create mode 100644 src/partsToMs.ts create mode 100644 src/utils/partToConverterMap.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f6f1dd8..b153005 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Renames `MILLISECONDS_IN_A_SECOND` to `MILLISECONDS_IN_SECOND` (thanks [@simmo]) - Renames `NANOSECONDS_IN_A_MICROSECOND` to `NANOSECONDS_IN_MICROSECOND` (thanks [@simmo]) +### Added + +- `partsToMs` function to convert an object with time unit properties to milliseconds (thanks [@simmo]) + ### Fixed - Incorrect/missing documentation (thanks [@simmo]) diff --git a/README.md b/README.md index 4b7af95..d13ac13 100644 --- a/README.md +++ b/README.md @@ -139,11 +139,15 @@ parseDuration('invalid', true); Converts a duration in milliseconds to a [Parts](#parts) object with properties for each time unit. +#### `partsToMs(parts: Partial): number` + +Converts a [Parts](#parts) object to duration in milliseconds. + ### Types #### `Parts` -Used in the [`msToParts`](#mstopartsmilliseconds-number-parts) function, this type represents the parts of a duration. +Used in the [`msToParts`](#mstopartsmilliseconds-number-parts) and [`partsToMs`](#partstomsparts-partialparts-number) functions. This interface represents the parts of a duration. ```ts interface Parts { diff --git a/src/index.test.ts b/src/index.test.ts index 0099016..a3f82f2 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -28,6 +28,7 @@ describe('api', () => { "msToParts": [Function], "nanoseconds": [Function], "parseDuration": [Function], + "partsToMs": [Function], "seconds": [Function], "weeks": [Function], } diff --git a/src/partsToMs.test.ts b/src/partsToMs.test.ts new file mode 100644 index 0000000..2b1187e --- /dev/null +++ b/src/partsToMs.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from 'vitest'; +import { partsToMs } from './partsToMs.js'; + +describe('partsToMs()', () => { + it('returns the correct parts for a positive duration', () => { + const result = partsToMs({ + days: 1, + hours: 20, + milliseconds: 500, + minutes: 56, + seconds: 46, + weeks: 1, + }); + + expect(result).toEqual(766_606_500); + }); + + it('returns the correct parts for a negative duration', () => { + const result = partsToMs({ + days: 1, + hours: 20, + isNegative: true, + milliseconds: 500, + minutes: 56, + seconds: 46, + weeks: 1, + }); + + expect(result).toEqual(-766_606_500); + }); + + it('returns the correct parts for a positive duration with decimal places', () => { + const result = partsToMs({ + days: 1, + hours: 20, + microseconds: 3, + milliseconds: 500, + minutes: 56, + nanoseconds: 2, + seconds: 46, + weeks: 1, + }); + + expect(result).toEqual(766_606_500.003002); + }); + + it('returns the correct parts for a negative duration with decimal places', () => { + const result = partsToMs({ + days: 1, + hours: 20, + isNegative: true, + microseconds: 3, + milliseconds: 500, + minutes: 56, + nanoseconds: 2, + seconds: 46, + weeks: 1, + }); + + expect(result).toEqual(-766_606_500.003002); + }); +}); diff --git a/src/partsToMs.ts b/src/partsToMs.ts new file mode 100644 index 0000000..83b9e39 --- /dev/null +++ b/src/partsToMs.ts @@ -0,0 +1,37 @@ +import { Parts } from './interfaces/Parts.js'; +import { UnitConverter } from './utils/createUnitConverter.js'; +import { floatOperation } from './utils/floatOperation.js'; +import { partToConverterMap } from './utils/partToConverterMap.js'; + +/** + * Converts a Parts object to duration in milliseconds. + * + * @param parts An partial object with properties for each time unit + * @returns The duration in milliseconds + */ + +export const partsToMs = ({ + isNegative = false, + ...parts +}: Partial): number => { + const convertedValues = Object.entries(parts).reduce( + (acc, [key, value]) => { + if (typeof value === 'number' && !isNaN(value) && value >= 0) { + const unitConverter: UnitConverter | undefined = + partToConverterMap[key as keyof typeof partToConverterMap]; + + if (unitConverter) acc.push(unitConverter(value)); + } + + return acc; + }, + [], + ); + + const result = floatOperation( + convertedValues, + (values, factor) => values.reduce((sum, value) => sum + value, 0) / factor, + ); + + return isNegative ? -result : result; +}; diff --git a/src/utilities.ts b/src/utilities.ts index e4ae4ff..404531b 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,2 +1,3 @@ export { msToParts } from './msToParts.js'; export { parseDuration } from './parseDuration.js'; +export { partsToMs } from './partsToMs.js'; diff --git a/src/utils/partToConverterMap.ts b/src/utils/partToConverterMap.ts new file mode 100644 index 0000000..4efeebd --- /dev/null +++ b/src/utils/partToConverterMap.ts @@ -0,0 +1,25 @@ +import { Parts } from '../types.js'; +import { UnitConverter } from './createUnitConverter.js'; +import { + days, + hours, + microseconds, + milliseconds, + minutes, + nanoseconds, + seconds, + weeks, +} from '../conversion.js'; + +export const partToConverterMap = { + nanoseconds, + microseconds, + milliseconds, + seconds, + minutes, + hours, + days, + weeks, +} as const satisfies { + [K in keyof Parts as Parts[K] extends number ? K : never]: UnitConverter; +}; From ba8d705276c216dfba094aa4343f78a531178f73 Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Thu, 12 Jun 2025 22:22:10 +0100 Subject: [PATCH 07/12] Add TimeUnit to docs --- CHANGELOG.md | 2 ++ README.md | 10 ++++++++++ src/partsToMs.ts | 2 +- src/types/TimeUnit.ts | 13 +++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b153005..39f7468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `partsToMs` function to convert an object with time unit properties to milliseconds (thanks [@simmo]) +- `Parts` interface to represent the parts of a duration (thanks [@simmo]) +- `TimeUnit` type to represent the time unit strings used in the `parseDuration` function (thanks [@simmo]) ### Fixed diff --git a/README.md b/README.md index d13ac13..0910234 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ setTimeout(() => /* Do something */}, minutes(2) + seconds(30)); Additionally, Niobe provides [utilities](#utilities) to parse durations, split them into their components and [convert](#conversion) between different time units. There is even a range of common [constants](#constants). +Take a look at the [FAQs](#faqs) for more information, the [CHANGELOG](./CHANGELOG.md) for the latest changes or the [contribution guide](./CONTRIBUTING.md) if you want to get involved. + --- ## Installation @@ -145,6 +147,14 @@ Converts a [Parts](#parts) object to duration in milliseconds. ### Types +#### `TimeUnit` + +This type represents the time unit strings used in the [`parseDuration`](#parsedurationduration-string-strict-boolean--false-number) function. It can be one of the following: + +```ts +type TimeUnit = 'ns' | 'μs' | 'ms' | 's' | 'm' | 'h' | 'd' | 'w'; +``` + #### `Parts` Used in the [`msToParts`](#mstopartsmilliseconds-number-parts) and [`partsToMs`](#partstomsparts-partialparts-number) functions. This interface represents the parts of a duration. diff --git a/src/partsToMs.ts b/src/partsToMs.ts index 83b9e39..a9fba5c 100644 --- a/src/partsToMs.ts +++ b/src/partsToMs.ts @@ -6,7 +6,7 @@ import { partToConverterMap } from './utils/partToConverterMap.js'; /** * Converts a Parts object to duration in milliseconds. * - * @param parts An partial object with properties for each time unit + * @param parts A partial object with properties for each time unit * @returns The duration in milliseconds */ diff --git a/src/types/TimeUnit.ts b/src/types/TimeUnit.ts index 673592b..ab9659e 100644 --- a/src/types/TimeUnit.ts +++ b/src/types/TimeUnit.ts @@ -1 +1,14 @@ +/** + * Represents all possible time units. + * + * - `ns` - Nanoseconds + * - `μs` - Microseconds + * - `ms` - Milliseconds + * - `s` - Seconds + * - `m` - Minutes + * - `h` - Hours + * - `d` - Days + * - `w` - Weeks + */ + export type TimeUnit = 'ns' | 'μs' | 'ms' | 's' | 'm' | 'h' | 'd' | 'w'; From f57c308d4d0f3ed05ece789ae7fb00b2e860fd8c Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Fri, 13 Jun 2025 07:57:59 +0100 Subject: [PATCH 08/12] Add clockToMs --- CHANGELOG.md | 1 + README.md | 13 ++++++ src/clockToMs.test.ts | 58 +++++++++++++++++++++++++ src/clockToMs.ts | 98 +++++++++++++++++++++++++++++++++++++++++++ src/index.test.ts | 1 + src/utilities.ts | 1 + 6 files changed, 172 insertions(+) create mode 100644 src/clockToMs.test.ts create mode 100644 src/clockToMs.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f7468..c29ce99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `clockToMs` function to convert a clock string to milliseconds (thanks [@simmo]) - `partsToMs` function to convert an object with time unit properties to milliseconds (thanks [@simmo]) - `Parts` interface to represent the parts of a duration (thanks [@simmo]) - `TimeUnit` type to represent the time unit strings used in the `parseDuration` function (thanks [@simmo]) diff --git a/README.md b/README.md index 0910234..9bdd2c6 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,19 @@ _[This seems pointless, why is it here?](#this-seems-pointless-why-is-it-here)_ ### Utilities +#### `clockToMs(clock: string): number` + +Converts a `hh:mm:ss.ms_μs_ns` string to milliseconds. + +- `hh` - Hours - Optional, optional leading zero +- `mm` - Minutes - Optional, optional leading zero +- `ss` - Seconds - Required, optional leading zero +- `ms` - Milliseconds - Optional, optional trailing zeros +- `μs` - Microseconds - Optional, optional trailing zeros +- `ns` - Nanoseconds - Optional, optional trailing zeros + +Milliseconds, microseconds, and nanoseconds can be optionally separated by underscores, if not, you must provide padding. + #### `parseDuration(duration: string, strict: boolean = false): number` Parses a duration string, returning milliseconds. diff --git a/src/clockToMs.test.ts b/src/clockToMs.test.ts new file mode 100644 index 0000000..57700b5 --- /dev/null +++ b/src/clockToMs.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from 'vitest'; + +import { clockToMs } from './clockToMs.js'; + +const testCases = [ + ['00:00:01', 1_000], + ['00:01:01', 61_000], + ['01:01:01', 3_661_000], + ['1', 1_000], + ['01', 1_000], + ['1:1', 61_000], + ['1:01', 61_000], + ['01:1', 61_000], + ['1:1:1', 3_661_000], + ['1:1:01', 3_661_000], + ['1:01:01', 3_661_000], + ['00:00:01.500000005', 1_500.000_005], + ['00:01:01.500000005', 61_500.000_005], + ['01:01:01.500000005', 3_661_500.000_005], + ['1.500000005', 1_500.000_005], + ['01.500000005', 1_500.000_005], + ['1:1.500000005', 61_500.000_005], + ['1:01.500000005', 61_500.000_005], + ['01:1.500000005', 61_500.000_005], + ['1:1:1.500000005', 3_661_500.000_005], + ['1:1:01.500000005', 3_661_500.000_005], + ['1:01:01.500000005', 3_661_500.000_005], + ['00:00:01.500_000_005', 1_500.000_005], + ['00:01:01.500_000_005', 61_500.000_005], + ['01:01:01.500_000_005', 3_661_500.000_005], + ['1.500_000_005', 1_500.000_005], + ['01.500_000_005', 1_500.000_005], + ['1:1.500_000_005', 61_500.000_005], + ['1:01.500_000_005', 61_500.000_005], + ['01:1.500_000_005', 61_500.000_005], + ['1:1:1.500_000_005', 3_661_500.000_005], + ['1:1:01.500_000_005', 3_661_500.000_005], + ['1:01:01.500_000_005', 3_661_500.000_005], + ['1:01:01.500_000_005', 3_661_500.000_005], + ['1:01:01.5_0_005', 3_661_500.000_005], + ['1:01:01.5_5_5', 3_661_500.500_500], +] as const; + +describe('clockToMs()', () => { + describe.each([ + ['positive', false], + ['negative', true], + ])('%s time', (_, isNegative) => { + const [offset, sign] = isNegative ? [-1, '-'] : [1, '']; + + it.each(testCases)( + `returns ${sign}%s for ${sign}%s`, + (clock, milliseconds) => { + expect(clockToMs(`${sign}${clock}`)).toBe(milliseconds * offset); + }, + ); + }); +}); diff --git a/src/clockToMs.ts b/src/clockToMs.ts new file mode 100644 index 0000000..084d97f --- /dev/null +++ b/src/clockToMs.ts @@ -0,0 +1,98 @@ +import { + hours, + microseconds, + milliseconds, + minutes, + nanoseconds, + seconds, +} from './conversion.js'; + +/** + * Converts a `hh:mm:ss.ms_μs_ns` string to milliseconds. + * + * - `hh` - Hours - Optional, optional leading zero + * - `mm` - Minutes - Optional, optional leading zero + * - `ss` - Seconds - Required, optional leading zero + * - `ms` - Milliseconds - Optional, optional trailing zeros + * - `μs` - Microseconds - Optional, optional trailing zeros + * - `ns` - Nanoseconds - Optional, optional trailing zeros + * + * Milliseconds, microseconds, and nanoseconds can be optionally separated by underscores, if not, you must provide padding. + * + * @example Seconds + * ```ts + * clockToMs('01:10') + * // => 70_000 + * ``` + * + * @example Minutes and seconds + * ```ts + * clockToMs('01:10') + * // => 70_000 + * ``` + * + * @example Hours, minutes, seconds + * ```ts + * clockToMs('01:01:01') + * // => 3_661_000 + * ``` + * + * @example Decimal notation with underscores + * ```ts + * clockToMs('01:01:01.500_000_005') + * // => 3_661_500.000_005 + * ``` + +* @example Decimal notation without underscores + * ```ts + * clockToMs('01:01:01.500000005') + * // => 3_661_500.000_005 + * ``` + +* @example Negative time + * ```ts + * clockToMs('-01:01:01.500000005') + * // => -3_661_500.000_005 + * ``` + */ + +export const clockToMs = (clock: string): number => { + const [time, fraction] = clock.split('.'); + const [timeAbsolute, isNegative] = time.startsWith('-') + ? [time.slice(1), true] + : [time, false]; + const [s = 0, m = 0, h = 0] = timeAbsolute.split(':').map(Number).reverse(); + + let millis = 0; + let micros = 0; + let nanos = 0; + + if (fraction) { + const parts = fraction.split('_'); + + if (parts.length === 1) { + const fractionStr = (parts[0] + '000000000').slice(0, 9); + + millis = parseInt(fractionStr.slice(0, 3), 10); + micros = parseInt(fractionStr.slice(3, 6), 10); + nanos = parseInt(fractionStr.slice(6, 9), 10); + } else { + if (typeof parts[0] !== 'undefined') + millis = parseInt(parts[0].padEnd(3, '0'), 10); + if (typeof parts[1] !== 'undefined') + micros = parseInt(parts[1].padEnd(3, '0'), 10); + if (typeof parts[2] !== 'undefined') + nanos = parseInt(parts[2].padEnd(3, '0'), 10); + } + } + + return ( + (hours(h) + + minutes(m) + + seconds(s) + + milliseconds(millis) + + microseconds(micros) + + nanoseconds(nanos)) * + (isNegative ? -1 : 1) + ); +}; diff --git a/src/index.test.ts b/src/index.test.ts index a3f82f2..ab1bdcd 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -20,6 +20,7 @@ describe('api', () => { "SECOND": 1000, "SECONDS_IN_MINUTE": 60, "WEEK": 604800000, + "clockToMs": [Function], "days": [Function], "hours": [Function], "microseconds": [Function], diff --git a/src/utilities.ts b/src/utilities.ts index 404531b..d83fa79 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,3 +1,4 @@ +export { clockToMs } from './clockToMs.js'; export { msToParts } from './msToParts.js'; export { parseDuration } from './parseDuration.js'; export { partsToMs } from './partsToMs.js'; From b8e8ff60a6b5032a43844d5306804c6e5184713f Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Sat, 14 Jun 2025 00:27:33 +0100 Subject: [PATCH 09/12] Add msToClock --- CHANGELOG.md | 1 + README.md | 6 +++ src/clockToMs.test.ts | 75 ++++++++++++++------------- src/clockToMs.ts | 12 +++-- src/constants/DecimalSeparator.ts | 1 + src/constants/DecimalUnitSeparator.ts | 1 + src/constants/HmsSeparator.ts | 1 + src/index.test.ts | 1 + src/msToClock.test.ts | 49 +++++++++++++++++ src/msToClock.ts | 66 +++++++++++++++++++++++ src/msToParts.ts | 4 +- src/utilities.ts | 1 + src/utils/getDecimalPlaces.ts | 11 ++-- src/utils/padLeadingZero.test.ts | 15 ++++++ src/utils/padLeadingZero.ts | 2 + 15 files changed, 198 insertions(+), 48 deletions(-) create mode 100644 src/constants/DecimalSeparator.ts create mode 100644 src/constants/DecimalUnitSeparator.ts create mode 100644 src/constants/HmsSeparator.ts create mode 100644 src/msToClock.test.ts create mode 100644 src/msToClock.ts create mode 100644 src/utils/padLeadingZero.test.ts create mode 100644 src/utils/padLeadingZero.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c29ce99..4b064cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `msToClock` function to convert milliseconds to a clock string (thanks [@simmo]) - `clockToMs` function to convert a clock string to milliseconds (thanks [@simmo]) - `partsToMs` function to convert an object with time unit properties to milliseconds (thanks [@simmo]) - `Parts` interface to represent the parts of a duration (thanks [@simmo]) diff --git a/README.md b/README.md index 9bdd2c6..84939ef 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,12 @@ Converts a `hh:mm:ss.ms_μs_ns` string to milliseconds. Milliseconds, microseconds, and nanoseconds can be optionally separated by underscores, if not, you must provide padding. +#### `msToClock(milliseconds: number, options?: { separateDecimal = true }): string` + +Converts milliseconds to a string in the format 'hh:mm:ss' optionally suffixing '.ms_μs_ns' if there are any remaining milliseconds, microseconds or nanoseconds. + +`separateDecimal` - If true, milliseconds, microseconds, and nanoseconds will be separated by underscores. If false, they will be concatenated without separators. + #### `parseDuration(duration: string, strict: boolean = false): number` Parses a duration string, returning milliseconds. diff --git a/src/clockToMs.test.ts b/src/clockToMs.test.ts index 57700b5..41940ea 100644 --- a/src/clockToMs.test.ts +++ b/src/clockToMs.test.ts @@ -3,42 +3,43 @@ import { describe, expect, it } from 'vitest'; import { clockToMs } from './clockToMs.js'; const testCases = [ - ['00:00:01', 1_000], - ['00:01:01', 61_000], - ['01:01:01', 3_661_000], - ['1', 1_000], - ['01', 1_000], - ['1:1', 61_000], - ['1:01', 61_000], - ['01:1', 61_000], - ['1:1:1', 3_661_000], - ['1:1:01', 3_661_000], - ['1:01:01', 3_661_000], - ['00:00:01.500000005', 1_500.000_005], - ['00:01:01.500000005', 61_500.000_005], - ['01:01:01.500000005', 3_661_500.000_005], - ['1.500000005', 1_500.000_005], - ['01.500000005', 1_500.000_005], - ['1:1.500000005', 61_500.000_005], - ['1:01.500000005', 61_500.000_005], - ['01:1.500000005', 61_500.000_005], - ['1:1:1.500000005', 3_661_500.000_005], - ['1:1:01.500000005', 3_661_500.000_005], - ['1:01:01.500000005', 3_661_500.000_005], - ['00:00:01.500_000_005', 1_500.000_005], - ['00:01:01.500_000_005', 61_500.000_005], - ['01:01:01.500_000_005', 3_661_500.000_005], - ['1.500_000_005', 1_500.000_005], - ['01.500_000_005', 1_500.000_005], - ['1:1.500_000_005', 61_500.000_005], - ['1:01.500_000_005', 61_500.000_005], - ['01:1.500_000_005', 61_500.000_005], - ['1:1:1.500_000_005', 3_661_500.000_005], - ['1:1:01.500_000_005', 3_661_500.000_005], - ['1:01:01.500_000_005', 3_661_500.000_005], - ['1:01:01.500_000_005', 3_661_500.000_005], - ['1:01:01.5_0_005', 3_661_500.000_005], - ['1:01:01.5_5_5', 3_661_500.500_500], + [1_000, '00:00:01'], + [61_000, '00:01:01'], + [3_661_000, '01:01:01'], + [1_000, '1'], + [1_000, '01'], + [61_000, '1:1'], + [61_000, '1:01'], + [61_000, '01:1'], + [3_661_000, '1:1:1'], + [3_661_000, '1:1:01'], + [3_661_000, '1:01:01'], + [1_500.000_005, '00:00:01.500000005'], + [61_500.000_005, '00:01:01.500000005'], + [3_661_500.000_005, '01:01:01.500000005'], + [1_500.000_005, '1.500000005'], + [1_500.000_005, '01.500000005'], + [61_500.000_005, '1:1.500000005'], + [61_500.000_005, '1:01.500000005'], + [61_500.000_005, '01:1.500000005'], + [3_661_500.000_005, '1:1:1.500000005'], + [3_661_500.000_005, '1:1:01.500000005'], + [3_661_500.000_005, '1:01:01.500000005'], + [1_500.000_005, '00:00:01.500_000_005'], + [61_500.000_005, '00:01:01.500_000_005'], + [3_661_500.000_005, '01:01:01.500_000_005'], + [1_500.000_005, '1.500_000_005'], + [1_500.000_005, '01.500_000_005'], + [61_500.000_005, '1:1.500_000_005'], + [61_500.000_005, '1:01.500_000_005'], + [61_500.000_005, '01:1.500_000_005'], + [3_661_500.000_005, '1:1:1.500_000_005'], + [3_661_500.000_005, '1:1:01.500_000_005'], + [3_661_500.000_005, '1:01:01.500_000_005'], + [3_661_500.000_005, '1:01:01.500_000_005'], + [3_661_500.000_005, '1:01:01.5_0_005'], + [3_661_500.500_500, '1:01:01.5_5_5'], + [3_661_500.500_500, '01:01:01.500_500_500'], ] as const; describe('clockToMs()', () => { @@ -50,7 +51,7 @@ describe('clockToMs()', () => { it.each(testCases)( `returns ${sign}%s for ${sign}%s`, - (clock, milliseconds) => { + (milliseconds, clock) => { expect(clockToMs(`${sign}${clock}`)).toBe(milliseconds * offset); }, ); diff --git a/src/clockToMs.ts b/src/clockToMs.ts index 084d97f..e3de2f7 100644 --- a/src/clockToMs.ts +++ b/src/clockToMs.ts @@ -1,3 +1,6 @@ +import { DECIMAL_SEPARATOR } from './constants/DecimalSeparator.js'; +import { DECIMAL_UNIT_SEPARATOR } from './constants/DecimalUnitSeparator.js'; +import { HMS_SEPARATOR } from './constants/HmsSeparator.js'; import { hours, microseconds, @@ -57,18 +60,21 @@ import { */ export const clockToMs = (clock: string): number => { - const [time, fraction] = clock.split('.'); + const [time, fraction] = clock.split(DECIMAL_SEPARATOR); const [timeAbsolute, isNegative] = time.startsWith('-') ? [time.slice(1), true] : [time, false]; - const [s = 0, m = 0, h = 0] = timeAbsolute.split(':').map(Number).reverse(); + const [s = 0, m = 0, h = 0] = timeAbsolute + .split(HMS_SEPARATOR) + .map(Number) + .reverse(); let millis = 0; let micros = 0; let nanos = 0; if (fraction) { - const parts = fraction.split('_'); + const parts = fraction.split(DECIMAL_UNIT_SEPARATOR); if (parts.length === 1) { const fractionStr = (parts[0] + '000000000').slice(0, 9); diff --git a/src/constants/DecimalSeparator.ts b/src/constants/DecimalSeparator.ts new file mode 100644 index 0000000..a2c0d5c --- /dev/null +++ b/src/constants/DecimalSeparator.ts @@ -0,0 +1 @@ +export const DECIMAL_SEPARATOR = '.'; diff --git a/src/constants/DecimalUnitSeparator.ts b/src/constants/DecimalUnitSeparator.ts new file mode 100644 index 0000000..44db997 --- /dev/null +++ b/src/constants/DecimalUnitSeparator.ts @@ -0,0 +1 @@ +export const DECIMAL_UNIT_SEPARATOR = '_'; diff --git a/src/constants/HmsSeparator.ts b/src/constants/HmsSeparator.ts new file mode 100644 index 0000000..98e1c7e --- /dev/null +++ b/src/constants/HmsSeparator.ts @@ -0,0 +1 @@ +export const HMS_SEPARATOR = ':'; diff --git a/src/index.test.ts b/src/index.test.ts index ab1bdcd..841876d 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -26,6 +26,7 @@ describe('api', () => { "microseconds": [Function], "milliseconds": [Function], "minutes": [Function], + "msToClock": [Function], "msToParts": [Function], "nanoseconds": [Function], "parseDuration": [Function], diff --git a/src/msToClock.test.ts b/src/msToClock.test.ts new file mode 100644 index 0000000..1d8bb25 --- /dev/null +++ b/src/msToClock.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'vitest'; + +import { msToClock } from './msToClock.js'; +import { DECIMAL_UNIT_SEPARATOR } from './constants/DecimalUnitSeparator.js'; + +const testCases = [ + ['00:00:01', 1_000], + ['00:01:01', 61_000], + ['01:01:01', 3_661_000], + ['00:00:01.001', 1_001], + ['00:00:01.500', 1_500], + ['00:00:01.500_400', 1_500.4], + ['00:00:01.500_400_300', 1_500.4003], + ['00:00:01.000_400_300', 1_000.4003], + ['00:00:01.000_000_300', 1_000.0003], + ['00:00:01.001_002_003', 1_001.002003], + ['00:00:01.010_020_030', 1_010.02003], +] as const satisfies [clock: string, Parameters[0]][]; + +describe('msToClock()', () => { + describe.each([ + ['positive', false], + ['negative', true], + ])('%s time', (_, isNegative) => { + const [offset, sign] = isNegative ? [-1, '-'] : [1, '']; + + it.each(testCases)( + `returns ${sign}%s for ${sign}%s`, + (clock, milliseconds) => { + const output = msToClock(milliseconds * offset); + + expect(output).toBe(`${sign}${clock}`); + }, + ); + + it.each(testCases)( + `returns ${sign}%s for ${sign}%s without decimal separators`, + (clock, milliseconds) => { + const output = msToClock(milliseconds * offset, { + separateDecimal: false, + }); + + expect(output).toBe( + `${sign}${clock.replaceAll(DECIMAL_UNIT_SEPARATOR, '')}`, + ); + }, + ); + }); +}); diff --git a/src/msToClock.ts b/src/msToClock.ts new file mode 100644 index 0000000..8e5cb7c --- /dev/null +++ b/src/msToClock.ts @@ -0,0 +1,66 @@ +import { DECIMAL_SEPARATOR } from './constants/DecimalSeparator.js'; +import { DECIMAL_UNIT_SEPARATOR } from './constants/DecimalUnitSeparator.js'; +import { HMS_SEPARATOR } from './constants/HmsSeparator.js'; +import { msToParts } from './msToParts.js'; +import { padLeadingZero } from './utils/padLeadingZero.js'; + +interface Options { + separateDecimal?: boolean; +} + +/** + * Converts milliseconds to a string in the format 'hh:mm:ss' optionally suffixing '.ms_μs_ns' if there are any remaining milliseconds, microseconds or nanoseconds. + * + * @example + * ```ts + * msToClock(7_501_500); + * // => '02:05:01.500' + * ``` + * + * @example + * ```ts + * msToClock(-7_501_500); + * // => '-02:05:01.500' + * ``` + * + * @example + * ```ts + * msToClock(7_501_000); + * // => '02:05:01' + * ``` + */ + +export const msToClock = ( + ms: number, + { separateDecimal = true }: Options = {}, +): string => { + const { + hours, + isNegative, + nanoseconds, + microseconds, + milliseconds, + minutes, + seconds, + } = msToParts(ms); + const time = [hours, minutes, seconds] + .map(value => padLeadingZero(value, 2)) + .join(HMS_SEPARATOR); + + const decimal = [nanoseconds, microseconds, milliseconds].reduce( + (acc, value, index) => { + if (!acc && value === 0) return acc; + + return ( + padLeadingZero(value, 3) + + (separateDecimal && acc && index !== 0 ? DECIMAL_UNIT_SEPARATOR : '') + + acc + ); + }, + '', + ); + + return [`${isNegative ? '-' : ''}${time}`, decimal] + .filter(Boolean) + .join(DECIMAL_SEPARATOR); +}; diff --git a/src/msToParts.ts b/src/msToParts.ts index 150ca49..31f8c2a 100644 --- a/src/msToParts.ts +++ b/src/msToParts.ts @@ -35,10 +35,10 @@ export const msToParts = (ms: number): Parts => { nanoseconds: Math.round( nanoseconds.from(absoluteMs) % NANOSECONDS_IN_MICROSECOND, ), - microseconds: Math.round( + microseconds: Math.floor( microseconds.from(absoluteMs) % MICROSECONDS_IN_MILLISECOND, ), - milliseconds: Math.round(absoluteMs % MILLISECONDS_IN_SECOND), + milliseconds: Math.floor(absoluteMs % MILLISECONDS_IN_SECOND), minutes: Math.floor(minutes.from(absoluteMs) % MINUTES_IN_HOUR), seconds: Math.floor(seconds.from(absoluteMs) % SECONDS_IN_MINUTE), weeks: Math.floor(weeks.from(absoluteMs)), diff --git a/src/utilities.ts b/src/utilities.ts index d83fa79..e1d18ce 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,4 +1,5 @@ export { clockToMs } from './clockToMs.js'; +export { msToClock } from './msToClock.js'; export { msToParts } from './msToParts.js'; export { parseDuration } from './parseDuration.js'; export { partsToMs } from './partsToMs.js'; diff --git a/src/utils/getDecimalPlaces.ts b/src/utils/getDecimalPlaces.ts index 895fe47..241ff77 100644 --- a/src/utils/getDecimalPlaces.ts +++ b/src/utils/getDecimalPlaces.ts @@ -1,13 +1,12 @@ +import { DECIMAL_SEPARATOR } from '../constants/DecimalSeparator.js'; + export const getDecimalPlaces = (number: number): number => { const str = number.toExponential(); // handles cases like 1e-7 const match = str.match(/e-(\d+)/); - const decimalPart = number.toString().split('.').at(1)?.length || 0; - - if (match) { - const decimals = parseInt(match[1], 10); + const decimalPart = + number.toString().split(DECIMAL_SEPARATOR).at(1)?.length || 0; - return Math.max(decimals, decimalPart); - } + if (match) return Math.max(parseInt(match[1], 10), decimalPart); return Math.min(7, decimalPart); }; diff --git a/src/utils/padLeadingZero.test.ts b/src/utils/padLeadingZero.test.ts new file mode 100644 index 0000000..c4e319c --- /dev/null +++ b/src/utils/padLeadingZero.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from 'vitest'; +import { padLeadingZero } from './padLeadingZero.js'; + +describe('padLeadingZero()', () => { + it.each([ + ['01', 1, 2], + ['10', 10, 2], + ['100', 100, 2], + ['001', 1, 3], + ['010', 10, 3], + ['100', 100, 3], + ])('returns %o when padding %s to length %s', (expected, value, length) => { + expect(padLeadingZero(value, length)).toBe(expected); + }); +}); diff --git a/src/utils/padLeadingZero.ts b/src/utils/padLeadingZero.ts new file mode 100644 index 0000000..c9b2bb4 --- /dev/null +++ b/src/utils/padLeadingZero.ts @@ -0,0 +1,2 @@ +export const padLeadingZero = (value: number, length: number): string => + String(value).padStart(length, '0'); From 93743a4d59ff176a3055f86ced05308bb97d64d4 Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Sat, 14 Jun 2025 12:22:07 +0100 Subject: [PATCH 10/12] Remove files --- src/index.ts | 9 +++++++-- src/types.ts | 2 -- src/utilities.ts | 5 ----- 3 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 src/types.ts delete mode 100644 src/utilities.ts diff --git a/src/index.ts b/src/index.ts index fde9b3b..97f37ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,10 +14,15 @@ export * from './conversion.js'; * Utilities */ -export * from './utilities.js'; +export { clockToMs } from './clockToMs.js'; +export { msToClock } from './msToClock.js'; +export { msToParts } from './msToParts.js'; +export { parseDuration } from './parseDuration.js'; +export { partsToMs } from './partsToMs.js'; /** * Types */ -export * from './types.js'; +export type { Parts } from './interfaces/Parts.js'; +export type { TimeUnit } from './types/TimeUnit.js'; diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 6af6b21..0000000 --- a/src/types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { Parts } from './interfaces/Parts.js'; -export type { TimeUnit } from './types/TimeUnit.js'; diff --git a/src/utilities.ts b/src/utilities.ts deleted file mode 100644 index e1d18ce..0000000 --- a/src/utilities.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { clockToMs } from './clockToMs.js'; -export { msToClock } from './msToClock.js'; -export { msToParts } from './msToParts.js'; -export { parseDuration } from './parseDuration.js'; -export { partsToMs } from './partsToMs.js'; From 892b0e122dadd1424c7582a64da658a961cacb81 Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Sat, 14 Jun 2025 12:27:09 +0100 Subject: [PATCH 11/12] Import fixes --- src/conversion.test.ts | 2 +- src/msToParts.ts | 2 +- src/parseDuration.ts | 2 +- src/partsToMs.ts | 2 +- src/utils/partToConverterMap.ts | 4 ++-- src/utils/unitToConverterMap.ts | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/conversion.test.ts b/src/conversion.test.ts index 775d0fa..4f68966 100644 --- a/src/conversion.test.ts +++ b/src/conversion.test.ts @@ -9,7 +9,7 @@ import { seconds, weeks, } from './conversion.js'; -import { UnitConverter } from './utils/createUnitConverter.js'; +import { type UnitConverter } from './utils/createUnitConverter.js'; const testCases: [ unit: string, diff --git a/src/msToParts.ts b/src/msToParts.ts index 31f8c2a..5c685a3 100644 --- a/src/msToParts.ts +++ b/src/msToParts.ts @@ -16,7 +16,7 @@ import { seconds, weeks, } from './conversion.js'; -import { Parts } from './interfaces/Parts.js'; +import { type Parts } from './interfaces/Parts.js'; /** * Converts a duration in milliseconds to an object with properties for each time unit. diff --git a/src/parseDuration.ts b/src/parseDuration.ts index 84ac40d..c019205 100644 --- a/src/parseDuration.ts +++ b/src/parseDuration.ts @@ -1,4 +1,4 @@ -import { TimeUnit } from './types/TimeUnit.js'; +import { type TimeUnit } from './types/TimeUnit.js'; import { unitToConverterMap } from './utils/unitToConverterMap.js'; const regex = new RegExp( diff --git a/src/partsToMs.ts b/src/partsToMs.ts index a9fba5c..595f64b 100644 --- a/src/partsToMs.ts +++ b/src/partsToMs.ts @@ -1,4 +1,4 @@ -import { Parts } from './interfaces/Parts.js'; +import { type Parts } from './interfaces/Parts.js'; import { UnitConverter } from './utils/createUnitConverter.js'; import { floatOperation } from './utils/floatOperation.js'; import { partToConverterMap } from './utils/partToConverterMap.js'; diff --git a/src/utils/partToConverterMap.ts b/src/utils/partToConverterMap.ts index 4efeebd..8af78cf 100644 --- a/src/utils/partToConverterMap.ts +++ b/src/utils/partToConverterMap.ts @@ -1,5 +1,4 @@ -import { Parts } from '../types.js'; -import { UnitConverter } from './createUnitConverter.js'; +import { type UnitConverter } from './createUnitConverter.js'; import { days, hours, @@ -10,6 +9,7 @@ import { seconds, weeks, } from '../conversion.js'; +import { type Parts } from '../interfaces/Parts.js'; export const partToConverterMap = { nanoseconds, diff --git a/src/utils/unitToConverterMap.ts b/src/utils/unitToConverterMap.ts index 8837a4c..2e8b8fc 100644 --- a/src/utils/unitToConverterMap.ts +++ b/src/utils/unitToConverterMap.ts @@ -8,8 +8,8 @@ import { seconds, weeks, } from '../conversion.js'; -import { TimeUnit } from '../types/TimeUnit.js'; -import { UnitConverter } from './createUnitConverter.js'; +import { type TimeUnit } from '../types/TimeUnit.js'; +import { type UnitConverter } from './createUnitConverter.js'; export const unitToConverterMap = { ns: nanoseconds, From 7376243f5e2e81e91b77e72268f4c70efa059e7d Mon Sep 17 00:00:00 2001 From: Mike Simmonds Date: Sat, 14 Jun 2025 12:32:04 +0100 Subject: [PATCH 12/12] Changelog clean up --- CHANGELOG.md | 73 ++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b064cf..0a25743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,52 +9,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking -- Renames `toParts` to `msToParts` (thanks [@simmo]) -- Renames `MICROSECONDS_IN_A_MILLISECOND` to `MICROSECONDS_IN_MILLISECOND` (thanks [@simmo]) -- Renames `MILLISECONDS_IN_A_SECOND` to `MILLISECONDS_IN_SECOND` (thanks [@simmo]) -- Renames `NANOSECONDS_IN_A_MICROSECOND` to `NANOSECONDS_IN_MICROSECOND` (thanks [@simmo]) +- Renames `toParts` to `msToParts` +- Renames `MICROSECONDS_IN_A_MILLISECOND` to `MICROSECONDS_IN_MILLISECOND` +- Renames `MILLISECONDS_IN_A_SECOND` to `MILLISECONDS_IN_SECOND` +- Renames `NANOSECONDS_IN_A_MICROSECOND` to `NANOSECONDS_IN_MICROSECOND` ### Added -- `msToClock` function to convert milliseconds to a clock string (thanks [@simmo]) -- `clockToMs` function to convert a clock string to milliseconds (thanks [@simmo]) -- `partsToMs` function to convert an object with time unit properties to milliseconds (thanks [@simmo]) -- `Parts` interface to represent the parts of a duration (thanks [@simmo]) -- `TimeUnit` type to represent the time unit strings used in the `parseDuration` function (thanks [@simmo]) +- `msToClock` function to convert milliseconds to a clock string +- `clockToMs` function to convert a clock string to milliseconds +- `partsToMs` function to convert an object with time unit properties to milliseconds +- `Parts` interface to represent the parts of a duration +- `TimeUnit` type to represent the time unit strings used in the `parseDuration` function ### Fixed -- Incorrect/missing documentation (thanks [@simmo]) -- Floating point conversion error (thanks [@simmo]) +- Incorrect/missing documentation +- Floating point conversion error ## [1.2.0] - 2025-06-10 ### Added -- Code coverage check (thanks [@simmo]) -- Microseconds and nanoseconds support (thanks [@simmo]) +- Code coverage check +- Microseconds and nanoseconds support ## [1.1.2] - 2025-05-06 ### Changed -- Workflow adjustments (thanks [@simmo]) +- Workflow adjustments ### Fixed -- Missing package files path (thanks [@simmo]) +- Missing package files path ## [1.1.1] - 2025-05-05 ### Added -- Contributing guide (thanks [@simmo]) -- Security policy (thanks [@simmo]) -- PR + issue templates (thanks [@simmo]) +- Contributing guide +- Security policy +- PR + issue templates ### Changed -- README adjustments (thanks [@simmo]) +- README adjustments ### Fixed @@ -64,52 +64,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `weeks` unit converter (thanks [@simmo]) -- `WEEK` and `DAYS_IN_WEEK` constants (thanks [@simmo]) -- `weeks` to `toParts` function output (thanks [@simmo]) +- `weeks` unit converter +- `WEEK` and `DAYS_IN_WEEK` constants +- `weeks` to `toParts` function output ### Changed -- Updated dev dependencies (thanks [@simmo]) -- Output modules in distribution (thanks [@simmo]) +- Updated dev dependencies +- Output modules in distribution ### Fixes -- README typos (thanks [@simmo]) -- Clean up (thanks [@simmo]) -- Changelog URLs (thanks [@simmo]) +- README typos +- Clean up +- Changelog URLs ## [1.0.0] - 2025-05-05 ### Changed -- Update readme (thanks [@simmo]) +- Update readme ## [0.0.2] - 2025-05-05 ### Fixed -- Update incorrect documentation (thanks [@simmo]) -- Update README (thanks [@simmo]) +- Update incorrect documentation +- Update README ## [0.0.1] - 2025-05-04 ### Added -- Initial setup (thanks [@simmo]) -- Constants for time units (thanks [@simmo]) -- Unit converter functions for `days`, `hours`, `minutes`, `seconds` and `milliseconds` (thanks [@simmo]) -- `toParts` function to convert milliseconds to an object with properties for each time unit (thanks [@simmo]) -- `parseDuration` function to parse a duration string into milliseconds (thanks [@simmo]) +- Initial setup +- Constants for time units +- Unit converter functions for `days`, `hours`, `minutes`, `seconds` and `milliseconds` +- `toParts` function to convert milliseconds to an object with properties for each time unit +- `parseDuration` function to parse a duration string into milliseconds [#3]: https://github.com/simmo/niobe/pull/3 +[@spyros-uk]: https://github.com/spyros-uk [unreleased]: https://github.com/simmo/niobe/compare/1.2.0...HEAD [1.0.0]: https://github.com/simmo/niobe/compare/0.0.2...1.0.0 [0.0.2]: https://github.com/simmo/niobe/compare/0.0.1...0.0.2 [0.0.1]: https://github.com/simmo/niobe/compare/f3751e...0.0.1 [1.1.0]: https://github.com/simmo/niobe/compare/1.0.1-beta.1...1.1.0 -[@simmo]: https://github.com/simmo -[@spyros-uk]: https://github.com/spyros-uk [1.1.1]: https://github.com/simmo/niobe/compare/1.1.1-beta.2...1.1.1 [1.1.2]: https://github.com/simmo/niobe/compare/1.1.2-beta.0...1.1.2 [1.2.0]: https://github.com/simmo/niobe/releases/tag/1.2.0