2017-09-01 21 views
3

这已经asked(严重)之前 - 我不认为在该职位的答案真的解决问题,然后它去陈旧。我将试图再次提出这个问题的更清楚的证明。如何可靠地实现“下一个” /“之前的”月

的Javascript Date.setMonth()的实施似乎并不遵循最小惊讶的原则。在浏览器控制台试试这个:

d = new Date('2017-08-31') // Set to last day of August 
d.getMonth() // 7 - months are zero-based 
d.setMonth(8) // Try to set the month to 8 (September) 
d.getMonth() // 9 - October. WTF Javascript? 

同理:

d = new Date('2017-10-31') 
d.getMonth() // 9 
d.setMonth(8) 
d.getMonth() // 9 (still?) 

的Firefox在Linux上出现更糟糕 - 有时getMonth()返回十月的日期,并由此不当月匹配!

我的问题(我认为从那个链接问题的OP)是如何在例如一致性实现“下一个”/“前一个月”功能。日期选择器?有没有不感到惊讶用户,例如,跳过九月,当他们开始在8月31日,然后单击“下一步”,这样做的一个众所周知的方法是什么?从1月31日会是更难预料目前 - 你最终会在任3月2日或3月3日,取决于它是否是一个闰年!

我个人认为,最不感到惊讶的是转入最后一天的下一个/上个月。但是,这需要setMonth()落实到关心天在有关的月数,而不仅仅是加/减一个固定的时间。据this线程时,moment.js办法是加/减毫秒的30天数,这表明该库将易于相同不一致。

回答

0

如果setMonth用于加减月份时,则如果开始月份的日期在结束月份中不存在,则额外的日期会导致日期“翻转”到下一个月份,因此3月31日减1个月给3月2日或3日。

一个简单的算法就是测试开始日期和结束日期,如果它们不同,设定结束日期,以0如此这般上月的最后一天。与此

一个问题是,减1每月两次可能不会产生相同的结果为减2个月一次。 2017年3月31日减一月份给出了2月28日,减去一个月给1月28日减去2个月,从3月31日,你会得到1月31日

这就是生活。

function addMonths(date, num) { 
 
    var d = date.getDate(); 
 
    date.setMonth(date.getMonth() + num); 
 
    if (date.getDate() != d) date.setDate(0); 
 
    return date; 
 
} 
 

 
// Subtract one month from 31 March 
 
var a = new Date(2017,2,31); 
 
console.log(addMonths(a, -1).toString()); // 28 Feb 
 

 
// Add one month to 31 January 
 
var b = new Date(2017,0,31); 
 
console.log(addMonths(b, 1).toString()); // 28 Feb 
 

 
// 29 Feb plus 12 months 
 
var c = new Date(2016,1,29) 
 
console.log(addMonths(c, 12).toString()); // 28 Feb 
 

 
// 29 Feb minus 12 months 
 
var c = new Date(2016,1,29) 
 
console.log(addMonths(c, -12).toString()); // 28 Feb 
 

 
// 31 Jul minus 1 month 
 
var d = new Date(2016,6,31) 
 
console.log(addMonths(d, -1).toString()); // 30 Jun

+0

是的,我认为这与我想出的解决方案非常相似。我检查了一个月不等于你想要的月份,但我想检查一天发生变化的几乎是相当的。 – dsl101

0

由于getMonth()返回一个整数,你可以简单地实现在Date对象的发电机,其设置当月+ 1或 - 1,只要不分别在你的11个月或月0。

function nextMonth(dateObj) { 
    var month = dateObj.getMonth(); 
    if(month != 11) dateObj.setMonth(month + 1); 
    return dateObj; 
} 

function prevMonth(dateObj) { 
    var month = dateObj.getMonth(); 
    if(month != 0) dateObj.setMonth(month - 1); 
    return dateObj; 
} 

如果要匹配上个月的日期,可以使用对象查找表。现在

,月供问题,在最后一天:

function getLastDayofMonth(month) { 
    var lookUp = { 
    0:31, 
    1:28, 
    2:30, 
    3:31 
    }; 
    return lookUp[month]; 
} 

//and then a revised version 

function nextMonth(dateObj) { 
    var month = dateObj.getMonth(); 
    var day = dateObj.getDate(); 
    if(month != 12) dateObj.setMonth(month + 1); 
    if(getLastDayofMonth(month)<day)dateObj.setDate(getLastDayofMonth(month)); 
    return dateObj; 
} 
+0

这不回答这个问题 –

+0

固定它考虑到不同的天量 –

+0

无法处理闰年或几个月的休息,将无法正常占优势的情况下'setMonth'设置错误的月份。 –

2

这一切都简单和逻辑。让我们拿你的榜样去看看id是什么。

所以第一线

d = new Date('2017-08-31') // Set to last day of August 
 
console.log(d);    // "2017-08-31T00:00:00.000Z" 
 
console.log(d.getMonth()); // 7 - months are zero-based

因此,所有好为止。下一步:你的评论说:// Try to set the month to 8 (September)所以它没有尝试。您可以将其设置为九月或者您没有。在你的例子中,你将它设置为十月。进一步向下解释。

d = new Date('2017-08-31') // Set to last day of August 
 
console.log(d);    // "2017-08-31T00:00:00.000Z" 
 
console.log(d.getMonth()); // 7 - months are zero-based 
 
