FCC Bonfire Series 147: Friendly Date Ranges

Sad but true. Today, we’ll be tackling Friendly Date Ranges, the last bonfire in the entire FCC Bonfires Series. If you’ve made it up to this point, this next exercise shouldn’t pose much of a problem, but we’ll go through it anyway.

This time, we must write a function that given an array containing two strings that representing two dates (in ‘YYYY-MM-DD’ format) returns a human friendly date range. What the hell does that mean?

According to Free Code Camp, it’s a date range with no redundant information, in other words, if both dates are in the same year and month, only display the day range within that month etc. Keep in mind that if starting in the current year, the following year can be inferred by the reader and thus, should not be displayed. But I’ll stop babbling nonsense and show you some examples so we can get started:

['2015-10-04', '2015-10-20'] -> ['October 4th', '20th']
['2015-12-24', '2016-01-03'] -> ['December 24th', 'January 3rd']
['2020-01-21', '2021-05-25'] -> ['January 1st, 2020', 'May 25th, 2021'];

We are going to be working with the JavaScript Date object quite heavily this time. We’ll learn as we go, let’s start by getting the function bare-bones going and dissecting the input dates for easier use:

function friendly(arr) {
  var startDate = new Date(arr[0]), // We create a date object for the start and end dates.
    endDate = new Date(arr[1]);

  var startYear = startDate.getFullYear(),  // We dissect each date into year, month and day.
    startMonth = startDate.getMonth(),    // Keep in mind that .getMonth() returns a number in the 0-11 range, and not 1-12!
    startDay = startDate.getDate();

  var endYear = endDate.getFullYear(),
    endMonth = endDate.getMonth(),
    endDay = endDate.getDate();
}

 

Great! Now, we need to get some logic going; as you may notice, the output strings contain the full month name, as well as ‘st’, ‘nd’, ‘rd’ and ‘th’ appended after each day number. We are going to get that fixed first.

Getting the month names right is easy, we’ll create an array called monthNames, and fill it with every month name in order. This is very convenient, since getMonth() returns a number in the 0-11 range, we can take advantage of JavaScript’s zero indexing!

function friendly(arr) {
  var startDate = new Date(arr[0]),
    endDate = new Date(arr[1]);

  var startYear = startDate.getFullYear(),
    startMonth = startDate.getMonth(),
    startDay = startDate.getDate();

  var endYear = endDate.getFullYear(),
    endMonth = endDate.getMonth(),
    endDay = endDate.getDate();

  var monthNames = [
    'January', 'February', 'March', 'April',
    'May', 'June', 'July', 'August',
    'September', 'October', 'November', 'December'
  ];
}

 

With that array in place, we can do this:

var monthNames = [
  'January', 'February', 'March', 'April',
  'May', 'June', 'July', 'August',
  'September', 'October', 'November', 'December'
];

var myDate = new Date('2015-10-20');
var month = myDate.getMonth();

console.log(monthNames[month]); //-> 'October'

 

Next, we are going to create a function that given a number between 1 and 31, returns it with an appended ‘st’, ‘nd’, ‘rd’ or ‘th’ at the end of it. It’s pretty simple and can be done in a million different ways really, here’s one of them:

function addEnding(day) {
  if (day === 1 || day === 21 || day === 31) {
    return day + 'st';
  } else if (day === 2 || day === 22) {
    return day + 'nd';
  } else if (day === 3 || day === 23) {
    return day + 'rd';
  } else {
    return day + 'th';
  }
}

 

With all of that out of the way, we can start doing the cool stuff! First, let’s make sure that if we are getting the same date twice, we return that date instead of a range:

function friendly(arr) {
  var startDate = new Date(arr[0]),
    endDate = new Date(arr[1]);

  var startYear = startDate.getFullYear(),
    startMonth = startDate.getMonth(),
    startDay = startDate.getDate();

  var endYear = endDate.getFullYear(),
    endMonth = endDate.getMonth(),
    endDay = endDate.getDate();

  var monthNames = ['January', 'February', 'March', 'April',
            'May', 'June', 'July', 'August',
            'September', 'October', 'November', 'December'];


  // getTime() will get us the unix time for each date. If they're the same,
  // we return that date in the following format: ['January 1st, 2015']
  // We are making use of the monthNames array and addEnding function to format the output.
  if (startDate.getTime() === endDate.getTime()) {
    return [monthNames[startMonth] + ' ' + addEnding(startDay) + ', ' + startYear];
  }

  function addEnding(day) {
    if (day === 1 || day === 21 || day === 31) {
      return day + 'st';
    } else if (day === 2 || day === 22) {
      return day + 'nd';
    } else if (day === 3 || day === 23) {
      return day + 'rd';
    } else {
      return day + 'th';
    }
  }
}

 

For each case, we are going to simply append additional else if statements after that if. The next requirement states that if the year and month are the same, we just need to return the day range:

function friendly(arr) {
  var startDate = new Date(arr[0]),
    endDate = new Date(arr[1]);

  var startYear = startDate.getFullYear(),
    startMonth = startDate.getMonth(),
    startDay = startDate.getDate();

  var endYear = endDate.getFullYear(),
    endMonth = endDate.getMonth(),
    endDay = endDate.getDate();

  var monthNames = ['January', 'February', 'March', 'April',
            'May', 'June', 'July', 'August',
            'September', 'October', 'November', 'December'];

  if (startDate.getTime() === endDate.getTime()) {
    return [monthNames[startMonth] + ' ' + addEnding(startDay) + ', ' + startYear];
  } else if (startYear === endYear && startMonth === endMonth) {              // We return something like this:
    return [monthNames[startMonth] + ' ' + addEnding(startDay), addEnding(endDay)];   // ['January 1st', '10th']
  } 

  function addEnding(day) {
    if (day === 1 || day === 21 || day === 31) {
      return day + 'st';
    } else if (day === 2 || day === 22) {
      return day + 'nd';
    } else if (day === 3 || day === 23) {
      return day + 'rd';
    } else {
      return day + 'th';
    }
  }
}

 

Let’s just keep going! The next case states that if the starting year is the current year and the ending year can be inferred, we can omit it. We can get the current year by creating a new Date object and not passing any arguments; then, we use the getFullYear() getter method:

var currentYear = new Date().getFullYear(); //-> 2015

 

Using this method, we’ll create a new else if block and check for this. We also need to make sure that the ending year is the current or next year at the most so that the reader can make sense of it. Not only that, but let’s go one step ahead, and make sure that the month is also different, in case we get something like this:

['2015-12-10', '2016-12-15'] //-> We would get ['December 10th', 'December 15th'], not so understandable...

 

For this last case, we could even check if the day is lesser than the ending day to be 100% precise; but that, my friend, I’ll leave for you to decide. Let’s add that next else if block before I put you to sleep, we’ll need to check for dates within the same year and different months, even if not in the current year. It’s going to be a LONG if block, prepare yourself!

function friendly(arr) {
  var startDate = new Date(arr[0]),
    endDate = new Date(arr[1]);

  var startYear = startDate.getFullYear(),
    startMonth = startDate.getMonth(),
    startDay = startDate.getDate();

  var endYear = endDate.getFullYear(),
    endMonth = endDate.getMonth(),
    endDay = endDate.getDate();

  var monthNames = ['January', 'February', 'March', 'April',
            'May', 'June', 'July', 'August',
            'September', 'October', 'November', 'December'];

  if (startDate.getTime() === endDate.getTime()) {
    return [monthNames[startMonth] + ' ' + addEnding(startDay) + ', ' + startYear];
  } else if (startYear === endYear && startMonth === endMonth) {
    return [monthNames[startMonth] + ' ' + addEnding(startDay), addEnding(endDay)];
  } else if (((startYear === new Date().getFullYear() && (startYear === endYear || startYear === endYear - 1)) || startYear === endYear) && startMonth != endMonth) {
    // We return the following: ['December 1st', 'May 5th'] for example.
    return [monthNames[startMonth] + ' ' + addEnding(startDay), monthNames[endMonth] + ' ' + addEnding(endDay)];
  } 

  function addEnding(day) {
    if (day === 1 || day === 21 || day === 31) {
      return day + 'st';
    } else if (day === 2 || day === 22) {
      return day + 'nd';
    } else if (day === 3 || day === 23) {
      return day + 'rd';
    } else {
      return day + 'th';
    }
  }
}

 

Look closely at the else if statements and make sense of them, they can be intimidating when put together, but they are very simple if dissected, here’s what it means in human language:

if ((startYear === new Date().getFullYear() && (startYear === endYear || startYear === endYear - 1)) || startYear === endYear) && startMonth != endMonth)

/*

  IF (startYear is the current year AND starYear is equal to endYear OR endYear - 1)
  OR forget the previous and startYear and endYear are the same
  
  When any of the previous is true AND startMonth and endMonth are different.

*/

 

I probably confused you even more with that. I’m very sorry, but I tried; I’m just not too good at explaining myself. If it’s unclear, just look at it for very long and very hard, you’ll make sense of it in time.

Lastly, we need to account for every other possible case, in which we return month, day and year for the range:

function friendly(arr) {
  var startDate = new Date(arr[0]),
    endDate = new Date(arr[1]);

  var startYear = startDate.getFullYear(),
    startMonth = startDate.getMonth(),
    startDay = startDate.getDate();

  var endYear = endDate.getFullYear(),
    endMonth = endDate.getMonth(),
    endDay = endDate.getDate();

  var monthNames = ['January', 'February', 'March', 'April',
            'May', 'June', 'July', 'August',
            'September', 'October', 'November', 'December'];

  if (startDate.getTime() === endDate.getTime()) {
    return [monthNames[startMonth] + ' ' + addEnding(startDay) + ', ' + startYear];
  } else if (startYear === endYear && startMonth === endMonth) {
    return [monthNames[startMonth] + ' ' + addEnding(startDay), addEnding(endDay)];
  } else if (((startYear === new Date().getFullYear() && (startYear === endYear || startYear === endYear - 1)) || startYear === endYear) && startMonth != endMonth) {
    return [monthNames[startMonth] + ' ' + addEnding(startDay), monthNames[endMonth] + ' ' + addEnding(endDay)];
  } else {
    // Any other case returns something like this: ['January 25th, 2020', 'October 23rd, 2045']
    return [monthNames[startMonth] + ' ' + addEnding(startDay) + ', ' + startYear, monthNames[endMonth] + ' ' + addEnding(endDay) + ', ' + endYear];
  }

  function addEnding(day) {
    if (day === 1 || day === 21 || day === 31) {
      return day + 'st';
    } else if (day === 2 || day === 22) {
      return day + 'nd';
    } else if (day === 3 || day === 23) {
      return day + 'rd';
    } else {
      return day + 'th';
    }
  }
}

 

And that’s our answer right there. You are done…! We are done! Bonfires out of the way, let’s make some room for Ziplines. Starting soon, the FCC Zipline Series will take you through every the FCC Zipline challenge, giving tips and teaching a few things along the way rather than providing a solution directly.

Remember to shoot me an email, ping me on twitter or post a comment below if you have any queries!