TimePickerの時刻形式によるUIを指定する(AM/PMセレクターの表示/非表示等)

# はじめに

時刻を選択できる TimePicker は、使用するデバイスの時刻の表示形式によってUIが変わります。

* 12時間形式: AM/PMセレクターあり。
* 24時間形式: AM/PMセレクターなし。奇数時刻（1時, 23時等)を選択する場合は表示されている時刻の間をタップする。

|  12時間形式  |  24時間形式   |
| ---- | ---- |
|  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658739/fb514cc2-3804-6fd1-4b2b-811686bee0c3.png" width="300px">  |  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658739/207b0351-7671-e1df-ec5d-7c19c809064b.png" width="300px">  |

デバイスの時刻の表示形式によらず、UIを固定したいことがあり調査したのでそれについて記載します。

# 実装

デバイスの時刻の表示形式によらずUIを固定したい場合は、`alwaysUse24HourFormat`を指定すれば可能です。

alwaysUse24HourFormatが
* true -> 24時間形式のUI
* false　-> 12時間形式のUI

例: 12時間形式のUIで固定したい場合

```dart
  void _selectTime() async {
    final TimeOfDay? newTime = await showTimePicker(
      context: context,
      initialTime: TimeOfDay.now(),
      builder: (context, child) {
        return MediaQuery(
          data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: false),
          child: child!,
        );
      },
    );
  }
```

# おまけ: TimeOfDayのフォーマットについて

`TimeOfDay` は `format()`メソッドを持っており、`hh:mm` の形式で出力してくれます。しかし、これもデバイスの時計の設定が12時間形式か24時間形式かで変わってきます。

例えば以下のような感じで出力した場合。

```dart
Text(
  'Selected time: ${_time.format(context)}',
),
```

下図のように、デバイスの時計の設定が12時間形式の場合は、`hh:mm AM/PM` の形式で出力されます。

|  12時間形式  |  24時間形式  |
| ---- | ---- |
|  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658739/c183f6a0-4980-7c2e-16fd-7dab6aa0418e.png" width="300px">  |  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658739/86cbf415-1cd9-1c04-8c13-e5d2c059b843.png" width="300px">  |

これをデバイスの設定によらず固定にしたい場合は、以下のような拡張関数を作るとよさそう。

### TimeOfDayのフィールドを用いて整形する拡張関数 (24時間形式で固定)

[Flutter Convert TimeOfDay to 24 hours format - technicalfeeder.com](https://www.technicalfeeder.com/2022/02/flutter-convert-timeofday-to-24-hours-format/) で紹介されていた方法です。

`TimeOfDay`の`hour`と`minute`を用いて整形します。１時２分の場合、`1:2` のようにならないように `padLeft(2, "0")`で調整します。


```dart
extension TimeOfDayConverter on TimeOfDay {
  String to24hours() {
    final hour = this.hour.toString().padLeft(2, "0");
    final min = this.minute.toString().padLeft(2, "0");
    return "$hour:$min";
  }
}
```

### MaterialLocalizationsを使った拡張関数 (24時間/12時間形式どちらの固定も可)

`TimeOfDay#format()` の実装は以下のようになっていました。

```dart
  String format(BuildContext context) {
    assert(debugCheckHasMediaQuery(context));
    assert(debugCheckHasMaterialLocalizations(context));
    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
    return localizations.formatTimeOfDay(
      this,
      alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat,
    );
  }
```

なので、`MaterialLocalizations#formatTimeOfDay()`を呼び、引数の `alwaysUse24HourFormat`に特定のboolを渡してやる拡張関数を作るという方法もあります。

24時間形式で固定したい場合は `true` を渡して以下のような感じ。

```dart
extension TimeOfDayFormatter on TimeOfDay {
  String to24hours(BuildContext context) {
    final MaterialLocalizations localizations =
        MaterialLocalizations.of(context);
    return localizations.formatTimeOfDay(
      this,
      alwaysUse24HourFormat: true,
    );
  }
}
```

逆に `alwaysUse24HourFormat: false` とすれば常に `hh:mm AM/PM` の形式で出力ができます。




以上です。

# 参考

[showTimePicker - api.flutter.dev](https://api.flutter.dev/flutter/material/showTimePicker.html)

[Time pickers - material.io](https://material.io/components/time-pickers)

[How to use 24 hour clock when invoking showTimePicker() in Flutter? - stackoverflow.com](https://stackoverflow.com/questions/52002388/how-to-use-24-hour-clock-when-invoking-showtimepicker-in-flutter)

[Flutter Convert TimeOfDay to 24 hours format - technicalfeeder.com](https://www.technicalfeeder.com/2022/02/flutter-convert-timeofday-to-24-hours-format/)