Skip to content
Snippets Groups Projects
Unverified Commit c88f78b6 authored by Jacques Erasmus's avatar Jacques Erasmus Committed by GitLab
Browse files

Merge branch 'cngo-organise-datetime-utils' into 'master'

Organize datetime utility functions

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167799



Merged-by: default avatarJacques Erasmus <jerasmus@gitlab.com>
Approved-by: default avatarJacques Erasmus <jerasmus@gitlab.com>
Reviewed-by: default avatarCoung Ngo <cngo@gitlab.com>
Co-authored-by: default avatarCoung Ngo <cngo@gitlab.com>
parents 150cf5c2 98afd5e1
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -33,7 +33,7 @@ export const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
* for UTC-8 timezone.
*
* @param {string|number|Date} date
* @returns {Date|null|undefined}
* @returns {Date|null|undefined} A Date object in local time
*/
export const newDate = (date) => {
if (date === null) {
Loading
Loading
@@ -42,6 +42,7 @@ export const newDate = (date) => {
if (date === undefined) {
return undefined;
}
// Fix historical bug so we return a local time for `yyyy-mm-dd` date-only strings
if (typeof date === 'string' && DATE_ONLY_REGEX.test(date)) {
const parts = date.split('-');
const year = parseInt(parts[0], 10);
Loading
Loading
@@ -545,48 +546,6 @@ export const dateAtFirstDayOfMonth = (date) => new Date(cloneDate(date).setDate(
*/
export const datesMatch = (date1, date2) => differenceInMilliseconds(date1, date2) === 0;
 
/**
* A utility function which checks if two date ranges overlap.
*
* @param {Object} givenPeriodLeft - the first period to compare.
* @param {Object} givenPeriodRight - the second period to compare.
* @returns {Object} { overlap: number of days the overlap is present, overlapStartDate: the start date of the overlap in time format, overlapEndDate: the end date of the overlap in time format }
* @throws {Error} Uncaught Error: Invalid period
*
* @example
* getOverlappingDaysInPeriods(
* { start: new Date(2021, 0, 11), end: new Date(2021, 0, 13) },
* { start: new Date(2021, 0, 11), end: new Date(2021, 0, 14) }
* ) => { daysOverlap: 2, overlapStartDate: 1610323200000, overlapEndDate: 1610496000000 }
*
*/
export const getOverlappingDaysInPeriods = (givenPeriodLeft = {}, givenPeriodRight = {}) => {
const leftStartTime = new Date(givenPeriodLeft.start).getTime();
const leftEndTime = new Date(givenPeriodLeft.end).getTime();
const rightStartTime = new Date(givenPeriodRight.start).getTime();
const rightEndTime = new Date(givenPeriodRight.end).getTime();
if (!(leftStartTime <= leftEndTime && rightStartTime <= rightEndTime)) {
throw new Error(__('Invalid period'));
}
const isOverlapping = leftStartTime < rightEndTime && rightStartTime < leftEndTime;
if (!isOverlapping) {
return { daysOverlap: 0 };
}
const overlapStartDate = Math.max(leftStartTime, rightStartTime);
const overlapEndDate = rightEndTime > leftEndTime ? leftEndTime : rightEndTime;
const differenceInMs = overlapEndDate - overlapStartDate;
return {
daysOverlap: Math.ceil(differenceInMs / MILLISECONDS_IN_DAY),
overlapStartDate,
overlapEndDate,
};
};
/**
* Mimics the behaviour of the rails distance_of_time_in_words function
* https://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words
Loading
Loading
@@ -634,21 +593,6 @@ export const approximateDuration = (seconds = 0) => {
return n__('1 day', '%d days', seconds < ONE_DAY_LIMIT ? 1 : days);
};
 
/**
* A utility function which helps creating a date object
* for a specific date. Accepts the year, month and day
* returning a date object for the given params.
*
* @param {Int} year the full year as a number i.e. 2020
* @param {Int} month the month index i.e. January => 0
* @param {Int} day the day as a number i.e. 23
*
* @return {Date} the date object from the params
*/
export const dateFromParams = (year, month, day) => {
return new Date(year, month, day);
};
/**
* A utility function which computes a formatted 24 hour
* time string from a positive int in the range 0 - 24.
Loading
Loading
Loading
Loading
@@ -529,3 +529,15 @@ export const humanTimeframe = (startDate, dueDate) => {
}
return '';
};
/**
* Formats seconds into a human readable value of elapsed time,
* optionally limiting it to hours.
* @param {Number} seconds Seconds to format
* @param {Boolean} limitToHours Whether or not to limit the elapsed time to be expressed in hours
* @return {String} Provided seconds in human readable elapsed time format
*/
export const formatTimeSpent = (seconds, limitToHours) => {
const negative = seconds < 0;
return (negative ? '- ' : '') + stringifyTime(parseSeconds(seconds, { limitToHours }));
};
import { stringifyTime, parseSeconds } from './date_format_utility';
/**
* Formats seconds into a human readable value of elapsed time,
* optionally limiting it to hours.
* @param {Number} seconds Seconds to format
* @param {Boolean} limitToHours Whether or not to limit the elapsed time to be expressed in hours
* @return {String} Provided seconds in human readable elapsed time format
*/
export const formatTimeSpent = (seconds, limitToHours) => {
const negative = seconds < 0;
return (negative ? '- ' : '') + stringifyTime(parseSeconds(seconds, { limitToHours }));
};
Loading
Loading
@@ -2,5 +2,4 @@ export * from './datetime/timeago_utility';
export * from './datetime/date_format_utility';
export * from './datetime/date_calculation_utility';
export * from './datetime/pikaday_utility';
export * from './datetime/time_spent_utility';
export * from './datetime/locale_dateformat';
Loading
Loading
@@ -29470,9 +29470,6 @@ msgstr ""
msgid "Invalid name %{input} was given for this default stage, allowed names: %{names}"
msgstr ""
 
msgid "Invalid period"
msgstr ""
msgid "Invalid pin code."
msgstr ""
 
Loading
Loading
@@ -117,53 +117,75 @@ describe('date_format_utility.js', () => {
);
});
});
});
 
describe('formatTimeAsSummary', () => {
it.each`
unit | value | result
${'months'} | ${1.5} | ${'1.5 months'}
${'weeks'} | ${1.25} | ${'1.5 weeks'}
${'days'} | ${2} | ${'2 days'}
${'hours'} | ${10} | ${'10 hours'}
${'minutes'} | ${20} | ${'20 minutes'}
${'seconds'} | ${10} | ${'<1 minute'}
${'seconds'} | ${0} | ${'-'}
`('will format $value $unit to $result', ({ unit, value, result }) => {
expect(utils.formatTimeAsSummary({ [unit]: value })).toBe(result);
describe('formatTimeAsSummary', () => {
it.each`
unit | value | result
${'months'} | ${1.5} | ${'1.5 months'}
${'weeks'} | ${1.25} | ${'1.5 weeks'}
${'days'} | ${2} | ${'2 days'}
${'hours'} | ${10} | ${'10 hours'}
${'minutes'} | ${20} | ${'20 minutes'}
${'seconds'} | ${10} | ${'<1 minute'}
${'seconds'} | ${0} | ${'-'}
`('will format $value $unit to $result', ({ unit, value, result }) => {
expect(utils.formatTimeAsSummary({ [unit]: value })).toBe(result);
});
});
});
 
describe('formatUtcOffset', () => {
it.each`
offset | expected
${-32400} | ${'-9'}
${'-12600'} | ${'-3.5'}
${0} | ${' 0'}
${'10800'} | ${'+3'}
${19800} | ${'+5.5'}
${0} | ${' 0'}
${[]} | ${' 0'}
${{}} | ${' 0'}
${true} | ${' 0'}
${null} | ${' 0'}
${undefined} | ${' 0'}
`('returns $expected given $offset', ({ offset, expected }) => {
expect(utils.formatUtcOffset(offset)).toEqual(expected);
describe('formatUtcOffset', () => {
it.each`
offset | expected
${-32400} | ${'-9'}
${'-12600'} | ${'-3.5'}
${0} | ${' 0'}
${'10800'} | ${'+3'}
${19800} | ${'+5.5'}
${0} | ${' 0'}
${[]} | ${' 0'}
${{}} | ${' 0'}
${true} | ${' 0'}
${null} | ${' 0'}
${undefined} | ${' 0'}
`('returns $expected given $offset', ({ offset, expected }) => {
expect(utils.formatUtcOffset(offset)).toEqual(expected);
});
});
});
 
describe('humanTimeframe', () => {
it.each`
startDate | dueDate | returnValue
${'2021-1-1'} | ${'2021-2-28'} | ${'Jan 1 – Feb 28, 2021'}
${'2021-1-1'} | ${'2022-2-28'} | ${'Jan 1, 2021 – Feb 28, 2022'}
${'2021-1-1'} | ${null} | ${'Jan 1, 2021 – No due date'}
${null} | ${'2021-2-28'} | ${'No start date – Feb 28, 2021'}
`(
'returns string "$returnValue" when startDate is $startDate and dueDate is $dueDate',
({ startDate, dueDate, returnValue }) => {
expect(utils.humanTimeframe(startDate, dueDate)).toBe(returnValue);
},
);
describe('humanTimeframe', () => {
it.each`
startDate | dueDate | returnValue
${'2021-1-1'} | ${'2021-2-28'} | ${'Jan 1 – Feb 28, 2021'}
${'2021-1-1'} | ${'2022-2-28'} | ${'Jan 1, 2021 – Feb 28, 2022'}
${'2021-1-1'} | ${null} | ${'Jan 1, 2021 – No due date'}
${null} | ${'2021-2-28'} | ${'No start date – Feb 28, 2021'}
`(
'returns string "$returnValue" when startDate is $startDate and dueDate is $dueDate',
({ startDate, dueDate, returnValue }) => {
expect(utils.humanTimeframe(startDate, dueDate)).toBe(returnValue);
},
);
});
describe('formatTimeSpent', () => {
describe('with limitToHours false', () => {
it('formats 34500 seconds to `1d 1h 35m`', () => {
expect(utils.formatTimeSpent(34500)).toEqual('1d 1h 35m');
});
it('formats -34500 seconds to `- 1d 1h 35m`', () => {
expect(utils.formatTimeSpent(-34500)).toEqual('- 1d 1h 35m');
});
});
describe('with limitToHours true', () => {
it('formats 34500 seconds to `9h 35m`', () => {
expect(utils.formatTimeSpent(34500, true)).toEqual('9h 35m');
});
it('formats -34500 seconds to `- 9h 35m`', () => {
expect(utils.formatTimeSpent(-34500, true)).toEqual('- 9h 35m');
});
});
});
});
import { formatTimeSpent } from '~/lib/utils/datetime/time_spent_utility';
describe('Time spent utils', () => {
describe('formatTimeSpent', () => {
describe('with limitToHours false', () => {
it('formats 34500 seconds to `1d 1h 35m`', () => {
expect(formatTimeSpent(34500)).toEqual('1d 1h 35m');
});
it('formats -34500 seconds to `- 1d 1h 35m`', () => {
expect(formatTimeSpent(-34500)).toEqual('- 1d 1h 35m');
});
});
describe('with limitToHours true', () => {
it('formats 34500 seconds to `9h 35m`', () => {
expect(formatTimeSpent(34500, true)).toEqual('9h 35m');
});
it('formats -34500 seconds to `- 9h 35m`', () => {
expect(formatTimeSpent(-34500, true)).toEqual('- 9h 35m');
});
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment