ProJSLib/jsbits

View on GitHub
packages/add-months/index.ts

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * Increment or decrement the given date by `count` months.
 *
 * @param {Date} date Local date
 * @param {number} count Months to add or subtract
 * @returns {Date} New local date
 * @private
 */
const addMonthsLoc = (date: Date, count: number) => {
  // Get the day of this date, we just need to take care of days >28
  const day = date.getDate()

  // Set the month and change the day if it's above 28 to avoid overflow
  date.setMonth(date.getMonth() + count, day > 28 ? 28 : day)

  // Restore the day if necessary, preserving the month and we are done.
  if (day > 28) {
    const month = date.getMonth()
    /*
    "If there is an overflow in the day, the date is adjusted to the last
      valid day of the expected month."
    */
    date.setDate(day)
    if (date.getMonth() !== month) {
      date.setDate(0)
    }
  }

  return date
}

/**
 * Like the `addMonthsLoc` function, but using UTC methods.
 *
 * @param {Date} date UTC date
 * @param {number} count Months to add or subtract
 * @returns {Date} New UTC date
 * @private
 */
const addMonthsUTC = (date: Date, count: number) => {
  const day = date.getUTCDate()

  date.setUTCMonth(date.getUTCMonth() + count, day > 28 ? 28 : day)

  if (day > 28) {
    const month = date.getUTCMonth()

    date.setUTCDate(day)
    if (date.getUTCMonth() !== month) {
      date.setUTCDate(0)
    }
  }

  return date
}

/** Shortcut */
const _toString = Object.prototype.toString

/**
 * Convert a number or Date object to a Date, for other types, returns
 * an invalid date (i.e. NaN).
 *
 * @param {*} src Date or number
 * @return {Date} Parsed date
 * @private
 */
const toDate = (src: any) => {
  const type = _toString.call(src)

  return new Date(type === '[object Date]' || type === '[object Number]' ? +src : NaN)
}

/**
 * Returns a date occurring `count` months after `startdate` or, if `count` is
 * negative, the date occurring `count` months before `startdate`.
 *
 * - If `startdate` is not a Date or number that can be converted to a
 *    valid date, returns a new Date instance with an invalid date.
 *
 * - If `count` is evaluated as zero, returns a new Date instance with the
 *    the same value as `startdate`.
 *
 * - If there is an overflow in the day, the date is adjusted to the last
 *    valid day of the expected month.
 *
 * The third parameter is optional and indicates if the date is UTC. It is
 * necessary to differentiate UTC dates from locals and avoid errors due to the
 * [Daylight Saving Time](https://en.wikipedia.org/wiki/Daylight_saving_time)
 * (DST).
 *
 * This function does not change the original date.
 *
 * @param {Date|number} startdate A value parseable as a JavaScript Date
 * @param {number} count Number of months to add or subtract
 * @param {boolean} [asUTC=false] If `true`, handle the date as UTC
 * @returns {Date} A new, adjusted Date instance.
 * @since 1.0.0
 */
const addMonths = function _addMonths (startdate: Date | number, count: number, asUTC?: boolean) {

  // Create a new `Date` instance.
  // Wrong types return NaN dates without throwing exceptions.
  const date = toDate(startdate)

  // Corce `count` with 'ToInt32'
  count |= 0

  // Filter out zero count and invalid dates.
  if (!count || isNaN(date as any)) {
    return date
  }

  // Return the result with the UTC methods if required by the flags
  return asUTC ? addMonthsUTC(date, count) : addMonthsLoc(date, count)
}

export = addMonths