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 = () => {
};
};
/**
* 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}-remaining`, memoizedLocaleRemaining());
timeago.register(`${timeagoLanguageCode}-duration`, memoizedLocaleDuration());
let memoizedFormatter = null;
......@@ -133,3 +166,16 @@ export const timeFor = (time, expiredLabel) => {
}
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 ""
msgid "Duration"
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."
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 '~/commons/bootstrap';
......@@ -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', () => {
beforeEach(() => {
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