V-Calendarで世界の祝日カレンダを作った

# はじめに
自分：中国販社の〇〇さんとベトナム販社の△△さんとのMTG設定しよーっと。
えらい人：中国は今旧正月だから〇〇さん今週ずっと休みだよ？
えらい人：あ、ベトナムは月末に祝日あるからそれも気を付けてね！
自分：・・・

ということがあったので世界の祝日カレンダを作りました。
![calendar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734544/69918f75-051e-8571-fd3e-11bf61b073ed.png)

これで世界中の人とMTGになっても安心です！

# 技術要素
- Vue.js
- V-Calendar

https://vcalendar.io/

V-Calendarを使ってカレンダを表示し、祝日をその国の国旗で表現します。
世界の祝日はgoogleカレンダから取得します。
世界の祝日は下記を参考にgoogleカレンダのグループから取得しました。

https://gist.github.com/mattn/1438183/984ed17251336f8b2e8911c02fe4e22d8e6e303e

# 構成

```:構成図
├── images //国旗画像
├── src //コンパイル前ソース
    ├── index.js
├── dist //コンパイル後ソース
├── index.html
├── package.json
├── webpack.config.js
```

国旗の画像は適当なものをご用意ください。
index.jsでgoogleカレンダのAPIから祝日を取得、
その結果をV-Calendarが見てる変数に入れてるだけです。

一つ注意なのはgoogleカレンダのAPIから取得できる結果には祝日以外も含まれてるということです。
description == "Public holiday"が祝日なのでちゃんと絞り込んでおきましょう。

国をもっと増やしたい場合はSetHolidayListの引数を変えて追記してください。


```js:index.js
import Vue from 'vue';
import VCalendar from 'v-calendar';
import axios from 'axios';

// Use v-calendar & v-date-picker components
Vue.use(VCalendar);

var tmpattr = [];

new Vue({
  el: '#app',
  data() {
    return {
      attrs: tmpattr

    };
  },
  async mounted () {
      await Promise.all([
      this.SetHolidayList('en.china','icon chinese','blue'), 
      this.SetHolidayList('en.singapore','icon singapore','blue'), 
      this.SetHolidayList('en.indian','icon india','blue'),
      this.SetHolidayList('en.malaysia','icon malaysia','blue'),
      this.SetHolidayList('en.philippines','icon philippine','blue'),
      this.SetHolidayList('en.vietnamese','icon vietnam','blue'),
      this.SetHolidayList('en.indonesian','icon indonesia','blue'),
      this.SetHolidayList('en.south_korea','icon south_korea','blue'),
      this.SetHolidayList('en.taiwan','icon taiwan','blue'),
      this.SetHolidayList('en.australian','icon australia','blue'),
      this.SetHolidayList('en.turkish','icon turkey','blue'),
      this.SetHolidayList('en.sa','icon south_africa','blue'),
      this.SetHolidayList('en.japanese','icon japanese','red')
      ]);
  },
  methods: {
    async SetHolidayList(county, classname, dotcolor) {
      return axios
      .get('https://www.googleapis.com/calendar/v3/calendars/'+county+'%23holiday%40group.v.calendar.google.com/events?key=取得したgoogleAPIKey')
      .then((response) => {
        let obj = response["data"];
        if(obj["items"] !== undefined){
          var add_date = {};
          add_date["dot"] = {
            color:dotcolor,
            class:classname
          };
          add_date["dates"] = [];
          for (const o of obj["items"]) {
            if(o.description == "Public holiday"){
              let enddate = new Date(o.end.date);
              enddate.setDate(enddate.getDate() - 1);
              let add =  { start: o.start.date, end: enddate };
              add_date["dates"].push(add);
            }
          };
          tmpattr.push(add_date);
        }
      }
      );
    }
  }
})
```

国旗の画像は26*17ぐらいの大きさのがちゃんと並ぶようにCSSを調整しています。
9つまでは祝日が重なっても国旗が並んで表示されることを確認してますが、
それ以上だと高さ等を調整したほうがいいかもしれません。

