utils/time.js

import isString from 'lodash/isString';
import isEmpty from 'lodash/isEmpty';
import moment from 'moment';
import NumberUtils from 'number-utils';
import StringUtils from 'string-utils';
import SHGlobals from 'sh-globals';

/**
 * Utilities for working with times.
 * @module TimeUtils
 */
const TimeUtils = {
    /**
     * Normalizes the provided time down to the provided step.
     * @static
     * @function normalizeToStep
     * @param {String} time - The input value in the format hh:mm A.
     * @param {Number} [step=1] - The step to round down to.
     * @example
     * TimeUtils.normalizeToStep('07:07 PM', 15); // '07:00 PM'
     * @returns {String} - The normalized time.
     */
    normalizeToStep(time, step = 1) {
        if (isEmpty(time)) { return time; }

        if (!isString(time)) {
            throw new Error('The supplied time is not a string.');
        }

        const {hours, mins} = TimeUtils.getValuesFromString(time, step);

        return TimeUtils.getFromValuesAsString(hours, mins);
    },

    /**
     * Provides the hours, minutes, and meridiem from a time string.
     * @static
     * @function getValuesFromString
     * @param {String} time - The input value in the format hh:mm A.
     * @param {Number} [step=1] - The step to round down to.
     * @example
     * TimeUtils.getValuesFromString('10:19 AM');
     * @returns {Object} - The object containing the hours, minutes, and meridiem.
     */
    getValuesFromString(time, step = 1) {
        if (isEmpty(time)) { return time; }

        if (!isString(time)) {
            throw new Error('The supplied time is not a string.');
        }

        const firstPartials = time.split(':');
        const secondPartials = firstPartials[1].split(' ');
        const isAM = secondPartials[1] === 'AM';
        const mins = parseInt(secondPartials[0], 10);
        const minsRounded = NumberUtils.roundDownToNearestStep(mins, step);
        let hours = parseInt(firstPartials[0], 10);

        if (isAM && hours === 12) {
            hours = 0;
        } else if (!isAM && hours !== 12) {
            hours += 12;
        }

        return {
            hours,
            mins: minsRounded,
            isAM
        };
    },

    /**
     * Provides the formatted time from hours and minutes values.
     * @static
     * @function getFromValuesAsString
     * @param {Number} hours - The hours in a 24 hour format.
     * @param {Number} minutes - The minutes from 0 - 60.
     * @param {Boolean} [roundMinutesDown=false] - Whether to round the minutes down to the provided step.
     * @param {Number} [step=1] - The step to round minutes down to.
     * @example
     * TimeUtils.getFromValuesAsString(20, 35); // '08:35 PM'
     * @returns {String} - The formatted time.
     */
    getFromValuesAsString(hours, minutes, roundMinutesDown = false, step = 1) {
        const meridiem = (hours < 12) ? 'AM' : 'PM';

        if (hours > 12) {
            hours -= 12;
        } else if (hours === 0) {
            hours = 12;
        }

        if (roundMinutesDown) {
            minutes = NumberUtils.roundDownToNearestStep(minutes, step);
        }

        return `${StringUtils.pad(hours)}:${StringUtils.pad(minutes)} ${meridiem}`;
    },

    /**
     * Converts a mobile input time to a string in the format hh:mm A.
     * @static
     * @function getMobileAsString
     * @param {String} time - The input value in the format HH:mm (24 hour format).
     * @param {Number} [step=1] - The step to round down to.
     * @example
     * TimeUtils.getMobileAsString('22:19'); // 10:19 PM
     * @returns {String} - The formatted time.
     */
    getMobileAsString(time, step = 1) {
        if (!isString(time)) {
            throw new Error('The supplied time is not a string.');
        }

        const frags = time.split(':');
        const mins = parseInt(frags[1], 10);
        const minsRounded = NumberUtils.roundDownToNearestStep(mins, step);
        const hours = parseInt(frags[0], 10);
        const meridiem = (hours < 12) ? 'AM' : 'PM';
        let hh = hours;

        if (hours > 12) {
            hh -= 12;
        } else if (hours === 0) {
            hh = 12;
        }

        return `${StringUtils.pad(hh)}:${StringUtils.pad(minsRounded)} ${meridiem}`;
    },

    /**
     * Converts a time to a date object.
     * @static
     * @function getAsDate
     * @param {String} time - The input value in the format hh:mm A.
     * @example
     * TimeUtils.getAsDate('10:19 PM');
     * @returns {Date} - The time inside of a date object.
     */
    getAsDate(time) {
        if (isEmpty(time)) { return null; }

        if (!isString(time)) {
            throw new Error('The supplied time is not a string.');
        }

        const {hours, mins} = TimeUtils.getValuesFromString(time);
        let d = new Date();
        d.setHours(hours);
        d.setMinutes(mins);
        d.setSeconds(0);

        return d;
    },

    /**
     * Converts a time to a moment instance.
     * @static
     * @function getAsMoment
     * @param {String} time - The input value in the format hh:mm A.
     * @example
     * TimeUtils.getAsMoment('10:19 PM');
     * @returns {Moment} - The time inside of a moment instance.
     */
    getAsMoment(time) {
        if (isEmpty(time)) { return null; }

        if (!isString(time)) {
            throw new Error('The supplied time is not a string.');
        }

        const {hours, mins} = TimeUtils.getValuesFromString(time);

        return moment().startOf('day').add(hours, 'hours').add(mins, 'minutes');
    },

    /**
     * Generate an array of times.
     * @static
     * @function getTimes
     * @param {Object} data - The data to pass to the function.
     * @param {Number} [data.step=30] - Increment times by this many minutes.
     * @param {String} [data.format='hh:mm A'] - Moment.js format (http://momentjs.com/docs/#/displaying/format/).
     * @example
     * TimeUtils.getTimes({
     *     step: 30,
     *     format: 'hh:mm A'
     * });
     * @returns {Array} times - An array of times.
     */
    getTimes({step = 30, format = SHGlobals.DESKTOP_TIME_FORMAT}) {
        const total = 1440;
        const len = (total / step);
        const times = [];
        let i = 0;
        let id = 0;

        for (i; i < len; i++) {
            id = (i * step);

            const time = moment().startOf('day').add(id, 'minutes');

            times.push({
                id: id,
                formatted: time.format(format)
            });
        }

        return times;
    }
};

export default TimeUtils;