Commit b07cf08c authored by Paul Gascou-Vaillancourt's avatar Paul Gascou-Vaillancourt Committed by Andrew Fontaine

Add date time format utilities

This adds a few date time formatting utils to be used in the DAST
on-demand scans scheduling options.

* isoDateToSameLocaleDate will be used to reset the date and time inputs
  to the correct values when initializing the form with a stored
  datetime and timezone.
* dateAndTimeToUTCString will let us compute a Date instance based on a
  Date generated by a datepicker and a time string from a time input.
* dateToTimeInputValue will serve to restore a time input's value from a
  Date instance.
parent 854c64da
import dateFormat from 'dateformat';
import { isString, mapValues, reduce } from 'lodash';
import { isString, mapValues, reduce, isDate } from 'lodash';
import { s__, n__, __ } from '../../../locale';
/**
......@@ -258,3 +258,100 @@ export const parseSeconds = (
return periodCount;
});
};
/**
* Pads given items with zeros to reach a length of 2 characters.
*
* @param {...any} args Items to be padded.
* @returns {Array<String>} Padded items.
*/
export const padWithZeros = (...args) => args.map((arg) => `${arg}`.padStart(2, '0'));
/**
* This removes the timezone from an ISO date string.
* This can be useful when populating date/time fields along with a distinct timezone selector, in
* which case we'd want to ignore the timezone's offset when populating the date and time.
*
* Examples:
* stripTimezoneFromISODate('2021-08-16T00:00:00.000-02:00') => '2021-08-16T00:00:00.000'
* stripTimezoneFromISODate('2021-08-16T00:00:00.000Z') => '2021-08-16T00:00:00.000'
*
* @param {String} date The ISO date string representation.
* @returns {String} The ISO date string without the timezone.
*/
export const stripTimezoneFromISODate = (date) => {
if (Number.isNaN(Date.parse(date))) {
return null;
}
return date.replace(/(Z|[+-]\d{2}:\d{2})$/, '');
};
/**
* Extracts the year, month and day from a Date instance and returns them in an object.
* For example:
* dateToYearMonthDate(new Date('2021-08-16')) => { year: '2021', month: '08', day: '16' }
*
* @param {Date} date The date to be parsed
* @returns {Object} An object containing the extracted year, month and day.
*/
export const dateToYearMonthDate = (date) => {
if (!isDate(date)) {
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('Argument should be a Date instance');
}
const [year, month, day] = date.toISOString().replace(/T.*$/, '').split('-');
return { year, month, day };
};
/**
* Extracts the hours and minutes from a string representing a time.
* For example:
* timeToHoursMinutes('12:46') => { hours: '12', minutes: '46' }
*
* @param {String} time The time to be parsed in the form HH:MM.
* @returns {Object} An object containing the hours and minutes.
*/
export const timeToHoursMinutes = (time = '') => {
if (!time || !time.match(/\d{1,2}:\d{1,2}/)) {
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('Invalid time provided');
}
const [hours, minutes] = padWithZeros(...time.split(':'));
return { hours, minutes };
};
/**
* This combines a date and a time and returns the computed Date's ISO string representation.
*
* @param {Date} date Date object representing the base date.
* @param {String} time String representing the time to be used, in the form HH:MM.
* @param {String} offset An optional Date-compatible offset.
* @returns {String} The combined Date's ISO string representation.
*/
export const dateAndTimeToUTCString = (date, time, offset = '') => {
const { year, month, day } = dateToYearMonthDate(date);
const { hours, minutes } = timeToHoursMinutes(time);
return new Date(
`${year}-${month}-${day}T${hours}:${minutes}:00.000${offset || 'Z'}`,
).toISOString();
};
/**
* Converts a Date instance to time input-compatible value consisting in a 2-digits hours and
* minutes, separated by a semi-colon, in the 24-hours format.
*
* @param {Date} date Date to be converted
* @returns {String} time input-compatible string in the form HH:MM.
*/
export const dateToTimeInputValue = (date) => {
if (!isDate(date)) {
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('Argument should be a Date instance');
}
return date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
};
import * as utils from '~/lib/utils/datetime/date_format_utility';
describe('date_format_utility.js', () => {
describe('padWithZeros', () => {
it.each`
input | output
${0} | ${'00'}
${'1'} | ${'01'}
${'10'} | ${'10'}
${'100'} | ${'100'}
${100} | ${'100'}
${'a'} | ${'0a'}
${'foo'} | ${'foo'}
`('properly pads $input to match $output', ({ input, output }) => {
expect(utils.padWithZeros(input)).toEqual([output]);
});
it('accepts multiple arguments', () => {
expect(utils.padWithZeros(1, '2', 3)).toEqual(['01', '02', '03']);
});
it('returns an empty array provided no argument', () => {
expect(utils.padWithZeros()).toEqual([]);
});
});
describe('stripTimezoneFromISODate', () => {
it.each`
input | expectedOutput
${'2021-08-16T00:00:00Z'} | ${'2021-08-16T00:00:00'}
${'2021-08-16T10:30:00+02:00'} | ${'2021-08-16T10:30:00'}
${'2021-08-16T10:30:00-05:30'} | ${'2021-08-16T10:30:00'}
`('returns $expectedOutput when given $input', ({ input, expectedOutput }) => {
expect(utils.stripTimezoneFromISODate(input)).toBe(expectedOutput);
});
it('returns null if date is invalid', () => {
expect(utils.stripTimezoneFromISODate('Invalid date')).toBe(null);
});
});
describe('dateToYearMonthDate', () => {
it.each`
date | expectedOutput
${new Date('2021-08-05')} | ${{ year: '2021', month: '08', day: '05' }}
${new Date('2021-12-24')} | ${{ year: '2021', month: '12', day: '24' }}
`('returns $expectedOutput provided $date', ({ date, expectedOutput }) => {
expect(utils.dateToYearMonthDate(date)).toEqual(expectedOutput);
});
it('throws provided an invalid date', () => {
expect(() => utils.dateToYearMonthDate('Invalid date')).toThrow(
'Argument should be a Date instance',
);
});
});
describe('timeToHoursMinutes', () => {
it.each`
time | expectedOutput
${'23:12'} | ${{ hours: '23', minutes: '12' }}
${'23:12'} | ${{ hours: '23', minutes: '12' }}
`('returns $expectedOutput provided $time', ({ time, expectedOutput }) => {
expect(utils.timeToHoursMinutes(time)).toEqual(expectedOutput);
});
it('throws provided an invalid time', () => {
expect(() => utils.timeToHoursMinutes('Invalid time')).toThrow('Invalid time provided');
});
});
describe('dateAndTimeToUTCString', () => {
it('computes the date properly', () => {
expect(utils.dateAndTimeToUTCString(new Date('2021-08-16'), '10:00')).toBe(
'2021-08-16T10:00:00.000Z',
);
});
it('computes the date properly with an offset', () => {
expect(utils.dateAndTimeToUTCString(new Date('2021-08-16'), '10:00', '-04:00')).toBe(
'2021-08-16T14:00:00.000Z',
);
});
it('throws if date in invalid', () => {
expect(() => utils.dateAndTimeToUTCString('Invalid date', '10:00')).toThrow(
'Argument should be a Date instance',
);
});
it('throws if time in invalid', () => {
expect(() => utils.dateAndTimeToUTCString(new Date('2021-08-16'), '')).toThrow(
'Invalid time provided',
);
});
it('throws if offset is invalid', () => {
expect(() =>
utils.dateAndTimeToUTCString(new Date('2021-08-16'), '10:00', 'not an offset'),
).toThrow('Invalid time value');
});
});
describe('dateToTimeInputValue', () => {
it.each`
input | expectedOutput
${new Date('2021-08-16T10:00:00.000Z')} | ${'10:00'}
${new Date('2021-08-16T22:30:00.000Z')} | ${'22:30'}
${new Date('2021-08-16T22:30:00.000-03:00')} | ${'01:30'}
`('extracts $expectedOutput out of $input', ({ input, expectedOutput }) => {
expect(utils.dateToTimeInputValue(input)).toBe(expectedOutput);
});
it('throws if date is invalid', () => {
expect(() => utils.dateToTimeInputValue('Invalid date')).toThrow(
'Argument should be a Date instance',
);
});
});
});
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