```html:index.html
<html>
  <head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
    <meta http-equiv='x-ua-compatible' content='ie=edge'>
    <style type="text/css">
      .custom-calendar.vc-container {
      border-radius: 0;
      width: 100%;
      margin-top:44px;
      }
      .custom-calendar.vc-container  .vc-header {
        background-color: #f1f5f8;
        padding: 10px 0;
      }
      .custom-calendar.vc-container  .vc-weeks {
        padding: 0;
      }
      .custom-calendar.vc-container  .vc-weekday {
        background-color: #f8fafc;
        border-bottom: 1px solid #b8c2cc;
        border-top: 1px solid #b8c2cc;
        padding: 5px 0;
      }
      .custom-calendar.vc-container  .vc-day {
        padding: 0 5px 3px 5px;
        text-align: left;
        height: 110px;
        min-width: 90px;
        background-color: #f1f5f8;
      }
      .custom-calendar.vc-container  .vc-day.weekday-1,
      .custom-calendar.vc-container  .vc-day.weekday-7 {
        background-color: #eff8ff;
      }
      .custom-calendar.vc-container  .vc-day:not(.on-bottom) {
        border-bottom: 1px solid #b8c2cc;
      }
      .custom-calendar.vc-container  .vc-day:not(.on-bottom).weekday-1 {
          border-bottom: 1px solid #b8c2cc;
      }
      .custom-calendar.vc-container  .vc-day:not(.on-right) {
        border-right: 1px solid #b8c2cc;
      }
      .vc-pane {
          min-width: auto !important;
          border-right: 1px solid #b8c2cc;
          border-bottom: 1px solid #b8c2cc;
      }
      .country_header {
        position:fixed;
        top:0;
        left:0;
        z-index: 1000;
        background-color: #f1f5f8;
        width: 100%;
      }
      .vc-dots{
        flex-wrap: wrap !important;
        justify-content: flex-start !important;
        align-items:flex-start !important;
      }
      .vc-day-box-center-center {
        align-items:flex-start !important;
      }
      .vc-day-box-center-bottom {
        align-items:flex-start !important;
        margin-top: 30px;
      }
      .icon {
        width: 26px !important;
        height:16px !important;
        border-radius: 0% !important;
        background-color: transparent !important;
        margin-bottom: 4px;
      }
     .chinese {
      content: url(./images/chinese.png);
     }
     .singapore {
      content: url(./images/singapore.png);
     }
     .india {
      content: url(./images/india.png);
     }
     .malaysia {
      content: url(./images/malaysia.png);
     }
     .thailand {
      content: url(./images/thailand.png);
     }
     .philippine {
      content: url(./images/philippine.png);
     }
     .vietnam {
      content: url(./images/vietnam.png);
     }
     .indonesia {
      content: url(./images/indonesia.png);
     }
     .south_korea {
      content: url(./images/south_korea.png);
     }
     .taiwan {
      content: url(./images/taiwan.png);
     }
     .australia {
      content: url(./images/australia.png);
     }
     .turkey {
      content: url(./images/turkey.png);
     }
     .south_africa {
      content: url(./images/south_africa.png);
     }
     .japanese {
      content: url(./images/japanese.png);
     }
    </style>
  </head>
  <body>
    <div id='app'>
        <div class="country_header">
          <div>
            <label>中国：</label><span class="chinese"></span>
            <label>シンガポール：</label><span class="singapore"></span>
            <label>インド：</label><span class="india"></span>
            <label>マレーシア：</label><span class="malaysia"></span>
            <label>タイ：</label><span class="thailand"></span>
            <label>フィリピン：</label><span class="philippine"></span>
            <label>ベトナム：</label><span class="vietnam"></span>
          </div>
          <div>
            <label>インドネシア：</label><span class="indonesia"></span>
            <label>韓国：</label><span class="south_korea"></span>
            <label>台湾：</label><span class="taiwan"></span>
            <label>オーストラリア：</label><span class="australia"></span>
            <label>トルコ：</label><span class="turkey"></span>
            <label>南アフリカ：</label><span class="south_africa"></span>
            <label>日本：</label><span class="japanese"></span>
          </div>
        </div>
        <v-calendar 
        class="custom-calendar"
          :attributes="attrs" 
          :columns="$screens({ default: 1, lg: 2 })"
          :rows="$screens({ default: 1, lg: 3 })"
          :is-expanded="$screens({ default: true, lg: true })"
          />
    </div>
    <script src="./dist/main.js"></script>
  </body>
</html>
```

```json:package.json
{
  "name": "calendar",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "npx webpack --mode development",
    "watch": "npx webpack -w --mode development"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.25.0",
    "v-calendar": "^2.4.0",
    "vue": "^2.6.14",
    "webpack": "^5.68.0"
  },
  "devDependencies": {
    "@vue/cli": "^4.5.15",
    "webpack-cli": "^4.9.2"
  }
}

```

```js:webpack.config.js
module.exports = {
    //...
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
        }
    },
};
```

# おわりに
現在の仕事に携わるまで他の国の祝日なんて意識することはありませんでした。
ググってもパッと見で世界の祝日がわかるものがなかったので自分の業務にはそこそこ活用できています。