본문 바로가기

프론트엔드

Google Calendar API로 공휴일 연동하기

  • 키워드: google calendar API, Public Holidays
  • 상황
    1. 클라이언트에서 국가공휴일 정보가 필요하다.
    2. 자동화를 위해 코드에 하드코딩하지 않고 구글 캘린더 API를 통해 공휴일 정보를 얻고자한다.
  • 해결 과정
    1. 구글 캘린더에 기본적으로 있는 한국 공휴일 캘린더 정보를 API로 불러올 수 있는지 확인한다.
      google calendar 추석
      기본 제공되는 'Holidays in South Korea(한국의 공휴일)'을 사용하면 되는 듯하다.
      Settings > Add Calendar > Browse Calendars of interest
      만약 공휴일 캘린더가 보이지 않는다면, 캘린더 설정 메뉴에서 Holidays in South Korea 캘린더를 추가하면 된다.
    2. Google API를 사용하기 위해선 Google Cloud Platform(GCP)에서 API key를 발급받아야한다.
      google cloud console의 'API 및 서비스' 페이지로 들어간다.
      먼저 '라이브러리' 메뉴에서 'Calendar'를 검색하여 'Google Calendar API 사용' 버튼을 클릭한다.
      라이브러리 > calendar 검색
    3. 이제 달력 API를 사용할 수 있으니, key를 발급받기위해 '사용자 인증 정보' 메뉴에 들어간다.
      사용자 인증 정보 만들기 > 이름 입력 >
      애플리케이션 제한사항(없음 또는 HTTP 리퍼러(웹사이트)) >
      웹사이트 제한사항(허용하는 도메인 추가 ex> *.loplat.com/*) >
      API 제한 사항에서 Calendar API만 사용 가능하도록 제한

      을 순서대로 실행하고 저장을 눌러 새로운 인증 정보를 생성한다.
      그리고 발급된 API key를 복사한다.

    4. 이제 아래의 url을 통해 공휴일 캘린더 정보를 얻어올 수 있다. 
        # API 형식
      https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events?key=${apiKey}&orderBy=startTime&singleEvents=true&timeMin=${startDate}&timeMax=${endDate}
      # 예시
      https://www.googleapis.com/calendar/v3/calendars/ko.south_korea.official%23holiday%40group.v.calendar.google.com/events?key=API_KEY&orderBy=startTime&singleEvents=true&timeMin=2022-01-01T00:00:00Z&timeMax=2023-01-01T00:00:00Z
      이 때, "ko.south_korea.official%23holiday%40group.v.calendar.google.com"는 대한민국 공휴일에 해당하는 구글 캘린더 ID 이다.

    5. 이제 위 API를 client에서 쉽게 사용할 수 있도록 hook을 작성한다.
        // useHoliday.ts 예시
      import { useEffect, useState } from 'react';
      import dayjs from 'dayjs';
      
      type RawData = {
        // 달력에 표시되는 휴일 이름
        summary: string;
        start: {
          date: string;
        };
      };
      
      type Holiday = {
        name: string;
        date: Date;
      };
      
      ...
      
      export const useHoliday = (): Return => {
        const [holidays, setHolidays] = useState<Holiday[]>([]);
        const storageKey = '__holidays';
      
        useEffect(() => {
          const storageHolidays = localStorage.getItem(storageKey);
          if (storageHolidays) {
            /** localStorage에 저장되어 있다면 그 값을 사용 */
            const newHolidays = JSON.parse(storageHolidays).map((holiday: StorageHoliday) => ({
              ...holiday,
              date: new Date(holiday.date),
            }));
            setHolidays(newHolidays);
          } else {
            /** localStorage에 값이 없다면 Google Calendar API 호출 */
            const calendarId = 'ko.south_korea.official%23holiday%40group.v.calendar.google.com';
            const apiKey = 'API_KEY';
            const startDate = new Date('2022-09-01').toISOString();
            const endDate = new Date('2023-12-31').toISOString();
            fetch(
              `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events?key=${apiKey}&orderBy=startTime&singleEvents=true&timeMin=${startDate}&timeMax=${endDate}`,
            ).then((response) => {
              response.json().then((result) => {
                const newHolidays = result.items.map((item: RawData) => ({
                  name: item.summary,
                  date: new Date(item.start.date),
                }));
                setHolidays(newHolidays);
                localStorage.setItem(storageKey, JSON.stringify(newHolidays));
              });
            });
          }
        }, []);
      
        const getMatchedHoliday = (date: Date): Holiday | null => {
          return holidays.find((holiday) => dayjs(holiday.date).isSame(date, 'date')) ?? null;
        };
      
        return { getMatchedHoliday };
      };
      API response raw data는 사용하기 불편하기 때문에 { date, name } 형태로 변환하여 저장한다.
      API 호출을 줄이기 위해, 호출된 결과를 localStorage에 저장하는 로직을 추가하였다.
      date 형식을 맞추기 위해 Date.prototype.toISOString() API를 사용하였다.

    6. 이제 필요한 곳에서 useHoliday hook을 호출하여 사용하면 된다!
        const { getMatchedHoliday } = useHoliday();
      const colorByDate = (date) => {
        // 공휴일이면 빨강
        const matchedHoliday = getMatchedHoliday(date);
        ...
      }