Source: app/util/dateUtils.js

import moment from 'moment-timezone';
import Configuration from 'common/src/app/config/Configuration';
import MonthNumber from '../data/enum/MonthNumber';
import { YYYYMMDD } from '../data/enum/dateFormats';

/**
 * This util includes a set of functions which populate the date select filters on
 * the weigh history page
 */

const allMonths = Object.entries(MonthNumber);
/**
 * Returns all 12 month options in assending order but disables the ones the
 * user cannot choose based on available months.
 * @param {array.object}  availableMonths Is the result of the getAvailableMonths function.
 * It is an array of objects which includes year, month, month  and yearcombined and utc date.
 * @returns {array.object}  An array of objects that can passed into the select component
 * to a select with all 12 months displayed but some disabled.
 */
export const filterMonths = availableMonths => {
  const justMonths = availableMonths.map(monthYear => monthYear.month);
  return allMonths.map(month => ({
    title: `${month[1]}`,
    value: month[0].toString().padStart(2, '0'),
    disabled: !justMonths.includes(parseInt(month[0], 10)),
  }));
};

/**
 * Returns all 12 month options, populating the select component. Allowing
 *  the user to select any month to filter by
 * @returns {array.object}  An array of objects that can passed into the select component
 * to create a select with all 12 months.
 * Heres an example of the structure of the option object
 * @example {title: May, value: "5"}
 */
export const getAllMonths = () =>
  allMonths.map(month => ({ title: `${month[1]}`, value: month[0] }));

export const loadMonthOptions = (yearSelected, userJoinDate) => {
  let endDate;

  if (moment(`${yearSelected}-01-01`).isSame(moment(), 'year')) {
    endDate = moment();
  } else if (moment(`${yearSelected}-01-01`).isSame(moment(userJoinDate), 'year')) {
    endDate = moment(`${yearSelected}-01-01`).endOf('year');
  } else {
    endDate = moment().endOf('year');
  }

  if (moment(`${yearSelected}-01-01`).isSame(userJoinDate, 'year')) {
    return filterMonths(getAvailableMonths(userJoinDate, endDate));
  }
  return filterMonths(getAvailableMonths(moment().startOf('year'), endDate));
};

/**
 * Returns available years that can be selected, ordering them most recent first
 * @returns {array.object}  An array of objects that can passed into the select component
 * which displays the years a user can filter by
 * Heres an example of the structure of the option object
 * @example {title: May 2018, value: "2018-05-05T00:00:00+01:00"}
 */
export const loadYearsOptions = userJoinDate => {
  // Get the years a user has been a member sort by most recent and pass to select
  const availableYears = getYearsSince(userJoinDate, moment().format());
  const yearOptions = availableYears
    .sort((a, b) => b - a)
    .map(year => ({ title: year.toString(), value: year.toString() }));
  return yearOptions;
};

/**
 * Returns number of weeks in a given month
 * @param {string} month
 * @returns {number} number of weeks in a month
 * @example 5
 */
export const getNumWeeksInMonth = utc =>
  moment.duration(moment(utc).endOf('month') - moment(utc).startOf('month')).weeks() + 1;

/**
 * Returns number of week number in month
 * @param {number} week number in year (e.g 37)
 * @returns {number} number of the week relative to the month
 * @example 3
 */
export const findWeekNumberInMonth = week =>
  moment(week).week() -
  moment(week)
    .startOf('month')
    .week();

/**
 * Returns the months between two dates
 * @function
 * @param {object} startDate Moment date
 * @param {object} endDate Moment date
 * @return {array.object}  An array of objects which include year, month
 * year and month combined and utc date
 */

export const getAvailableMonths = (startDate, endDate) => {
  const availableMonthObject = date => ({
    month: parseInt(moment(date).format('M'), 10),
    year: date.format('YYYY'),
    date,
  });
  // If equal to the first of that year just return january
  const availableMonths = [];
  if (startDate === endDate) {
    availableMonths.push(availableMonthObject(startDate));
  } else {
    const formattedEndDate = moment(endDate);
    const formattedStartDate = moment(startDate);
    while (
      formattedEndDate > formattedStartDate ||
      formattedStartDate.format('M') === formattedEndDate.format('M')
    ) {
      availableMonths.push(availableMonthObject(formattedStartDate));
      formattedStartDate.add(1, 'month');
    }
  }
  return availableMonths;
};

