In a recent discussion with a colleague of mine, we touched upon the subject of projects for our portfolios. The conversation eventually touched upon the idea that you don’t need to create huge functioning programs to showcase your knowledge. Mini-projects are more than enough to showcase a specific skill-set, something I kept in mind as I worked on my next item.
In my everyday work, I am primarily a back-end developer working with NodeJs and serverless frameworks. Every once in a while, I’ll work on front-end code, but always find myself struggling to grasp the latest changes that my colleagues put out into our product. With this in mind, I set about creating my own components while brushing up on my knowledge with React. So in this 4 part series, I will show how I went about creating a calendar component in React.
Please note, that I will not be going deep into the setup of the project and going straight into the process. If you want more knowledge or an easy way to start your own React App, check out Create React App.
Created using React, Javascript, and MomentJs
In this post (part 1), we will strictly look at the logic that went into generating the days of the month and the overflow dates of the previous and following month. Having come across little “gotcha” moments, I hope that this post will provide some knowledge and an interesting read for you if you choose to create your own components in the future.
At a quick glance, a calendar seems simple and straightforward. Let’s grab a bunch of dates and slap it on a screen! Of course, things aren’t as simple as we want them to be and a few things had to be considered before writing any code:
- How do I get the number of days in a given month?
- How do I know the first day of the week for a given month?
- How do I get the overflow dates of the previous/following months?
Before moving forward, I encourage you to take a moment and think about how you would go about this. After all, mental exercise keeps you fit!
Challenge 1: How do I get the number of days in a given month?
Dates are a tricky thing in programming and the Javascript and NodeJs community have a special place in their hearts for MomentJs. This challenge is simple with the amazing functionality this library provides which we will be taking advantage of by installing the package in our project root with npm install --save moment
.
MomentJs has the function daysInMonth()
. Problem solved! Let’s see what it gives when you give it a month.
import moment from 'moment';
const getDaysInMonth = (month) => {
return moment(month, 'MM').daysInMonth();
}
// '01' = January...
console.log(getDaysInMonth('01'))
The console should spit back 31 as the result. Simple right? This works, but this has a few challenges. Moment by default assumes any missing information by getting it from the current date which means that this is getting the days of the month of January 2020 even though we don’t pass in the year directly. Of course 2020 is a unique year… which you guessed, a leap year!
This function would work if I passed in getDaysInMonth('02') // 29
, but what if I want the days of February in 2019? Thankfully, MomentJs has functionality to handle that for us.
import moment from 'moment';
const getDaysInMonth = (month, year) => {
return moment(\`${month}-${year}\`, 'MM-YYYY').daysInMonth();
}
console.log(getDaysInMonth(2, 2019))
Ta da! Challenge solved. The function can now accept the month and the year as its arguments and correctly determine the number of days in the month of a specific year.
Challenge 2: How do I know the first day of the week for a given month?
This challenge is a relatively simple one, so I won’t spend too much time on this. However, let’s take some time to figure out why we want this information. If you’ve seen most date objects in Javascript, you’ll see something like this:
new Date() // 2020-07-07T05:00:00:000Z
There are two ways to do this. First, lets take a look at the vanilla Javascript way:
const getFirstWeekdayOfMonth = (month, year) => {
return new Date(year, month, 1).getDay()
}
// Note, the month and result is 0-indexed
console.log(getFirstWeekdayOfMonth(0, 2020))
The month is represented as 0
which equals January so the first weekday of January in 2020 was Wednesday which is indexed at 3
. If you’re confused about the indexing, [0, 1, 2, 3, 4, 5, 6] = Sunday — Saturday
. The solution is simple and for the most part will work, but working with dates is a pain and little caveats here and there cause some challenges. Still, it is good to know so if you are interested, learn more about Javascript Dates.
Now, let’s take advantage of MomentJs to do the same thing!
const getFirstWeekdayOfMonth = (month, year) => {
return moment(
\`${month}-${year}\`,
'MM-YYYY'
).startOf('month').weekday()
}
// Note, since we use MomentJs's formatting, we do not index the month. This is getting the first weekday of the month for January 2020. Result is 0-indexed
console.log(getFirstWeekdayOfMonth(1, 2020))
Simple, result is the same with 3!
Challenge 3: How do I get the overflow dates of the previous/following months?
The last challenge is to figure out exactly how many days we need to show in the overflow of the previous and following months. Looking at this picture of the finished component, we want the information regarding the dates that are greyed out.
But before we begin, let’s do some quick logic.
We know that there can be anywhere from 28–31 days in a given month. In one week, there are 7 days. Assuming that the first day of the month can be on any given weekday, we want to know how many weeks a given month can be a part of. Taking a look at the picture above, we know that July 2020 lands on 5 weeks. But, wait… what if the first day of the month is Saturday?
If the first day of the month is Saturday, a month can be a part of 6 weeks. Given that 31 days is the maximum number of days in a month, 6 weeks is the most any given month can be a part of. This is considering no new date conventions arise in our lifetimes. I hope that I won’t be around when developers need to start working with dates across multiple planets!
Since we know that 6 weeks is the maximum number of weeks any given month can be a part of, let’s say that our calendar needs to show a total of 42 dates (7 * 6 = 42).
Now, let’s figure out the previous month’s overflow dates. To do so, we need to know the current month and first weekday to display using the functions we created above. First, let’s add the first month into an array after constructing the date using MomentJs.
const getDatesInMonthDisplay = (month, year) => {
const daysInMonth = getDaysInMonth(month, year);
const firstWeekday = getFirstWeekdayOfMonth(month, year);
const result = \[\];
for (let i = 1; i <= daysInMonth; i++) {
result.push(
moment(\`${month}-${i}-${year}\`, 'MM-DD-YYYY').toDate()
)
}
return result;
}
// July 2020
// Note, since we use MomentJs's formatting, we do not index the month. This is getting the first weekday of the month for January 2020. Result is 0-indexed
console.log(getDatesInMonthDisplay(7, 2020))
The result should consist of an array of date objects representing each day of the month of July 2020.
[
2020-07-01T07:00:00.000Z,
2020-07-02T07:00:00.000Z,
2020-07-03T07:00:00.000Z,
2020-07-04T07:00:00.000Z,
2020-07-05T07:00:00.000Z,
...
]
Taking a look at the firstWeekday
variable, we can determine how many days of the previous month we need in our overflow. For July 2020, the first weekday, as we determined above, is Wednesday or an index number of 3. Therefore, we know that we need 3 days of the previous month to complete a full week at the start of the month display… [0, 1, 2…].
First, let’s add two quick helper functions to determine the previous/following month and years!
const getPrevMonthYear = (month, year) => {
// If it is January... prev month is Dec of the previous year
if (month === 1) {
return {
month: 12,
year: year - 1
}
}
// Otherwise, same year, but month - 1
return {
month: month - 1,
year
}
}
const getNextMonthYear = (month, year) => {
// If it is January... prev month is Dec of the previous year
if (month === 1) {
return {
month: month + 1,
year
}
}
// Otherwise, same year, but month - 1
return {
month: 12,
year: year + 1
}
}
Now, using the helper function…
const getDatesInMonthDisplay = (month, year) => {
const daysInMonth = getDaysInMonth(month, year);
const firstWeekday = getFirstWeekdayOfMonth(month, year);
const result = \[\];
const prev = getPrevMonthYear(month, year);
const prevDaysInMonth = getDaysInMonth(
prev.month,
prev.year
);
// Add prev overflow dates...
for (let j = firstWeekday - 1; j >= 0; j--) {
result.push(
moment(
\`${prev.month}-${prevDaysInMonth - j}-${prev.year}\`,
'MM-DD-YYYY'
).toDate()
)
}
// Add current month's dates
for (let i = 1; i <= daysInMonth; i++) {
result.push(
moment(\`${month}-${i}-${year}\`, 'MM-DD-YYYY').toDate()
)
}
return result;
}
// July 2020
// Note, since we use MomentJs's formatting, we do not index the month. This is getting the first weekday of the month for January 2020. Result is 0-indexed
console.log(getDatesInMonthDisplay(7, 2020))
Now we should have the array correctly start with the previous month’s days leading up to the first weekday of the current active month on display. Let’s fill up the rest with the next month’s overflow dates!
Using the helper getNextMonthYear
…
const getDatesInMonthDisplay = (month, year) => {
const daysInMonth = getDaysInMonth(month, year);
const firstWeekday = getFirstWeekdayOfMonth(month, year);
const result = \[\];
const prev = getPrevMonthYear(month, year);
const prevDaysInMonth = getDaysInMonth(
prev.month,
prev.year
);
// Add prev overflow dates...
for (let j = firstWeekday - 1; j >= 0; j--) {
result.push(
moment(
\`${prev.month}-${prevDaysInMonth - j}-${prev.year}\`,
'MM-DD-YYYY'
).toDate()
)
}
// Add current month's dates
for (let i = 1; i <= daysInMonth; i++) {
result.push(
moment(\`${month}-${i}-${year}\`, 'MM-DD-YYYY').toDate()
)
}
// Overflow dates for next month to meet 42 days per month display requirement
if (result.length < 42) {
const daysToAdd = 42 - result.length;
const next = getNextMonthYear(month, year);
for (let k = 1; k <= daysToAdd; k++) {
result.push(
moment(
\`${next.month}-${k}-${next.year}\`,
'MM-DD-YYYY'
).toDate()
)
}
}
return result;
}
// July 2020
// Note, since we use MomentJs's formatting, we do not index the month. This is getting the first weekday of the month for January 2020. Result is 0-indexed
console.log(getDatesInMonthDisplay(7, 2020))
And… viola! We have the total number of days in date objects to pass into the month display in our calendar component including the current month and the previous and following overflow dates! This result.length
is 42
ensuring that the first index is a Sunday and the last index is a Saturday.
[
2020-06-28T07:00:00.000Z,
2020-06-29T07:00:00.000Z,
...,
2020-08-07T07:00:00.000Z,
2020-08-08T07:00:00.000Z
]
Before we wrap up, let’s add some quick information to make it easier to determine which dates are a part of the current month on display.
const getDatesInMonthDisplay = (month, year) => {
const daysInMonth = getDaysInMonth(month, year);
const firstWeekday = getFirstWeekdayOfMonth(month, year);
const result = \[\];
const prev = getPrevMonthYear(month, year);
const prevDaysInMonth = getDaysInMonth(
prev.month,
prev.year
);
// Add prev overflow dates...
for (let j = firstWeekday - 1; j >= 0; j--) {
result.push({
date: moment(
\`${prev.month}-${prevDaysInMonth - j}-${prev.year}\`,
'MM-DD-YYYY'
).toDate(),
currentMonth: false
})
}
// Add current month's dates
for (let i = 1; i <= daysInMonth; i++) {
result.push({
date:moment(\`${month}-${i}-${year}\`, 'MM-DD-YYYY').toDate(),
currentMonth: true
})
}
// Overflow dates for next month to meet 42 days per month display requirement
if (result.length < 42) {
const daysToAdd = 42 - result.length;
const next = getNextMonthYear(month, year);
for (let k = 1; k <= daysToAdd; k++) {
result.push({
date: moment(
\`${next.month}-${k}-${next.year}\`,
'MM-DD-YYYY'
).toDate(),
currentMonth: false
})
}
}
return result;
}
// July 2020
// Note, since we use MomentJs's formatting, we do not index the month. This is getting the first weekday of the month for January 2020. Result is 0-indexed
console.log(getDatesInMonthDisplay(7, 2020))
Minor details like this make it easier when we pass it to the component and they help more than you’d think. Here’s an example of the new result.
[
{ date: 2020-06-28T07:00:00.000Z, currentMonth: false },
{ date: 2020-06-29T07:00:00.000Z, currentMonth: false },
{ date: 2020-06-30T07:00:00.000Z, currentMonth: false },
{ date: 2020-07-01T07:00:00.000Z, currentMonth: true },
...,
{ date: 2020-08-07T07:00:00.000Z, currentMonth: false },
{ date: 2020-08-08T07:00:00.000Z, currentMonth: false }
]
Next up in part 2, we will take a look at the rendering logic of React using this function to construct the dates to display for us.
Top comments (6)
Hi ! You're Article is awesome :) It really helped me a lot with building calendar.
However I've found problem in your method
getNextMonthYear
You should have been checking for December and not January. But this mistake probably happened by while coping to article because there are comments from method
getPrevMonthYear
.So I'm adding correction of method
getNextMonthYear
And also by the way if you use
while adding Javascript code to article it will have also syntax highlight :) it's just a quick tip :)
Great Work ! :)
Oh great! Thanks for catching that, I will modify the article with the fix. Didn't know about the
Could you share a repository with the created files? It would be of great help
Here you go: github.com/bert-bae/bae-components...
i was wondering how much time did it take to code this calendar ?
Depending on your knowledge and experience, it can take anywhere from few hours to days. I would focus less on how long it takes to code a component like this and more on understanding the problem solving process to know how various components should be approached.