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';
const MINIMUM_DATE = new Date(0);
const DEFAULT_DIRECTION = 'before';
const durationToMillis = duration => {
if (Object.entries(duration).length === 1 && Number.isFinite(duration.seconds)) {
return secondsToMilliseconds(duration.seconds);
}
// 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 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 endDate;
if (direction === 'before') {
startDate = before;
endDate = anchor;
if (direction === DEFAULT_DIRECTION) {
startDate = minDate;
endDate = anchorDate;
} else {
startDate = anchor;
endDate = after;
startDate = anchorDate;
endDate = maxDate;
}
return {
startDate,
endDate,
};
}
};
/**
* 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) {
//
return {
startTime: new Date(fixedRange.startTime).toISOString(),
endTime: new Date(fixedRange.endTime).toISOString(),
};
}
const convertFixedToFixed = ({ start, end }) => ({
start,
end,
});
/**
* 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 anchor = new Date(anchoredRange.anchor);
const convertAnchoredToFixed = ({ anchor, duration, direction }) => {
const anchorDate = new Date(anchor);
const { startDate, endDate } = handleRangeDirection({
before: dateMinusDuration(anchor, anchoredRange.duration),
after: datePlusDuration(anchor, anchoredRange.duration),
direction: anchoredRange.direction,
anchor,
minDate: dateMinusDuration(anchorDate, duration),
maxDate: datePlusDuration(anchorDate, duration),
direction,
anchorDate,
});
return {
startTime: startDate.toISOString(),
endTime: endDate.toISOString(),
start: startDate.toISOString(),
end: endDate.toISOString(),
};
}
};
/**
* 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());
return convertAnchoredToFixed({
duration: rollingRange.duration,
direction: rollingRange.direction,
duration,
direction,
anchor: now.toISOString(),
});
}
};
/**
* 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 anchor = new Date(openRange.anchor);
const { startDate, endDate } = handleRangeDirection({
before: MINIMUM_DATE,
after: now,
direction: openRange.direction,
anchor,
minDate: MINIMUM_DATE,
maxDate: now,
direction,
anchorDate: new Date(anchor),
});
return {
startTime: startDate.toISOString(),
endTime: 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.',
start: startDate.toISOString(),
end: endDate.toISOString(),
};
/* 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 = {
invalid: handleInvalidRange,
......@@ -122,38 +142,39 @@ const handlers = {
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) {
const hasStart = range.startTime;
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,
};
const { start, end, anchor, duration } = range;
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`.
*
* A a range of time can be understood as an arbitrary period
* 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:
* The following types of a `ranges of time` can be represented:
*
* Fixed Range: A range with fixed start and end (e.g. From January 1st 2020 to January 31st 2020)
* Anchored Range: A duration of time relative to an fixed point in time (2 minutes before January 1st, 1 day after)
* Rolling Range: A time range relative to now (Last 2 minutes, Next 2 days)
* Open Range: A time range relative to an anchor (Before 1st of January, After 1st of January)
* 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 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 (e.g., "last 2 minutes", or "next 2 days")
* 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
* It contains the data needed to create a fixed time range plus
......@@ -166,7 +187,7 @@ export function getRangeType(range) {
* seconds: number;
* }
*
* type Direction = 'before' | 'after'; // Direction of time
* type Direction = 'before' | 'after'; // Direction of time relative to an anchor
*
* type FixedRange = {
* start: ISO8601;
......@@ -177,27 +198,26 @@ export function getRangeType(range) {
* type AnchoredRange = {
* anchor: ISO8601;
* duration: Duration;
* direction: Direction;
* direction: Direction; // defaults to 'before'
* label: string;
* }
*
* type RollingRange = {
* duration: Duration;
* direction: Direction;
* direction: Direction; // defaults to 'before'
* label: string;
* }
*
* type OpenRange = {
* anchor: ISO8601;
* direction: Direction;
* direction: Direction; // defaults to 'before'
* label: string;
* }
*
* type DateTimeRange = FixedRange | AnchoredRange | RollingRange | OpenRange;
*
*
* @returns An object with a fixed startTime and endTime that
* corresponds to the input time.
* @returns {FixedRange} An object with a start and end in ISO8601 format.
*/
export const convertToFixedRange = 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