From 809ad1885aec798523f88a921e4f25a795cd5e67 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 14 Nov 2023 07:52:39 +0100 Subject: [PATCH 1/5] feat: seconds to duration --- packages/utils/src/index.ts | 1 + packages/utils/src/utils/date.utils.spec.ts | 128 ++++++++++++++++++++ packages/utils/src/utils/date.utils.ts | 84 +++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 packages/utils/src/utils/date.utils.spec.ts create mode 100644 packages/utils/src/utils/date.utils.ts diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index d411e20a1..f8d45bc8b 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -10,6 +10,7 @@ export * from "./utils/arrays.utils"; export * from "./utils/asserts.utils"; export * from "./utils/base32.utils"; export * from "./utils/crc.utils"; +export * from "./utils/date.utils"; export * from "./utils/debounce.utils"; export * from "./utils/did.utils"; export * from "./utils/json.utils"; diff --git a/packages/utils/src/utils/date.utils.spec.ts b/packages/utils/src/utils/date.utils.spec.ts new file mode 100644 index 000000000..2885e5079 --- /dev/null +++ b/packages/utils/src/utils/date.utils.spec.ts @@ -0,0 +1,128 @@ +import { secondsToDuration } from "./date.utils"; + +describe("secondsToDuration", () => { + // This function should not be smart. It should just make it easier to add + // numbers together to get the number of seconds we want to test. + const renderSeconds = ({ + nonLeapYears = 0, + days = 0, + hours = 0, + minutes = 0, + seconds = 0, + }: { + nonLeapYears?: number; + days?: number; + hours?: number; + minutes?: number; + seconds?: number; + }) => { + days += 365 * nonLeapYears; + hours += 24 * days; + minutes += 60 * hours; + seconds += 60 * minutes; + return secondsToDuration(BigInt(seconds)); + }; + + it("should give year details", () => { + expect(renderSeconds({ nonLeapYears: 1 })).toBe("1 year"); + expect(renderSeconds({ nonLeapYears: 1, seconds: 59 })).toBe("1 year"); + expect(renderSeconds({ nonLeapYears: 1, minutes: 59 })).toBe( + "1 year, 59 minutes", + ); + expect(renderSeconds({ nonLeapYears: 1, hours: 23 })).toBe( + "1 year, 23 hours", + ); + expect(renderSeconds({ nonLeapYears: 1, days: 1, seconds: -1 })).toBe( + "1 year, 23 hours", + ); + expect(renderSeconds({ nonLeapYears: 1, days: 1 })).toBe("1 year, 1 day"); + expect(renderSeconds({ nonLeapYears: 1, days: 2 })).toBe("1 year, 2 days"); + expect(renderSeconds({ nonLeapYears: 2, seconds: -1 })).toBe( + "1 year, 364 days", + ); + expect(renderSeconds({ nonLeapYears: 2 })).toBe("2 years"); + expect(renderSeconds({ nonLeapYears: 2, minutes: 59 })).toBe( + "2 years, 59 minutes", + ); + expect(renderSeconds({ nonLeapYears: 2, hours: 23 })).toBe( + "2 years, 23 hours", + ); + expect(renderSeconds({ nonLeapYears: 2, days: 1 })).toBe("2 years, 1 day"); + expect(renderSeconds({ nonLeapYears: 2, days: 2 })).toBe("2 years, 2 days"); + expect(renderSeconds({ nonLeapYears: 3, seconds: -1 })).toBe( + "2 years, 364 days", + ); + expect(renderSeconds({ nonLeapYears: 3 })).toBe("3 years"); + // 4 actual years have a leap day so we add 1 day to 4 nonLeap years. + expect(renderSeconds({ nonLeapYears: 4, days: 1, seconds: -1 })).toBe( + "3 years, 365 days", + ); + expect(renderSeconds({ nonLeapYears: 4, days: 1 })).toBe("4 years"); + expect(renderSeconds({ nonLeapYears: 5, days: 1, seconds: -1 })).toBe( + "4 years, 364 days", + ); + expect(renderSeconds({ nonLeapYears: 5, days: 1 })).toBe("5 years"); + expect(renderSeconds({ nonLeapYears: 6, days: 1, seconds: -1 })).toBe( + "5 years, 364 days", + ); + expect(renderSeconds({ nonLeapYears: 6, days: 1 })).toBe("6 years"); + expect(renderSeconds({ nonLeapYears: 7, days: 1, seconds: -1 })).toBe( + "6 years, 364 days", + ); + expect(renderSeconds({ nonLeapYears: 7, days: 1 })).toBe("7 years"); + // 4 actual years have 2 leap days so we add 2 days to 8 nonLeap years. + expect(renderSeconds({ nonLeapYears: 8, days: 2, seconds: -1 })).toBe( + "7 years, 365 days", + ); + expect(renderSeconds({ nonLeapYears: 8, days: 2 })).toBe("8 years"); + expect(renderSeconds({ nonLeapYears: 9, days: 2, seconds: -1 })).toBe( + "8 years, 364 days", + ); + expect(renderSeconds({ nonLeapYears: 9, days: 2 })).toBe("9 years"); + }); + + it("should give day details", () => { + expect(renderSeconds({ days: 1 })).toBe("1 day"); + expect(renderSeconds({ days: 1, seconds: 59 })).toBe("1 day"); + expect(renderSeconds({ days: 1, minutes: 59 })).toBe("1 day, 59 minutes"); + expect(renderSeconds({ days: 1, hours: 1 })).toBe("1 day, 1 hour"); + expect(renderSeconds({ days: 1, hours: 2 })).toBe("1 day, 2 hours"); + expect(renderSeconds({ days: 2, seconds: -1 })).toBe("1 day, 23 hours"); + expect(renderSeconds({ days: 2 })).toBe("2 days"); + expect(renderSeconds({ days: 365, seconds: -1 })).toBe( + "364 days, 23 hours", + ); + }); + + it("should give hour details", () => { + expect(renderSeconds({ hours: 1 })).toBe("1 hour"); + expect(renderSeconds({ hours: 1, seconds: 59 })).toBe("1 hour"); + expect(renderSeconds({ hours: 1, minutes: 59 })).toBe("1 hour, 59 minutes"); + expect(renderSeconds({ hours: 2, seconds: -1 })).toBe("1 hour, 59 minutes"); + expect(renderSeconds({ hours: 2 })).toBe("2 hours"); + expect(renderSeconds({ hours: 2, minutes: 59 })).toBe( + "2 hours, 59 minutes", + ); + expect(renderSeconds({ hours: 24, seconds: -1 })).toBe( + "23 hours, 59 minutes", + ); + }); + + it("should give minute details", () => { + expect(renderSeconds({ minutes: 1 })).toBe("1 minute"); + expect(renderSeconds({ minutes: 1, seconds: 1 })).toBe("1 minute"); + expect(renderSeconds({ minutes: 1, seconds: 59 })).toBe("1 minute"); + expect(renderSeconds({ minutes: 2 })).toBe("2 minutes"); + expect(renderSeconds({ minutes: 2, seconds: 59 })).toBe("2 minutes"); + expect(renderSeconds({ minutes: 60, seconds: -1 })).toBe("59 minutes"); + }); + + it("should give seconds details", () => { + expect(secondsToDuration(BigInt(2))).toBe("2 seconds"); + expect(secondsToDuration(BigInt(59))).toBe("59 seconds"); + }); + + it("should give a second details", () => { + expect(secondsToDuration(BigInt(1))).toBe("1 second"); + }); +}); diff --git a/packages/utils/src/utils/date.utils.ts b/packages/utils/src/utils/date.utils.ts new file mode 100644 index 000000000..bc187c6bb --- /dev/null +++ b/packages/utils/src/utils/date.utils.ts @@ -0,0 +1,84 @@ +const SECONDS_IN_MINUTE = 60; +const MINUTES_IN_HOUR = 60; +const HOURS_IN_DAY = 24; +const DAYS_IN_NON_LEAP_YEAR = 365; + +/** + * Convert seconds to a human-readable duration, such as "6 days, 10 hours." + * @param {number} seconds - The number of seconds to convert. + */ +export const secondsToDuration = (seconds: bigint): string => { + let minutes = seconds / BigInt(SECONDS_IN_MINUTE); + + let hours = minutes / BigInt(MINUTES_IN_HOUR); + minutes -= hours * BigInt(MINUTES_IN_HOUR); + + let days = hours / BigInt(HOURS_IN_DAY); + hours -= days * BigInt(HOURS_IN_DAY); + + const years = fullYearsInDays(days); + days -= daysInYears(years); + + const time: Record = { + year: "year", + year_plural: "years", + month: "month", + month_plural: "months", + day: "day", + day_plural: "days", + hour: "hour", + hour_plural: "hours", + minute: "minute", + minute_plural: "minutes", + second: "second", + second_plural: "seconds", + }; + + const periods = [ + createLabel("year", years), + createLabel("day", days), + createLabel("hour", hours), + createLabel("minute", minutes), + ...(seconds > BigInt(0) && seconds < BigInt(60) + ? [createLabel("second", seconds)] + : []), + ]; + + return periods + .filter(({ amount }) => amount > 0) + .slice(0, 2) + .map( + (labelInfo) => + `${labelInfo.amount} ${ + labelInfo.amount === 1 + ? time[labelInfo.labelKey] + : time[`${labelInfo.labelKey}_plural`] + }`, + ) + .join(", "); +}; + +const fullYearsInDays = (days: bigint): bigint => { + // Use integer division. + let years = days / BigInt(DAYS_IN_NON_LEAP_YEAR); + while (daysInYears(years) > days) { + years--; + } + return years; +}; + +const daysInYears = (years: bigint): bigint => { + // Use integer division. + const leapDays = years / BigInt(4); + return years * BigInt(DAYS_IN_NON_LEAP_YEAR) + leapDays; +}; + +type LabelKey = "year" | "month" | "day" | "hour" | "minute" | "second"; +type LabelInfo = { + labelKey: LabelKey; + amount: number; +}; +const createLabel = (labelKey: LabelKey, amount: bigint): LabelInfo => ({ + labelKey, + amount: Number(amount), +}); From 7208ec491fd6dd54f7bd86698bf2620ea79c963f Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 06:53:55 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=A4=96=20Documentation=20auto-update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/utils/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/utils/README.md b/packages/utils/README.md index c8f9e4c3a..e2143a34f 100644 --- a/packages/utils/README.md +++ b/packages/utils/README.md @@ -49,6 +49,7 @@ npm i @dfinity/agent @dfinity/candid @dfinity/principal - [encodeBase32](#gear-encodebase32) - [decodeBase32](#gear-decodebase32) - [bigEndianCrc32](#gear-bigendiancrc32) +- [secondsToDuration](#gear-secondstoduration) - [debounce](#gear-debounce) - [isNullish](#gear-isnullish) - [nonNullish](#gear-nonnullish) @@ -234,6 +235,20 @@ Parameters: [:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/utils/src/utils/crc.utils.ts#L61) +#### :gear: secondsToDuration + +Convert seconds to a human-readable duration, such as "6 days, 10 hours." + +| Function | Type | +| ------------------- | ----------------------------- | +| `secondsToDuration` | `(seconds: bigint) => string` | + +Parameters: + +- `seconds`: - The number of seconds to convert. + +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/utils/src/utils/date.utils.ts#L10) + #### :gear: debounce | Function | Type | From 11887be90c57358613ec88b642c38ad6ede41c53 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 14 Nov 2023 08:21:42 +0100 Subject: [PATCH 3/5] feat: multi languages --- packages/utils/src/utils/date.utils.spec.ts | 203 ++++++++++++++------ packages/utils/src/utils/date.utils.ts | 62 ++++-- 2 files changed, 191 insertions(+), 74 deletions(-) diff --git a/packages/utils/src/utils/date.utils.spec.ts b/packages/utils/src/utils/date.utils.spec.ts index 2885e5079..e48afa609 100644 --- a/packages/utils/src/utils/date.utils.spec.ts +++ b/packages/utils/src/utils/date.utils.spec.ts @@ -1,6 +1,40 @@ -import { secondsToDuration } from "./date.utils"; +import { describe } from "@jest/globals"; +import { I18nSecondsToDuration, secondsToDuration } from "./date.utils"; -describe("secondsToDuration", () => { +const EN_TIME = { + year: "year", + year_plural: "years", + month: "month", + month_plural: "months", + day: "day", + day_plural: "days", + hour: "hour", + hour_plural: "hours", + minute: "minute", + minute_plural: "minutes", + second: "second", + second_plural: "seconds", +}; + +const FR_TIME = { + year: "an", + year_plural: "ans", + month: "mois", + month_plural: "mois", + day: "jour", + day_plural: "jours", + hour: "heure", + hour_plural: "heures", + minute: "minute", + minute_plural: "minutes", + second: "seconde", + second_plural: "secondes", +}; + +const test = ( + i18nResult: I18nSecondsToDuration, + i18n?: I18nSecondsToDuration, +) => { // This function should not be smart. It should just make it easier to add // numbers together to get the number of seconds we want to test. const renderSeconds = ({ @@ -20,109 +54,168 @@ describe("secondsToDuration", () => { hours += 24 * days; minutes += 60 * hours; seconds += 60 * minutes; - return secondsToDuration(BigInt(seconds)); + return secondsToDuration({ seconds: BigInt(seconds), i18n }); }; it("should give year details", () => { - expect(renderSeconds({ nonLeapYears: 1 })).toBe("1 year"); - expect(renderSeconds({ nonLeapYears: 1, seconds: 59 })).toBe("1 year"); + expect(renderSeconds({ nonLeapYears: 1 })).toBe(`1 ${i18nResult.year}`); + expect(renderSeconds({ nonLeapYears: 1, seconds: 59 })).toBe( + `1 ${i18nResult.year}`, + ); expect(renderSeconds({ nonLeapYears: 1, minutes: 59 })).toBe( - "1 year, 59 minutes", + `1 ${i18nResult.year}, 59 ${i18nResult.minute_plural}`, ); expect(renderSeconds({ nonLeapYears: 1, hours: 23 })).toBe( - "1 year, 23 hours", + `1 ${i18nResult.year}, 23 ${i18nResult.hour_plural}`, ); expect(renderSeconds({ nonLeapYears: 1, days: 1, seconds: -1 })).toBe( - "1 year, 23 hours", + `1 ${i18nResult.year}, 23 ${i18nResult.hour_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 1, days: 1 })).toBe( + `1 ${i18nResult.year}, 1 ${i18nResult.day}`, + ); + expect(renderSeconds({ nonLeapYears: 1, days: 2 })).toBe( + `1 ${i18nResult.year}, 2 ${i18nResult.day_plural}`, ); - expect(renderSeconds({ nonLeapYears: 1, days: 1 })).toBe("1 year, 1 day"); - expect(renderSeconds({ nonLeapYears: 1, days: 2 })).toBe("1 year, 2 days"); expect(renderSeconds({ nonLeapYears: 2, seconds: -1 })).toBe( - "1 year, 364 days", + `1 ${i18nResult.year}, 364 ${i18nResult.day_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 2 })).toBe( + `2 ${i18nResult.year_plural}`, ); - expect(renderSeconds({ nonLeapYears: 2 })).toBe("2 years"); expect(renderSeconds({ nonLeapYears: 2, minutes: 59 })).toBe( - "2 years, 59 minutes", + `2 ${i18nResult.year_plural}, 59 ${i18nResult.minute_plural}`, ); expect(renderSeconds({ nonLeapYears: 2, hours: 23 })).toBe( - "2 years, 23 hours", + `2 ${i18nResult.year_plural}, 23 ${i18nResult.hour_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 2, days: 1 })).toBe( + `2 ${i18nResult.year_plural}, 1 ${i18nResult.day}`, + ); + expect(renderSeconds({ nonLeapYears: 2, days: 2 })).toBe( + `2 ${i18nResult.year_plural}, 2 ${i18nResult.day_plural}`, ); - expect(renderSeconds({ nonLeapYears: 2, days: 1 })).toBe("2 years, 1 day"); - expect(renderSeconds({ nonLeapYears: 2, days: 2 })).toBe("2 years, 2 days"); expect(renderSeconds({ nonLeapYears: 3, seconds: -1 })).toBe( - "2 years, 364 days", + `2 ${i18nResult.year_plural}, 364 ${i18nResult.day_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 3 })).toBe( + `3 ${i18nResult.year_plural}`, ); - expect(renderSeconds({ nonLeapYears: 3 })).toBe("3 years"); // 4 actual years have a leap day so we add 1 day to 4 nonLeap years. expect(renderSeconds({ nonLeapYears: 4, days: 1, seconds: -1 })).toBe( - "3 years, 365 days", + `3 ${i18nResult.year_plural}, 365 ${i18nResult.day_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 4, days: 1 })).toBe( + `4 ${i18nResult.year_plural}`, ); - expect(renderSeconds({ nonLeapYears: 4, days: 1 })).toBe("4 years"); expect(renderSeconds({ nonLeapYears: 5, days: 1, seconds: -1 })).toBe( - "4 years, 364 days", + `4 ${i18nResult.year_plural}, 364 ${i18nResult.day_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 5, days: 1 })).toBe( + `5 ${i18nResult.year_plural}`, ); - expect(renderSeconds({ nonLeapYears: 5, days: 1 })).toBe("5 years"); expect(renderSeconds({ nonLeapYears: 6, days: 1, seconds: -1 })).toBe( - "5 years, 364 days", + `5 ${i18nResult.year_plural}, 364 ${i18nResult.day_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 6, days: 1 })).toBe( + `6 ${i18nResult.year_plural}`, ); - expect(renderSeconds({ nonLeapYears: 6, days: 1 })).toBe("6 years"); expect(renderSeconds({ nonLeapYears: 7, days: 1, seconds: -1 })).toBe( - "6 years, 364 days", + `6 ${i18nResult.year_plural}, 364 ${i18nResult.day_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 7, days: 1 })).toBe( + `7 ${i18nResult.year_plural}`, ); - expect(renderSeconds({ nonLeapYears: 7, days: 1 })).toBe("7 years"); // 4 actual years have 2 leap days so we add 2 days to 8 nonLeap years. expect(renderSeconds({ nonLeapYears: 8, days: 2, seconds: -1 })).toBe( - "7 years, 365 days", + `7 ${i18nResult.year_plural}, 365 ${i18nResult.day_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 8, days: 2 })).toBe( + `8 ${i18nResult.year_plural}`, ); - expect(renderSeconds({ nonLeapYears: 8, days: 2 })).toBe("8 years"); expect(renderSeconds({ nonLeapYears: 9, days: 2, seconds: -1 })).toBe( - "8 years, 364 days", + `8 ${i18nResult.year_plural}, 364 ${i18nResult.day_plural}`, + ); + expect(renderSeconds({ nonLeapYears: 9, days: 2 })).toBe( + `9 ${i18nResult.year_plural}`, ); - expect(renderSeconds({ nonLeapYears: 9, days: 2 })).toBe("9 years"); }); it("should give day details", () => { - expect(renderSeconds({ days: 1 })).toBe("1 day"); - expect(renderSeconds({ days: 1, seconds: 59 })).toBe("1 day"); - expect(renderSeconds({ days: 1, minutes: 59 })).toBe("1 day, 59 minutes"); - expect(renderSeconds({ days: 1, hours: 1 })).toBe("1 day, 1 hour"); - expect(renderSeconds({ days: 1, hours: 2 })).toBe("1 day, 2 hours"); - expect(renderSeconds({ days: 2, seconds: -1 })).toBe("1 day, 23 hours"); - expect(renderSeconds({ days: 2 })).toBe("2 days"); + expect(renderSeconds({ days: 1 })).toBe(`1 ${i18nResult.day}`); + expect(renderSeconds({ days: 1, seconds: 59 })).toBe(`1 ${i18nResult.day}`); + expect(renderSeconds({ days: 1, minutes: 59 })).toBe( + `1 ${i18nResult.day}, 59 ${i18nResult.minute_plural}`, + ); + expect(renderSeconds({ days: 1, hours: 1 })).toBe( + `1 ${i18nResult.day}, 1 ${i18nResult.hour}`, + ); + expect(renderSeconds({ days: 1, hours: 2 })).toBe( + `1 ${i18nResult.day}, 2 ${i18nResult.hour_plural}`, + ); + expect(renderSeconds({ days: 2, seconds: -1 })).toBe( + `1 ${i18nResult.day}, 23 ${i18nResult.hour_plural}`, + ); + expect(renderSeconds({ days: 2 })).toBe(`2 ${i18nResult.day_plural}`); expect(renderSeconds({ days: 365, seconds: -1 })).toBe( - "364 days, 23 hours", + `364 ${i18nResult.day_plural}, 23 ${i18nResult.hour_plural}`, ); }); it("should give hour details", () => { - expect(renderSeconds({ hours: 1 })).toBe("1 hour"); - expect(renderSeconds({ hours: 1, seconds: 59 })).toBe("1 hour"); - expect(renderSeconds({ hours: 1, minutes: 59 })).toBe("1 hour, 59 minutes"); - expect(renderSeconds({ hours: 2, seconds: -1 })).toBe("1 hour, 59 minutes"); - expect(renderSeconds({ hours: 2 })).toBe("2 hours"); + expect(renderSeconds({ hours: 1 })).toBe(`1 ${i18nResult.hour}`); + expect(renderSeconds({ hours: 1, seconds: 59 })).toBe( + `1 ${i18nResult.hour}`, + ); + expect(renderSeconds({ hours: 1, minutes: 59 })).toBe( + `1 ${i18nResult.hour}, 59 ${i18nResult.minute_plural}`, + ); + expect(renderSeconds({ hours: 2, seconds: -1 })).toBe( + `1 ${i18nResult.hour}, 59 ${i18nResult.minute_plural}`, + ); + expect(renderSeconds({ hours: 2 })).toBe(`2 ${i18nResult.hour_plural}`); expect(renderSeconds({ hours: 2, minutes: 59 })).toBe( - "2 hours, 59 minutes", + `2 ${i18nResult.hour_plural}, 59 ${i18nResult.minute_plural}`, ); expect(renderSeconds({ hours: 24, seconds: -1 })).toBe( - "23 hours, 59 minutes", + `23 ${i18nResult.hour_plural}, 59 ${i18nResult.minute_plural}`, ); }); it("should give minute details", () => { - expect(renderSeconds({ minutes: 1 })).toBe("1 minute"); - expect(renderSeconds({ minutes: 1, seconds: 1 })).toBe("1 minute"); - expect(renderSeconds({ minutes: 1, seconds: 59 })).toBe("1 minute"); - expect(renderSeconds({ minutes: 2 })).toBe("2 minutes"); - expect(renderSeconds({ minutes: 2, seconds: 59 })).toBe("2 minutes"); - expect(renderSeconds({ minutes: 60, seconds: -1 })).toBe("59 minutes"); + expect(renderSeconds({ minutes: 1 })).toBe(`1 ${i18nResult.minute}`); + expect(renderSeconds({ minutes: 1, seconds: 1 })).toBe( + `1 ${i18nResult.minute}`, + ); + expect(renderSeconds({ minutes: 1, seconds: 59 })).toBe( + `1 ${i18nResult.minute}`, + ); + expect(renderSeconds({ minutes: 2 })).toBe(`2 ${i18nResult.minute_plural}`); + expect(renderSeconds({ minutes: 2, seconds: 59 })).toBe( + `2 ${i18nResult.minute_plural}`, + ); + expect(renderSeconds({ minutes: 60, seconds: -1 })).toBe( + `59 ${i18nResult.minute_plural}`, + ); }); it("should give seconds details", () => { - expect(secondsToDuration(BigInt(2))).toBe("2 seconds"); - expect(secondsToDuration(BigInt(59))).toBe("59 seconds"); + expect(secondsToDuration({ seconds: BigInt(2), i18n })).toBe( + `2 ${i18nResult.second_plural}`, + ); + expect(secondsToDuration({ seconds: BigInt(59), i18n })).toBe( + `59 ${i18nResult.second_plural}`, + ); }); it("should give a second details", () => { - expect(secondsToDuration(BigInt(1))).toBe("1 second"); + expect(secondsToDuration({ seconds: BigInt(1), i18n })).toBe( + `1 ${i18nResult.second}`, + ); }); -}); +}; + +describe("secondsToDuration default lang", () => test(EN_TIME, undefined)); +describe.each([EN_TIME, FR_TIME])("secondsToDuration %p", (time) => + test(time, time), +); diff --git a/packages/utils/src/utils/date.utils.ts b/packages/utils/src/utils/date.utils.ts index bc187c6bb..b74446a4c 100644 --- a/packages/utils/src/utils/date.utils.ts +++ b/packages/utils/src/utils/date.utils.ts @@ -3,11 +3,50 @@ const MINUTES_IN_HOUR = 60; const HOURS_IN_DAY = 24; const DAYS_IN_NON_LEAP_YEAR = 365; +export interface I18nSecondsToDuration { + year: string; + year_plural: string; + month: string; + month_plural: string; + day: string; + day_plural: string; + hour: string; + hour_plural: string; + minute: string; + minute_plural: string; + second: string; + second_plural: string; +} + +const EN_TIME: I18nSecondsToDuration = { + year: "year", + year_plural: "years", + month: "month", + month_plural: "months", + day: "day", + day_plural: "days", + hour: "hour", + hour_plural: "hours", + minute: "minute", + minute_plural: "minutes", + second: "second", + second_plural: "seconds", +}; + /** * Convert seconds to a human-readable duration, such as "6 days, 10 hours." - * @param {number} seconds - The number of seconds to convert. + * @param {Object} options - The options object. + * @param {bigint} options.seconds - The number of seconds to convert. + * @param {I18nSecondsToDuration} [options.i18n] - The i18n object for customizing language and units. Defaults to English. + * @returns {string} The human-readable duration string. */ -export const secondsToDuration = (seconds: bigint): string => { +export const secondsToDuration = ({ + seconds, + i18n = EN_TIME, +}: { + seconds: bigint; + i18n?: I18nSecondsToDuration; +}): string => { let minutes = seconds / BigInt(SECONDS_IN_MINUTE); let hours = minutes / BigInt(MINUTES_IN_HOUR); @@ -19,21 +58,6 @@ export const secondsToDuration = (seconds: bigint): string => { const years = fullYearsInDays(days); days -= daysInYears(years); - const time: Record = { - year: "year", - year_plural: "years", - month: "month", - month_plural: "months", - day: "day", - day_plural: "days", - hour: "hour", - hour_plural: "hours", - minute: "minute", - minute_plural: "minutes", - second: "second", - second_plural: "seconds", - }; - const periods = [ createLabel("year", years), createLabel("day", days), @@ -51,8 +75,8 @@ export const secondsToDuration = (seconds: bigint): string => { (labelInfo) => `${labelInfo.amount} ${ labelInfo.amount === 1 - ? time[labelInfo.labelKey] - : time[`${labelInfo.labelKey}_plural`] + ? i18n[labelInfo.labelKey] + : i18n[`${labelInfo.labelKey}_plural`] }`, ) .join(", "); From f86c07155b22b5bf8a0ee3d1d9d5a8b4e57ded5f Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 07:23:57 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=A4=96=20Documentation=20auto-update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/utils/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/utils/README.md b/packages/utils/README.md index e2143a34f..94a829c0b 100644 --- a/packages/utils/README.md +++ b/packages/utils/README.md @@ -239,15 +239,15 @@ Parameters: Convert seconds to a human-readable duration, such as "6 days, 10 hours." -| Function | Type | -| ------------------- | ----------------------------- | -| `secondsToDuration` | `(seconds: bigint) => string` | +| Function | Type | +| ------------------- | ------------------------------------------------------------------------------------ | +| `secondsToDuration` | `({ seconds, i18n, }: { seconds: bigint; i18n?: I18nSecondsToDuration; }) => string` | Parameters: -- `seconds`: - The number of seconds to convert. +- `options`: - The options object. -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/utils/src/utils/date.utils.ts#L10) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/utils/src/utils/date.utils.ts#L43) #### :gear: debounce From 5ba360066cd894b6169f4613109f46ba93a33fb2 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 14 Nov 2023 14:10:15 +0100 Subject: [PATCH 5/5] build: bump max size --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97b6e3fe5..e4318c88c 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ { "name": "@dfinity/utils", "path": "./packages/utils/dist/index.js", - "limit": "4 kB", + "limit": "5 kB", "ignore": [ "@dfinity/agent", "@dfinity/candid",