Fancy date ranges for Moment.js and moment-range.
moment-ranges works in both the browser and node.js.
Install via npm:
// "moment-ranges" has two peer dependencies "moment" and "moment-range".
npm install --save moment moment-range moment-ranges
ES6:
import moment from 'moment';
import { extendMoment as rangeExtendMoment } from 'moment-range';
import { extendMoment } from 'moment-ranges';
rangeExtendMoment(moment);
extendMoment(moment);
CommonJS:
const moment = require('moment');
const MomentRange = require('moment-range');
const MomentRanges = require('moment-ranges');
MomentRange.extendMoment(moment);
MomentRanges.extendMoment(moment);
<script src="moment.js"></script>
<script src="moment-range.js"></script>
<script src="moment-ranges.js"></script>
window['moment-range'].extendMoment(moment);
window['moment-ranges'].extendMoment(moment);
Thanks to the fine people at cdnjs, you can link to moment-ranges from
the cdnjs servers.
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:
Create a date-ranges with date-range(s), and also sort and merge overlapping ranges:
const moment_a = new Date(2009, 1, 8);
const moment_c = new Date(2012, 3, 7);
const range_ac = moment.range(moment_a, moment_c);
const moment_b = new Date(2012, 0, 15);
const moment_d = new Date(2012, 4, 23);
const range_bd = moment.range(moment_b, moment_d);
const moment_e = new Date(2019, 10, 26);
const moment_f = new Date(2019, 11, 3);
const range_ef = moment.range(moment_e, moment_f);
const ranges = moment.ranges(range_ac, range_bd, range_ef);
// => DateRanges [moment.range(moment_a, moment_d), moment.range(moment_e, moment_f)]
// Arrays work too:
const ranges = moment.ranges([range_ac, range_bd, range_ef]);
Because range_ac
overlaps with range_bd
, they merges into range_ad
.
range_ac [---------------------]
range_bd [---------------------]
range_ef [---------]
ranges_ad_ef [--------------------------------] [---------]
...---------|----------|----------|----------|---|---------|---...
moment a b c d e f
Date-Ranges is inherit from Array. So you can use any Array methods and attributes of Array:
const ranges = moment.range(range1, range2, range3);
// Print out all ranges
ranges.forEach((range) => console.log(range))
// Filter out ranges duration more than 1 day
ranges.filter((range) => range > moment.duration(1, 'days'))
Many of the following examples make use of these moments:
const moment_a = moment('2016-03-10');
const moment_b = moment('2016-03-15');
const moment_c = moment('2016-03-29');
const moment_d = moment('2016-04-01');
const range_ab = moment.range(moment_a, moment_b);
const range_bc = moment.range(moment_b, moment_c);
const range_cd = moment.range(moment_c, moment_d);
const range_ad = moment.range(moment_a, moment_d);
Check to see if your date-ranges contains a date/moment/range. By default the start and end dates are included in the search. E.g.:
// Ranges-Range
ranges_ab_cd.contains(range_ab); // true
ranges_ab_cd.contains(range_cd); // true
ranges_ab_cd.contains(range_bc); // false
ranges_ab_cd.contains(range_ad); // false
// Ranges-Moment
ranges_ac.contains(moment_b); // true
// Ranges-Ranges
ranges_ac.contains(ranges_ab); // true
ranges_ab_cd [----------] [----------] contains
range_ab [----------] => true
range_cd [----------] => true
range_bc [----------] => false
range_ad [--------------------------------] => false
ranges_ad [--------------------------------] contains
moment_b | => true
ranges_ab_cd [----------] [----------] => true
...---------|----------|----------|----------|---------...
moment a b c d
You can also control whether the start or end dates should be excluded from the
search with the excludeStart
and excludeEnd
options:
// Ranges-Moment
ranges_ab_cd.contains(moment_c); // true
ranges_ab_cd.contains(moment_c, { excludeStart: true }); // false
ranges_ab_cd.contains(moment_b); // true
ranges_ab_cd.contains(moment_b, { excludeEnd: true; }); // false
// Ranges-Range
ranges_ab_cd.contains(range_cd, { excludeEnd: true; }); // false
// Ranges-Ranges
ranges_ab_cd.contains(ranges_cd, { excludeEnd: true; }); // false
ranges_ab_cd [----------] [----------] contains
moment_c | => true
moment_b | => true
ranges_ab_cd (----------] (----------] contains (exclude start)
moment_c | => false
ranges_ab_cd [----------) [----------) contains (exclude end)
moment_b | => false
range_cd [----------] => false
ranges_cd [----------] => false
...---------|----------|----------|----------|---------...
moment a b c d
Note: You can obtain the same functionality by setting { excludeStart: true, excludeEnd: true }
Find out if your moment or date-range falls within a date range:
b.within(ranges); // true
range_ac.within(ranges); // true
Does it overlap another range?
// Ranges-Ranges
ranges_ac.overlaps(ranges_bd); // true
// Ranges-Range
ranges_ac.overlaps(range_bd); // true
ranges_ac (---------------------) overlaps
ranges_bd (---------------------) => true
range_bd (---------------------) => true
...---------|----------|----------|----------|---------...
moment a b c d
Include adjacent ranges:
ranges_ac.overlaps(range_bc) // true
ranges_ac.overlaps(range_cd, { adjacent: false }) // false
ranges_ac.overlaps(range_cd, { adjacent: true }) // true
ranges_ac (---------------------) overlaps
range_bc (----------) => true
range_cd (----------) => false
ranges_ac [---------------------] overlaps (adjacent)
range_cd [----------] => true
...---------|----------|----------|----------|---------...
moment a b c d
What is the intersecting range?
// Ranges-Range
ranges_ac.intersect(range_bd); // DateRanges [moment.range(b, c)]
// Ranges-Ranges
ranges_ac.intersect(ranges_bd); // DateRanges [moment.range(b, c)]
ranges_ac [---------------------] intersect
range_bd(ranges_bd) [---------------------]
↓↓
ranges_bc [----------]
...---------|----------|----------|----------|---------...
moment a b c d
Is it a Ranges?
moment.isRanges(ranges); // true
moment.isRanges(IamNotRanges); // false
Add/combine/merge overlapping or adjacent ranges.
// Ranges-Range
ranges_ac.add(range_bd); // DateRanges [moment.range(moment_a, moment_d)]
// Merge into one date-range in date-ranges
// Ranges-Ranges
ranges_cd.add(ranges_ab); // DateRanges [moment.range(moment_a, moment_b), moment.range(moment_c moment_d)]
// No merge but sorted
ranges_ac [---------------------] add
range_bd [---------------------]
↓↓
ranges_ad [--------------------------------]
ranges_cd [----------] add
ranges_ab [----------]
↓↓
ranges_ab_cd [----------] [----------]
...---------|----------|----------|----------|---------...
moment a b c d
Deep clone a ranges
const cloned_ranges_ab = ranges_ab.clone();
cloned_ranges_ab[0].start.add(2, 'days');
cloned_ranges_ab[0].start.toDate().valueOf() === cloned_ranges_ab[0].start.toDate().valueOf() // false
ranges_ab [----------] clone
↓↓
cloned_ranges_ab [----------]
...---------|----------|---------...
moment a b
Subtracting one range from another ranges or range.
ranges_ad.subtract(range_bc); // [moment.range(moment_a, moment_b) moment.range(moment_c, moment_d)]
ranges_ac.subtract(range_bc); // [moment.range(moment_a, moment_b)]
ranges_ab.subtract(range_bc); // [moment.range(moment_a, moment_b)]
ranges_ab.subtract(range_ac); // []
ranges_ad [--------------------------------] subtract
range_bc [----------]
↓↓
ranges_ab_cd [----------] [----------]
ranges_ac [---------------------] subtract
range_bc [----------]
↓↓
ranges_ab [----------]
ranges_ab [----------] subtract
range_bc [----------]
↓↓
ranges_ab [----------]
ranges_ab [----------] subtract
range_ac [---------------------]
↓↓
ranges_empty
...---------|----------|----------|----------|---------...
moment a b c d
Compare range lengths or whole ranges length. 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));
const ranges1 = moment.ranges(range1, range2)
const range3 = moment.range(new Date(2011, 3, 5), new Date(2011, 3, 15));
const range4 = moment.range(new Date(1995, 1, 1), new Date(1995, 12, 25));
const ranges2 = moment.ranges(range3, range4)
ranges2 > ranges1 // true
ranges2 > range3 // true
ranges1 + ranges2 // duration of both ranges in milliseconds
ranges1 + range3 // duration of ranges and range in milliseconds
Math.abs(ranges1 - ranges2); // difference of ranges in milliseconds
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));
const range4 = moment.range(new Date(2011, 3, 5), new Date(2011, 6, 15));
const range5 = moment.range(new Date(2011, 4, 5), new Date(2011, 9, 15));
const range6 = moment.range(new Date(2011, 4, 5), new Date(2011, 9, 15));
const ranges1 = moment.ranges(range1, range2)
const ranges2 = moment.ranges(range3, range4)
const ranges3 = moment.ranges(range5, range6)
ranges1.isSame(ranges2); // true
ranges2.isSame(ranges3); // false
ranges1.isEqual(ranges2); // true
ranges2.isEqual(ranges3); // false
The difference of the entire ranges given various units.
Any of the units accepted by moment.js' add
method may be used.
const start1 = new Date(2011, 2, 5);
const end1 = new Date(2011, 5, 5);
const range1 = moment.range(start, end);
const start2 = new Date(2014, 3, 5);
const end2 = new Date(2014, 5, 5);
const range2 = moment.range(start, end);
const ranges = moment.ranges(range1, range2);
ranges.diff('months'); // 6
ranges.diff('days'); // ?
ranges.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);
const ranges = moment.ranges(range);
ranges.diff('days') // 4
ranges.diff('days', false) // 4
ranges.diff('days', true) // 4.75
#duration
is an alias for #diff
and they may be used interchangeably.
Converts the DateRanges
to an Array
of Array
of the start and end Date
objects.
const range1 = moment.range(new Date(2011, 2, 5), new Date(2011, 5, 5));
const range2 = moment.range(new Date(2014, 3, 10), new Date(2014, 3, 20));
const ranges = moment.ranges(range1, range2);
ranges.toDate();
// [
// [new Date(2011, 2, 5), new Date(2011, 5, 5)],
// [new Date(2011, 3, 10), new Date(2011, 3, 20)]
// ]
Converting a DateRange
to a String
will format it as many ISO 8601 time
interval concatenated by comma:
const range1 = moment.range(moment.utc('2015-01-17T09:50:04+08:00'), moment.utc('2015-04-17T08:29:55+08:00'));
const range2 = moment.range(moment.utc('2015-03-10T02:70:04+08:00'), moment.utc('2015-03-20T07:56:51+08:00'));
const ranges = moment.ranges(range1, range2)
ranges.toString()
// "2015-01-17T09:50:04+08:00/2015-04-17T08:29:55+08:00,2015-03-10T02:70:04+08:00/2015-03-20T07:56:51+08:00"
The difference between the end date and start date in milliseconds.
const range1 = moment.range(new Date(2011, 2, 5), new Date(2011, 5, 5));
const range2 = moment.range(new Date(2014, 3, 10), new Date(2014, 3, 20));
const ranges = moment.ranges(range1, range2);
range1.valueOf(); // 7945200000
range2.valueOf(); // 864000000
ranges.valueOf(); // 8809200000
// 7945200000 + 864000000 = 8809200000
Clone this bad boy:
git clone https://[email protected]/rotaready/moment-ranges.git
Install the dependencies:
yarn install
Do all the things!
yarn run check
yarn run test
yarn run lint
Many thanks to these contributors. They built the predecessor of this project.
- Gianni Chiappetta (https://butt.zone) (Author of [moment-range][moment-range])
- Adam Biggs (http://lightmaker.com)
- Mats Julian Olsen
- Matt Patterson (http://reprocessed.org/)
- Wilgert Velinga (http://neocles.io)
- Tomasz Bak (http://twitter.com/tomaszbak)
- Stuart Kelly
- Jeremy Forsythe
- Александр Гренишин
- @scotthovestadt
- Thomas van Lankveld
- nebel
- Kevin Ross (http://www.alienfast.com)
- Thomas Walpole
- Jonathan Kim (http://jkimbo.co.uk)
- Tymon Tobolski (http://teamon.eu)
- Aristide Niyungeko
- Bradley Ayers
- Ross Hadden (http://rosshadden.github.com/resume)
- Victoria French
- Jochen Diekenbrock
moment-ranges is MIT license.