JavaScript:获取两个日期之间的所有月份?

36

我有两个日期字符串如下:

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

我希望最终得到像这样的字符串数组:

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

到目前为止,这就是我得到的,但它相当丑陋:

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

有更好的方法吗?JSFiddle


1
开始日期和结束日期是否总保证是每月的午夜? - thatidiotguy
2
你的解决方案易读且易于证明正确性(或易于修复)。我怀疑基于日期对象或朱利安值的“更好”解决方案实际上可能会变成更糟糕的解决方案。它将不太易读,可能存在错误(例如对于某些日期输入,例如10月31日加一个月后是什么?),并且要证明其正确性将更加困难。 - jarmod
1
使用 moment.js 更容易,可以查看此解决方案:https://dev59.com/2l0b5IYBdhLWcg3wM-4c - Andres
关于各种受Momentjs启发的答案:Moment现在是一个处于维护模式的遗留项目 - jarmod
13个回答

37

这应该会产生所期望的输出:

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

只需使用您现有的日期格式进行调用:

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

结果似乎超出了输入变量的范围(您已经输入了 2014-06-01,但您的结果一直延伸到 2014-12-01)。 - Chris Villa
@ChrisVilla感谢您指出这一点,endMonth计算的问题现在应该已经解决了。 - Rob M.
请使用moment.js - 日期时间计算非常棘手且容易出错! - Anirudh Ramanathan

29

你还可以使用优秀的 moment.js 库:

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


看起来我们不需要克隆startDate的currentDate变量。只需执行result.push(startDate.format())即可。 - Dmitry
1
感谢您的指正,@DimaEscaroda。我已经采纳了您的建议。需要注意的一点是,startDate.add(1, 'month')会改变startDate对象。如果startDate作为参数传递给函数,您需要克隆startDate。 - McCroskey
moment.js 值得用于像这样的代码片段。 - alexandre-rousseau

8
如果加载额外的库不是问题,你可以尝试使用很棒的MomentJS库。它提供了非常干净和强大的日期操作。
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 链接


这是momentjs的最佳答案,因为只有在这里作者没有更改初始startDate的值。 - S Panfilov

7

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/


这个没有正确运行。例如,如果我通过 getMonths(new Date('2022-08-01'), new Date('2022-08-31')),会得到 [{ "year" 2022, "month": 7 }]。我认为你只需要在 push 中将 month 改为 month + 1 - Dan Atkinson
我已经修复了你的代码问题。现在代码示例应该按预期工作。但是你的jsFiddle仍然有问题。 - Dan Atkinson

4

这里有一个解决方案,仅对特定的YYYY-MM-DD格式使用字符串操作:

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


4
你正在处理“逻辑”跳跃,因此你实际上并不需要时间算术。这是一个简单的计数问题:
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);

2

这里有另一种解决方案,使用日期对象:

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
}

这个解决方案假设您提供了日期对象或ISO 8601字符串。请注意,ISO 8601日期不一定需要包含小时-分钟-秒的部分。 "2012-01-14" 是一个有效的ISO 8601日期。


1
如果有人想获取从今天到11个月前的月份列表,请尝试以下方法。
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"]

0

以上所有解决方案的时间复杂度均为O(n^2),效率不是很高。 以下是O(n)时间复杂度的解决方案:

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

可能会分享更简单的代码


这个函数总是返回12个月,如果我们测试几个月的日期差异。例如getAllMonths('2018-8-01', '2018-11-01')返回 - ["2018-8", "2018-9", "2018-10", "2018-11", "2019-12", "2019-0", "2019-1", "2019-2", "2019-3", "2019-4", "2019-5", "2019-6"] - Tushar Walzade
“...上面的解决方案…”:答案可能以不同的顺序出现。这里没有绝对的上下之分。 - trincot

0
使用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;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接