Angular MaterialのDatePickerで和暦入力や月日入力に対応する

# Angular MaterialのDatePickerの問題点
Angular MaterialにはDatePickerコンポーネントがあります。

https://material.angular.io/components/datepicker/overview

これを使うことでカレンダーから日付を指定したり、キーボードで入力した文字列日付として扱ってくれたりします。
ただ、当たり前なのですが和暦での入力には対応していません。
また、**月日のみ入力した**場合（例えば3/15とか）に、デフォルトだと**2001**/3/15に変換されてしまいます。

こういった入力方法をカスタマイズする方法があります。

# NativeDateAdapterを拡張する

方法としては、NativeDateAdapterで入力文字列をparseしている処理をoverrideして　、独自の記法に対応します。
app.module.tsに以下のように追加します。

```ts:src/app/app.module.ts
import {
  DateAdapter,
  MAT_DATE_LOCALE,
  NativeDateAdapter,
} from '@angular/material/core';
// ...その他importは省略

class MyDateAdapter extends NativeDateAdapter {
  // 入力された文字列をparseしてDateに変換する処理をoverride
  override parse(value: any, parseFormat?: any): Date | null {
    let convertValue = value;
    if (typeof convertValue === 'string') {
      // 全角英数記号を半角に変換
      convertValue = convertValue
        .replace(/[！-～]/g, function (tmpStr) {
          // 文字コードをシフト
          return String.fromCharCode(tmpStr.charCodeAt(0) - 0xfee0);
        })
        .replace(/−/g, '-') // 全角ハイフンマイナス
        .replace(/―/g, '-') // 全角ダッシュ
        .replace(/‐/g, '-') // 全角ハイフン
        .replace(/ｰ/g, '-') //  半角カタカナ
        .replace(/ー/g, '-'); // 長音

      // MM/DD形式で入力された場合、
      const mdMatch = convertValue.match(
        /^([0-9]{1,2})[-,/. ]{1}([0-9]{1,2})$/
      );
      if (mdMatch) {
        const today = new Date();
        const month = Number.parseInt(mdMatch[1]);
        const year =
          today.getFullYear() +
          (Math.abs(today.getMonth() + 1 - month) < 6
            ? 0
            : Math.sign(today.getMonth() + 1 - month));
        const parsed = new Date(year, month - 1, Number.parseInt(mdMatch[2]));
        return parsed;
      }

      // 和暦で入力された場合
      const warekiMatch = convertValue.match(
        /^([mMtTsShHrR])(\d+)[-,/. ](\d+)[-,/. ](\d+)$/
      );
      if (warekiMatch) {
        let baseYear = 0;
        switch (warekiMatch[1].toUpperCase()) {
          case 'R':
            baseYear = 2018;
            break;
          case 'H':
            baseYear = 1988;
            break;
          case 'S':
            baseYear = 1925;
            break;
          case 'T':
            baseYear = 1911;
            break;
          case 'M':
            baseYear = 1867;
            break;
          default:
            break;
        }
        const year = baseYear + Number.parseInt(warekiMatch[2]);
        return new Date(
          year,
          Number.parseInt(warekiMatch[3]) - 1,
          Number.parseInt(warekiMatch[4])
        );
      }
    }

    // それ以外の場合は標準の処理でパース
    return super.parse(convertValue, parseFormat);
  }
}

@NgModule({
  declarations: [
    // ...省略
  ],
  imports: [
    // ...省略
  ],
  providers: [
    // カレンダー表示設定
    { provide: DateAdapter, useClass: MyDateAdapter }, // ←これを追加
    // ...省略
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

こんな感じで、`override parse`をしてやれば、通常なら日付として認識されない文字列も日付として扱えるようになります。
ちなみに今回は以下の入力に対応させました。

* 和暦での入力（`R2/3/4`や`H30.6.23`など）
* 全角英数記号での入力（２０２２／１／１やＲ４．５．２など）
* 月日だけでの入力を近い方の年の同月日に変換（1月時点での`4/5`→`2023/4/5`や`12/23`→`2022/12/23`など）

これで十分かなぁとは思いますが、他にも「年月だけでの入力」など、入力可能なパターンを増やすことは可能です。

# 注意点
こちらの対応は、「入力時のパターンを増やす」のみの対応ですので、DatePickerの表示は通常通りの表示になります。
（例えば和暦で入力しても、Blurしたタイミングで西暦表記に直る）
表示の修正（和暦で表示したいとか）については別途対応が必要です。

この辺りが参考になりますので確認してみてください。
ただ、表示方法に西暦と和暦を混在することは出来ないので、基本的には西暦に寄せておくのが無難かなとは思います。

https://material.angular.io/components/datepicker/overview#customizing-the-parse-and-display-formats