Skip to content

[Suggestion] rangeFromInterval call using an Object instead of a String interval #209

@dolma

Description

@dolma

It would be nice if we could create a range using a Duration or Object argument in addition to (or instead of) the (String, Number) format.

'Problem'

For now we can only use a String interval type with number to create a range from an interval. For example:

moment.rangeFromInterval('year', 3, date);

This works great for very simple intervals, but what if we want to create a range using a more specific interval? e.g: 3 years, 4 months, and 5 days

Suggested change

The add() function from moment already allows this kind of flexibility (function signature of add):

moment().add(Number, String);
moment().add(Duration);
moment().add(Object);

Using the same kind of format we could do something like:

moment.rangeFromInterval({'year': 3, 'month': 4, 'day': 5}, date);
or moment.rangeFromInterval(duration, date);

As of now a simple workaround is to create the second date with the add() function and use it to create a regular range as so:

moment.range(date, date.clone().add({'year': 3, 'month': 4, 'day': 5}));
or moment.range(date, date.clone().add(duration));

but it would be nice to do it directly from the rangeFromInterval() constructor instead. Not a world changing feature but would look nicer in constructor calls than adding the extra add() and clone() calls.

Execution

Knowing that rangeFromInterval() uses the add() function internally anyway, it would be quite straightforward to add the same argument flexibility. The only issue would be caused by the current argument order (interval, count, date) which would mean one of two things would have to be changed:

  • Option 1: The interval, count arguments would have to always be replaced by the equivalent Object call to always have only one argument in before date
  • Option 2: Change the argument order to place date first, which would allow a variable number of trailing arguments

Both options would require refactoring, but the first one would only require adding some curly brackets and colon on existing arguments, whereas the second option would require reordering call arguments AND explicitly adding moment() when the default value of date is needed.

Examples of changes:

  • Option 1:
    • rangeFromInterval('year', 3, date) => rangeFromInterval({'year': 3}, date)
    • rangeFromInterval('month', 2) => rangeFromInterval({'month': 2})
  • Option 2:
    • rangeFromInterval('year', 3, date) => rangeFromInterval(date, 'year', 3)
    • rangeFromInterval('month', 2) => rangeFromInterval(moment(), 'month', 2)

There's also always a 3rd option which would be to create a separate function for this purpose, but the code change would be so minimal that it would barely make sense to separate it.

I personally prefer Option 1, which would maintain the same structure and readability for the user, while also requiring only a tiny implementation change of the rangeFromInterval() function.

Code Proposal

For Option 1:

/**
 * Build a date range between a date (or now) and a specified interval.
 * @param {Object|Duration} interval The interval to use
 * @param {Moment|Date} [date=moment()] The date to use
 * @return {DateRange}
 */
moment.rangeFromInterval = function(interval, date = moment()) {
  if (!moment.isMoment(date)) date = moment(date);
  if (!date.isValid()) throw new Error('Invalid date.');

  const dateWithInterval = date.clone().add(interval);

  // Handle negative interval counts by assembling the dates in chronological order.
  const dates = [];
  dates.push(moment.min(date, dateWithInterval));
  dates.push(moment.max(date, dateWithInterval));

  return new DateRange(dates);
};

For Option 2:

/**
 * Build a date range between a date and a specified interval.
 * @param {Moment|Date} date The date to use
 * @param {String|Object|Duration} interval The type of interval or interval object to use
 * @param {Number} [count=1] The number of intervals (positive or negative)
 * @return {DateRange}
 */
moment.rangeFromInterval = function(date, interval, count = 1) {
  if (!moment.isMoment(date)) date = moment(date);
  if (!date.isValid()) throw new Error('Invalid date.');

  if (typeof interval === 'object') {
      const dateWithInterval = date.clone().add(interval);
  } else {
      const dateWithInterval = date.clone().add(count, interval);
  }

  // Handle negative interval counts by assembling the dates in chronological order.
  const dates = [];
  dates.push(moment.min(date, dateWithInterval));
  dates.push(moment.max(date, dateWithInterval));

  return new DateRange(dates);
};

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions