0

I know that dates in Javascript are funky so I think I'm going to just move to moment.js or since I'm doing this project in Inertia I might handle this server side in PHP to avoid adding another dependency. But I still find this fascinating so wanted to post here to see if I can get a better understanding of what I'm doing wrong. Maybe what I'm screwing up has nothing to do with dates.

Anyway I'm trying to populate an array with a date element for each month given a certain start date and amount of months the array should hold (incrementing by 1 month in each element). I found a highly upvoted stackoverflow post with a addMonths function I stole for this. The function seems to be straightforward. However, instead of adding a single month, it adds 2. What's even crazier is that in my Vue.js code it works when I console.log it but not when I push it to an array (using the same exact code).

This is the code:

//addMonths came from stack overflow here: https://stackoverflow.com/questions/2706125/javascript-function-to-add-x-months-to-a-date

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

let starting_date = "2023-03-23";
let months_left = 6;
let header_months = [];
for (let i = 0; i < months_left + 1; i++) {
  let start_date = new Date(starting_date);
  console.log(addMonths(start_date, i));
  header_months.push(addMonths(start_date, i));
}

console.log(header_months);

Codesandbox here: https://codesandbox.io/s/thirsty-curran-w3w2cr?file=/src/index.js

As mentioned above it adds 2 months, not 1.

0: Wed Mar 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
1: Mon May 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
2: Sat Jul 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
3: Fri Sep 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
4: Wed Nov 22 2023 18:00:00 GMT-0700 (Mountain Standard Time)
5: Mon Jan 22 2024 18:00:00 GMT-0700 (Mountain Standard Time)
6: Fri Mar 22 2024 18:00:00 GMT-0600 (Mountain Daylight Time)

At least that's consistent and both console.logs in the above code show the same unexpected result. I don't know why this is wrong. I assume I'm screwing up something with variable scope and instead of start_date resetting on each loop run it remembers the last value. If that was the only problem with it I'd likely not even post the question here and just move to doing this another way. But...

What's even stranger is that if I run this in my actual Vue3 code the first console.log in the for loop gives me the results I expect:


Show.vue:394 Wed Mar 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
Show.vue:394 Sat Apr 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
Show.vue:394 Mon May 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
Show.vue:394 Thu Jun 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
Show.vue:394 Sat Jul 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
Show.vue:394 Tue Aug 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)

But the header_months array is returning the same unexpected results as Vanilla JS.

0: Wed Mar 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
1: Mon May 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
2: Sat Jul 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
3: Fri Sep 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
4: Wed Nov 22 2023 18:00:00 GMT-0700 (Mountain Standard Time)
5: Mon Jan 22 2024 18:00:00 GMT-0700 (Mountain Standard Time)
6: Fri Mar 22 2024 18:00:00 GMT-0600 (Mountain Daylight Time)

???

I imagine if I figure out what I'm doing wrong this will work in my Vue3 project as well. But just the fact I'm getting different results when running the same identical basic javascript code is extremely strange to me. Unfortunately since it's part of a bigger internal project I can't really share the larger Vue3 code here. But here are screenshots showing the relevant parts:

https://i.stack.imgur.com/pybYK.png

I'm honestly impressed with myself how I can screw something this basic up so much. Any thoughts/ideas? Thanks!

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Pawel K
  • 5
  • 3
  • 1
    What happens if you don't use the inner `console.log(addMonths(start_date, i));` in the loop and just the one at the end with the array? What do you notice? (hint: the date is being changed in your addMonths function, and you call it twice because of the console.log, hence 2 months being added). – chrisbyte Mar 23 '23 at 17:34

1 Answers1

2

The problem with the snippet is that dates are mutable, and you're changing the date both when doing the console.log:

console.log(addMonths(start_date, i));
//          ^−−−− adds a month to `start_date`

and when pushing to the array:

header_months.push(addMonths(start_date, i));
//                 ^−−−− adds a _second_ month to `start_date`

So naturally, it gets incremented twice.

If you want to be able to call it twice like that without worrying about double-increments, have it leave the date you pass into it alone and instead return the updated date:

function addMonths(date, months) {
    let day = date.getDate();
    let result = new Date(date);
    result.setMonth(result.getMonth() + +months);
    if (result.getDate() !== day) {
        result.setDate(0);
    }
    return result;
}

Then you get consistent results:

function addMonths(date, months) {
    let day = date.getDate();
    let result = new Date(date);
    result.setMonth(result.getMonth() + +months);
    if (result.getDate() !== day) {
        result.setDate(0);
    }
    return result;
}

let starting_date = "2023-03-23";
let months_left = 6;
let header_months = [];
for (let i = 0; i < months_left + 1; i++) {
    let start_date = new Date(starting_date);
    console.log(addMonths(start_date, i));
    header_months.push(addMonths(start_date, i));
}

console.log(header_months);
.as-console-wrapper {
    max-height: 100% !important;
}

Side note: There's no need for the unary + in result.getMonth() + +months. The binary + will do any necessary conversion, the unary is completely redudant: result.getMonth() + months.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thank you all. That makes perfect sense. – Pawel K Mar 23 '23 at 18:59
  • Sorry thinking about this more. I guess I still don't understand the difference between doing it in my Vue code vs doing it in vanillaJS. In vanillaJS why is the first console.log also added twice? – Pawel K Mar 23 '23 at 19:03
  • Never mind ignore my comment above, this isn't a issue with vanillaJS, it's an issue with CodeSandbox and however they are compiling the code. Lesson learned, if you want to test with actual vanillaJS just run the code in the console. Thanks again all! – Pawel K Mar 23 '23 at 19:55