Richard
Richard

Reputation: 65540

JavaScript: get all months between two dates?

I have two date strings like this:

var startDate = '2012-04-01';
var endDate = '2014-11-01';

And I want to end up with an array of strings like this:

var dates = ['2012-04-01', '2012-05-01', '2012-06-01' .... '2014-11-01',];

So far this is what I've got, but it's pretty ugly:

var startDate = '2012-04-01';
var endDate = '2014-11-01';
var start = new Date(Date.parse(startDate));
var end = new Date(Date.parse(endDate))
var dates = [];
for (var i = start.getFullYear(); i < end.getFullYear() + 1; i++) {
    dates.push(i + '-' + '-01');
}
console.log(dates);

Is there a better way? JSFiddle.

Upvotes: 37

Views: 44846

Answers (13)

Rob M.
Rob M.

Reputation: 36511

update (2024)

Revisiting this answer a decade later: this use case is easily solved by date-fns#eachMonthOfInterval. For those interested in rolling their own, I would revise my original code to not rely on string parsing and instead lean on native Date operations:

/**
 * dateRange returns an array of year-month dates between the supplied start and end dates
 * @param startDate Date or natively parseable string (e.g. 2024-04-01)
 * @param endDate Date or natively parseable string (e.g. 2024-04-01)
 * @returns string[] of ISO 8601 strings for each month between start and end
 */
function dateRange(startDate, endDate) {
  // we use UTC methods so that timezone isn't considered
  let start = new Date(startDate);
  const end = new Date(endDate).setUTCHours(12);
  const dates = [];
  while (start <= end) {
    // compensate for zero-based months in display
    const displayMonth = start.getUTCMonth() + 1;
    dates.push([
      start.getUTCFullYear(),
      // months are zero based, ensure leading zero
      (displayMonth).toString().padStart(2, '0'),
      // always display the first of the month
      '01',
    ].join('-'));

    // progress the start date by one month
    start = new Date(start.setUTCMonth(displayMonth));
  }

  return dates;
}

original answer (2014)

This should produce the desired output:

function dateRange(startDate, endDate) {
  var start      = startDate.split('-');
  var end        = endDate.split('-');
  var startYear  = parseInt(start[0]);
  var endYear    = parseInt(end[0]);
  var dates      = [];

  for(var i = startYear; i <= endYear; i++) {
    var endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
    var startMon = i === startYear ? parseInt(start[1])-1 : 0;
    for(var j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j+1) {
      var month = j+1;
      var displayMonth = month < 10 ? '0'+month : month;
      dates.push([i, displayMonth, '01'].join('-'));
    }
  }
  return dates;
}

Just call it with your existing date format:

dateRange('2013-11-01', '2014-06-01')
// ["2013-11-01", "2013-12-01", "2014-01-01", "2014-02-01", "2014-03-01", "2014-04-01", "2014-05-01", "2014-06-01", "2014-07-01", "2014-08-01", "2014-09-01", "2014-10-01", "2014-11-01", "2014-12-01"]

Upvotes: 42

Zia
Zia

Reputation: 683

If anyone want to get list of months between today and last 11 months ago, try this.

const endDate = new Date(); // current date
const startDate = new Date(endDate.getFullYear(), endDate.getMonth() - 11, 1); // 11 months ago
const months = [];

let currentDate = new Date(startDate);

while (currentDate <= endDate) {
  months.push(currentDate.toLocaleString('default', { month: 'long' }));
  currentDate.setMonth(currentDate.getMonth() + 1);
}

console.log(months); // ["May", "June", "July", ..., "March", "April"]

Upvotes: 1

Matthieu Chavigny
Matthieu Chavigny

Reputation: 218

const getMonths = (fromDate, toDate) => {
    const fromYear = fromDate.getFullYear();
    const fromMonth = fromDate.getMonth();
    const toYear = toDate.getFullYear();
    const toMonth = toDate.getMonth();
    const months = [];

    for(let year = fromYear; year <= toYear; year++) {
        let monthNum = year === fromYear ? fromMonth : 0;
        const monthLimit = year === toYear ? toMonth : 11;

        for(; monthNum <= monthLimit; monthNum++) {
            let month = monthNum + 1;
            months.push({ year, month });
        }
    }
    return months;
}

const sample = getMonths(new Date('2022-07-28'), new Date('2023-03-20'));
console.log(sample);
document.write('check the console output');

https://jsfiddle.net/xfayoqvs/

Upvotes: 7

trincot
trincot

Reputation: 350310

Here is a solution which just uses string manipulation on that specific YYYY-MM-DD format:

function monthsBetween(...args) {
    let [a, b] = args.map(arg => arg.split("-").slice(0, 2)
                                    .reduce((y, m) => m - 1 + y * 12));
    return Array.from({length: b - a + 1}, _ => a++)
        .map(m => ~~(m / 12) + "-" + ("0" + (m % 12 + 1)).slice(-2) + "-01");
}

console.log(monthsBetween('2012-04-01', '2014-11-01'));

Upvotes: 4

vcarel
vcarel

Reputation: 1804

Here is another solution, using Date objects:

const enumerateMonths = (from, to) => {
  const current = new Date(from)
  current.setUTCDate(1)
  current.setUTCHours(0, 0, 0, 0)
  const toDate = new Date(to)
  const months = []
  while (current.getTime() <= toDate.getTime()) {
    months.push(current.getUTCFullYear() + "-" + `${current.getUTCMonth() + 1}`.padStart(2, "0"))
    current.setUTCMonth(current.getUTCMonth() + 1)
  }
  return months
}

This solution presumes you provide Date objects or ISO 8601 strings. Please mind that an ISO 8601 date does not necessarily have to contain the hours-minutes-seconds part. "2012-01-14" is a valid ISO 8601 date.

Upvotes: 2

Ben Stickley
Ben Stickley

Reputation: 2120

Here is another option:

getRangeOfMonths(startDate: Date, endDate: Date) {
    const dates = new Array<string>();
    const dateCounter = new Date(startDate);
    // avoids edge case where last month is skipped
    dateCounter.setDate(1);
    while (dateCounter < endDate) {
      dates.push(`${dateCounter.getFullYear()}-${dateCounter.getMonth() + 1}`);
      dateCounter.setMonth(dateCounter.getMonth() + 1);
    }
    return dates;
  }

Upvotes: -2

helle
helle

Reputation: 11650

This is my solution, with help of math and O(n)

determineMonthInInterval(startDate, endDate) {

    let startYear = startDate.getFullYear();
    let endYear = endDate.getFullYear();

    let startMonth = startDate.getMonth() + 1;
    let endMonth = endDate.getMonth() + 1;

    let monthAmount = (endMonth - startMonth) + 1 + (12 * (endYear - startYear));

    let dates = [];
    let currMonth = startMonth;
    let currYear = startYear;
    for( let i=0; i<monthAmount; i++){
        
        let date = new Date(currYear + "/"+currMonth+"/1");
        dates.push(date);

        currYear = startYear + Math.floor((startMonth+i) / 12);
        currMonth = (currMonth) % 12 +1;

    }

    return dates;
}

Upvotes: -1

McCroskey
McCroskey

Reputation: 1161

You can also use the excellent moment.js library:

var startDate = moment('2012-04-01');
var endDate = moment('2014-11-01');

var result = [];

if (endDate.isBefore(startDate)) {
    throw "End date must be greated than start date."
}      

while (startDate.isBefore(endDate)) {
    result.push(startDate.format("YYYY-MM-01"));
    startDate.add(1, 'month');
}

JSFiddle

Upvotes: 30

golf4sp
golf4sp

Reputation: 129

All solutions above run in O(n^2) time complexity, which is not very efficient. See below solution in O(n) time complexity:

function getAllMonths(start, end){	
	let startDate = new Date(start);
	let startYear = startDate.getFullYear();
	let startMonth = startDate.getMonth()+1;
	
	let endDate = new Date(end);
	let endYear = endDate.getFullYear();
	let endMonth = endDate.getMonth()+1;
	
	let countMonth = 0;
	let countYear = 0;
	let finalResult = [];

	for(let a=startYear; a<=endYear; a++){

		if(startYear<endYear){
			if(countYear==0){
				countMonth += 12-startMonth;
            }else 
			if(countYear>0){
				countMonth += 12;
            }
			countYear+=1;
			startYear++;
        }else 
		if(startYear==endYear){
			countMonth+=endMonth;
        }
    }
	for(let i=startMonth; i<=countMonth+startMonth; i++){
		finalResult.push(startDate.getFullYear()+(Math.floor(i/12)) + "-" + Math.round(i%13) + "-" + "01");
    }
	return finalResult;
}

getAllMonths('2016-04-01', '2018-01-01');

Might share a much more simpler code

Upvotes: 0

Rami
Rami

Reputation: 11

An example to get all first days of months between a given date and now using moment.js.

   var getMonths = function (startDate) {
    var dates = [];
    for (var year = startDate.year(); year <= moment().year(); year++) {
        var endMonth = year != moment().year() ? 11 : moment().month();
        var startMonth = year === startDate.year() ? startDate.month() : 0;
        for (var currentMonth = startMonth; currentMonth <= endMonth; currentMonth = currentMonth > 12 ? currentMonth % 12 || 11 : currentMonth + 1) {
            var month = currentMonth + 1;
            var displayMonth = month < 10 ? '0' + month : month;
            dates.push([year, displayMonth, '01'].join('-'));
        }
    }
    return dates;
};

Upvotes: 0

Michiel
Michiel

Reputation: 709

If loading an extra library isn't a problem, you could always try the awesome MomentJS.
Gives for very clean and powerful date manipulation.

var startDate = moment('2012-04-01');
var endDate = moment('2014-11-01');

var dates = [];
endDate.subtract(1, "month"); //Substract one month to exclude endDate itself

var month = moment(startDate); //clone the startDate
while( month < endDate ) {
    month.add(1, "month");
    dates.push(month.format('YYYY-MM-DD'));
}

console.log(dates);

JSFiddle here

Upvotes: 8

bitifet
bitifet

Reputation: 3669

You are handling "logical" jumps, so you doesn't actually need timing arthmetics. So this is a simple counting problem:

var startDate = '2012-04-01';
var endDate = '2014-11-01';
var dates = [];

var d0 = startDate.split('-');
var d1 = endDate.split('-');

for (
    var y = d0[0];
    y <= d1[0];
    y++
) {
    for (
        var m = d0[1];
        m <= 12;
        m++
    ) {
        dates.push(y+"-"+m+"-1");
        if (y >= d1[0] && m >= d1[1]) break;
    };
    d0[1] = 1;
};

console.log(dates);

Upvotes: 4

Robert Cunningham
Robert Cunningham

Reputation: 19

Still not a very elegant answer, but arrives at the array of strings you want:

var startDate = '2012-04-01';
var endDate = '2014-11-01';
var start = new Date(startDate);
var end = new Date(endDate);
var dates = [];
for (var i = start.getFullYear(); i < end.getFullYear() + 1; i++) {
    for (var j = 1; j <= 12; j++) {
      if (i === end.getFullYear() && j === end.getMonth() + 3) {
        break;
      }
      else if (i === 2012 && j < 4){
        continue;
      }
      else if (j < 10) {
        var dateString = [i, '-', '0' + j, '-','01'].join('');
        dates.push(dateString)
        }
      else {
        var dateString = [i, '-', j, '-','01'].join('');
        dates.push(dateString);
        }
    }
}
console.log(dates);

jsfiddle link here: http://jsfiddle.net/8kut035a/

Upvotes: -1

Related Questions