/**
 * Returns the years that two date span over
 * @function
 * @param {object} startDate Moment date
 * @param {object} endDate Moment date
 * @return {array.number}  An array of years
 * @example Passing in start date of 2017 and end date of 2019 would return [2017, 2018, 2019]
 */

export const getYearsSince = (startDate, endDate) => {
  const formatStartYear = parseInt(moment(startDate).format('YYYY'), 10);
  const formatEndYear = parseInt(moment(endDate).format('YYYY'), 10);
  const yearDiff = formatEndYear - formatStartYear;
  const availableYears = [];
  availableYears.push(formatStartYear);
  let value = 0;
  while (value < yearDiff) {
    value += 1;
    const year = formatStartYear + value;
    availableYears.push(year);
  }
  return availableYears;
};

/**
 * When using moment to format days which get sent off as parameters to an API it expects them
 * to be in the format
 **/

export const apiDayDateFormat = YYYYMMDD;

/**
 * Returns true if a date or time is in the future
 * Will default to market config if no format is passed in
 * todo: SWO-4930 update Configuration.dateFormat to return a ISO or RFC2822 value
 * @function
 * @param {string} startDate Moment date
 * @param {string} moment format e.g 'YYYY-MM-DD'
 * @return {boolean}
 */

export const isBeforeOrAfter = (dateToCheck, format) => {
  const formatRule = format || Configuration.dateFormat;
  const now = moment().format(formatRule);
  return moment(now).isBefore(moment(dateToCheck).format(formatRule));
};

/**
 * Returns true if date is the same or before the current date
 * @function
 * @param {string} date Date you want to compare againist the current date
 * @return {boolean}
 */

export const isPastOrCurrentDate = date =>
  moment(date).isBefore(moment(), 'day') || moment(date).isSame(moment(), 'day');

/**
 * Returns true if the date passed in is on the start or end date or inbetween it
 * @function
 * @param {string} date Date you want to check is within the date range
 * @param {object} dateRange The range in which you want to check the date is within
 * this must have a end & start date within
 * @return {boolean}
 */
export const isWithinDateRange = (dateRange, date) => {
  const { end, start } = dateRange;
  const momentDate = moment(date);
  return (
    momentDate.isSame(start, 'd') || momentDate.isSame(end, 'd') || momentDate.isBetween(start, end)
  );
};

/**
 * Takes day as a number and returns text string
 * @function
 * @example given 4, will return "Thursday"
 */

export const getWeekName = dayNumber =>
  moment()
    .weekday(dayNumber)
    .format('dddd');

/**
 * Takes a date and returns how many days have past
 * @param date
 * @returns {number}
 */
export function daysSince(days) {
  return moment.utc().diff(moment.utc(days), 'days');
}

/**
 * Format weekday as the name of the day of the week
 * e.g. 2 becomes Tuesday
 */
export const getDayName = numericWeighInDay =>
  numericWeighInDay || numericWeighInDay === 0
    ? moment()
        .weekday(numericWeighInDay)
        .format('dddd')
    : null;

/**
 * Add ordinal to the day digit
 */
const getOrdinalNum = n =>
  n + ['st', 'nd', 'rd'][(((((n < 0 ? -n : n) + 90) % 100) - 10) % 10) - 1] || `${n}th`;

/**
 * Show the Ordinal date for example: 12th Mar, 3rd Jun etc etc
 *
 * @param theDate
 * @returns {string}
 */
export const getOrdinalDate = theDate => {
  const date = new Date(theDate);
  const formatDay = getOrdinalNum(date.getDate());
  const formatMonth = date.toLocaleDateString('en-GB', {
    month: 'short',
  });

  return `${formatDay} ${formatMonth}`;
};

/**
 * Check for showing NOW or the fromDate
 *
 * @param from
 * @param until
 * @returns {boolean|boolean}
 */

export const isNowDate = (from, until) => {
  const fromDate = Date.parse(from);
  const untilDate = Date.parse(until);
  const nowDate = Date.now();

  return nowDate >= fromDate && nowDate <= untilDate;
};