Skip to content

Fancy date ranges for Moment.js and moment-range

License

Notifications You must be signed in to change notification settings

ALiangLiang/moment-ranges

 
 

Repository files navigation

moment-ranges

moment-ranges

Fancy date ranges for Moment.js and moment-range.

npm CircleCI npm type definitions NPM

Installation

moment-ranges works in both the browser and node.js.

Node / NPM

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);

Browser

<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.

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-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

Inherit from Array

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'))

Querying

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);

Contains

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 }

Within

Find out if your moment or date-range falls within a date range:

b.within(ranges); // true
range_ac.within(ranges); // true

Overlaps

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

Intersect

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

IsRanges

Is it a Ranges?

moment.isRanges(ranges); // true
moment.isRanges(IamNotRanges); // false

Manipulation

Add

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

Clone

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

Subtract

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

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

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));
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

Difference

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.

Conversion

toDate

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)]
// ]

toString

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"

valueOf

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

Running Tests

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

Contributors

Contributors of moment-range

Many thanks to these contributors. They built the predecessor of this project.

License

moment-ranges is MIT license.

Packages

No packages published

Languages

  • JavaScript 90.0%
  • TypeScript 10.0%