简体   繁体   中英

`date.setMonth` causes the month to be set too high if `date` is at the end of the month

A Date object's getMonth() method seems to have a bug. Assuming the Date d is 2013-01-31 , I attempt to set the month on d like this:

const d = new Date(); // 2013-01-31

d.setMonth(8);
console.log(d.getMonth());

The result is 9 . Why? I tested this both in Chrome and Firefox.

I found out that when it's the 31st, 30th, or 29th of a month, setting the date to a month that has a fewer number of days causes getMonth to return the wrong value.

The getMonth() method returns the month (from 0 to 11) for the specified date, according to local time.

Note : January is 0, February is 1, and so on.

Let's break this down:

var d = new Date(); // date is now 2013-01-31
d.setMonth(1);      // date is now 2013-02-31, which is 3 days past 2013-02-28
x = d.getMonth();   // what to do, what to do, 3 days past 2013-02-28 is in March
                    // so, expect x to be March, which is 2

This is only an issue when the day value of d is greater than the maximum number of days in the month passed to setMonth() . Otherwise, it works as you'd expect.

Simplest solution to this is to add second argument to setMonth:

var d = new Date();
d.setMonth(8,1);
d.getMonth(); //outputs 8

http://www.w3schools.com/jsref/jsref_setmonth.asp

Date.setMonth(month,day)

day: Optional. An integer representing the day of month Expected values are 1-31, but other values are allowed:

0 will result in the last day of the previous month -1 will result in the day before the last day of the previous month If the month has 31 days:

32 will result in the first day of the next month If the month has 30 days:

32 will result in the second day of the next month

Months in JavaScript are represented from 0-11. Month 1 would be February which only has 28/29 days, so when you set the month to 1, it tries to auto-correct the date to March to make a date that makes sense (since Feb 31st, makes no sense). Try it out by using the toDateString function to see what I mean:

 var d = new Date('2013/01/31');
 d.setMonth(2);
 console.log(d.toDateString()); // outputs Match 3rd, 2013

A little weird perhaps, but not buggy.

The getMonth() method returns the month (from 0 to 11) for the specified date, according to local time, eg. January is 0, February is 1.

In javascript month is start from 0. Assume today is 02/04/2012, when you setMonth(1) it will try to set to feb. Since max day in feb is 28/29, it move to the next month (March, which is 2)

The setMonth here is not what you expect, and what happens in better libraries. If you set a date 31.x.yyyy to x+1, probably the next month (x+1) will not have 31 days. So 31.x+1.yyyy will be changed to 1.x+2.yyyy.

In such case, the month will be increased by 2 - the date completely changed.

I had this problem with changing February->March, thanks God that was only a test.

This has to do with the current local date, even if the date you're setting is in the past or future (assuming you are correctly using the month range 0-11). This is because when you initialize Date(), it is using the current date. When you modify the date by using setFullYear(), setMonth() or setDate(), the default "current" values are still present and can shift the date.

If your current day of the month (today) is the 31st of the month and you then set a date in the past using setMonth(), setDate() where the month has less than your current 31 days, then Date() will give you the wrong month, even if you specifically state it. This is because when you setMonth(), the current date value of 31, which doesn't exist in the month you set, pushes it into the next month. Then when you set the date, the month never changes back.

If you set the day of the month before the month - setDate(), setMonth(), this fixes the problem, but has another similar issue / bug. If your current month only has 30 days and the date you're trying to set has 31 days, it will set the date of the month as 1, not 31. This is resolved if you set the month before the day - setMonth(), setDate(), but then you're back to the first problem.

Solutions:

  1. Get the values and then set the day of the month and month at the same time (as reetah suggested) - setMonth(8,1)
  2. Set the year with optional values to initialize setFullYear(1972, 0, 1), then setMonth, setDate.
  3. Set the month, set the day, then verify the month equals what you set it to and set it again if not - setMonth(), setDate(), setMonth()

 function formatDate(date) { var monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; var day = date.getDate(); var monthIndex = date.getMonth(); var year = date.getFullYear(); return day + ' ' + monthNames[monthIndex] + ' ' + year; } var date = new Date(); if (date.getDate() < 31) { $("#mydate").text("YOUR LOCAL DATE (on your computer) MUST BE SET TO THE 31st for this bug to show."); } else { date.setFullYear(1972); date.setMonth(8); // (month-1) date.setDate(24); var breakdown = "<br>Day: " + date.getDate() + "<br>Month: " + date.getMonth() + "<br>Year: " + date.getFullYear(); $("#mydate").html("Set as September (8)<br><br>" + formatDate(date) + breakdown); var date = new Date(); date.setFullYear(1972); date.setMonth(8, 24); // (month-1) var breakdown = "<br>Day: " + date.getDate() + "<br>Month: " + date.getMonth() + "<br>Year: " + date.getFullYear(); $("#mydate4").html("Set as September (8) but set day before month<br><br>" + formatDate(date) + breakdown); date.setFullYear(1972); date.setMonth(9); //October (month-1) date.setDate(24); var breakdown = "<br>Day: " + date.getDate() + "<br>Month: " + date.getMonth() + "<br>Year: " + date.getFullYear(); $("#mydate2").html("Set as October (9)<br><br>" + formatDate(date) + breakdown); var date = new Date(); date.setFullYear(1972); date.setMonth(7); //Aug (month-1) date.setDate(24); var breakdown = "<br>Day: " + date.getDate() + "<br>Month: " + date.getMonth() + "<br>Year: " + date.getFullYear(); $("#mydate3").html("Set as August (7)<br><br>" + formatDate(date) + breakdown); }
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> September 1972 has only 30 days. If your current local day is 31, the Date will return October 1972 when you specify September before you specify the day of the month. <br><br><div id="mydate" style="color: red;"> </div> <br><br> <div id="mydate4"> </div><br><br> <div id="mydate2"> </div> <br><br> <div id="mydate3"> </div>

Well, from a standpoint of a consumer of setMonth() method I would expect it to behave logically and not overflow to a different calendar month. Setting the actual day in the setMonth() method also is not the best option since in major cases I wouldn't like to get a first day of the changed month.

Yet, having in mind the Javascript logic about dates this behavior takes place in numerous flows: any month with 31 days in case the setMonth() is executed for the 31st day of that month and changed month does not have this day then the actual month as a result will not be the expected one.

Therefore I came up with the following solution that does cover all of the possible cases and results in the most close result to the desired one. Ie setting a month for 31st day to a month that does not have it will set the month correctly and the day will be the last day of that month. This will work for all occasions (for instance for months with 30 and 29 days and the desired month of February).

Here's my solution in case anyone is interested:

public static addMonths(date: Date, value: number): Date {
    let expectedMonth: number = date.getMonth() + value;
    if (expectedMonth > 12) {
        expectedMonth = expectedMonth % 12;
    }
    if (expectedMonth < 0) {
        expectedMonth += 12;
    }

    date.setMonth(date.getMonth() + value);
    const daysToAdd: number = date.getMonth() > expectedMonth ? -1 : 1;
    while (date.getMonth() !== expectedMonth) {
        date.setDate(date.getDate() + daysToAdd);
    }

    return date;
}

I just resolved this way:

Example

var d = new Date('2013/01/31');

m = d.getMonth();

month = m*100/100 + 1;

Var month contains the correct value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM