3

I need to get a list of Sundays for the next three months. I wrote a function which worked up until today. Three months from today is January, which is 0 so my for loop doesn't work.

function getSundays(year) {
  const offdays = [];
  let i = -1;
  const currentDate = new Date();
  currentDate.setDate(currentDate.getDate() + 90);
  for ( let month = new Date().getMonth(); month < currentDate.getMonth(); month += 1) {
    const tdays = new Date(year, month, 0).getDate();
    for (let date = 1; date <= tdays; date += 1) {
      const smonth = (month < 9) ? `0${month + 1}` : month + 1;
      const sdate = (date < 10) ? `0${date}` : date;
      const dd = `${year}-${smonth}-${sdate}`;
      const day = new Date();
      day.setDate(date);
      day.setMonth(month);
      day.setFullYear(year);
      if (day.getDay()  ===  0 ) {
        offdays[i += 1] = dd;
      }
    }
  }
  return offdays;
}

How can I get around this?

stealththeninja
  • 2,854
  • 1
  • 18
  • 38
Tom Bomb
  • 1,204
  • 3
  • 10
  • 20
  • 1
    why use `const` ? – Raptor Nov 01 '18 at 03:26
  • 1
    @Raptor it is not reassigned, changing it to let would not change the outcome – Tom Bomb Nov 01 '18 at 03:30
  • 1
    For something like this you may find it easier to use a proper date library, like Moment.JS – Obsidian Age Nov 01 '18 at 03:31
  • @Raptor There's a lint rule for that https://eslint.org/docs/rules/prefer-const. Knowing that something won't be reassigned can make it easier to follow code or as the link explains `const declaration tells readers, “this variable is never reassigned,” reducing cognitive load and improving maintainability.` – Juan Mendes Nov 01 '18 at 03:49
  • Could you help us understand how the question measures "Sundays for the next three months"? – stealththeninja Nov 01 '18 at 04:24

5 Answers5

0

Please test code below.

var startDate = new Date(2018, 0, 1);
var endDate = new Date(2018,11, 31);
var day = 0;  
for (i = 0; i <= 7; i++) { 
    if(startDate.toString().indexOf('Sun') !== -1){
       break;
    }
    startDate =  new Date(2018, 0, i);
}

var result = [];
startDate = moment(startDate);
endDate = moment(endDate);
var current = startDate.clone();
while (current.day(7 + day).isBefore(endDate)) {
  result.push(current.clone());
}

//console.log(result.map(m => m.format('LLLL')));
console.log(result.map(m => m.format('YYYY-MM-DD')));
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js"></script>
son pham
  • 287
  • 1
  • 6
  • Fair warning, MomentJS is awesome with dates but it's also a big library, latest version 64.2K minified and gzipped ([source](https://bundlephobia.com/result?p=moment@2.22.2)). Helpful for timezones and localization, but worth pausing to ask if you need another library. – stealththeninja Nov 01 '18 at 03:54
  • @stealththeninja agreed - momentjs is great, but a bit heavy – Dacre Denny Nov 01 '18 at 03:55
  • YYYY-MM-DD https://stackoverflow.com/questions/23593052/format-javascript-date-to-yyyy-mm-dd – Steve Nov 01 '18 at 04:03
  • You can change m.format('LLLL') ---> m.format('YYYY-MM-DD') if you want. – son pham Nov 01 '18 at 04:13
0

Assuming there are about 4 Sundays per month.

function getSundays() {
    const sundays = [];

    var sunday = new Date()
    sunday.setDate(sunday.getDate() + 7 - sunday.getDay());

    for (var i = 0; i < 12; i++) {
        console.log(sunday.toLocaleString());
        sundays.push(new Date(sunday.getTime()));
        sunday.setDate(sunday.getDate() + 7);
    }

    return sundays;
}

getSundays();
Steve
  • 802
  • 6
  • 8
  • Good point, stealththeninja. In Al-josh's question, he used 90 days to represent the target time period. The loop could be modified to take that into account. `while (sundayEpoc < ninetyDaysFromNowEpoc)` – Steve Nov 01 '18 at 04:09
  • Sorry for deleting my comment. Great use of native Date object. My comment wondered how the question measures "three months of Sundays": 12 weeks, 90 days, 3 months (inclusive?). I'm betting OP could do a quick change to a while-loop and address any of those with this solution though. Again, nice answer. – stealththeninja Nov 01 '18 at 04:22
  • The code isn't bad, but the assumption is. There are many situations where the Sundays for the next three months will not be 12. For example, this code leaves out 1/27/2019 as a Sunday in the next three months when running it on 10/31/2018. Don't forget February as well. There needs to be more oversight in the loop. – Travis J Nov 01 '18 at 06:08
  • @TravisJ I appreciated the simplicity of the answer. Months are ambiguous length where weeks are not. The core solution is the same (getDate, setDate) so the loop condition can be modified when the OP clarified how they measure out “three months worth”. – stealththeninja Nov 01 '18 at 15:35
  • I really like this solution, I'm trying to implement my 3 months and I understand my questions wasn't clear. Basically 3 month period is this month + 3. So on the first of December it will show up to January 31st, and on December 25 it will also show up to January 31. On any date in January it should show sundays until March 31st. – Tom Bomb Nov 05 '18 at 04:00
0

If you wanted a non-momentjs based approach, perhaps you could do something like this?

This method increments one day at a time, continuing three months into the future and looks for any date instances where getDay() === 0 (ie Sunday).

The increment is done via milliseconds as a convenient way to create date objects for specific day/month/years. The month counter tracks changes in the month/year between the currentDate and nextDate to determine if the month counter should be incremented:

function getSundays(year) {
  
  var start = new Date();
  start.setYear(year);
  var startEpoch = start.valueOf();
  
  var dayDuration = 86400000; // Milliseconds in a day
  var currentEpoch = startEpoch;
  
  var offdays = [];
  
  // Iterate over three month period from as required
  for(var monthCounter = 0; monthCounter < 3; ) {
   
    var currentDate = new Date(currentEpoch);
    var nextDate = new Date(currentEpoch + dayDuration);
    
    // If the next month or next year has increased from 
    // current month or year, then increment the month counter
    if(nextDate.getMonth() > currentDate.getMonth() || 
    nextDate.getYear() > currentDate.getYear()) {
     monthCounter ++;
    }
   
    if(currentDate.getDay() === 0) {
     offdays.push(currentDate);
    }
    
    currentEpoch += dayDuration;
   
  }
  
  return offdays;
}

console.log(getSundays(2018).map(date => date.toString()));
Dacre Denny
  • 26,362
  • 5
  • 28
  • 48
  • 2
    The dayDuration value will become inaccurate during day light savings as it will be off by one hour from that point on. – Travis J Nov 01 '18 at 06:09
0

You can make using methods of Date get|setMonth and get|setDay, like a make this:

const next = new Date()
next.setMonth( (new Date()).getMonth()+1 )

const allSundays = []
const sundaysByMonth = []

for( let i = 0; i<3; i++ ){

    const m = next.getMonth()
    
    const sundays = []
    for( let j = 0; j < 7; j++ ){
     
     if ( next.getDay() == 0 ){
            sundays.push( next.getDate() )
            allSundays.push( next.getDate() )
            next.setDate( next.getDate() + 6 )  // increment in 6 days not 7 (one week) because line below increase again
        }
        
        next.setDate( next.getDate() + 1 ) // increase month day until arrive in sunday
        if( next.getMonth() != m ){ // check if not exceeded the month
         sundaysByMonth.push( sundays )
           break // break to go to next month
        }
        
    }
}

console.log( allSundays )
console.log( sundaysByMonth )
const el = document.getElementById("demo");

for( let i = 0; i < allSundays.length; i++ ){
  const li = document.createElement('li')
  li.innerHTML = 'Sunday '+ allSundays[i]+' '
  el.appendChild(li)
}
<p>The getDay() method returns the weekday as a number:</p>

<ul id="demo"></ul>
0

Looked at some of the answers already given and they seemed close and inspired me to give a shot without using moment.js - Commented pretty heavily, hopefully explains it, but it works for my understanding of the problem, although you probably need to shape the result value for your purposes, but it contains what you need, I believe.

function getSundaysForNextCalendarMonths(numberOfMonths, dateVal){
 let trackingObj = {};  // We'll use this object to track our trackingObjs by month in our loop.
 let result = []; // We'll just push the date from each iteration here rather than make a mapping function on the result for this example.  might be overkill anyways, this array shouldn't contain more than 15 days
 if(!dateVal){
  dateVal = new Date(); // initialize the date to today or wherever you want to start from
 }
 // Using today's date as reference the first time through the loop we find out how far off Sunday it is, and then add the difference times the time of day in milliseconds in UNIX time
 // but first - we'll also set the time to 2 AM to work around any potential daylight savings time weirdness in case the hour shifts up or down over a the period
 dateVal.setHours(2);
 dateVal.setTime(dateVal.getTime() + (7 - dateVal.getDay()) * (24*60*60*1000)); // mutated dateVal to now equal following Sunday
 result.push(dateVal.toDateString());
 // This loop in theory will break before iterates all the way through, because you'd never have 15 Sundays in a 3 month, but I removed the while loop because it's easier to ascertain the runtime of the loop
 for(let i = 0; i < numberOfMonths * 5; i++){ 
  // we just add 7 days in milliseconds to dateVal to get next Sunday
  dateVal.setTime(dateVal.getTime() + 7 * (24*60*60*1000));
  if(trackingObj[dateVal.getMonth()]) {  // If there's already a month in the reuslt, push to the array that's there.
   result.push(dateVal.toDateString());
  } else if(!trackingObj[dateVal.getMonth()] && Object.keys(trackingObj).length < numberOfMonths){  // Otherwise if there's not too many months in  the trackingObj put the object in the array
   trackingObj[dateVal.getMonth()] = true;
   result.push(dateVal.toDateString());
  } else {
   break;  // more than 3 months break loop.  you have your trackingObj
  }
 }
 return result;
}
console.log(getSundaysForNextCalendarMonths(3));
enter code here
chairmanmow
  • 639
  • 7
  • 20
  • Using milliseconds to measure how many days have passed will falter during daylight savings. – Travis J Nov 01 '18 at 06:14
  • May not be a deal-breaker, although I'm probably wrong as it seems like an oversimplifcation, but provided all daylight savings times happen at 2AM as I'm aware of, and no daylight savings time "time-shift" is greater than an hour or at least more or less adhere to the principle (i.e. a time change never changes a calendar day unit, just an hour unit by no more than one hour at a time periodically up and down) that perhaps setting the initial date to 2AM may compensate for that time-change phenomenon were it consistent, perhaps over limited range, but 3 month would work. – chairmanmow Nov 01 '18 at 06:32
  • There is truth to that, rounding the date in one form or another would bypass the one hour discrepancy. – Travis J Nov 01 '18 at 06:38
  • Cool, I wasn't positive so thanks for the back up on my theory -working with time can feel like a rabbit hole but maybe there's some patterns that can be worked with glad you raised the point. Edited my code a bit to handle that, and wrap in a function, but interested in other approaches as well. – chairmanmow Nov 01 '18 at 07:51