d.setMonth(8)    // Try to set the month to 8 (September) 
 
console.log(d);    // but now I see I was wrong it is (October)

因此,好问题是为什么呢?From MDN

注意:当日期被称为具有一个以上 参数的构造中,如果值低于它们的逻辑范围时(例如,图13是用于在微小的值设置为月份值或70 ),相邻的 值将被调整。例如。新的日期(2013,13,1)相当于 新的日期(2014年1,1),既创造2014年2月1日的日期(注意 月份是基于0)。类似地,对于其它值:新的日期(2013年,2,1,0, 70)等效于新日期(2013年,2,1,1,10),其既创建一个 日期2013-03-01T01:10: 00。

因此,说9月只有30天,但日期对象有31个。这就是为什么它给你10月,而不是9月。

最简单的将是把你的日期并将其设置为每月的第一天。事情是这样:

var d = new Date('2017-08-31') // Set to last day of August 
 
// simplest fix take the date you have and set it to first day of month 
 
d = new Date(d.getFullYear(), d.getMonth(), 1); 
 
console.log(d);    // "2017-08-31T00:00:00.000Z" 
 
console.log(d.getMonth()); // 7 - months are zero-based 
 
d.setMonth(8)    // Set the month to 8 (September) 
 
console.log(d.getMonth()); // get 8 it is (September)

+0

您的解决方案归结为将日期设置为1以避免边缘情况。再次,我不认为一个月后退/前进按钮应该将月份从月底改为开始 - 更多的用户惊喜......我可以理解这个解释,但我仍然认为'setMonth()'应该是真的多做。迎合这一点。 – dsl101

+0

@ dsl101好的,投票结束这个问题。你更愿意联系javascript开发人员,告诉他们你对他们的'setMonth()'的实现不满意,但说实话,我不认为他们想听那个。因为它不是'setMonth()'谁应该关心它,它是你。因为你是知道边缘案例会发生什么的人,你决定。 javascript文档告诉你到底发生了什么,什么时候发生,如何发生,为什么会发生。你可以处理这一切。这就像自由。 'setMonth()'没有错,但看到这是我的意见... – caramba

0

这应该递增工作一个月,你可以使用类似的策略递减。

// isLeapYear :: Number -> Boolean 
const isLeapYear = ((err) => { 
    return yr => { 
    // check for the special years, see https://www.wwu.edu/skywise/leapyear.html 
    if (yr === 0) { 
     throw err; 
    } 
    // after 8 AD, follows 'normal' leap year rules 
    let passed = true; 
    // not technically true as there were 13 LY BCE, but hey. 
    if (yr === 4 || yr < 0 || (yr % 4)) { 
     passed = false; 
    } else { 
     if (yr % 400) { 
     if (!(yr % 100)) { 
      passed = false; 
     } 
     } 
    } 
    return passed; 
    }; 
})(new Error('Year zero does not exist, refers to 1 BCE')); 

const daysInMonth = [ 
    31, 
    28, 
    31, 
    30, 
    31, 
    30, 
    31, 
    31, 
    30, 
    31, 
    30, 
    31 
]; 

// isLastDay :: Number, Number -> Boolean 
const isLastDay = (d, m, y) => { 
    let dm = isLeapYear(y) && m === 1 ? 29 : daysInMonth(m); 
    return dm === d; 
}; 

// getLastDay :: Number, Number -> Number 
const getLastDay = (m, y) => isLeapYear(y) && m === 1 ? 29 : daysInMonth[m]; 

// incMonth :: Date -> Date 
const incMonth = d => { 
    let dd = new Date(d.getTime()); 
    let day = dd.getDate(); 
    let month = dd.getMonth() + 1; 
    dd.setDate(5); // should avoid edge-case shenanigans 
    dd.setMonth(month); 
    let year = dd.getFullYear(); 
    if (isLastDay(day, month, year)) day = getLastDay(month, year); 
    dd.setDate(day); 
    return dd; 
}; 
+0

与@ caramba的答案一样,它归结为改变一天以避免边缘情况 - 我真的不想这样做。如果您选择了8月27日,然后点击“下一个”(月),则不会期望显示9月5日... – dsl101

0

这是我想出的解决方案,据我所知,这似乎是小而可靠的。它不需要任何额外的数据结构,并依靠setDate(0)来选择边缘情况下月份的最后一天。否则,它只会保留日期,这是我想要的行为。它还可以处理从一年到下一轮包裹(在任一方向):

function reallySetMonth(dateObj, targetMonth) { 
    const newDate = new Date(dateObj.setMonth(targetMonth)) 
    if (newDate.getMonth() !== ((targetMonth % 12) + 12) % 12) { // Get the target month modulo 12 (see https://stackoverflow.com/a/4467559/1454454 for details about modulo in Javascript) 
    newDate.setDate(0) 
    } 
    return newDate 
} 

注意,我只用targetMonth测试这是任何一个比当月更高或更低,因为我用它与'下一个'/'后退'按钮。它需要用任意月份测试更多的用户。

+0

“targetMonth%12”是多余的。 – RobG

+0

在这种情况下,它可能是皮带和大括号,但它来自这里:https://stackoverflow.com/a/4467559/1454454如果targetMonth小于-12,没有它我不认为它不会工作。 – dsl101

+0

嗯,有趣的问题。也许你应该在答案中加入一个参考。 – RobG

相关问题