How to enumerate dates between two dates in Moment
Asked Answered
S

10

70

I have two moment dates:

var fromDate = moment(new Date('1/1/2014'));
var toDate   = moment(new Date('6/1/2014'));

Does moment provide a way to enumerate all of the dates between these two dates?

If not, is there any better solution other than to make a loop which increments the fromDate by 1 until it reaches the toDate?

Edit: Adding date enumeration method and problem

I've mocked up a method for enumerating the days between two dates, but I'm running into an issue.

  var enumerateDaysBetweenDates = function(startDate, endDate) {
    var dates = [];

    startDate = startDate.add(1, 'days');

    while(startDate.format('M/D/YYYY') !== endDate.format('M/D/YYYY')) {
      console.log(startDate.toDate());
      dates.push(startDate.toDate());
      startDate = startDate.add(1, 'days');
    }

    return dates;
  };

Take a look at the output when I run enumerateDaysBetweenDates( moment(new Date('1/1/2014')), moment(new Date('1/5/2014'));

Thu Jan 02 2014 00:00:00 GMT-0800 (PST)
Fri Jan 03 2014 00:00:00 GMT-0800 (PST)
Sat Jan 04 2014 00:00:00 GMT-0800 (PST)
[ Sun Jan 05 2014 00:00:00 GMT-0800 (PST),
  Sun Jan 05 2014 00:00:00 GMT-0800 (PST),
  Sun Jan 05 2014 00:00:00 GMT-0800 (PST) ]

It's console.logging the right dates, but only the final date is being added to the array. How/why is this? This smells like some sort of variable reference issue - but I'm not seeing it.

Sherellsherer answered 21/5, 2014 at 23:30 Comment(0)
A
111

.add() is a mutator method, so the assignment in this line is unnecessary:

startDate = startDate.add(1, 'days');

You can just do this, and have the same effect:

startDate.add(1, 'days');

While it's name would imply the creation of a new Date object, the toDate() method really just returns the existing internal Date object.

So, none of your method calls are creating new Date or moment object instances. Fix that by using .clone() to get a new instance:

startDate = startDate.clone().add(1, 'days');

Or better yet, wrap the values in a call to moment() as Mtz suggests in a comment, and it will clone the instance, if the value is a moment object, or it will parse the input to create a new moment instance.

startDate = moment(startDate).add(1, 'days');

I think a date enumerator method should not change either of the arguments passed in. I'd create a separate variable for enumerating. I'd also compare the dates directly, rather than comparing strings:

var enumerateDaysBetweenDates = function(startDate, endDate) {
    var dates = [];

    var currDate = moment(startDate).startOf('day');
    var lastDate = moment(endDate).startOf('day');

    while(currDate.add(1, 'days').diff(lastDate) < 0) {
        console.log(currDate.toDate());
        dates.push(currDate.clone().toDate());
    }

    return dates;
};
Aeniah answered 22/5, 2014 at 0:43 Comment(9)
Didn't know about the clone method. Sweet! Thanks.Sherellsherer
Note that moment().add(period, number) is deprecated. Please use moment().add(number, period).Transpontine
Interesting about the argument order switch. I guess they did that to make a chain of date manipulation functions read more like an English sentence, but if you wanted to use Moment together with a currying/partial application library you'd want .add(period, number), wouldn't you?Verniavernice
Instead of classic while I used "do{ } while (condition)" to don't skip the first dayOmegaomelet
I would do var currDate = moment(startDate).startOf('day') and same for lastDate because then the input doesn't need to be a moment instance but can be anything moment can handle. It automatically creates a clone if it's already a moment instance so it's equivalent.Marr
Thank you @Omegaomelet your solution fixed the first day issue!Chinookan
@Chinookan great! Note that do{} while(condition) in JS is the equivalent do until(not condition) of other languagesOmegaomelet
The moment solution is ingenious! I made a solution for enumerating days, weeks, months or years.Eldest
If you want to include the currDate and lastDate you could do dates.push(currDate.toDate()) before your while loop and then do dates.push(lastDate.toDate()) after the loop.Portaltoportal
S
56

Got it for you:

var enumerateDaysBetweenDates = function(startDate, endDate) {
    var now = startDate.clone(), dates = [];

    while (now.isSameOrBefore(endDate)) {
        dates.push(now.format('M/D/YYYY'));
        now.add(1, 'days');
    }
    return dates;
};

Referencing now rather than startDate made all the difference.

If you're not after an inclusive search then change .isSameOrBefore to .isBefore

Fiddle: http://jsfiddle.net/KyleMuir/sRE76/118/

Squeegee answered 22/5, 2014 at 0:41 Comment(7)
had to swap now.format('M/D/YYYY') <= endDate.format('M/D/YYYY') for now.isBefore(endDate) to get this working in fiddleGhyll
@Ghyll Thanks, I've updated my answer to include better methods. I also added || now.isSame(endDate) as the original answer was endDate inclusive.Squeegee
Super. Also used spread on Set to use this on multiple date ranges and get a set of results without duplicates when date ranges overlap: https://mcmap.net/q/98851/-simplest-way-to-merge-es6-maps-setsHutt
You can use now.isSameOrBefore(endDate) instead of now.isBefore(endDate) || now.isSame(endDate) See moment docs hereSwen
@Swen thanks for that. I've updated this answer to reflect the new methods.Squeegee
Amazing! this was better then the other answer that was skipping the first day.Chinookan
Or use now.clone() instead of now.format('M/D/YYYY') to get the actual Moment instances.Stilly
Z
16

use moment and work with while loop, code will run in loop untill startDate is equal to endDate and push startDate and then increment it with 1 day so can get next date

function enumerateDaysBetweenDates (startDate, endDate){
  let date = []
  while(moment(startDate) <= moment(endDate)){
    date.push(startDate);
    startDate = moment(startDate).add(1, 'days').format("YYYY-MM-DD");
  }
  return date;
}

you can test it by calling function like this

let dateArr = enumerateDaysBetweenDates('2019-01-01', '2019-01-10');
Zarla answered 24/1, 2020 at 17:32 Comment(3)
If startDate == endDate and i i need the array as ['2020-06-21','2020-06-21'], how to rewrite this code?Kryska
I think you have to clone the date before operating on them or it will mutate them all around the code. let clonedStartDate = startDate.clone(); and const clonedEndDate = endDate.clone();Heiser
if someone is facing a problem where the first date in the array isn't the same as the other dates just change date.push(startDate); to date.push(moment(startDate).format("YYYY-MM-DD"));Barrier
C
9

Using moment library and for loop you can enumerate between two dates.

let startDate = moment('2020-06-21');
let endDate = moment('2020-07-15');
let date = [];

for (var m = moment(startDate); m.isBefore(endDate); m.add(1, 'days')) {
    date.push(m.format('YYYY-MM-DD'));
}

console.log(date)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
Coffle answered 13/10, 2020 at 7:23 Comment(4)
If startDate == endDate and i i need the array as ['2020-06-21','2020-06-21'], how to rewrite this code ? @CoffleKryska
just change the line to: for (var m = moment(startDate); m.isSameOrBefore(endDate); m.add(1, 'days'))Leidaleiden
Just note that the isSameOrBefore and isSameOrAfter methods were introduced since version 2.11.0 of momentPerfuse
thank god. you saved my life. thanks tyagi bhaiFrodine
O
5

Momentjs doesn't provide this by itself but there is a plugin which offers it: moment-range.

Specifically, check out the Iteration docs.

Orbital answered 21/5, 2014 at 23:33 Comment(1)
now they do provide check this answer https://mcmap.net/q/277613/-how-to-enumerate-dates-between-two-dates-in-momentFrodine
O
3

As an extension of Kyle's answer - I've been trying to get this to work with Unix timestamps and after lots of trial and error I got it to work and thought I'd post it here in case anyone is seeking the same thing and needs it. See my code below:

fromDate = moment.unix(req.params.dateFrom).format('YYYY-MM-DD')
toDate = moment.unix(req.params.dateTo).format('YYYY-MM-DD')

// Returns an array of dates between the two dates
function enumerateDaysBetweenDates(startDate, endDate) {
    startDate = moment(startDate);
    endDate = moment(endDate);

    var now = startDate, dates = [];

    while (now.isBefore(endDate) || now.isSame(endDate)) {
        dates.push(now.format('YYYY-MM-DD'));
        now.add(1, 'days');
    }
    return dates;
};

Note that I convert it to Unix, then convert that value to moment again. This was the issue that I had, you need to make it a moment value again in order for this to work.

Example usage:

fromDate = '2017/03/11' // AFTER conversion from Unix
toDate = '2017/03/13' // AFTER conversion from Unix

console.log(enumerateDaysBetweenDates(fromDate, toDate));

Will return:

['2017/03/11', '2017/03/12', '2017/03/13']
Odine answered 14/3, 2017 at 15:45 Comment(0)
G
3

Using ES6 notation

const from = moment('01/01/2014', 'DD/MM/YYYY')
const to   = moment('06/01/2014', 'DD/MM/YYYY')

const nbDays = to.diff(from, 'days') + 1
const result = [...Array(nbDays).keys()]
          .map(i => from.clone().add(i, 'd'))

console.log(result)
<script src="https://momentjs.com/downloads/moment.min.js"></script>
Gutierrez answered 3/11, 2021 at 12:10 Comment(0)
E
2

You can easily enumerate with moment.js Here is a more generic solution for days, weeks, months or years:

https://gist.github.com/gvko/76f0d7b4b61b18fabfe9c0cc24fc3d2a

Eldest answered 22/5, 2018 at 9:41 Comment(1)
@AdarshMadrecha Page not found.Selfassured
O
1

Using moment library and for loop you can enumerate between two dates.

const moment = require('moment');

 let startDate = moment('2021-12-24');
 let endDate = moment('2022-1-4');
 let date = [];

 for (var m = moment(startDate); m.isSameOrBefore(endDate); m.add(1, 'days')) {
      date.push(m.format('DD/MM/YYYY'));
  }

 console.log(date)
Oregano answered 23/12, 2021 at 16:44 Comment(0)
B
0

A short solution using recursion

import type { Moment } from 'moment';

export const getDatesBetween = (start: Moment, end: Moment): Moment[] => {
  if (!start.isValid() || !end.isValid() || start.isAfter(end, 'day')) return [];

  if (start.isSame(end, 'day')) return [start];

  return [start].concat(getDatesBetween(start.clone().add(1, 'day'), end));
};
Bringhurst answered 22/2 at 8:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.