utils/date.js

import {isDate, isString} from 'lodash';
import moment from 'moment';
import SHGlobals from 'sh-globals';

const HUMAN_DATE_TIME_FORMAT = 'MMM D, h:mma';
const HUMAN_DATE_MONTHLY_FORMAT = 'MMM D, YYYY';
const HUMAN_TIME_FORMAT = 'h:mma';

const splitTextDate = (date, delimeter) => {
    const d = date.split(delimeter);

    return new Date(d[2], d[0] - 1, d[1]);
};

const splitMobileDate = date => {
    const d = date.split('-');

    return new Date(d[0], d[1] - 1, d[2]);
};

/**
 * Utilities for working with dates.
 * @module DateUtils
 */
const DateUtils = {
    /**
     * Check whether input type of 'date' is supported in the current browser.
     * @static
     * @function supportsDateInput
     * @example
     * DateUtils.supportsDateInput();
     * @returns {Boolean} - Whether the input type is supported.
     */
    supportsDateInput() {
        const fakeValue = 'not-a-date';
        const input = document.createElement('input');

        input.setAttribute('type', 'date');
        input.setAttribute('value', fakeValue);

        return (input.value !== fakeValue);
    },

    /**
     * Converts a mobile input date to a string using the specified format.
     * @static
     * @function convertToDesktop
     * @param {String} date - The input value in the format YYYY-MM-DD.
     * @param {String} [format='MM/DD/YYYY'] - The output format.
     * @example
     * DateUtils.convertToDesktop('1983-01-19');
     * @returns {String} - The formatted date.
     */
    convertToDesktop(date, format = SHGlobals.DESKTOP_DATE_FORMAT) {
        if (!isString(date)) {
            throw new Error('The supplied date is not a string.');
        }

        if (date.indexOf('-') !== -1) {
            return moment(splitMobileDate(date)).format(format);
        } else {
            return date;
        }
    },

    /**
     * Converts a desktop input date to the native mobile date format.
     * @static
     * @function convertToMobile
     * @param {String} date - The input value in the format MM/DD/YYYY.
     * @example
     * DateUtils.convertToMobile('01/19/1983');
     * @returns {String} - The mobile formatted date.
     */
    convertToMobile(date) {
        if (!isString(date)) {
            throw new Error('The supplied date is not a string.');
        }

        if (date.indexOf('/') !== -1) {
            return moment(splitTextDate(date, '/')).format(SHGlobals.MOBILE_DATE_FORMAT);
        } else {
            return date;
        }
    },

    /**
     * Converts a string date from a text or date input to a date object.
     * @static
     * @function getAsDate
     * @param {String} date - The date in the format MM/DD/YYYY (text input) or YYYY-MM-DD (date input).
     * @example
     * DateUtils.getAsDate('1983-01-19');
     * DateUtils.getAsDate('01/19/1983');
     * @returns {Date} - The date object.
     */
    getAsDate(date) {
        if (!isString(date)) {
            throw new Error('The supplied date is not a string.');
        }

        if (date.indexOf('-') !== -1) {
            // date input
            return splitMobileDate(date);
        } else {
            // text input
            return splitTextDate(date, '/');
        }
    },

    /**
     * Converts a string date from a text or date input to a moment instance.
     * @static
     * @function getAsMoment
     * @param {String} date - The date in the format MM/DD/YYYY (text input) or YYYY-MM-DD (date input).
     * @example
     * DateUtils.getAsMoment('1983-01-19');
     * DateUtils.getAsMoment('01/19/1983');
     * @returns {Moment} - The moment instance.
     */
    getAsMoment(date) {
        if (!isString(date)) {
            throw new Error('The supplied date is not a string.');
        }

        if (date.indexOf('-') !== -1) {
            // date input
            return moment(splitMobileDate(date));
        } else {
            // text input
            return moment(splitTextDate(date, '/'));
        }
    },

    /**
     * Combines a date moment with a time moment to return one moment instance.
     * @static
     * @function combineDateAndTime
     * @param {Moment} date - The date as a moment instance.
     * @param {Moment} time - The time as a moment instance.
     * @example
     * DateUtils.combineDateAndTime(moment(), moment().add(7, 'hours').add(23, 'minutes'));
     * @returns {Moment} - The combined moment instance.
     */
    combineDateAndTime(date, time) {
        if (!moment.isMoment(date) || !moment.isMoment(time)) {
            throw new Error('The date and/or time supplied was not an instance of Moment.');
        }

        return date.hours(time.hours()).minutes(time.minutes());
    },

    /**
     * Checks if one date is before another.
     * @static
     * @function isBefore
     * @param {Date} dateToCheck - The date to check.
     * @param {Date} dateToCompare - The date to check against.
     * @param {Boolean} [useTime=false] - Whether to use the time in the comparison.
     * @example
     * DateUtils.isBefore(new Date(2016, 1, 19), new Date(2016, 3, 14));
     * @returns {Boolean} - Whether the provided date is before the one being checked against.
     */
    isBefore(dateToCheck, dateToCompare, useTime = false) {
        if (!isDate(dateToCheck) || !isDate(dateToCompare)) {
            throw new Error('The dates supplied must be instances of the Date object.');
        }

        if (!useTime) {
            dateToCheck.setHours(0, 0, 0, 0);
            dateToCompare.setHours(0, 0, 0, 0);
        }

        return dateToCheck < dateToCompare;
    },

    /**
     * Checks if one date is after another.
     * @static
     * @function isAfter
     * @param {Date} dateToCheck - The date to check.
     * @param {Date} dateToCompare - The date to check against.
     * @param {Boolean} [useTime=false] - Whether to use the time in the comparison.
     * @example
     * DateUtils.isAfter(new Date(2016, 1, 19), new Date(2016, 3, 14));
     * @returns {Boolean} - Whether the provided date is after the one being checked against.
     */
    isAfter(dateToCheck, dateToCompare, useTime = false) {
        if (!isDate(dateToCheck) || !isDate(dateToCompare)) {
            throw new Error('The dates supplied must be instances of the Date object.');
        }

        if (!useTime) {
            dateToCheck.setHours(0, 0, 0, 0);
            dateToCompare.setHours(0, 0, 0, 0);
        }

        return dateToCheck > dateToCompare;
    },

    /**
     * Returns human readable start and end datetime strings from moment objects.
     * @static
     * @function getHumanDateTimesAsStrings
     * @param {Object} data - The data to pass to the function.
     * @param {Moment} data.startDateTimeObj - The date as a moment instance.
     * @param {Moment} data.endDateTimeObj - The time as a moment instance.
     * @param {Boolean} data.isMonthly - Is startDateTimeObj a monthly datetime?
     * @example
     * DateUtils.getHumanDateTimesAsStrings({
     *     startDateTimeObj: moment(),
     *     endDateTimeObj: moment().add(7, 'hours').add(23, 'minutes')
     * });
     * @returns {Object} - Human readable strings returned for start and end
     */
    getHumanDateTimesAsStrings({startDateTimeObj, endDateTimeObj, isMonthly}) {
        if (!moment.isMoment(startDateTimeObj) || (!isMonthly && !moment.isMoment(endDateTimeObj))) {
            throw new Error('The datetimes supplied are not an instance of Moment.');
        }

        let startString = null;
        let endString = null;

        if (startDateTimeObj) {
            if (moment().startOf('day').diff(startDateTimeObj.clone().startOf('day')) === 0) {
                startString = `Today${(!isMonthly) ? `, ${startDateTimeObj.format(HUMAN_TIME_FORMAT)}` : ''}`;
            } else {
                if (isMonthly) {
                    startString = startDateTimeObj.format(HUMAN_DATE_MONTHLY_FORMAT);
                } else {
                    startString = startDateTimeObj.format(HUMAN_DATE_TIME_FORMAT);
                }
            }
        }

        if (!isMonthly && endDateTimeObj) {
            if (startDateTimeObj.clone().startOf('day').diff(endDateTimeObj.clone().startOf('day')) === 0) {
                if (moment().startOf('day').diff(endDateTimeObj.clone().startOf('day')) === 0) {
                    endString = 'Today, ' + endDateTimeObj.format(HUMAN_TIME_FORMAT);
                } else {
                    endString = endDateTimeObj.format(HUMAN_DATE_TIME_FORMAT);
                }
            } else {
                if (moment().clone().startOf('day').diff(endDateTimeObj.clone().startOf('day'), 'days') === -1) {
                    endString = `Tomorrow, ${endDateTimeObj.format(HUMAN_TIME_FORMAT)}`;
                } else {
                    endString = endDateTimeObj.format(HUMAN_DATE_TIME_FORMAT);
                }
            }
        }

        return {
            start: startString,
            end: endString
        };
    }
};

export default DateUtils;