moment-range
Fancy date ranges for Moment.js.
Installation
moment-range works in both the browser and node.js.
Node / NPM
Install via npm:
npm install --save moment-range
ES6:
import Moment from 'moment';
import { extendMoment } from 'moment-range';
const moment = extendMoment(Moment);
CommonJS:
const Moment = require('moment');
const MomentRange = require('moment-range');
const moment = MomentRange.extendMoment(Moment);
Browser
<script src="moment.js"></script>
<script src="moment-range.js"></script>
window['moment-range'].extendMoment(moment);
Thanks to the fine people at cdnjs, you can link to moment-range from
the cdnjs servers.
Older browsers and IE11
This library makes use of Symbol.iterator
to provide the iteration
protocols now that there is broad support for them, if you need to support
older browsers (specifically IE11) you will need to include a polyfill. Any of
the following should work, depending on your project configuration:
Examples
Create
Create a date range:
const start = new Date(2012, 0, 15);
const end = new Date(2012, 4, 23);
const range = moment.range(start, end);
You can also create a date range with moment objects:
const start = moment('2011-04-15', 'YYYY-MM-DD');
const end = moment('2011-11-27', 'YYYY-MM-DD');
const range = moment.range(start, end);
Arrays work too:
const dates = [moment('2011-04-15', 'YYYY-MM-DD'), moment('2011-11-27', 'YYYY-MM-DD')];
const range = moment.range(dates);
You can also create a range from an ISO 8601 time interval string:
const timeInterval = '2015-01-17T09:50:04+00:00/2015-04-17T08:29:55+00:00';
const range = moment.range(timeInterval);
You can also create a range from the start until the end of a named interval:
const date = moment('2011-04-15', 'YYYY-MM-DD');
const range = date.range('month');
You can also create open-ended ranges which go to the earliest or latest possible date:
const rangeUntil = moment.range(null, '2011-05-05');
const rangeFrom = moment.range('2011-03-05');
const rangeAllTime = moment.range();
Note that any falsy value except 0 is treated as a missing date, resulting in an open-ended range.
Note: Dates and moment objects both use a timestamp of 00:00:000 if none is
provided. To ensure your range includes any timestamp for the given end date,
use .setHours(23,59,59,999)
when constructing a Date object, or
.endOf('day')
when constructing a moment object.
rangeFromInterval
You can also create a range between an interval and a specified date. This accepts positive or negative values
for count
and the date will default to now if not provided.
const interval = 'month';
const count = 4;
const date = moment('2017-07-20');
const range1 = moment.rangeFromInterval(interval, count, date);
const range2 = moment.rangeFromInterval('month', -2, date);
Note: The date can be provided as a Date, String, or Moment.
When using a negative interval, the date provided will be set as the end of the range.
parseZoneRange
DEPRECATED in 4.0.0
: Replaced by rangeFromISOString
to follow naming conventions.
rangeFromISOString
Converts an ISO 8601 time interval string into a date range while
preserving the time zones using moment.parseZone.
const interval = '2015-01-17T09:50:00+03:00/2015-04-17T08:29:55-04:00';
const range = moment.rangeFromISOString(interval);
range.toString();
Attributes
You can access the start and end moments of the range easily enough:
const start = new Date(2012, 0, 15);
const end = new Date(2012, 4, 23);
const range = moment.range(start, end);
range.start
range.end
Querying
Many of the following examples make use of these moments:
const a = moment('2016-03-10');
const b = moment('2016-03-15');
const c = moment('2016-03-29');
const d = moment('2016-04-01');
Adjacent
Check if two ranges are touching but not overlapping:
const range1 = moment.range(a, b);
const range2 = moment.range(b, c);
const range3 = moment.range(c, d);
range1.adjacent(range2)
range1.adjacent(range3)
Center
Calculate the center of a range:
const start = new Date(2011, 2, 5);
const end = new Date(2011, 3, 5);
const range = moment.range(start, end);
range.center();
Contains
Check to see if your range contains a date/moment. By default the start and end
dates are included in the search. E.g.:
const range = moment.range(a, c);
range.contains(a);
range.contains(b);
range.contains(c);
range.contains(d);
You can also control whether the start or end dates should be excluded from the
search with the excludeStart
and excludeEnd
options:
const range = moment.range(a, c);
range.contains(a);
range.contains(a, { excludeStart: true });
range.contains(c);
range.contains(c, { excludeEnd: true; });
DEPRECATED in 4.0.0
: The exclusive
options is used to indicate if the start/end of
the range should be excluded when testing for inclusion:
Note: You can obtain the same functionality by setting { excludeStart: true, excludeEnd: true }
range.contains(c);
range.contains(c, { exclusive: false });
range.contains(c, { exclusive: true });
Within
Find out if your moment falls within a date range:
const range = moment.range(a, c);
b.within(range);
Overlaps
Does it overlap another range?
const range1 = moment.range(a, c);
const range2 = moment.range(b, d);
range1.overlaps(range2);
Include adjacent ranges:
const range1 = moment.range(a, b);
const range2 = moment.range(b, c);
range1.overlaps(range2)
range1.overlaps(range2, { adjacent: false })
range1.overlaps(range2, { adjacent: true })
Intersect
What is the intersecting range?
const range1 = moment.range(a, c);
const range2 = moment.range(b, d);
range1.intersect(range2);
IsRange
Is it a Range?
moment.isRange(range);
moment.isRange(IamNotRange);
Manipulation
Add
Add/combine/merge overlapping or adjacent ranges.
const range1 = moment.range(a, c);
const range2 = moment.range(b, d);
range1.add(range2);
const range3 = moment.range(a, b);
const range4 = moment.range(c, d);
range3.add(range4);
Include adjacent ranges:
const range1 = moment.range(a, b);
const range2 = moment.range(b, c);
range1.add(range2);
range1.add(range2, { adjacent: false });
range1.add(range2, { adjacent: true });
Clone
Deep clone a range
const range1 = moment.range(a, d);
const range2 = range1.clone();
range2.start.add(2, 'days');
range1.start.toDate().getTime() === range2.start.toDate().getTime()
SnapTo
Snap the start and end of a range to a given interval.
const start = moment('2018-01-25 17:05:33');
const end = moment('2018-01-28 06:10:00');
const range1 = moment.range(start, end);
const range2 = range1.snapTo('day');
range1.diff('days');
range2.diff('days');
Subtract
Subtracting one range from another.
const range_ab = moment.range(a, b);
const range_bc = moment.range(b, c);
const range_cd = moment.range(c, d);
const range_ad = moment.range(a, d);
range_ad.subtract(range_bc);
range_ac.subtract(range_bc);
range_ab.subtract(range_cd);
range_bc.subtract(range_bd);
Iteration
Each of the iteration methods returns an Iterable, providing
a convenient and performant interface to iterating over your ranges by a given
period.
by
Iterate over your range by a given period. Any of the units accepted by
moment.js' add
method may be used. E.g.: 'years' | 'quarters' | 'months' | 'weeks' | 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds'
const range = moment.range('2010-01-01', '2015-01-01');
for (let month of range.by('month')) {
month.format('YYYY-MM-DD');
}
const years = Array.from(range.by('year'));
years.length == 6
years.map(m => m.format('YYYY'))
Iteration also supports excluding the final time slice of the range by setting the
excludeEnd
option to true
. In the example below, the 5:00 -> 6:00 time slice is omitted.
const range = moment.range('2018-01-01 00:00', '2018-01-01 05:30');
const hours = Array.from(range.by('hour', { excludeEnd: true }));
hours.length == 5
hours.map(m => m.format('HH:mm'))
Additionally it's possible to iterate by a given step that defaults to 1
:
const start = new Date(2012, 2, 2);
const end = new Date(2012, 2, 6);
const range1 = moment.range(start, end);
let acc = Array.from(range1.by('day', { step: 2 }));
acc.map(m => m.format('DD'))
acc = Array.from(range1.by('day', { excludeEnd: true, step: 2 }));
acc.map(m => m.format('DD'))
You can iterate over the span of a range for a period that is entered but not complete by using the snapTo() method:
const start = moment("2017-01-01T13:30:00");
const end = moment("2017-01-05T01:45:12");
const r1 = moment.range(start, end);
const r2 = r1.snapTo('day');
Array.from(r1.by('days')).map(m => m.format('DD'));
Array.from(r2.by('days')).map(m => m.format('DD'));
DEPRECATED in 4.0.0
: The exclusive
options is used to indicate if the
end of the range should be excluded when testing for inclusion:
Note: You can obtain the same functionality by setting { excludeEnd: true }
byRange
const start = new Date(2012, 2, 1);
const two = new Date(2012, 2, 2);
const end = new Date(2012, 2, 5);
const range1 = moment.range(start, end);
const range2 = moment.range(start, two);
Iterate by another range:
const acc = Array.from(range1.byRange(range2));
acc.length == 5
acc.map(m => m.format('DD'))
Exclude the end time slice:
const acc = Array.from(range1.byRange(range2, { excludeEnd: true }));
acc.length == 4
acc.map(m => m.format('DD'))
By step:
let acc = Array.from(range1.byRange(range2, { step: 2 }));
acc.map(m => m.format('DD'))
acc = Array.from(range1.byRange(range2, { excludeEnd, true, step: 2 }));
acc.map(m => m.format('DD'))
DEPRECATED in 4.0.0
: The exclusive
options is used to indicate if the
end of the range should be excluded when testing for inclusion:
Note: You can obtain the same functionality by setting { excludeEnd: true }
reverseBy
Iterate over a range in reverse:
const range = moment.range('2012-01-01', '2015-01-01');
const acc = Array.from(range.reverseBy('years'));
acc.map(m => m.format('YYYY'))
Exclude the start time slice:
const range = moment.range('2012-01-01', '2015-01-01');
const acc = Array.from(range.reverseBy('years', { excludeStart: true }));
acc.map(m => m.format('YYYY'))
By step:
const start = new Date(2012, 2, 2);
const end = new Date(2012, 2, 6);
const range1 = moment.range(start, end);
let acc = Array.from(range1.reverseBy('day', { step: 2 }));
acc.map(m => m.format('DD'))
acc = Array.from(range1.reverseBy('day', { excludeStart: true, step: 2 }));
acc.map(m => m.format('DD'))
DEPRECATED in 4.0.0
: The exclusive
options is used to indicate if the
start of the range should be excluded when testing for inclusion:
Note: You can obtain the same functionality by setting { excludeStart: true }
reverseByRange
const start = new Date(2012, 2, 1);
const two = new Date(2012, 2, 2);
const end = new Date(2012, 2, 5);
const range1 = moment.range(start, end);
const range2 = moment.range(start, two);
Iterate by another range in reverse:
const acc = Array.from(range1.reverseByRange(range2));
acc.length == 5
acc.map(m => m.format('DD'))
Exclude the start value:
const acc = Array.from(range1.reverseByRange(range2, { excludeStart: true }));
acc.length == 4
acc.map(m => m.format('DD'))
By step:
let acc = Array.from(range1.reverseByRange(range2, { step: 2 }));
acc.map(m => m.format('DD'))
acc = Array.from(range1.reverseByRange(range2, { excludeStart: true, step: 2 }));
acc.map(m => m.format('DD'))
DEPRECATED in 4.0.0
: The exclusive
options is used to indicate if the
start of the range should be excluded when testing for inclusion:
Note: You can obtain the same functionality by setting { excludeStart: true }
Compare
Compare range lengths or add them together with simple math:
const range1 = moment.range(new Date(2011, 2, 5), new Date(2011, 3, 15));
const range2 = moment.range(new Date(1995, 0, 1), new Date(1995, 12, 25));
range2 > range1
range1 + range2
Math.abs(range1 - range2);
Equality
Check if two ranges are the same, i.e. their starts and ends are the same:
const range1 = moment.range(new Date(2011, 2, 5), new Date(2011, 3, 15));
const range2 = moment.range(new Date(2011, 2, 5), new Date(2011, 3, 15));
const range3 = moment.range(new Date(2011, 3, 5), new Date(2011, 6, 15));
range1.isSame(range2);
range2.isSame(range3);
range1.isEqual(range2);
range2.isEqual(range3);
Difference
The difference of the entire range given various units.
Any of the units accepted by moment.js' add
method may be used.
const start = new Date(2011, 2, 5);
const end = new Date(2011, 5, 5);
const range = moment.range(start, end);
range.diff('months');
range.diff('days');
range.diff();
Optionally you may specify if the difference should not be truncated. By default it
mimics moment-js' behaviour and truncates the values:
const d1 = new Date(Date.UTC(2011, 4, 1));
const d2 = new Date(Date.UTC(2011, 4, 5, 12));
const range = moment.range(d1, d2);
range.diff('days')
range.diff('days', false)
range.diff('days', true)
#duration
is an alias for #diff
and they may be used interchangeably.
Conversion
toDate
Converts the DateRange
to an Array
of the start and end Date
objects.
const start = new Date(2011, 2, 5);
const end = new Date(2011, 5, 5);
const range = moment.range(start, end);
range.toDate();
toString
Converting a DateRange
to a String
will format it as an ISO 8601 time
interval:
const start = '2015-01-17T09:50:04+00:00';
const end = '2015-04-17T08:29:55+00:00';
const range = moment.range(moment.utc(start), moment.utc(end));
range.toString()
valueOf
The difference between the end date and start date in milliseconds.
const start = new Date(2011, 2, 5);
const end = new Date(2011, 5, 5);
const range = moment.range(start, end);
range.valueOf();
Running Tests
Clone this bad boy:
git clone https://git@github.com/rotaready/moment-range.git
Install the dependencies:
yarn install
Do all the things!
yarn run check
yarn run test
yarn run lint
Contributors
License
moment-range is UNLICENSED.