From 9648e1fa3ba76cb2f81fbec583c081c01bc55dcf Mon Sep 17 00:00:00 2001 From: Cameron Perera Date: Fri, 13 Sep 2024 09:43:08 -0500 Subject: [PATCH] Add duration unit format (#2483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 add @formatjs/intl-durationformat to addons/core * refactor: 💡 replace old duration format with new package * chore: 🤖 remove intl-unofficial-duration-unit-format * Add missing copyright headers * test: 💍 add more test cases --------- Co-authored-by: cameronperera --- .../addon/helpers/format-time-duration.js | 9 +-- addons/core/addon/helpers/time-remaining.js | 16 ++-- .../core/addon/utils/seconds-to-duration.js | 27 +++++++ addons/core/app/utils/seconds-to-duration.js | 6 ++ addons/core/package.json | 2 +- .../helpers/time-remaining-test.js | 28 +++++-- .../unit/utils/seconds-to-duration-test.js | 79 +++++++++++++++++++ yarn.lock | 14 ++-- 8 files changed, 157 insertions(+), 24 deletions(-) create mode 100644 addons/core/addon/utils/seconds-to-duration.js create mode 100644 addons/core/app/utils/seconds-to-duration.js create mode 100644 addons/core/tests/unit/utils/seconds-to-duration-test.js diff --git a/addons/core/addon/helpers/format-time-duration.js b/addons/core/addon/helpers/format-time-duration.js index 4d33558ef..6188f2824 100644 --- a/addons/core/addon/helpers/format-time-duration.js +++ b/addons/core/addon/helpers/format-time-duration.js @@ -5,7 +5,8 @@ import Helper from '@ember/component/helper'; import { inject as service } from '@ember/service'; -import DurationUnitFormat from 'intl-unofficial-duration-unit-format'; +import { DurationFormat } from '@formatjs/intl-durationformat'; +import { secondsToDuration } from 'core/utils/seconds-to-duration'; export default class extends Helper { // =services @@ -19,12 +20,10 @@ export default class extends Helper { */ compute([durationInMs]) { const durationInSeconds = durationInMs / 1000; - const durationFormat = new DurationUnitFormat(this.intl.primaryLocale, { + const durationFormat = new DurationFormat(this.intl.primaryLocale, { style: 'narrow', - format: '{days} {hours} {minutes} {seconds}', - round: true, }); - return durationFormat.format(durationInSeconds); + return durationFormat.format(secondsToDuration(durationInSeconds)); } } diff --git a/addons/core/addon/helpers/time-remaining.js b/addons/core/addon/helpers/time-remaining.js index 5e7f42652..ebd98fb87 100644 --- a/addons/core/addon/helpers/time-remaining.js +++ b/addons/core/addon/helpers/time-remaining.js @@ -5,7 +5,8 @@ import Helper from '@ember/component/helper'; import { inject as service } from '@ember/service'; -import DurationUnitFormat from 'intl-unofficial-duration-unit-format'; +import { DurationFormat } from '@formatjs/intl-durationformat'; +import { secondsToDuration } from 'core/utils/seconds-to-duration'; export default class extends Helper { // =services @@ -29,14 +30,13 @@ export default class extends Helper { difference > 0 ? Math.floor(difference / 1000) : 0; // Format the duration - const duration = new DurationUnitFormat(this.intl.primaryLocale, { - style: 'timer', - format: `{hours}:{minutes}:{seconds} ${this.intl.t( - 'resources.session.remaining', - )}`, + const duration = new DurationFormat(this.intl.primaryLocale, { + style: 'digital', }); + const formattedTime = duration.format( + secondsToDuration(differenceInSeconds), + ); - // Return the formatted duration - return duration.format(differenceInSeconds); + return `${formattedTime} ${this.intl.t('resources.session.remaining')}`; } } diff --git a/addons/core/addon/utils/seconds-to-duration.js b/addons/core/addon/utils/seconds-to-duration.js new file mode 100644 index 000000000..c8067aa8c --- /dev/null +++ b/addons/core/addon/utils/seconds-to-duration.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +/** + * Takes in the number of seconds and converts to an object with corresponding + * values for each time duration. For example: + * { weeks: 0, days: 0, hours: 22, minutes: 15, seconds: 11 } + * @param {number} value + * @returns {object} + */ +export const secondsToDuration = (value) => { + const second = 1; + const minute = second * 60; + const hour = minute * 60; + const day = hour * 24; + const week = day * 7; + + const weeks = Math.max(Math.floor(value / week), 0); + const days = Math.max(Math.floor((value % week) / day), 0); + const hours = Math.max(Math.floor((value % day) / hour), 0); + const minutes = Math.max(Math.floor((value % hour) / minute), 0); + const seconds = Math.max(Math.floor((value % minute) / second), 0); + + return { weeks, days, hours, minutes, seconds }; +}; diff --git a/addons/core/app/utils/seconds-to-duration.js b/addons/core/app/utils/seconds-to-duration.js new file mode 100644 index 000000000..e9862f594 --- /dev/null +++ b/addons/core/app/utils/seconds-to-duration.js @@ -0,0 +1,6 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +export { default } from 'core/utils/seconds-to-duration'; diff --git a/addons/core/package.json b/addons/core/package.json index ce96199c3..675e26fba 100644 --- a/addons/core/package.json +++ b/addons/core/package.json @@ -31,6 +31,7 @@ "doc:toc": "doctoc README.md" }, "dependencies": { + "@formatjs/intl-durationformat": "^0.2.4", "api": "*", "auth": "*", "ember-auto-import": "^2.6.3", @@ -43,7 +44,6 @@ "ember-loading": "^2.0.0", "ember-truth-helpers": "^3.0.0", "filesize": "^10.0.7", - "intl-unofficial-duration-unit-format": "^3.1.0", "sass": "^1.69.5" }, "devDependencies": { diff --git a/addons/core/tests/integration/helpers/time-remaining-test.js b/addons/core/tests/integration/helpers/time-remaining-test.js index 62f7ef607..634770987 100644 --- a/addons/core/tests/integration/helpers/time-remaining-test.js +++ b/addons/core/tests/integration/helpers/time-remaining-test.js @@ -13,9 +13,7 @@ module('Integration | Helper | time-remaining', function (hooks) { setupRenderingTest(hooks); setupIntl(hooks, 'en-us'); - test('it calculates the correct time remaining', async function (assert) { - assert.expect(1); - + test('it calculates the correct time remainin with less than 24 hours', async function (assert) { this.set('expirationTime', new Date(Date.now() + 1000 * 60 * 60 * 24)); await render(hbs`{{time-remaining this.expirationTime}}`); @@ -23,9 +21,29 @@ module('Integration | Helper | time-remaining', function (hooks) { assert.strictEqual(this.element.textContent.trim(), '23:59:59 remaining'); }); - test('it handles negative time remaining', async function (assert) { - assert.expect(1); + test('it calculates the correct time remaining with more than 24 hours', async function (assert) { + this.set('expirationTime', new Date(Date.now() + 1001 * 60 * 60 * 24)); + await render(hbs`{{time-remaining this.expirationTime}}`); + + assert.strictEqual( + this.element.textContent.trim(), + '1 day, 0:01:26 remaining', + ); + }); + + test('it calculates the correct time remaining with more than a week', async function (assert) { + this.set('expirationTime', new Date(Date.now() + 1001 * 60 * 60 * 24 * 8)); + + await render(hbs`{{time-remaining this.expirationTime}}`); + + assert.strictEqual( + this.element.textContent.trim(), + '1 wk, 1 day, 0:11:31 remaining', + ); + }); + + test('it handles negative time remaining', async function (assert) { this.set('expirationTime', new Date(Date.now() - 1000 * 60 * 60 * 24)); await render(hbs`{{time-remaining this.expirationTime}}`); diff --git a/addons/core/tests/unit/utils/seconds-to-duration-test.js b/addons/core/tests/unit/utils/seconds-to-duration-test.js new file mode 100644 index 000000000..ee71e78c0 --- /dev/null +++ b/addons/core/tests/unit/utils/seconds-to-duration-test.js @@ -0,0 +1,79 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { secondsToDuration } from 'core/utils/seconds-to-duration'; +import { module, test } from 'qunit'; + +module('Unit | Utility | seconds-to-duration', function () { + test('it handles 0 correctly', function (assert) { + const result = secondsToDuration(0); + + assert.strictEqual(result.seconds, 0); + assert.strictEqual(result.minutes, 0); + assert.strictEqual(result.hours, 0); + assert.strictEqual(result.days, 0); + assert.strictEqual(result.weeks, 0); + }); + + test('it converts seconds correctly', function (assert) { + const result = secondsToDuration(45); + + assert.strictEqual(result.seconds, 45); + assert.strictEqual(result.minutes, 0); + assert.strictEqual(result.hours, 0); + assert.strictEqual(result.days, 0); + assert.strictEqual(result.weeks, 0); + }); + + test('it converts minutes correctly', function (assert) { + const result = secondsToDuration(100); + + assert.strictEqual(result.seconds, 40); + assert.strictEqual(result.minutes, 1); + assert.strictEqual(result.hours, 0); + assert.strictEqual(result.days, 0); + assert.strictEqual(result.weeks, 0); + }); + + test('it converts hours correctly', function (assert) { + const result = secondsToDuration(5000); + + assert.strictEqual(result.seconds, 20); + assert.strictEqual(result.minutes, 23); + assert.strictEqual(result.hours, 1); + assert.strictEqual(result.days, 0); + assert.strictEqual(result.weeks, 0); + }); + + test('it converts days correctly', function (assert) { + const result = secondsToDuration(500000); + + assert.strictEqual(result.seconds, 20); + assert.strictEqual(result.minutes, 53); + assert.strictEqual(result.hours, 18); + assert.strictEqual(result.days, 5); + assert.strictEqual(result.weeks, 0); + }); + + test('it converts weeks correctly', function (assert) { + const result = secondsToDuration(800000); + + assert.strictEqual(result.seconds, 20); + assert.strictEqual(result.minutes, 13); + assert.strictEqual(result.hours, 6); + assert.strictEqual(result.days, 2); + assert.strictEqual(result.weeks, 1); + }); + + test('it handles negative values', function (assert) { + const result = secondsToDuration(-800000); + + assert.strictEqual(result.seconds, 0); + assert.strictEqual(result.minutes, 0); + assert.strictEqual(result.hours, 0); + assert.strictEqual(result.days, 0); + assert.strictEqual(result.weeks, 0); + }); +}); diff --git a/yarn.lock b/yarn.lock index 541d43be5..0c2196c58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3981,6 +3981,15 @@ "@formatjs/intl-localematcher" "0.5.4" tslib "^2.4.0" +"@formatjs/intl-durationformat@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl-durationformat/-/intl-durationformat-0.2.4.tgz#ac3c46a0b104ca8635afc9bf5aa8be2274ecd200" + integrity sha512-wtmC8eZeIkyQ8fVWdarAjNbqOkAq0wQ7GBoG6aBIK0+O9wU5gq9l0jOpKYymdQq4Hz3lZKpgXgXcZm+Z40w4Rw== + dependencies: + "@formatjs/ecma402-abstract" "2.0.0" + "@formatjs/intl-localematcher" "0.5.4" + tslib "^2.4.0" + "@formatjs/intl-listformat@7.5.7": version "7.5.7" resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.7.tgz#125e05105fabd1ae5f11881d6ab74484f2098ee4" @@ -12146,11 +12155,6 @@ intl-messageformat@10.5.14, intl-messageformat@^10.5.14: "@formatjs/icu-messageformat-parser" "2.7.8" tslib "^2.4.0" -intl-unofficial-duration-unit-format@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/intl-unofficial-duration-unit-format/-/intl-unofficial-duration-unit-format-3.1.0.tgz#994feefc8d903d9e5044dc7e4e1d38ebad59394b" - integrity sha512-yjvUDwV63zSGkbeUqpDRJmOgMQoJA8OFhNeo+1KY20q+lFCtcvRWszJNsq/ssrUJRy8g+Mt3BYNfwFvRRSAkow== - invert-kv@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-3.0.1.tgz#a93c7a3d4386a1dc8325b97da9bb1620c0282523"