Dateオブジェクトだけでカレンダーを作る

# 概要
既存のカレンダーライブラリはたくさんありますが、少々柔軟性に欠けるのでイチから自分で作ろうと考えました。今回はJavaScriptの標準組み込みオブジェクトである「Dateオブジェクト」のみを使ってカレンダー生成アルゴリズムを構築していきます。（マイブームのReactで開発を進めます。）

##### 完成したサンプルサイトは[こちら](https://calendar-maker.vercel.app/)↓↓

https://calendar-maker.vercel.app/

# アルゴリズム
年,月を引数にとり、その月の日にちをカレンダーに見立てた二次元配列で返す関数を作ります。
以下は2022年10月の配列です。
```ts:calendar:number[][]
[
    [25, 26, 27, 28, 29, 30, 1],
    [2,  3,  4,  5,  6,  7,  8],
    [9, 10, 11, 12, 13, 14, 15],
    [16, 17, 18, 19, 20, 21, 22],
    [23, 24, 25, 26, 27, 28, 29],
    [30, 31,  1,  2,  3,  4,  5]
]
```
この配列をmapでループ処理にかけてカレンダーを表示します。

# Dateオブジェクトの使い方
>JavaScript の Date オブジェクトは、単一の瞬間の時刻をプラットフォームに依存しない形式で表します。 Date オブジェクトは協定世界時 (UTC) の 1970 年 1 月 1 日からの経過ミリ秒数を表す Number の値を含んでいます。

[Date - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date)

## 任意の時刻の取得
2022年10月1日の情報を取得します。
```typescript
const day = new Date(2022, 10, 1);
```
引数を渡さなければ現在時刻を取得できます。
```typescript
const day = new Date();
```

## 使用するメソッド
```typescript
const today = new Date();

const year  = today.getFullYear()   //年を取得
const month = today.getMonth()      //月を取得
const date  = today.getDate()       //日を取得
const day   = today.getDay()        //曜日を取得

```
getDay()メソッドは日曜〜土曜に対応して0~6を返します。

# 手順
Date オブジェクトを駆使してカレンダーの配列を生成します。
## 各種宣言
```typescript:Make_calendar.ts
let calendar:number[][] = []                //カレンダー
const first_day = new Date(year, month, 1); //任意の月の1日を取得
let week:number[] = []                      //週生成用
```
## 第一週を生成
任意の月の第一週を生成してカレンダーにpushします。
Date関数の第3引数に0を渡すと前の月の最終日を取得できます。
```typescript:Make_calendar.ts
for(let i = first_day.getDay(); i < 7; i++){
    week.push(i - first_day.getDay() + 1)
}

const end_last_month = new Date(first_day.getFullYear(), first_day.getMonth(), 0)
for(let i = 0; i < first_day.getDay(); i++){
    week.unshift( end_last_month.getDate() - i)
}

calendar.push(week)
```

## 最終週を生成
```typescript:Make_calendar.ts
const last_day = new Date(first_day.getFullYear(), first_day.getMonth() + 1, 0)
let last_week:number[] = []

for(let i = 0; i <= last_day.getDay(); i++){
    last_week.unshift(last_day.getDate() - i)
}

if(last_day.getDay() !== 6){
    const begin_next_month = new Date(first_day.getFullYear(), first_day.getMonth() + 1, 1)
    for(let i = 0; i < 7 - begin_next_month.getDay(); i++){
        last_week.push( begin_next_month.getDate() + i)
    }
}
```

## 中間週を生成
中間週を生成してカレンダーにpushしていきます。最終週に到達したら先ほど生成したlast_week[]をpushしましょう。
```typescript:Make_calendar.ts
for(let i = 0; i < 5; i++){
    week = []
    for(let j = 0; j < 7 ; j++){
        week.push((8 - first_day.getDay() + j) + 7 * i)
    }
    if(week[0] !== last_week[0]){
        calendar.push(week)
    }else{
        calendar.push(last_week)
        break
    }
}
```
# 関数全体
```typescript:Make_calendar.ts
const Make_calendar = (year:number, month:number):number[][] => {
    let calendar:number[][] = []                //カレンダー
    const first_day = new Date(year, month, 1); //任意の月の1日を取得
    let week:number[] = []                      //週生成用

    // first week========
    for(let i = first_day.getDay(); i < 7; i++){
        week.push(i - first_day.getDay() + 1)
    }

    const end_last_month = new Date(first_day.getFullYear(), first_day.getMonth(), 0)
    for(let i = 0; i < first_day.getDay(); i++){
        week.unshift( end_last_month.getDate() - i)
    }
    calendar.push(week)

    // last week============
    const last_day = new Date(first_day.getFullYear(), first_day.getMonth() + 1, 0)
    let last_week:number[] = []

    for(let i = 0; i <= last_day.getDay(); i++){
        last_week.unshift(last_day.getDate() - i)
    }

    if(last_day.getDay() !== 6){
        const begin_next_month = new Date(first_day.getFullYear(), first_day.getMonth() + 1, 1)
        for(let i = 0; i < 7 - begin_next_month.getDay(); i++){
            last_week.push( begin_next_month.getDate() + i)
        }
    }

    // middle week===========
    for(let i = 0; i < 5; i++){
        week = []
        for(let j = 0; j < 7 ; j++){
            week.push((8 - first_day.getDay() + j) + 7 * i)
        }
        if(week[0] !== last_week[0]){
            calendar.push(week)
        }else{
            calendar.push(last_week)
            break
        }
    }
    // ======================

    return calendar
}
```
# コンポーネント設計
React × Typescript で進めます。
```mermaid
graph LR
    A[Home]-->B[Calendar]-->C[Column]-->D[Square]
```
```tsx
const Square = (props):JSX.Element => {
    return(
        <div>
            {props.date}
        </div>
    )
}

const Column = (props):JSX.Element => {
    return(
        <div>
            {props.week.map((date) => <Square date = {date} />)}
        </div>
    )
}

const Calendar = (props):JSX.Element => {

    let calendar:number[][] = Make_calendar(props.year, props.month)

    return(
        <div>
            {calendar.map((week) => <Column week = {week} />)}
        </div> 
    )
}

const Home = ():JSX.Element => {
    const today = new Date();
    const [year, setYear] = useState<number>(Number(today.getFullYear()))
    const [month, setMonth] = useState<number>(Number(today.getMonth()))
    
    return(
        <div>
            <Calendar 
                year = {year}
                month = {month}
            />
        </div>
    )
}
```
# 完成
完成したサンプルサイトは[こちら](https://calendar-maker.vercel.app/)↓↓

https://calendar-maker.vercel.app/

# まとめ
Dateオブジェクトだけでカレンダーを作ってみました。カレンダー配列生成マシンはそこそこ便利なのでぜひ参考にしてください。

# 参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date