캘린더 구현 방법
개인 프로젝트를 하면서, 캘린더 기능이 필요했고, 전에도 그래프와 차트를 SVG
태그로 구현한 것처럼 캘린더 기능도 직접 구현해 보기로 했다.
지금까지 혼자 공부를 하거나, 팀플을 할 때 캘린더는 라이브러리를 사용했던거 같다. 많이 써본적은 없어 정확히 기억은 나지 않지만, 항상 React
로 개발을 했으니, react-calendar
, react-datepicker
등의 라이브러리를 사용했던거 같다.
날짜 관련되서 사용했던 라이브러리는 day.js
를 사용했던거 같고, 이제 캘린더를 어떻게 만들어야 할까.. 고민을 시작했다.
처음에는 365일, 1 ~ 12월, 1일 ~ 31일, 월~일 등을 어떻게 월별로 날짜를 나누고, 매월 1일은 월요일이 아닌데.. 4년마다 윤년은 어떻게 하지.. 감이 잡히지 않았다.
그래서 우선 JavaScript
내장 객체인 Date method
로 이용하면 되지 않을까? 라는 생각에 MDN
에 들어가 보았고, 다양한 인스턴스 메서드들이 있었다. 이제 이것들을 활용하면 캘린더를 구현할 수 있을거 같았고, 그렇게 캘린더 구현이 시작되었다.
Date Method
Date()
Date method
는 다양한 인스턴트 객체가 있고, 그 중에서 4가지 정도만 알고 있어도 충분히 캘린더를 구현 가능하다.
먼저 JavaScript
내장 객체인 Date method
의 기본인 Date()
이다. Date
객체를 생성하고, 사용하는 방법을 알아보자
console.log(Date()) // Wed May 10 2023 16:36:44 GMT+0900 (한국 표준시) const day = new Date() console.log(day) // Wed May 10 2023 16:36:44 GMT+0900 (한국 표준시) const newDay = new Date(1995, 0, 24, 3, 24, 12) // Tue Jan 24 1995 03:24:12 GMT+0900 (한국 표준시)
첫 번째 Date()
를 console.log
에 찍어보면, 현재 시간을 알려준다.
두 번째 new
키워드를 사용해 객체를 생성해서 사용한다. 역시 console.log
에 찍어보니, 요일, 월, 일, 년, 시간 순으로 알려준다. 필자는 string
을 split
를 활용해서 날짜의 데이터를 다루기도 했다.
마지막으로 new Date()
안에 인자를 넣을때 년, 월, 일, 시, 분, 초 순으로 넣으면 원하는 날짜의 데이터가 형성된다. 주의할점은 월은 제로베이스로 0~11
에 맞춰서 입력하면 된다.
getDate()
const newDay = new Date(1995, 0, 24, 3, 24, 12) // Tue Jan 24 1995 03:24:12 GMT+0900 (한국 표준시) console.log(newDay.getDate()) // 24
getDate()
메소드는 현지 시간 기준의 일1 ~ 31
을 반환하는 메소드이고, 1부터 시작합니다.
getDay()
const newDay = new Date(1995, 0, 24, 3, 24, 12) // Tue Jan 24 1995 03:24:12 GMT+0900 (한국 표준시) console.log(newDay.getDay()) // 2
getDay()
메소드는 현지 시간 기준의 요일0~6
을 반환하는 메소드이고 0부터 일월화수목금토 순으로 입니다.
getMonth()
const newDay = new Date(1995, 0, 24, 3, 24, 12) // Tue Jan 24 1995 03:24:12 GMT+0900 (한국 표준시) console.log(newDay.getMonth()) // 0
getMonth()
메소드는 현지 시간 기준 월0~11
을 반환하고 0부터 1월~12월 순입니다.
getFullYear()
const newDay = new Date(1995, 0, 24, 3, 24, 12) // Tue Jan 24 1995 03:24:12 GMT+0900 (한국 표준시) console.log(newDay.getFullYear()) // 1995
getFullYear()
메소드는 현지 시간 기준으로 연도를 4자리로 반환합니다.
지금 까지 메소들을 활용해서 캘린더를 구현해 봅시다!
캘린더 만들기
이번달 첫 시작 요일
이제 캘린더를 만들려면 위에서 배웠던 메소드들을 활용하면 되는데, 첫 번째로 이번달 첫 시작 요일
을 얻는 방법입니다. 이것을 구하는 이유는 매월 1일은 월요일이 아니기 때문에 캘린더에서 보면, 항상 1~31일 까지 적혀 있고, 해당월의 일수에 맞게 채워지고 빈날은 전달과 다음달의 날짜를 가져다 쓰고 있기 때문입니다.
const firstDayOfMonth = new Date(2023, 4, 1).getDay() // 1
예제 보면 2023년 5월 1일의 요일은 월요일인 것을 알 수 있네요. 만약 예제 날짜를 가지고 달력을 만들때, 첫날은 월요일이니까 보통 달력이 일요일 부터 시작하니까, 1일 앞에 전달의 마지막 날이 들어가고 그 다음에 이번달 1일이 들어가겠네요. 이런식으로 요일, 날짜 별 숫자를 통해 배열에 각각 순서대로 값을 넣어서 구현하겠습니다.
이번달 마지막 날짜
const lastDateOfMonth = new Date(2023, 4 + 1, 0).getDate() // 31
예제를 보면 월
입력 자리에 +1
이 되어 있고, 일
입력자리에 0
값이 입력되어 있습니다. 일
입력자리에 0
을 입력하면 전달의 마지막 날짜를 반환하기 때문에, 월
자리에 +1
을 하면 6월의 전달인 5월의 마지막 날짜를 반환하기 위한 것입니다. 이제 배열에 마지막 날짜까지 순환해서 넣어주면 되겠네요.
이번달 마지막 요일
const lastDayOfMonth = new Date(2023, 4, lastDateOfMonth).getDay() // 3
예제에 일
을 넣는 자리에 앞서 구했던 이번달 마지막 날짜
를 인자를 넣어주면, 이번달의 마지막 요일도 알 수 있습니다. 이번달 마지막 요일을 알아야하는 이유는 날짜가 비는 요일 만큼 앞달을 채우기 위함입니다.
지난달 마지막 날짜
const lastDateOfLastMonth = new Date(2023, 4, 0).getDate() // 30
앞서 이번달 마지막 날짜
를 구하는 방식과 똑같이 구하면 됩니다. 지난달 마지막 날짜를 구하는 이유는 이번달의 1일이 화요일 이라면 일, 월 날짜가 비어있게 되고, 위에 코드 값을 예로 들어, 월요일에 30
이 들어가고, 일요일에 29
가 들어가면서 자연스럽게 비어있는 달력에 전달에 날짜가 채워지게 됩니다.
배열로 캘린더 만들기

