Commit 11c5e561 authored by Miguel Rincon's avatar Miguel Rincon

Added fixes from reviews

- Improve validations of time ranges
- Use deconstructing for named parameters in functions
- Improve time range examples in jsdoc
- Switch to placing the functions in `const`
parent ea3c62fb
import dateformat from 'dateformat';
import { secondsToMilliseconds } from './datetime_utility'; import { secondsToMilliseconds } from './datetime_utility';
const MINIMUM_DATE = new Date(0); const MINIMUM_DATE = new Date(0);
const DEFAULT_DIRECTION = 'before';
const durationToMillis = duration => { const durationToMillis = duration => {
if (Object.entries(duration).length === 1 && Number.isFinite(duration.seconds)) { if (Object.entries(duration).length === 1 && Number.isFinite(duration.seconds)) {
return secondsToMilliseconds(duration.seconds); return secondsToMilliseconds(duration.seconds);
} }
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
throw new Error('The only duration allowed is Number of `seconds`.'); throw new Error('Invalid duration: only `seconds` is supported');
}; };
const dateMinusDuration = (date, duration) => new Date(date.getTime() - durationToMillis(duration)); const dateMinusDuration = (date, duration) => new Date(date.getTime() - durationToMillis(duration));
const datePlusDuration = (date, duration) => new Date(date.getTime() + durationToMillis(duration)); const datePlusDuration = (date, duration) => new Date(date.getTime() + durationToMillis(duration));
function handleRangeDirection({ direction = 'before', anchor, before, after }) { const isValidDuration = duration => Boolean(duration && Number.isFinite(duration.seconds));
const isValidDateString = dateString => {
if (typeof dateString !== 'string' || !dateString.trim()) {
return false;
}
try {
// dateformat throws error that can be caught.
// This is better than using `new Date()`
dateformat(dateString, 'isoUtcDateTime');
return true;
} catch (e) {
return false;
}
};
const handleRangeDirection = ({ direction = DEFAULT_DIRECTION, anchorDate, minDate, maxDate }) => {
let startDate; let startDate;
let endDate; let endDate;
if (direction === 'before') { if (direction === DEFAULT_DIRECTION) {
startDate = before; startDate = minDate;
endDate = anchor; endDate = anchorDate;
} else { } else {
startDate = anchor; startDate = anchorDate;
endDate = after; endDate = maxDate;
} }
return { return {
startDate, startDate,
endDate, endDate,
}; };
} };
/** /**
* Converts a fixed range to a fixed range * Converts a fixed range to a fixed range
* @param {Object} fixedRange - A range with fixed start and end (e.g. From January 1st 2020 to January 31st 2020) * @param {Object} fixedRange - A range with fixed start and
* end (e.g. "midnight January 1st 2020 to midday January31st 2020")
*/ */
function convertFixedToFixed(fixedRange) { const convertFixedToFixed = ({ start, end }) => ({
// start,
return { end,
startTime: new Date(fixedRange.startTime).toISOString(), });
endTime: new Date(fixedRange.endTime).toISOString(),
};
}
/** /**
* Converts an anchored range to a fixed range * Converts an anchored range to a fixed range
* @param {Object} anchoredRange - A duration of time relative to an fixed point in time (2 minutes before January 1st, 1 day after) * @param {Object} anchoredRange - A duration of time
* relative to a fixed point in time (e.g., "the 30 minutes
* before midnight January 1st 2020", or "the 2 days
* after midday on the 11th of May 2019")
*/ */
function convertAnchoredToFixed(anchoredRange) { const convertAnchoredToFixed = ({ anchor, duration, direction }) => {
const anchor = new Date(anchoredRange.anchor); const anchorDate = new Date(anchor);
const { startDate, endDate } = handleRangeDirection({ const { startDate, endDate } = handleRangeDirection({
before: dateMinusDuration(anchor, anchoredRange.duration), minDate: dateMinusDuration(anchorDate, duration),
after: datePlusDuration(anchor, anchoredRange.duration), maxDate: datePlusDuration(anchorDate, duration),
direction: anchoredRange.direction, direction,
anchor, anchorDate,
}); });
return { return {
startTime: startDate.toISOString(), start: startDate.toISOString(),
endTime: endDate.toISOString(), end: endDate.toISOString(),
}; };
} };
/** /**
* Converts a rolling change to a fixed range * Converts a rolling change to a fixed range
* @param {Object} rollingRange - a time range relative to now (Last 2 minutes, Next 2 days) *
* @param {Object} rollingRange - A time range relative to
* now (e.g., "last 2 minutes", or "next 2 days")
*/ */
function convertRollingToFixed(rollingRange) { const convertRollingToFixed = ({ duration, direction }) => {
// Use Date.now internally for easier mocking in tests
const now = new Date(Date.now()); const now = new Date(Date.now());
return convertAnchoredToFixed({ return convertAnchoredToFixed({
duration: rollingRange.duration, duration,
direction: rollingRange.direction, direction,
anchor: now.toISOString(), anchor: now.toISOString(),
}); });
} };
/** /**
* Converts an open range to a fixed range * Converts an open range to a fixed range
* @param {Object} openRange - a time range relative to an anchor (Before 1st of January, After 1st of January) *
* @param {Object} openRange - A time range relative
* to an anchor (e.g., "before midnight on the 1st of
* January 2020", or "after midday on the 11th of May 2019")
*/ */
function convertOpenToFixed(openRange) { const convertOpenToFixed = ({ anchor, direction }) => {
// Use Date.now internally for easier mocking in tests
const now = new Date(Date.now()); const now = new Date(Date.now());
const anchor = new Date(openRange.anchor);
const { startDate, endDate } = handleRangeDirection({ const { startDate, endDate } = handleRangeDirection({
before: MINIMUM_DATE, minDate: MINIMUM_DATE,
after: now, maxDate: now,
direction: openRange.direction, direction,
anchor, anchorDate: new Date(anchor),
}); });
return { return {
startTime: startDate.toISOString(), start: startDate.toISOString(),
endTime: endDate.toISOString(), end: endDate.toISOString(),
};
}
function handleInvalidRange(range) {
const hasStart = range.startTime;
const hasEnd = range.endTime;
/* eslint-disable @gitlab/i18n/no-non-i18n-strings */
const messages = {
[true]: 'The input range does not have the right format.',
[!hasStart && hasEnd]: 'The input fixed range does not have a start time.',
[hasStart && !hasEnd]: 'The input fixed range does not have an end time.',
}; };
/* eslint-enable @gitlab/i18n/no-non-i18n-strings */ };
throw new Error(messages.true); /**
} * Handles invalid date ranges
*/
const handleInvalidRange = () => {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
throw new Error('The input range does not have the right format.');
};
const handlers = { const handlers = {
invalid: handleInvalidRange, invalid: handleInvalidRange,
...@@ -122,38 +142,39 @@ const handlers = { ...@@ -122,38 +142,39 @@ const handlers = {
open: convertOpenToFixed, open: convertOpenToFixed,
}; };
/**
* Validates and returns the type of range
*
* @param {Object} Date time range
* @returns {String} `key` value for one of the handlers
*/
export function getRangeType(range) { export function getRangeType(range) {
const hasStart = range.startTime; const { start, end, anchor, duration } = range;
const hasEnd = range.endTime;
const hasAnchor = range.anchor;
const hasDuration = range.duration;
const types = {
fixed: hasStart && hasEnd,
anchored: hasAnchor && hasDuration,
rolling: hasDuration && !hasAnchor,
open: hasAnchor && !hasDuration,
};
return (Object.entries(types).find(([, truthy]) => truthy) || ['invalid'])[0]; if ((start || end) && !anchor && !duration) {
return isValidDateString(start) && isValidDateString(end) ? 'fixed' : 'invalid';
}
if (anchor && duration) {
return isValidDateString(anchor) && isValidDuration(duration) ? 'anchored' : 'invalid';
}
if (duration && !anchor) {
return isValidDuration(duration) ? 'rolling' : 'invalid';
}
if (anchor && !duration) {
return isValidDateString(anchor) ? 'open' : 'invalid';
}
return 'invalid';
} }
/** /**
* convertToFixedRange Transforms a `range of time` into a `fixed range of time`. * convertToFixedRange Transforms a `range of time` into a `fixed range of time`.
* *
* A a range of time can be understood as an arbitrary period * The following types of a `ranges of time` can be represented:
* of time that can represent points in time relative to the
* present moment:
*
* The range of time can take different shapes according to
* the point of time and type of time range it represents.
*
* The following types of ranges can be represented:
* *
* Fixed Range: A range with fixed start and end (e.g. From January 1st 2020 to January 31st 2020) * Fixed Range: A range with fixed start and end (e.g. "midnight January 1st 2020 to midday January 31st 2020")
* Anchored Range: A duration of time relative to an fixed point in time (2 minutes before January 1st, 1 day after) * Anchored Range: A duration of time relative to a fixed point in time (e.g., "the 30 minutes before midnight January 1st 2020", or "the 2 days after midday on the 11th of May 2019")
* Rolling Range: A time range relative to now (Last 2 minutes, Next 2 days) * Rolling Range: A time range relative to now (e.g., "last 2 minutes", or "next 2 days")
* Open Range: A time range relative to an anchor (Before 1st of January, After 1st of January) * Open Range: A time range relative to an anchor (e.g., "before midnight on the 1st of January 2020", or "after midday on the 11th of May 2019")
* *
* @param {Object} dateTimeRange - A Time Range representation * @param {Object} dateTimeRange - A Time Range representation
* It contains the data needed to create a fixed time range plus * It contains the data needed to create a fixed time range plus
...@@ -166,7 +187,7 @@ export function getRangeType(range) { ...@@ -166,7 +187,7 @@ export function getRangeType(range) {
* seconds: number; * seconds: number;
* } * }
* *
* type Direction = 'before' | 'after'; // Direction of time * type Direction = 'before' | 'after'; // Direction of time relative to an anchor
* *
* type FixedRange = { * type FixedRange = {
* start: ISO8601; * start: ISO8601;
...@@ -177,27 +198,26 @@ export function getRangeType(range) { ...@@ -177,27 +198,26 @@ export function getRangeType(range) {
* type AnchoredRange = { * type AnchoredRange = {
* anchor: ISO8601; * anchor: ISO8601;
* duration: Duration; * duration: Duration;
* direction: Direction; * direction: Direction; // defaults to 'before'
* label: string; * label: string;
* } * }
* *
* type RollingRange = { * type RollingRange = {
* duration: Duration; * duration: Duration;
* direction: Direction; * direction: Direction; // defaults to 'before'
* label: string; * label: string;
* } * }
* *
* type OpenRange = { * type OpenRange = {
* anchor: ISO8601; * anchor: ISO8601;
* direction: Direction; * direction: Direction; // defaults to 'before'
* label: string; * label: string;
* } * }
* *
* type DateTimeRange = FixedRange | AnchoredRange | RollingRange | OpenRange; * type DateTimeRange = FixedRange | AnchoredRange | RollingRange | OpenRange;
* *
* *
* @returns An object with a fixed startTime and endTime that * @returns {FixedRange} An object with a start and end in ISO8601 format.
* corresponds to the input time.
*/ */
export const convertToFixedRange = dateTimeRange => export const convertToFixedRange = dateTimeRange =>
handlers[getRangeType(dateTimeRange)](dateTimeRange); handlers[getRangeType(dateTimeRange)](dateTimeRange);
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