diff --git a/src/date/duration.spec.ts b/src/date/duration.spec.ts new file mode 100644 index 0000000..5b7630b --- /dev/null +++ b/src/date/duration.spec.ts @@ -0,0 +1,57 @@ +import { secondsToDuration, secondsToReadableDuration } from './duration'; + +describe('duration', () => { + describe('secondsToDuration', () => { + it('handles null input', () => { + expect(secondsToDuration(null)).toBe('00:00'); + }); + + it('formats seconds as mm:ss', () => { + expect(secondsToDuration(4)).toBe('00:04'); + expect(secondsToDuration(44)).toBe('00:44'); + }); + + it('formats minutes as mm:ss', () => { + expect(secondsToDuration(444)).toBe('07:24'); + }); + + it('formats hours as HH:mm:ss', () => { + expect(secondsToDuration(44444)).toBe('12:20:44'); + }); + + it('formats hours (more than 24) as dd:HH:mm:ss', () => { + expect(secondsToDuration(444444)).toBe('05:03:27:24'); + }); + + it('handles decimal duration inputs', () => { + expect(secondsToDuration(2565.568)).toBe('42:45'); + }); + }); + + describe('secondsToReadableDuration', () => { + it('handles null input', () => { + expect(secondsToDuration(null)).toBe('00:00'); + }); + + it('formats seconds as s', () => { + expect(secondsToReadableDuration(4)).toBe('4 s'); + expect(secondsToReadableDuration(44)).toBe('44 s'); + }); + + it('formats minutes as mm ss', () => { + expect(secondsToReadableDuration(444)).toBe('7m 24s'); + }); + + it('formats hours as HH mm', () => { + expect(secondsToReadableDuration(44444)).toBe('12h 21m'); + }); + + it('formats hours (more than 24) as dd HH', () => { + expect(secondsToReadableDuration(444444)).toBe('5d 3h'); + }); + + it('handles decimal duration inputs', () => { + expect(secondsToReadableDuration(2565.568)).toBe('43m'); + }); + }); +}); diff --git a/src/date/duration.ts b/src/date/duration.ts new file mode 100644 index 0000000..e06790a --- /dev/null +++ b/src/date/duration.ts @@ -0,0 +1,78 @@ +const ensureNumber = (value: number | unknown) => { + if (typeof value !== 'number') { + return 0; + } + + return value; +}; + +export const secondsToDuration = (seconds: number | unknown) => { + const normalizedSeconds = Math.floor(ensureNumber(seconds)); + + if (normalizedSeconds <= 0) { + return '00:00'; + } + + const days = Math.floor(normalizedSeconds / (60 * 60 * 24)); + const minutes = Math.floor(normalizedSeconds / 60) % 60; + let hours = Math.floor(normalizedSeconds / (60 * 60)); + + if (days) { + hours = Math.round((normalizedSeconds - days * (60 * 60 * 24)) / 60 / 60); + + return [days, hours, minutes, normalizedSeconds % 60] + .map((v) => (v < 10 ? `0${v}` : v)) + .filter((v, i) => v !== '00' || i > 0) + .join(':'); + } + + return [hours, minutes, normalizedSeconds % 60] + .map((v) => (v < 10 ? `0${v}` : v)) + .filter((v, i) => v !== '00' || i > 0) + .join(':'); +}; + +export const secondsToReadableDuration = (seconds: number | unknown) => { + const normalizedSeconds = Math.floor(ensureNumber(seconds)); + + if (normalizedSeconds <= 0) { + return ''; + } + + const fSec = 's'; + const fMin = 'm'; + const fHour = 'h'; + const fDay = 'd'; + + if (normalizedSeconds < 60) { + return `${seconds} ${fSec}`; + } + + if (normalizedSeconds > 60 * 60 * 24) { + const days = Math.floor(normalizedSeconds / (60 * 60 * 24)); + const hour = Math.round( + (normalizedSeconds - days * (60 * 60 * 24)) / 60 / 60 + ); + return `${days}${fDay}${hour > 0 ? ` ${hour}${fHour}` : ''}`; + } + + if (normalizedSeconds >= 60 * 60) { + const hour = Math.floor(normalizedSeconds / (60 * 60)); + const m = Math.round((normalizedSeconds - hour * 60 * 60) / 60); + let mins = ''; + + if (m > 0) { + mins = ` ${m}${fMin}`; + } + + return `${hour}${fHour}${mins}`; + } + + if (normalizedSeconds > 60) { + const m = Math.round(normalizedSeconds / 60); + const sec = normalizedSeconds - m * 60; + const secText = sec > 0 ? ` ${sec}${fSec}` : ''; + + return `${m}${fMin}${secText}`; + } +}; diff --git a/src/index.spec.ts b/src/index.spec.ts index 2ef1740..d1dc828 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -13,6 +13,8 @@ describe('lib', () => { "formatDate": [Function], "getUnixTimestamp": [Function], "isErrorLocalizer": [Function], + "secondsToDuration": [Function], + "secondsToReadableDuration": [Function], "toHumanFileSize": [Function], "toTimeAgo": [Function], } diff --git a/src/index.ts b/src/index.ts index 4b7b93e..9f3c9df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export { default as ExtendableError } from 'es6-error'; export * from './date/date'; +export * from './date/duration'; export * from './errors'; export * from './file/file'; export * from './localized-error/LocalizedError';