아주 기본적인 기능만 있는 캘린더를 구현했습니다. 아래 전체 코드를 보면
import { useState } from "react" import "./styles.css" export default function App() { const [month, setMonth] = useState(new Date().getMonth()) const [year, setYear] = useState(new Date().getFullYear()) const calendar = () => { const currentCalendar = [] const firstDayOfMonth = new Date(year, month, 1).getDay() // 이번달 첫 시작 요일 const lastDateOfMonth = new Date(year, month + 1, 0).getDate() // 이번달 마지막 날짜 const lastDayOfMonth = new Date(year, month, lastDateOfMonth).getDay() // 이번달 마지막 요일 const lastDateOfLastMonth = new Date(year, month, 0).getDate() // 지난달 마지막 날짜 for (let i = firstDayOfMonth; i > 0; i--) { currentCalendar.push(lastDateOfLastMonth - i + 1) } for (let i = 1; i <= lastDateOfMonth; i++) { currentCalendar.push(i) } for (let i = lastDayOfMonth; i < 6; i++) { currentCalendar.push(i - lastDayOfMonth + 1) } const weeks = [] while (currentCalendar.length) { weeks.push(currentCalendar.splice(0, 7)) } return weeks } const handlePrevMonth = () => { if (month === 0) { setMonth(11) setYear(year - 1) } else { setMonth(month - 1) } } const handleNextMonth = () => { if (month === 11) { setMonth(0) setYear(year + 1) } else { setMonth(month + 1) } } console.log(calendar()) return ( <div className='App'> <div style={{ display: "flex" }}> <button onClick={handlePrevMonth}>{`<`}</button> <div>{`${year}년 ${month}월`}</div> <button onClick={handleNextMonth}>{`>`}</button> </div> <tbody> {calendar().map((week, index) => ( <tr key={index}> {week.map((day) => ( <td className='day'>{day}</td> ))} {week.length < 7 && Array(7 - week.length).fill(<td></td>)} </tr> ))} </tbody> </div> ) }
대략 설명하자면 useState
를 활용해서 현재 월
과 년도
를 기본값으로 설정하고, calendar
함수에서 배열을 통해 캘린더를 생성하고, handle
함수를 통해 button
클릭시 전달, 다음달로 이동이 가능합니다. 마지막으로 table
태그를 통해서 배열로 렌더링해주고 있습니다.
const calendar = () => { const currentCalendar = [] const firstDayOfMonth = new Date(year, month, 1).getDay() // 이번달 첫 시작 요일 const lastDateOfMonth = new Date(year, month + 1, 0).getDate() // 이번달 마지막 날짜 const lastDayOfMonth = new Date(year, month, lastDateOfMonth).getDay() // 이번달 마지막 요일 const lastDateOfLastMonth = new Date(year, month, 0).getDate() // 지난달 마지막 날짜 for (let i = firstDayOfMonth; i > 0; i--) { currentCalendar.push(lastDateOfLastMonth - i + 1) } for (let i = 1; i <= lastDateOfMonth; i++) { currentCalendar.push(i) } for (let i = lastDayOfMonth; i < 6; i++) { currentCalendar.push(i - lastDayOfMonth + 1) } const weeks = [] while (currentCalendar.length) { weeks.push(currentCalendar.splice(0, 7)) } return weeks }
이부분이 가장 중요하다고 생각하는데, 지금 까지 알아보았던, Date method
를 활용해서 캘린더의 날짜들을 배열안에 push
해주고 있는데요.
첫 번째 for 문
을 보면, firstDayOfMonth
변수를 통해 5월 1일이 월요일이고, 보통 달력은 일요일 부터 시작하니까 월요일이 시작이면 일요일 날짜가 비워지니 그만큼 지난달의 마지막 날짜
를 push
를 해줘서 달력 첫줄을 채워주고 있고
두 번째 for 문
은 이번달 마지막 날짜까지 달력을 채워주고 있습니다.
세 번째 for 문
은 현재 달의 주
를 만들어줍니다. 6
보다 작다는 뜻은 5주
로 배열을 만든다는 의미입니다.
마지막 while 문
은 splice
를 통해 달력을 7일 기준으로 배열을 나눠주면서 weeks
를 반환합니다.
이렇게 되면 전달과 현재달, 다음달의 날짜 까지 모두 볼 수 있는 캘린더를 구현할 수 있습니다.
지금 위에 gif
를 보면 캘린더의 크기가 일정하지 않은데 이것은 전달과 다음달의 일자 수가 더 많기 때문에 몇몇 캘린더는 그렇게 보이는 것이고, 이것을 전체 배열 length
를 잡아주면 크기는 변하지 않고, 안에 캘린더 값들만 변하게 할 수 있겠네요.
회고
라이브러리를 쓰지 않고 캘린더를 구현하면서 어려움을 많이 느꼈던거 같다. 위에 예제들은 기본만 사용했지만, 개인 프로젝트에서 구현했던 캘린더는 날짜 뿐만 아니라, 그안에 운동 루틴 데이터 들도 들어있고, 달력이 클릭도 되고, 토
일
색도 빨강색, 파란색 스타일도 입히고, 좀 더 구현할게 많았었다.
지금 생각해보면, 라이브러리는 어떤식으로 구현했나 코드를 보았다면 좀 더 빠르게 이해하고 구현할 수 있었을 것 같은데.. 그래도 구현하였고, 공부가 많이 되었다. 라이브러리를 만든 사람들도 대단해 보이고, 앞으로도 종종 혼자 공부할겸 프로젝트를 할 때 라이브러리 없이 구현하는 연습을 더 해봐야 겠다.