Commit b3ce14eb authored by Miguel Rincon's avatar Miguel Rincon Committed by Mark Florian

Add a "time in words" formatter for durations

Creates a new formatter for human-readable durations that uses the same
scale as our "timeago" utilities.

It can be used as a way to format seconds into any amount of time.
parent c3a85d79
...@@ -70,8 +70,41 @@ const memoizedLocale = () => { ...@@ -70,8 +70,41 @@ const memoizedLocale = () => {
}; };
}; };
/**
* Registers timeago time duration
*/
const memoizedLocaleDuration = () => {
const cache = [];
const durations = [
() => [s__('Duration|%s seconds')],
() => [s__('Duration|%s seconds')],
() => [s__('Duration|1 minute')],
() => [s__('Duration|%s minutes')],
() => [s__('Duration|1 hour')],
() => [s__('Duration|%s hours')],
() => [s__('Duration|1 day')],
() => [s__('Duration|%s days')],
() => [s__('Duration|1 week')],
() => [s__('Duration|%s weeks')],
() => [s__('Duration|1 month')],
() => [s__('Duration|%s months')],
() => [s__('Duration|1 year')],
() => [s__('Duration|%s years')],
];
return (_, index) => {
if (cache[index]) {
return cache[index];
}
cache[index] = durations[index] && durations[index]();
return cache[index];
};
};
timeago.register(timeagoLanguageCode, memoizedLocale()); timeago.register(timeagoLanguageCode, memoizedLocale());
timeago.register(`${timeagoLanguageCode}-remaining`, memoizedLocaleRemaining()); timeago.register(`${timeagoLanguageCode}-remaining`, memoizedLocaleRemaining());
timeago.register(`${timeagoLanguageCode}-duration`, memoizedLocaleDuration());
let memoizedFormatter = null; let memoizedFormatter = null;
...@@ -133,3 +166,16 @@ export const timeFor = (time, expiredLabel) => { ...@@ -133,3 +166,16 @@ export const timeFor = (time, expiredLabel) => {
} }
return timeago.format(time, `${timeagoLanguageCode}-remaining`).trim(); return timeago.format(time, `${timeagoLanguageCode}-remaining`).trim();
}; };
/**
* Returns a duration of time given an amount.
*
* @param {number} milliseconds - Duration in milliseconds.
* @returns {string} A formatted duration, e.g. "10 minutes".
*/
export const duration = (milliseconds) => {
const now = new Date();
return timeago
.format(now.getTime() - Math.abs(milliseconds), `${timeagoLanguageCode}-duration`)
.trim();
};
...@@ -13409,6 +13409,45 @@ msgstr "" ...@@ -13409,6 +13409,45 @@ msgstr ""
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
msgid "Duration|%s days"
msgstr ""
msgid "Duration|%s hours"
msgstr ""
msgid "Duration|%s minutes"
msgstr ""
msgid "Duration|%s months"
msgstr ""
msgid "Duration|%s seconds"
msgstr ""
msgid "Duration|%s weeks"
msgstr ""
msgid "Duration|%s years"
msgstr ""
msgid "Duration|1 day"
msgstr ""
msgid "Duration|1 hour"
msgstr ""
msgid "Duration|1 minute"
msgstr ""
msgid "Duration|1 month"
msgstr ""
msgid "Duration|1 week"
msgstr ""
msgid "Duration|1 year"
msgstr ""
msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below." msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
msgstr "" msgstr ""
......
import { getTimeago, localTimeAgo, timeFor } from '~/lib/utils/datetime/timeago_utility'; import { getTimeago, localTimeAgo, timeFor, duration } from '~/lib/utils/datetime/timeago_utility';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import '~/commons/bootstrap'; import '~/commons/bootstrap';
...@@ -66,6 +66,54 @@ describe('TimeAgo utils', () => { ...@@ -66,6 +66,54 @@ describe('TimeAgo utils', () => {
}); });
}); });
describe('duration', () => {
const ONE_DAY = 24 * 60 * 60;
it.each`
secs | formatted
${0} | ${'0 seconds'}
${30} | ${'30 seconds'}
${59} | ${'59 seconds'}
${60} | ${'1 minute'}
${-60} | ${'1 minute'}
${2 * 60} | ${'2 minutes'}
${60 * 60} | ${'1 hour'}
${2 * 60 * 60} | ${'2 hours'}
${ONE_DAY} | ${'1 day'}
${2 * ONE_DAY} | ${'2 days'}
${7 * ONE_DAY} | ${'1 week'}
${14 * ONE_DAY} | ${'2 weeks'}
${31 * ONE_DAY} | ${'1 month'}
${61 * ONE_DAY} | ${'2 months'}
${365 * ONE_DAY} | ${'1 year'}
${365 * 2 * ONE_DAY} | ${'2 years'}
`('formats $secs as "$formatted"', ({ secs, formatted }) => {
const ms = secs * 1000;
expect(duration(ms)).toBe(formatted);
});
// `duration` can be used to format Rails month durations.
// Ensure formatting for quantities such as `2.months.to_i`
// based on ActiveSupport::Duration::SECONDS_PER_MONTH.
// See: https://api.rubyonrails.org/classes/ActiveSupport/Duration.html
const SECONDS_PER_MONTH = 2629746; // 1.month.to_i
it.each`
duration | secs | formatted
${'1.month'} | ${SECONDS_PER_MONTH} | ${'1 month'}
${'2.months'} | ${SECONDS_PER_MONTH * 2} | ${'2 months'}
${'3.months'} | ${SECONDS_PER_MONTH * 3} | ${'3 months'}
`(
'formats ActiveSupport::Duration of `$duration` ($secs) as "$formatted"',
({ secs, formatted }) => {
const ms = secs * 1000;
expect(duration(ms)).toBe(formatted);
},
);
});
describe('localTimeAgo', () => { describe('localTimeAgo', () => {
beforeEach(() => { beforeEach(() => {
document.body.innerHTML = document.body.innerHTML =
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment